1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.databinding.tool.expr; 18 19 import android.databinding.tool.reflection.ModelAnalyzer; 20 import android.databinding.tool.reflection.ModelClass; 21 import android.databinding.tool.writer.KCode; 22 23 import java.util.List; 24 25 public class MathExpr extends Expr { 26 final String mOp; 27 MathExpr(Expr left, String op, Expr right) { 28 super(left, right); 29 mOp = op; 30 } 31 32 @Override 33 protected String computeUniqueKey() { 34 return addTwoWay(join(getLeft().getUniqueKey(), mOp, getRight().getUniqueKey())); 35 } 36 37 @Override 38 protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { 39 if ("+".equals(mOp)) { 40 // TODO we need upper casting etc. 41 if (getLeft().getResolvedType().isString() 42 || getRight().getResolvedType().isString()) { 43 return modelAnalyzer.findClass(String.class); 44 } 45 } 46 return modelAnalyzer.findCommonParentOf(getLeft().getResolvedType(), 47 getRight().getResolvedType()); 48 } 49 50 @Override 51 protected List<Dependency> constructDependencies() { 52 return constructDynamicChildrenDependencies(); 53 } 54 55 public String getOp() { 56 return mOp; 57 } 58 59 public Expr getLeft() { 60 return getChildren().get(0); 61 } 62 63 public Expr getRight() { 64 return getChildren().get(1); 65 } 66 67 @Override 68 protected KCode generateCode(boolean expand) { 69 return new KCode().app("", getLeft().toCode(expand)).app(mOp, getRight().toCode(expand)); 70 } 71 72 @Override 73 public String getInvertibleError() { 74 if (mOp.equals("%")) { 75 return "The modulus operator (%) is not supported in two-way binding."; 76 } else if (getResolvedType().isString()) { 77 return "String concatenation operator (+) is not supported in two-way binding."; 78 } 79 if (!getLeft().isDynamic()) { 80 return getRight().getInvertibleError(); 81 } else if (!getRight().isDynamic()) { 82 return getLeft().getInvertibleError(); 83 } else { 84 return "Arithmetic operator " + mOp + " is not supported with two dynamic expressions."; 85 } 86 } 87 88 private String inverseCast() { 89 if (!getLeft().isDynamic()) { 90 return inverseCast(getRight()); 91 } else { 92 return inverseCast(getLeft()); 93 } 94 } 95 96 private String inverseCast(Expr expr) { 97 if (!expr.getResolvedType().isAssignableFrom(getResolvedType())) { 98 return "(" + getResolvedType() + ")"; 99 } 100 return null; 101 } 102 103 @Override 104 public KCode toInverseCode(KCode value) { 105 if (!isDynamic()) { 106 return toCode(); 107 } 108 final Expr left = getLeft(); 109 final Expr right = getRight(); 110 final Expr constExpr = left.isDynamic() ? right : left; 111 final Expr varExpr = left.isDynamic() ? left : right; 112 final String cast = inverseCast(); 113 if (cast != null) { 114 value = new KCode(cast).app("(", value).app(")"); 115 } 116 switch (mOp.charAt(0)) { 117 case '+': // const + x = value => x = value - const 118 return varExpr.toInverseCode(value.app(" - (", constExpr.toCode()).app(")")); 119 case '*': // const * x = value => x = value / const 120 return varExpr.toInverseCode(value.app(" / (", constExpr.toCode()).app(")")); 121 case '-': 122 if (!left.isDynamic()) { // const - x = value => x = const - value) 123 return varExpr.toInverseCode(new KCode() 124 .app("(", constExpr.toCode()) 125 .app(") - (", value) 126 .app(")")); 127 } else { // x - const = value => x = value + const) 128 return varExpr.toInverseCode(value.app(" + ", constExpr.toCode())); 129 } 130 case '/': 131 if (!left.isDynamic()) { // const / x = value => x = const / value 132 return varExpr.toInverseCode(new KCode("(") 133 .app("", constExpr.toCode()) 134 .app(") / (", value) 135 .app(")")); 136 } else { // x / const = value => x = value * const 137 return varExpr.toInverseCode(new KCode("(") 138 .app("", value) 139 .app(") * (", constExpr.toCode()) 140 .app(")")); 141 } 142 } 143 throw new IllegalStateException("Invalid math operation is not invertible: " + mOp); 144 } 145 } 146