1 /**
  2  * Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
  3  *
  4  * @licstart
  5  * This file is part of WebODF.
  6  *
  7  * WebODF is free software: you can redistribute it and/or modify it
  8  * under the terms of the GNU Affero General Public License (GNU AGPL)
  9  * as published by the Free Software Foundation, either version 3 of
 10  * the License, or (at your option) any later version.
 11  *
 12  * WebODF is distributed in the hope that it will be useful, but
 13  * WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU Affero General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU Affero General Public License
 18  * along with WebODF.  If not, see <http://www.gnu.org/licenses/>.
 19  * @licend
 20  *
 21  * @source: http://www.webodf.org/
 22  * @source: https://github.com/kogmbh/WebODF/
 23  */
 24 
 25 /*global runtime, ops */
 26 
 27 /*
 28  * create specific operation instances.
 29  */
 30 
 31 
 32 /**
 33  * @constructor
 34  */
 35 ops.OperationTransformer = function OperationTransformer() {
 36     "use strict";
 37 
 38     var operationTransformMatrix = new ops.OperationTransformMatrix();
 39 
 40     /**
 41      * @param {!{optype:string}} opSpecA op with lower priority in case of tie breaking
 42      * @param {!{optype:string}} opSpecB op with higher priority in case of tie breaking
 43      * @return {?{opSpecsA:!Array.<!{optype:string}>,
 44      *            opSpecsB:!Array.<!{optype:string}>}}
 45      */
 46     function transformOpVsOp(opSpecA, opSpecB) {
 47         return operationTransformMatrix.transformOpspecVsOpspec(opSpecA, opSpecB);
 48     }
 49 
 50     /**
 51      * @param {!Array.<!{optype:string}>} opSpecsA   sequence of ops with lower priority in case of tie breaking
 52      * @param {?{optype:string}} opSpecB   op with higher priority in case of tie breaking
 53      * @return {?{opSpecsA:!Array.<!{optype:string}>,
 54      *            opSpecsB:!Array.<!Object>}}
 55      */
 56     function transformOpListVsOp(opSpecsA, opSpecB) {
 57         var transformResult, transformListResult,
 58             transformedOpspecsA = [],
 59             transformedOpspecsB = [];
 60 
 61         while (opSpecsA.length > 0 && opSpecB) {
 62             transformResult = transformOpVsOp(opSpecsA.shift(), opSpecB);
 63             // unresolvable operation conflicts?
 64             if (!transformResult) {
 65                 return null;
 66             }
 67 
 68             // take transformed ops of the list A
 69             transformedOpspecsA = transformedOpspecsA.concat(transformResult.opSpecsA);
 70 
 71             // handle transformed ops of the single op B
 72             // opB became a noop?
 73             if (transformResult.opSpecsB.length === 0) {
 74                 // so rest of opsAs stay unchanged, nothing else to do
 75                 transformedOpspecsA = transformedOpspecsA.concat(opSpecsA);
 76                 opSpecB = null;
 77                 break;
 78             }
 79             // in case of opspecB transformed into multiple ops,
 80             // transform the remaining opsAs against any additional opsBs
 81             // so we can continue as if there is only one opB
 82             while (transformResult.opSpecsB.length > 1) {
 83                 transformListResult = transformOpListVsOp(opSpecsA, transformResult.opSpecsB.shift());
 84                 // unresolvable operation conflicts?
 85                 if (!transformListResult) {
 86                     return null;
 87                 }
 88                 // take transformed ops of the single b
 89                 transformedOpspecsB = transformedOpspecsB.concat(transformListResult.opSpecsB);
 90                 opSpecsA = transformListResult.opSpecsA;
 91             }
 92             // continue with last of transformed opsB
 93             opSpecB = transformResult.opSpecsB.pop();
 94         }
 95 
 96         if (opSpecB) {
 97             transformedOpspecsB.push(opSpecB);
 98         }
 99         return {
100             opSpecsA:  transformedOpspecsA,
101             opSpecsB:  transformedOpspecsB
102         };
103     }
104 
105     /**
106      * @return {!ops.OperationTransformMatrix}
107      */
108     this.getOperationTransformMatrix = function () {
109         return operationTransformMatrix;
110     };
111 
112     /**
113      * Currently the priority of ops for tie breaking is defined by how they
114      * are passed to this method. Which usually reflects the origin of the ops,
115      * being created locally or coming from the master session.
116      * E. g. the pullbox backend gives this way higher priority to the ops from
117      * the master session.
118      * That is just a randomly chosen rule, because there are no cases known
119      * yet where priority needs to be derived from something non-random.
120      * @param {!Array.<!Object>} opSpecsA   sequence of opspecs with lower priority in case of tie breaking
121      * @param {!Array.<!{optype:string}>} opSpecsB   opspecs with higher priority in case of tie breaking
122      * @return {?{opSpecsA:!Array.<!Object>,
123      *            opSpecsB:!Array.<!Object>}}
124      */
125     this.transform = function (opSpecsA, opSpecsB) {
126         var transformResult,
127             transformedOpspecsB = [];
128 
129         // transform all opSpecsB vs. all unsent client ops
130         while (opSpecsB.length > 0) {
131             transformResult = transformOpListVsOp(opSpecsA, opSpecsB.shift());
132             // unresolvable operation conflicts?
133             if (!transformResult) {
134                 return null;
135             }
136 
137             opSpecsA = transformResult.opSpecsA;
138             transformedOpspecsB = transformedOpspecsB.concat(transformResult.opSpecsB);
139         }
140 
141         return {
142             opSpecsA: /**@type{!Array.<!Object>}*/(opSpecsA),
143             opSpecsB: /**@type{!Array.<!Object>}*/(transformedOpspecsB)
144         };
145     };
146 };
147