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; 18 19 import android.databinding.tool.expr.Expr; 20 import android.databinding.tool.expr.ExprModel; 21 import android.databinding.tool.expr.LambdaExpr; 22 import android.databinding.tool.processing.ErrorMessages; 23 import android.databinding.tool.processing.Scope; 24 import android.databinding.tool.processing.scopes.LocationScopeProvider; 25 import android.databinding.tool.reflection.ModelAnalyzer; 26 import android.databinding.tool.reflection.ModelClass; 27 import android.databinding.tool.reflection.ModelMethod; 28 import android.databinding.tool.store.Location; 29 import android.databinding.tool.store.SetterStore; 30 import android.databinding.tool.store.SetterStore.BindingSetterCall; 31 import android.databinding.tool.store.SetterStore.SetterCall; 32 import android.databinding.tool.util.L; 33 import android.databinding.tool.util.Preconditions; 34 import android.databinding.tool.writer.LayoutBinderWriterKt; 35 36 import java.util.List; 37 import java.util.Map; 38 39 public class Binding implements LocationScopeProvider { 40 41 private final String mName; 42 private Expr mExpr; 43 private final BindingTarget mTarget; 44 private BindingSetterCall mSetterCall; 45 Binding(BindingTarget target, String name, Expr expr)46 public Binding(BindingTarget target, String name, Expr expr) { 47 this(target, name, expr, null); 48 } 49 Binding(BindingTarget target, String name, Expr expr, BindingSetterCall setterCall)50 public Binding(BindingTarget target, String name, Expr expr, BindingSetterCall setterCall) { 51 mTarget = target; 52 mName = name; 53 mExpr = expr; 54 mSetterCall = setterCall; 55 } 56 57 @Override provideScopeLocation()58 public List<Location> provideScopeLocation() { 59 return mExpr.getLocations(); 60 } 61 resolveListeners()62 public void resolveListeners() { 63 final ModelClass listenerParameter = getListenerParameter(mTarget, mName, mExpr.getModel()); 64 Expr listenerExpr = mExpr.resolveListeners(listenerParameter, null); 65 if (listenerExpr != mExpr) { 66 listenerExpr.setBindingExpression(true); 67 mExpr = listenerExpr; 68 } 69 } 70 resolveCallbackParams()71 public void resolveCallbackParams() { 72 if (!(mExpr instanceof LambdaExpr)) { 73 return; 74 } 75 LambdaExpr lambdaExpr = (LambdaExpr) mExpr; 76 final ModelClass listener = getListenerParameter(mTarget, mName, mExpr.getModel()); 77 Preconditions.checkNotNull(listener, ErrorMessages.CANNOT_FIND_SETTER_CALL, mName, 78 "lambda", getTarget().getInterfaceType()); 79 //noinspection ConstantConditions 80 List<ModelMethod> abstractMethods = listener.getAbstractMethods(); 81 int numberOfAbstractMethods = abstractMethods.size(); 82 if (numberOfAbstractMethods != 1) { 83 L.e(ErrorMessages.CANNOT_FIND_ABSTRACT_METHOD, mName, listener.getCanonicalName(), 84 numberOfAbstractMethods, 1); 85 } 86 final ModelMethod method = abstractMethods.get(0); 87 final int argCount = lambdaExpr.getCallbackExprModel().getArgCount(); 88 if (argCount != 0 && argCount != method.getParameterTypes().length) { 89 L.e(ErrorMessages.CALLBACK_ARGUMENT_COUNT_MISMATCH, listener.getCanonicalName(), 90 method.getName(), method.getParameterTypes().length, argCount); 91 } 92 lambdaExpr.setup(listener, method, mExpr.getModel().obtainCallbackId()); 93 } 94 resolveTwoWayExpressions()95 public void resolveTwoWayExpressions() { 96 Expr expr = mExpr.resolveTwoWayExpressions(null); 97 if (expr != mExpr) { 98 mExpr = expr; 99 } 100 } 101 getSetterCall()102 private SetterStore.BindingSetterCall getSetterCall() { 103 if (mSetterCall == null) { 104 try { 105 Scope.enter(getTarget()); 106 Scope.enter(this); 107 resolveSetterCall(); 108 if (mSetterCall == null) { 109 L.e(ErrorMessages.CANNOT_FIND_SETTER_CALL, mName, mExpr.getResolvedType(), 110 getTarget().getInterfaceType()); 111 } 112 } finally { 113 Scope.exit(); 114 Scope.exit(); 115 } 116 } 117 return mSetterCall; 118 } 119 resolveSetterCall()120 private void resolveSetterCall() { 121 ModelClass viewType = mTarget.getResolvedType(); 122 if (viewType != null && viewType.extendsViewStub()) { 123 if (isListenerAttribute(mName)) { 124 ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance(); 125 ModelClass viewStubProxy = modelAnalyzer. 126 findClass("android.databinding.ViewStubProxy", null); 127 mSetterCall = SetterStore.get(modelAnalyzer).getSetterCall(mName, 128 viewStubProxy, mExpr.getResolvedType(), mExpr.getModel().getImports()); 129 } else if (isViewStubAttribute(mName)) { 130 mSetterCall = new ViewStubDirectCall(mName, viewType, mExpr.getResolvedType(), 131 mExpr.getModel().getImports()); 132 } else { 133 mSetterCall = new ViewStubSetterCall(mName); 134 } 135 } else { 136 final SetterStore setterStore = SetterStore.get(ModelAnalyzer.getInstance()); 137 mSetterCall = setterStore.getSetterCall(mName, 138 viewType, mExpr.getResolvedType(), mExpr.getModel().getImports()); 139 } 140 } 141 142 /** 143 * Similar to getSetterCall, but assumes an Object parameter to find the best matching listener. 144 */ getListenerParameter(BindingTarget target, String name, ExprModel model)145 private static ModelClass getListenerParameter(BindingTarget target, String name, 146 ExprModel model) { 147 ModelClass viewType = target.getResolvedType(); 148 SetterCall setterCall; 149 ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance(); 150 ModelClass objectParameter = modelAnalyzer.findClass(Object.class); 151 SetterStore setterStore = SetterStore.get(modelAnalyzer); 152 if (viewType != null && viewType.extendsViewStub()) { 153 if (isListenerAttribute(name)) { 154 ModelClass viewStubProxy = modelAnalyzer. 155 findClass("android.databinding.ViewStubProxy", null); 156 setterCall = SetterStore.get(modelAnalyzer).getSetterCall(name, 157 viewStubProxy, objectParameter, model.getImports()); 158 } else if (isViewStubAttribute(name)) { 159 setterCall = null; // view stub attrs are not callbacks 160 } else { 161 setterCall = new ViewStubSetterCall(name); 162 } 163 } else { 164 setterCall = setterStore.getSetterCall(name, viewType, objectParameter, 165 model.getImports()); 166 } 167 if (setterCall != null) { 168 return setterCall.getParameterTypes()[0]; 169 } 170 List<SetterStore.MultiAttributeSetter> setters = 171 setterStore.getMultiAttributeSetterCalls(new String[]{name}, viewType, 172 new ModelClass[] {modelAnalyzer.findClass(Object.class)}); 173 if (setters.isEmpty()) { 174 return null; 175 } else { 176 return setters.get(0).getParameterTypes()[0]; 177 } 178 } 179 getTarget()180 public BindingTarget getTarget() { 181 return mTarget; 182 } 183 toJavaCode(String targetViewName, String bindingComponent)184 public String toJavaCode(String targetViewName, String bindingComponent) { 185 final String currentValue = requiresOldValue() 186 ? "this." + LayoutBinderWriterKt.getOldValueName(mExpr) : null; 187 final String argCode = getExpr().toCode().generate(); 188 return getSetterCall().toJava(bindingComponent, targetViewName, currentValue, argCode); 189 } 190 getBindingAdapterInstanceClass()191 public String getBindingAdapterInstanceClass() { 192 return getSetterCall().getBindingAdapterInstanceClass(); 193 } 194 getComponentExpressions()195 public Expr[] getComponentExpressions() { 196 return new Expr[] { mExpr }; 197 } 198 requiresOldValue()199 public boolean requiresOldValue() { 200 return getSetterCall().requiresOldValue(); 201 } 202 203 /** 204 * The min api level in which this binding should be executed. 205 * <p> 206 * This should be the minimum value among the dependencies of this binding. For now, we only 207 * check the setter. 208 */ getMinApi()209 public int getMinApi() { 210 return getSetterCall().getMinApi(); 211 } 212 getName()213 public String getName() { 214 return mName; 215 } 216 getExpr()217 public Expr getExpr() { 218 return mExpr; 219 } 220 isViewStubAttribute(String name)221 private static boolean isViewStubAttribute(String name) { 222 return ("android:inflatedId".equals(name) || 223 "android:layout".equals(name) || 224 "android:visibility".equals(name) || 225 "android:layoutInflater".equals(name)); 226 } 227 isListenerAttribute(String name)228 private static boolean isListenerAttribute(String name) { 229 return ("android:onInflate".equals(name) || 230 "android:onInflateListener".equals(name)); 231 } 232 233 private static class ViewStubSetterCall extends SetterCall { 234 private final String mName; 235 ViewStubSetterCall(String name)236 public ViewStubSetterCall(String name) { 237 mName = name.substring(name.lastIndexOf(':') + 1); 238 } 239 240 @Override toJavaInternal(String componentExpression, String viewExpression, String converted)241 protected String toJavaInternal(String componentExpression, String viewExpression, 242 String converted) { 243 return "if (" + viewExpression + ".isInflated()) " + viewExpression + 244 ".getBinding().setVariable(BR." + mName + ", " + converted + ")"; 245 } 246 247 @Override toJavaInternal(String componentExpression, String viewExpression, String oldValue, String converted)248 protected String toJavaInternal(String componentExpression, String viewExpression, 249 String oldValue, String converted) { 250 return null; 251 } 252 253 @Override getMinApi()254 public int getMinApi() { 255 return 0; 256 } 257 258 @Override requiresOldValue()259 public boolean requiresOldValue() { 260 return false; 261 } 262 263 @Override getParameterTypes()264 public ModelClass[] getParameterTypes() { 265 return new ModelClass[] { 266 ModelAnalyzer.getInstance().findClass(Object.class) 267 }; 268 } 269 270 @Override getBindingAdapterInstanceClass()271 public String getBindingAdapterInstanceClass() { 272 return null; 273 } 274 } 275 276 private static class ViewStubDirectCall extends SetterCall { 277 private final SetterCall mWrappedCall; 278 ViewStubDirectCall(String name, ModelClass viewType, ModelClass resolvedType, Map<String, String> imports)279 public ViewStubDirectCall(String name, ModelClass viewType, ModelClass resolvedType, 280 Map<String, String> imports) { 281 mWrappedCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(name, 282 viewType, resolvedType, imports); 283 if (mWrappedCall == null) { 284 L.e("Cannot find the setter for attribute '%s' on %s with parameter type %s.", 285 name, viewType, resolvedType); 286 } 287 } 288 289 @Override toJavaInternal(String componentExpression, String viewExpression, String converted)290 protected String toJavaInternal(String componentExpression, String viewExpression, 291 String converted) { 292 return "if (!" + viewExpression + ".isInflated()) " + 293 mWrappedCall.toJava(componentExpression, viewExpression + ".getViewStub()", 294 null, converted); 295 } 296 297 @Override toJavaInternal(String componentExpression, String viewExpression, String oldValue, String converted)298 protected String toJavaInternal(String componentExpression, String viewExpression, 299 String oldValue, String converted) { 300 return null; 301 } 302 303 @Override getMinApi()304 public int getMinApi() { 305 return 0; 306 } 307 308 @Override requiresOldValue()309 public boolean requiresOldValue() { 310 return false; 311 } 312 313 @Override getParameterTypes()314 public ModelClass[] getParameterTypes() { 315 return new ModelClass[] { 316 ModelAnalyzer.getInstance().findClass(Object.class) 317 }; 318 } 319 320 @Override getBindingAdapterInstanceClass()321 public String getBindingAdapterInstanceClass() { 322 return mWrappedCall.getBindingAdapterInstanceClass(); 323 } 324 } 325 } 326