/* * Copyright (C) 2015 The Android Open Source Project * * 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 android.databinding.tool.expr; import android.databinding.tool.reflection.ModelAnalyzer; import android.databinding.tool.reflection.ModelClass; import android.databinding.tool.util.Preconditions; import android.databinding.tool.writer.KCode; import com.google.common.collect.Lists; import java.util.List; public class MathExpr extends Expr { static final String DYNAMIC_UTIL = "android.databinding.DynamicUtil"; final String mOp; MathExpr(Expr left, String op, Expr right) { super(left, right); mOp = op; } @Override protected String computeUniqueKey() { return join(getLeft().getUniqueKey(), mOp, getRight().getUniqueKey()); } @Override protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { if ("+".equals(mOp)) { // TODO we need upper casting etc. if (getLeft().getResolvedType().isString() || getRight().getResolvedType().isString()) { return modelAnalyzer.findClass(String.class); } } return modelAnalyzer.findCommonParentOf(getLeft().getResolvedType(), getRight().getResolvedType()); } @Override protected List constructDependencies() { return constructDynamicChildrenDependencies(); } public Expr getLeft() { return getChildren().get(0); } public Expr getRight() { return getChildren().get(1); } @Override protected KCode generateCode() { return new KCode().app("(", getLeft().toCode()) .app(") ") .app(mOp) .app(" (", getRight().toCode()) .app(")"); } @Override public String getInvertibleError() { if (mOp.equals("%")) { return "The modulus operator (%) is not supported in two-way binding."; } final Expr left = getLeft(); final Expr right = getRight(); if (left.isDynamic() == right.isDynamic()) { return "Two way binding with operator " + mOp + " supports only a single dynamic expressions."; } Expr dyn = left.isDynamic() ? left : right; if (getResolvedType().isString()) { Expr constExpr = left.isDynamic() ? right : left; if (!(constExpr instanceof SymbolExpr) || !"\"\"".equals(((SymbolExpr) constExpr).getText())) { return "Two-way binding with string concatenation operator (+) only supports the" + " empty string constant (`` or \"\")"; } if (!dyn.getResolvedType().unbox().isPrimitive()) { return "Two-way binding with string concatenation operator (+) only supports " + "primitives"; } } return dyn.getInvertibleError(); } @Override public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) { final Expr left = getLeft(); final Expr right = getRight(); Preconditions.check(left.isDynamic() ^ right.isDynamic(), "Two-way binding of a math " + "operations requires A single dynamic expression. Neither or both sides are " + "dynamic: (%s) %s (%s)", left, mOp, right); final Expr constExpr = (left.isDynamic() ? right : left).cloneToModel(model); final Expr varExpr = left.isDynamic() ? left : right; final Expr newValue; switch (mOp.charAt(0)) { case '+': // const + x = value => x = value - const if (getResolvedType().isString()) { // just convert back to the primitive type newValue = parseInverse(model, value, varExpr); } else { newValue = model.math(value, "-", constExpr); } break; case '*': // const * x = value => x = value / const newValue = model.math(value, "/", constExpr); break; case '-': if (!left.isDynamic()) { // const - x = value => x = const - (value) newValue = model.math(constExpr, "-", value); } else { // x - const = value => x = value + const) newValue = model.math(value, "+", constExpr); } break; case '/': if (!left.isDynamic()) { // const / x = value => x = const / value newValue = model.math(constExpr, "/", value); } else { // x / const = value => x = value * const newValue = model.math(value, "*", constExpr); } break; default: throw new IllegalStateException("Invalid math operation is not invertible: " + mOp); } return varExpr.generateInverse(model, newValue, bindingClassName); } private Expr parseInverse(ExprModel model, Expr value, Expr prev) { IdentifierExpr dynamicUtil = model.staticIdentifier(DYNAMIC_UTIL); dynamicUtil.setUserDefinedType(DYNAMIC_UTIL); return model.methodCall(dynamicUtil, "parse", Lists.newArrayList(value, prev)); } @Override public Expr cloneToModel(ExprModel model) { return model.math(getLeft().cloneToModel(model), mOp, getRight().cloneToModel(model)); } @Override public String toString() { return "(" + getLeft() + ") " + mOp + " (" + getRight() + ")"; } }