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      * TODO: priority could be read from op spec, here be an attribute from-server
 42      * @param {!{optype:string}} opSpecA op with lower priority in case of tie breaking
 43      * @param {!{optype:string}} opSpecB op with higher priority in case of tie breaking
 44      * @return {?{opSpecsA:!Array.<!{optype:string}>,
 45      *            opSpecsB:!Array.<!{optype:string}>}}
 46      */
 47     function transformOpVsOp(opSpecA, opSpecB) {
 48         return operationTransformMatrix.transformOpspecVsOpspec(opSpecA, opSpecB);
 49     }
 50 
 51     /**
 52      * @param {!Array.<!{optype:string}>} opSpecsA   sequence of ops with lower priority in case of tie breaking
 53      * @param {?{optype:string}} opSpecB   op with higher priority in case of tie breaking
 54      * @return {?{opSpecsA:!Array.<!{optype:string}>,
 55      *            opSpecsB:!Array.<!Object>}}
 56      */
 57     function transformOpListVsOp(opSpecsA, opSpecB) {
 58         var transformResult, transformListResult,
 59             transformedOpspecsA = [],
 60             transformedOpspecsB = [];
 61 
 62         while (opSpecsA.length > 0 && opSpecB) {
 63             transformResult = transformOpVsOp(opSpecsA.shift(), opSpecB);
 64             // unresolvable operation conflicts?
 65             if (!transformResult) {
 66                 return null;
 67             }
 68 
 69             // take transformed ops of the list A
 70             transformedOpspecsA = transformedOpspecsA.concat(transformResult.opSpecsA);
 71 
 72             // handle transformed ops of the single op B
 73             // opB became a noop?
 74             if (transformResult.opSpecsB.length === 0) {
 75                 // so rest of opsAs stay unchanged, nothing else to do
 76                 transformedOpspecsA = transformedOpspecsA.concat(opSpecsA);
 77                 opSpecB = null;
 78                 break;
 79             }
 80             // in case of opspecB transformed into multiple ops,
 81             // transform the remaining opsAs against any additional opsBs
 82             // so we can continue as if there is only one opB
 83             while (transformResult.opSpecsB.length > 1) {
 84                 transformListResult = transformOpListVsOp(opSpecsA, transformResult.opSpecsB.shift());
 85                 // unresolvable operation conflicts?
 86                 if (!transformListResult) {
 87                     return null;
 88                 }
 89                 // take transformed ops of the single b
 90                 transformedOpspecsB = transformedOpspecsB.concat(transformListResult.opSpecsB);
 91                 opSpecsA = transformListResult.opSpecsA;
 92             }
 93             // continue with last of transformed opsB
 94             opSpecB = transformResult.opSpecsB.pop();
 95         }
 96 
 97         if (opSpecB) {
 98             transformedOpspecsB.push(opSpecB);
 99         }
100         return {
101             opSpecsA:  transformedOpspecsA,
102             opSpecsB:  transformedOpspecsB
103         };
104     }
105 
106     /**
107      * @return {!ops.OperationTransformMatrix}
108      */
109     this.getOperationTransformMatrix = function () {
110         return operationTransformMatrix;
111     };
112 
113     /**
114      * @param {!Array.<!Object>} opSpecsA   sequence of opspecs with lower priority in case of tie breaking
115      * @param {!Array.<!{optype:string}>} opSpecsB   opspecs with higher priority in case of tie breaking
116      * @return {?{opSpecsA:!Array.<!Object>,
117      *            opSpecsB:!Array.<!Object>}}
118      */
119     this.transform = function (opSpecsA, opSpecsB) {
120         var transformResult,
121             transformedOpspecsB = [];
122 
123         // transform all opSpecsB vs. all unsent client ops
124         while (opSpecsB.length > 0) {
125             transformResult = transformOpListVsOp(opSpecsA, opSpecsB.shift());
126             // unresolvable operation conflicts?
127             if (!transformResult) {
128                 return null;
129             }
130 
131             opSpecsA = transformResult.opSpecsA;
132             transformedOpspecsB = transformedOpspecsB.concat(transformResult.opSpecsB);
133         }
134 
135         return {
136             opSpecsA: /**@type{!Array.<!Object>}*/(opSpecsA),
137             opSpecsB: /**@type{!Array.<!Object>}*/(transformedOpspecsB)
138         };
139     };
140 };
141