/** * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.waveprotocol.wave.model.operation.testing; import org.waveprotocol.wave.model.operation.Domain; import org.waveprotocol.wave.model.operation.OperationException; import org.waveprotocol.wave.model.operation.OperationPair; import org.waveprotocol.wave.model.operation.TransformException; public class RationalDomain implements Domain<RationalDomain.Data, RationalDomain.Affine> { /** * Mutable boxed number */ public static class Data { Rational value; public Data(Rational value) { this.value = value; } @Override public String toString() { return value.toString(); } } /** * f(x) = ax + b */ public static final class Affine { // The requirement of this field makes for a very stupid operation, // but it allows us to break it in interesting ways in order to test the tester. final Rational initialState; final Rational finalState; final Rational a; final Rational b; public Affine(Rational initialState, Rational a, Rational b) { this.initialState = initialState; this.a = a; this.b = b; this.finalState = a.times(initialState).plus(b); } public Affine inverse() { // x = 1/a y - b/a return new Affine(finalState, a.reciprocal(), Rational.MINUS_ONE.times(b.dividedBy(a))); } public Affine of(Affine other, Rational newInitialState) throws OperationException { if (!other.finalState.equals(newInitialState)) { throw new OperationException("Op " + this + " cannot compose with op " + other); } return new Affine(other.initialState, a.times(other.a), a.times(other.b).plus(b)); } public Affine of(Affine other) throws OperationException { if (!other.finalState.equals(initialState)) { throw new OperationException("Op " + this + " cannot compose with op " + other); } return new Affine(other.initialState, a.times(other.a), a.times(other.b).plus(b)); } public void apply(Data state) throws OperationException { if (!state.value.equals(initialState)) { throw new OperationException("Op " + this + " does not apply to state " + state); } state.value = a.times(state.value).plus(b); } @Override public String toString() { return "(" + a + "*" + initialState + " + " + b + ") -> " + finalState; } } @Override public Data initialState() { return new Data(Rational.ZERO); } @Override public void apply(Affine op, Data state) throws OperationException { op.apply(state); } @Override public Affine compose(Affine f, Affine g) throws OperationException { return f.of(g); } @Override public OperationPair<Affine> transform(Affine clientOp, Affine serverOp) throws TransformException { try { Affine undoAndDoOther = serverOp.of(clientOp.inverse()); return new OperationPair<Affine>( new Affine(serverOp.finalState, clientOp.a, clientOp.b), clientOp.of(undoAndDoOther, undoAndDoOther.finalState)); } catch(OperationException e) { throw new TransformException(e.getMessage()); } } @Override public Affine invert(Affine operation) { return operation.inverse(); } @Override public Affine asOperation(Data state) { return new Affine(Rational.ZERO, Rational.ONE, state.value); } @Override public boolean equivalent(Data state1, Data state2) { return state1.value.equals(state2.value); } }