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