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.Binding; 20 import android.databinding.tool.BindingTarget; 21 import android.databinding.tool.InverseBinding; 22 import android.databinding.tool.ext.ExtKt; 23 import android.databinding.tool.processing.ErrorMessages; 24 import android.databinding.tool.processing.Scope; 25 import android.databinding.tool.reflection.Callable; 26 import android.databinding.tool.reflection.Callable.Type; 27 import android.databinding.tool.reflection.ModelAnalyzer; 28 import android.databinding.tool.reflection.ModelClass; 29 import android.databinding.tool.store.SetterStore; 30 import android.databinding.tool.store.SetterStore.BindingGetterCall; 31 import android.databinding.tool.util.BrNameUtil; 32 import android.databinding.tool.util.L; 33 import android.databinding.tool.util.Preconditions; 34 import android.databinding.tool.writer.KCode; 35 36 import com.google.common.collect.Lists; 37 38 import java.util.List; 39 40 public class FieldAccessExpr extends MethodBaseExpr { 41 // notification name for the field. Important when we map this to a method w/ different name 42 String mBrName; 43 Callable mGetter; 44 boolean mIsListener; 45 boolean mIsViewAttributeAccess; 46 47 FieldAccessExpr(Expr parent, String name) { 48 super(parent, name); 49 mName = name; 50 } 51 52 public Callable getGetter() { 53 if (mGetter == null) { 54 getResolvedType(); 55 } 56 return mGetter; 57 } 58 59 @Override 60 public String getInvertibleError() { 61 if (getGetter() == null) { 62 return "Listeners do not support two-way binding"; 63 } 64 if (mGetter.setterName == null) { 65 return "Two-way binding cannot resolve a setter for " + getResolvedType().toJavaCode() + 66 " property '" + mName + "'"; 67 } 68 if (!mGetter.isDynamic()) { 69 return "Cannot change a final field in " + getResolvedType().toJavaCode() + 70 " property " + mName; 71 } 72 return null; 73 } 74 75 public int getMinApi() { 76 return mGetter == null ? 0 : mGetter.getMinApi(); 77 } 78 79 @Override 80 public boolean isDynamic() { 81 if (mGetter == null) { 82 getResolvedType(); 83 } 84 if (mGetter == null || mGetter.type == Type.METHOD) { 85 return true; 86 } 87 // if it is static final, gone 88 if (getTarget().isDynamic()) { 89 // if owner is dynamic, then we can be dynamic unless we are static final 90 return !mGetter.isStatic() || mGetter.isDynamic(); 91 } 92 93 if (mIsViewAttributeAccess) { 94 return true; // must be able to invalidate this 95 } 96 97 // if owner is NOT dynamic, we can be dynamic if an only if getter is dynamic 98 return mGetter.isDynamic(); 99 } 100 101 public boolean hasBindableAnnotations() { 102 return mGetter != null && mGetter.canBeInvalidated(); 103 } 104 105 @Override 106 public Expr resolveListeners(ModelClass listener, Expr parent) { 107 final ModelClass targetType = getTarget().getResolvedType(); 108 if (getGetter() == null && (listener == null || !mIsListener)) { 109 L.e("Could not resolve %s.%s as an accessor or listener on the attribute.", 110 targetType.getCanonicalName(), mName); 111 return this; 112 } 113 try { 114 Expr listenerExpr = resolveListenersAsMethodReference(listener, parent); 115 L.w("Method references using '.' is deprecated. Instead of '%s', use '%s::%s'", 116 toString(), getTarget(), getName()); 117 return listenerExpr; 118 } catch (IllegalStateException e) { 119 if (getGetter() == null) { 120 L.e("%s", e.getMessage()); 121 } 122 return this; 123 } 124 } 125 126 @Override 127 protected String computeUniqueKey() { 128 return join(mName, ".", getTarget().getUniqueKey()); 129 } 130 131 public String getBrName() { 132 if (mIsListener) { 133 return null; 134 } 135 try { 136 Scope.enter(this); 137 Preconditions.checkNotNull(mGetter, "cannot get br name before resolving the getter"); 138 return mBrName; 139 } finally { 140 Scope.exit(); 141 } 142 } 143 144 @Override 145 protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) { 146 if (mIsListener) { 147 return modelAnalyzer.findClass(Object.class); 148 } 149 if (mGetter == null) { 150 Expr target = getTarget(); 151 target.getResolvedType(); 152 boolean isStatic = target instanceof StaticIdentifierExpr; 153 ModelClass resolvedType = target.getResolvedType(); 154 L.d("resolving %s. Resolved class type: %s", this, resolvedType); 155 156 mGetter = resolvedType.findGetterOrField(mName, isStatic); 157 158 if (mGetter == null) { 159 mIsListener = !resolvedType.findMethods(mName, isStatic).isEmpty(); 160 if (!mIsListener) { 161 L.e("Could not find accessor %s.%s", resolvedType.getCanonicalName(), mName); 162 } 163 return modelAnalyzer.findClass(Object.class); 164 } 165 166 if (mGetter.isStatic() && !isStatic) { 167 // found a static method on an instance. register a new one 168 replaceStaticIdentifier(resolvedType); 169 target = getTarget(); 170 } 171 172 if (mGetter.resolvedType.isObservableField()) { 173 // Make this the ".get()" and add an extra field access for the observable field 174 target.getParents().remove(this); 175 getChildren().remove(target); 176 177 FieldAccessExpr observableField = getModel().observableField(target, mName); 178 getChildren().add(observableField); 179 observableField.getParents().add(this); 180 mGetter = mGetter.resolvedType.findGetterOrField("", false); 181 mName = ""; 182 mBrName = ExtKt.br(mName); 183 } else if (hasBindableAnnotations()) { 184 mBrName = ExtKt.br(BrNameUtil.brKey(mGetter)); 185 } 186 } 187 return mGetter.resolvedType; 188 } 189 190 protected void replaceStaticIdentifier(ModelClass staticIdentifierType) { 191 getTarget().getParents().remove(this); 192 getChildren().remove(getTarget()); 193 StaticIdentifierExpr staticId = getModel().staticIdentifierFor(staticIdentifierType); 194 getChildren().add(staticId); 195 staticId.getParents().add(this); 196 } 197 198 @Override 199 public Expr resolveTwoWayExpressions(Expr parent) { 200 final Expr child = getTarget(); 201 if (!(child instanceof ViewFieldExpr)) { 202 return this; 203 } 204 final ViewFieldExpr expr = (ViewFieldExpr) child; 205 final BindingTarget bindingTarget = expr.getBindingTarget(); 206 207 // This is a binding to a View's attribute, so look for matching attribute 208 // on that View's BindingTarget. If there is an expression, we simply replace 209 // the binding with that binding expression. 210 for (Binding binding : bindingTarget.getBindings()) { 211 if (attributeMatchesName(binding.getName(), mName)) { 212 final Expr replacement = binding.getExpr(); 213 replaceExpression(parent, replacement); 214 return replacement; 215 } 216 } 217 218 // There was no binding expression to bind to. This should be a two-way binding. 219 // This is a synthesized two-way binding because we must capture the events from 220 // the View and change the value when the target View's attribute changes. 221 final SetterStore setterStore = SetterStore.get(ModelAnalyzer.getInstance()); 222 final ModelClass targetClass = expr.getResolvedType(); 223 BindingGetterCall getter = setterStore.getGetterCall(mName, targetClass, null, null); 224 if (getter == null) { 225 getter = setterStore.getGetterCall("android:" + mName, targetClass, null, null); 226 if (getter == null) { 227 L.e("Could not resolve the two-way binding attribute '%s' on type '%s'", 228 mName, targetClass); 229 } 230 } 231 InverseBinding inverseBinding = null; 232 for (Binding binding : bindingTarget.getBindings()) { 233 final Expr testExpr = binding.getExpr(); 234 if (testExpr instanceof TwoWayListenerExpr && 235 getter.getEventAttribute().equals(binding.getName())) { 236 inverseBinding = ((TwoWayListenerExpr) testExpr).mInverseBinding; 237 break; 238 } 239 } 240 if (inverseBinding == null) { 241 inverseBinding = bindingTarget.addInverseBinding(mName, getter); 242 } 243 inverseBinding.addChainedExpression(this); 244 mIsViewAttributeAccess = true; 245 enableDirectInvalidation(); 246 return this; 247 } 248 249 private static boolean attributeMatchesName(String attribute, String field) { 250 int colonIndex = attribute.indexOf(':'); 251 return attribute.substring(colonIndex + 1).equals(field); 252 } 253 254 private void replaceExpression(Expr parent, Expr replacement) { 255 if (parent != null) { 256 List<Expr> children = parent.getChildren(); 257 int index; 258 while ((index = children.indexOf(this)) >= 0) { 259 children.set(index, replacement); 260 replacement.getParents().add(parent); 261 } 262 while (getParents().remove(parent)) { 263 // just remove all copies of parent. 264 } 265 } 266 if (getParents().isEmpty()) { 267 getModel().removeExpr(this); 268 } 269 } 270 271 @Override 272 protected String asPackage() { 273 String parentPackage = getTarget().asPackage(); 274 return parentPackage == null ? null : parentPackage + "." + mName; 275 } 276 277 @Override 278 protected KCode generateCode() { 279 // once we can deprecate using Field.access for callbacks, we can get rid of this since 280 // it will be detected when resolve type is run. 281 Preconditions.checkNotNull(getGetter(), ErrorMessages.CANNOT_RESOLVE_TYPE, this); 282 KCode code = new KCode() 283 .app("", getTarget().toCode()).app("."); 284 if (getGetter().type == Callable.Type.FIELD) { 285 return code.app(getGetter().name); 286 } else { 287 return code.app(getGetter().name).app("()"); 288 } 289 } 290 291 @Override 292 public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) { 293 Expr castExpr = model.castExpr(getResolvedType().toJavaCode(), value); 294 Expr target = getTarget().cloneToModel(model); 295 Expr result; 296 if (getGetter().type == Callable.Type.FIELD) { 297 result = model.assignment(target, mName, castExpr); 298 } else { 299 result = model.methodCall(target, mGetter.setterName, Lists.newArrayList(castExpr)); 300 } 301 return result; 302 } 303 304 @Override 305 public Expr cloneToModel(ExprModel model) { 306 final Expr clonedTarget = getTarget().cloneToModel(model); 307 return model.field(clonedTarget, mName); 308 } 309 310 @Override 311 public String toString() { 312 String name = mName.isEmpty() ? "get()" : mName; 313 return getTarget().toString() + '.' + name; 314 } 315 } 316