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 17package android.databinding.tool.expr; 18 19import android.databinding.tool.reflection.ModelAnalyzer; 20import android.databinding.tool.reflection.ModelClass; 21import android.databinding.tool.util.Preconditions; 22import android.databinding.tool.writer.KCode; 23 24import com.google.common.collect.Lists; 25 26import java.util.List; 27 28public class MathExpr extends Expr { 29 static final String DYNAMIC_UTIL = "android.databinding.DynamicUtil"; 30 final String mOp; 31 32 MathExpr(Expr left, String op, Expr right) { 33 super(left, right); 34 mOp = op; 35 } 36 37 @Override 38 protected String computeUniqueKey() { 39 return join(getLeft().getUniqueKey(), mOp, getRight().getUniqueKey()); 40 } 41 42 @Override 43 protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { 44 if ("+".equals(mOp)) { 45 // TODO we need upper casting etc. 46 if (getLeft().getResolvedType().isString() 47 || getRight().getResolvedType().isString()) { 48 return modelAnalyzer.findClass(String.class); 49 } 50 } 51 return modelAnalyzer.findCommonParentOf(getLeft().getResolvedType(), 52 getRight().getResolvedType()); 53 } 54 55 @Override 56 protected List<Dependency> constructDependencies() { 57 return constructDynamicChildrenDependencies(); 58 } 59 60 public Expr getLeft() { 61 return getChildren().get(0); 62 } 63 64 public Expr getRight() { 65 return getChildren().get(1); 66 } 67 68 @Override 69 protected KCode generateCode() { 70 return new KCode().app("(", getLeft().toCode()) 71 .app(") ") 72 .app(mOp) 73 .app(" (", getRight().toCode()) 74 .app(")"); 75 } 76 77 @Override 78 public String getInvertibleError() { 79 if (mOp.equals("%")) { 80 return "The modulus operator (%) is not supported in two-way binding."; 81 } 82 83 final Expr left = getLeft(); 84 final Expr right = getRight(); 85 if (left.isDynamic() == right.isDynamic()) { 86 return "Two way binding with operator " + mOp + 87 " supports only a single dynamic expressions."; 88 } 89 Expr dyn = left.isDynamic() ? left : right; 90 if (getResolvedType().isString()) { 91 Expr constExpr = left.isDynamic() ? right : left; 92 93 if (!(constExpr instanceof SymbolExpr) || 94 !"\"\"".equals(((SymbolExpr) constExpr).getText())) { 95 return "Two-way binding with string concatenation operator (+) only supports the" + 96 " empty string constant (`` or \"\")"; 97 } 98 if (!dyn.getResolvedType().unbox().isPrimitive()) { 99 return "Two-way binding with string concatenation operator (+) only supports " + 100 "primitives"; 101 } 102 } 103 return dyn.getInvertibleError(); 104 } 105 106 @Override 107 public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) { 108 final Expr left = getLeft(); 109 final Expr right = getRight(); 110 Preconditions.check(left.isDynamic() ^ right.isDynamic(), "Two-way binding of a math " + 111 "operations requires A single dynamic expression. Neither or both sides are " + 112 "dynamic: (%s) %s (%s)", left, mOp, right); 113 final Expr constExpr = (left.isDynamic() ? right : left).cloneToModel(model); 114 final Expr varExpr = left.isDynamic() ? left : right; 115 final Expr newValue; 116 switch (mOp.charAt(0)) { 117 case '+': // const + x = value => x = value - const 118 if (getResolvedType().isString()) { 119 // just convert back to the primitive type 120 newValue = parseInverse(model, value, varExpr); 121 } else { 122 newValue = model.math(value, "-", constExpr); 123 } 124 break; 125 case '*': // const * x = value => x = value / const 126 newValue = model.math(value, "/", constExpr); 127 break; 128 case '-': 129 if (!left.isDynamic()) { // const - x = value => x = const - (value) 130 newValue = model.math(constExpr, "-", value); 131 } else { // x - const = value => x = value + const) 132 newValue = model.math(value, "+", constExpr); 133 } 134 break; 135 case '/': 136 if (!left.isDynamic()) { // const / x = value => x = const / value 137 newValue = model.math(constExpr, "/", value); 138 } else { // x / const = value => x = value * const 139 newValue = model.math(value, "*", constExpr); 140 } 141 break; 142 default: 143 throw new IllegalStateException("Invalid math operation is not invertible: " + mOp); 144 } 145 return varExpr.generateInverse(model, newValue, bindingClassName); 146 } 147 148 private Expr parseInverse(ExprModel model, Expr value, Expr prev) { 149 IdentifierExpr dynamicUtil = model.staticIdentifier(DYNAMIC_UTIL); 150 dynamicUtil.setUserDefinedType(DYNAMIC_UTIL); 151 152 return model.methodCall(dynamicUtil, "parse", Lists.newArrayList(value, prev)); 153 } 154 155 @Override 156 public Expr cloneToModel(ExprModel model) { 157 return model.math(getLeft().cloneToModel(model), mOp, getRight().cloneToModel(model)); 158 } 159 160 @Override 161 public String toString() { 162 return "(" + getLeft() + ") " + mOp + " (" + getRight() + ")"; 163 } 164} 165