FieldAccessExpr.java revision d3f2b9229472c9dae9bf4ae8b3e2d653b5653b01
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.ext.ExtKt; 20import android.databinding.tool.Binding; 21import android.databinding.tool.BindingTarget; 22import android.databinding.tool.InverseBinding; 23import android.databinding.tool.processing.Scope; 24import android.databinding.tool.reflection.Callable; 25import android.databinding.tool.reflection.Callable.Type; 26import android.databinding.tool.reflection.ModelAnalyzer; 27import android.databinding.tool.reflection.ModelClass; 28import android.databinding.tool.reflection.ModelMethod; 29import android.databinding.tool.util.BrNameUtil; 30import android.databinding.tool.store.SetterStore; 31import android.databinding.tool.store.SetterStore.BindingGetterCall; 32import android.databinding.tool.util.L; 33import android.databinding.tool.util.Preconditions; 34import android.databinding.tool.writer.KCode; 35 36import java.util.List; 37 38public class FieldAccessExpr extends Expr { 39 String mName; 40 // notification name for the field. Important when we map this to a method w/ different name 41 String mBrName; 42 Callable mGetter; 43 final boolean mIsObservableField; 44 boolean mIsListener; 45 boolean mIsViewAttributeAccess; 46 47 FieldAccessExpr(Expr parent, String name) { 48 super(parent); 49 mName = name; 50 mIsObservableField = false; 51 } 52 53 FieldAccessExpr(Expr parent, String name, boolean isObservableField) { 54 super(parent); 55 mName = name; 56 mIsObservableField = isObservableField; 57 } 58 59 public Expr getChild() { 60 return getChildren().get(0); 61 } 62 63 public Callable getGetter() { 64 if (mGetter == null) { 65 getResolvedType(); 66 } 67 return mGetter; 68 } 69 70 @Override 71 public String getInvertibleError() { 72 if (getGetter().setterName == null) { 73 return "Two-way binding cannot resolve a setter for " + getResolvedType().toJavaCode() + 74 " property '" + mName + "'"; 75 } 76 if (!mGetter.isDynamic()) { 77 return "Cannot change a final field in " + getResolvedType().toJavaCode() + 78 " property " + mName; 79 } 80 return null; 81 } 82 83 public int getMinApi() { 84 return mGetter.getMinApi(); 85 } 86 87 @Override 88 public boolean isDynamic() { 89 if (mGetter == null) { 90 getResolvedType(); 91 } 92 if (mGetter == null || mGetter.type == Type.METHOD) { 93 return true; 94 } 95 // if it is static final, gone 96 if (getChild().isDynamic()) { 97 // if owner is dynamic, then we can be dynamic unless we are static final 98 return !mGetter.isStatic() || mGetter.isDynamic(); 99 } 100 101 if (mIsViewAttributeAccess) { 102 return true; // must be able to invalidate this 103 } 104 105 // if owner is NOT dynamic, we can be dynamic if an only if getter is dynamic 106 return mGetter.isDynamic(); 107 } 108 109 public boolean hasBindableAnnotations() { 110 return mGetter.canBeInvalidated(); 111 } 112 113 @Override 114 public Expr resolveListeners(ModelClass listener, Expr parent) { 115 if (mName == null || mName.isEmpty()) { 116 return this; // ObservableFields aren't listeners 117 } 118 final ModelClass childType = getChild().getResolvedType(); 119 if (getGetter() == null) { 120 if (listener == null || !mIsListener) { 121 L.e("Could not resolve %s.%s as an accessor or listener on the attribute.", 122 childType.getCanonicalName(), mName); 123 return this; 124 } 125 getChild().getParents().remove(this); 126 } else if (listener == null) { 127 return this; // Not a listener, but we have a getter. 128 } 129 List<ModelMethod> abstractMethods = listener.getAbstractMethods(); 130 int numberOfAbstractMethods = abstractMethods == null ? 0 : abstractMethods.size(); 131 if (numberOfAbstractMethods != 1) { 132 if (mGetter == null) { 133 L.e("Could not find accessor %s.%s and %s has %d abstract methods, so is" + 134 " not resolved as a listener", 135 childType.getCanonicalName(), mName, 136 listener.getCanonicalName(), numberOfAbstractMethods); 137 } 138 return this; 139 } 140 141 // Look for a signature matching the abstract method 142 final ModelMethod listenerMethod = abstractMethods.get(0); 143 final ModelClass[] listenerParameters = listenerMethod.getParameterTypes(); 144 boolean isStatic = getChild() instanceof StaticIdentifierExpr; 145 List<ModelMethod> methods = childType.findMethods(mName, isStatic); 146 if (methods == null) { 147 return this; 148 } 149 for (ModelMethod method : methods) { 150 if (acceptsParameters(method, listenerParameters) && 151 method.getReturnType(null).equals(listenerMethod.getReturnType(null))) { 152 resetResolvedType(); 153 // replace this with ListenerExpr in parent 154 Expr listenerExpr = getModel().listenerExpr(getChild(), mName, listener, 155 listenerMethod); 156 if (parent != null) { 157 int index; 158 while ((index = parent.getChildren().indexOf(this)) != -1) { 159 parent.getChildren().set(index, listenerExpr); 160 } 161 } 162 if (getModel().mBindingExpressions.contains(this)) { 163 getModel().bindingExpr(listenerExpr); 164 } 165 getParents().remove(parent); 166 if (getParents().isEmpty()) { 167 getModel().removeExpr(this); 168 } 169 return listenerExpr; 170 } 171 } 172 173 if (mGetter == null) { 174 L.e("Listener class %s with method %s did not match signature of any method %s.%s", 175 listener.getCanonicalName(), listenerMethod.getName(), 176 childType.getCanonicalName(), mName); 177 } 178 return this; 179 } 180 181 private boolean acceptsParameters(ModelMethod method, ModelClass[] listenerParameters) { 182 ModelClass[] parameters = method.getParameterTypes(); 183 if (parameters.length != listenerParameters.length) { 184 return false; 185 } 186 for (int i = 0; i < parameters.length; i++) { 187 if (!parameters[i].isAssignableFrom(listenerParameters[i])) { 188 return false; 189 } 190 } 191 return true; 192 } 193 194 @Override 195 protected List<Dependency> constructDependencies() { 196 final List<Dependency> dependencies = constructDynamicChildrenDependencies(); 197 for (Dependency dependency : dependencies) { 198 if (dependency.getOther() == getChild()) { 199 dependency.setMandatory(true); 200 } 201 } 202 return dependencies; 203 } 204 205 @Override 206 protected String computeUniqueKey() { 207 if (mIsObservableField) { 208 return addTwoWay(join(mName, "..", super.computeUniqueKey())); 209 } 210 return addTwoWay(join(mName, ".", super.computeUniqueKey())); 211 } 212 213 public String getName() { 214 return mName; 215 } 216 217 public String getBrName() { 218 if (mIsListener) { 219 return null; 220 } 221 try { 222 Scope.enter(this); 223 Preconditions.checkNotNull(mGetter, "cannot get br name before resolving the getter"); 224 return mBrName; 225 } finally { 226 Scope.exit(); 227 } 228 } 229 230 @Override 231 public void updateExpr(ModelAnalyzer modelAnalyzer) { 232 try { 233 Scope.enter(this); 234 resolveType(modelAnalyzer); 235 super.updateExpr(modelAnalyzer); 236 } finally { 237 Scope.exit(); 238 } 239 } 240 241 @Override 242 protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { 243 if (mIsListener) { 244 return modelAnalyzer.findClass(Object.class); 245 } 246 if (mGetter == null) { 247 Expr child = getChild(); 248 child.getResolvedType(); 249 boolean isStatic = child instanceof StaticIdentifierExpr; 250 ModelClass resolvedType = child.getResolvedType(); 251 L.d("resolving %s. Resolved class type: %s", this, resolvedType); 252 253 mGetter = resolvedType.findGetterOrField(mName, isStatic); 254 255 if (mGetter == null) { 256 mIsListener = resolvedType.findMethods(mName, isStatic) != null; 257 if (!mIsListener) { 258 L.e("Could not find accessor %s.%s", resolvedType.getCanonicalName(), mName); 259 } 260 return modelAnalyzer.findClass(Object.class); 261 } 262 263 if (mGetter.isStatic() && !isStatic) { 264 // found a static method on an instance. register a new one 265 child.getParents().remove(this); 266 getChildren().remove(child); 267 StaticIdentifierExpr staticId = getModel().staticIdentifierFor(resolvedType); 268 getChildren().add(staticId); 269 staticId.getParents().add(this); 270 child = getChild(); // replace the child for the next if stmt 271 } 272 273 if (mGetter.resolvedType.isObservableField()) { 274 // Make this the ".get()" and add an extra field access for the observable field 275 child.getParents().remove(this); 276 getChildren().remove(child); 277 278 FieldAccessExpr observableField = getModel().observableField(child, mName); 279 observableField.mGetter = mGetter; 280 281 getChildren().add(observableField); 282 observableField.getParents().add(this); 283 mGetter = mGetter.resolvedType.findGetterOrField("", false); 284 mName = ""; 285 mBrName = ExtKt.br(mName); 286 } else if (hasBindableAnnotations()) { 287 mBrName = ExtKt.br(BrNameUtil.brKey(mGetter)); 288 } 289 } 290 return mGetter.resolvedType; 291 } 292 293 @Override 294 public Expr resolveTwoWayExpressions(Expr parent) { 295 final Expr child = getChild(); 296 if (!(child instanceof ViewFieldExpr)) { 297 return this; 298 } 299 final ViewFieldExpr expr = (ViewFieldExpr) child; 300 final BindingTarget bindingTarget = expr.getBindingTarget(); 301 302 // This is a binding to a View's attribute, so look for matching attribute 303 // on that View's BindingTarget. If there is an expression, we simply replace 304 // the binding with that binding expression. 305 for (Binding binding : bindingTarget.getBindings()) { 306 if (attributeMatchesName(binding.getName(), mName)) { 307 final Expr replacement = binding.getExpr(); 308 replaceExpression(parent, replacement); 309 return replacement; 310 } 311 } 312 313 // There was no binding expression to bind to. This should be a two-way binding. 314 // This is a synthesized two-way binding because we must capture the events from 315 // the View and change the value when the target View's attribute changes. 316 final SetterStore setterStore = SetterStore.get(ModelAnalyzer.getInstance()); 317 final ModelClass targetClass = expr.getResolvedType(); 318 BindingGetterCall getter = setterStore.getGetterCall(mName, targetClass, null, null); 319 if (getter == null) { 320 getter = setterStore.getGetterCall("android:" + mName, targetClass, null, null); 321 if (getter == null) { 322 L.e("Could not resolve the two-way binding attribute '%s' on type '%s'", 323 mName, targetClass); 324 } 325 } 326 InverseBinding inverseBinding = null; 327 for (Binding binding : bindingTarget.getBindings()) { 328 final Expr testExpr = binding.getExpr(); 329 if (testExpr instanceof TwoWayListenerExpr && 330 getter.getEventAttribute().equals(binding.getName())) { 331 inverseBinding = ((TwoWayListenerExpr) testExpr).mInverseBinding; 332 break; 333 } 334 } 335 if (inverseBinding == null) { 336 inverseBinding = bindingTarget.addInverseBinding(mName, getter); 337 } 338 inverseBinding.addChainedExpression(this); 339 mIsViewAttributeAccess = true; 340 enableDirectInvalidation(); 341 return this; 342 } 343 344 private static boolean attributeMatchesName(String attribute, String field) { 345 int colonIndex = attribute.indexOf(':'); 346 return attribute.substring(colonIndex + 1).equals(field); 347 } 348 349 private void replaceExpression(Expr parent, Expr replacement) { 350 if (parent != null) { 351 List<Expr> children = parent.getChildren(); 352 int index; 353 while ((index = children.indexOf(this)) >= 0) { 354 children.set(index, replacement); 355 replacement.getParents().add(parent); 356 } 357 while (getParents().remove(parent)) { 358 // just remove all copies of parent. 359 } 360 } 361 if (getParents().isEmpty()) { 362 getModel().removeExpr(this); 363 } 364 } 365 366 @Override 367 protected String asPackage() { 368 String parentPackage = getChild().asPackage(); 369 return parentPackage == null ? null : parentPackage + "." + mName; 370 } 371 372 @Override 373 protected KCode generateCode(boolean expand) { 374 KCode code = new KCode(); 375 if (expand) { 376 String defaultValue = ModelAnalyzer.getInstance().getDefaultValue( 377 getResolvedType().toJavaCode()); 378 code.app("(", getChild().toCode(true)) 379 .app(" == null) ? ") 380 .app(defaultValue) 381 .app(" : "); 382 } 383 code.app("", getChild().toCode(expand)).app("."); 384 if (getGetter().type == Callable.Type.FIELD) { 385 return code.app(getGetter().name); 386 } else { 387 return code.app(getGetter().name).app("()"); 388 } 389 } 390 391 @Override 392 public KCode toInverseCode(KCode value) { 393 if (mGetter.setterName == null) { 394 throw new IllegalStateException("There is no inverse for " + toCode().generate()); 395 } 396 KCode castValue = new KCode("(").app(getResolvedType().toJavaCode() + ")(", value).app(")"); 397 String type = getChild().getResolvedType().toJavaCode(); 398 KCode code = new KCode("targetObj_."); 399 if (getGetter().type == Callable.Type.FIELD) { 400 code.app(getGetter().setterName).app(" = ", castValue).app(";"); 401 } else { 402 code.app(getGetter().setterName).app("(", castValue).app(")").app(";"); 403 } 404 return new KCode() 405 .app("final ") 406 .app(type) 407 .app(" targetObj_ = ", getChild().toCode(true)) 408 .app(";") 409 .nl(new KCode("if (targetObj_ != null) {")) 410 .tab(code) 411 .nl(new KCode("}")); 412 } 413} 414