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 package android.databinding.tool.reflection; 17 18 import android.databinding.tool.reflection.Callable.Type; 19 import android.databinding.tool.util.L; 20 import android.databinding.tool.util.StringUtils; 21 22 import java.util.ArrayList; 23 import java.util.Arrays; 24 import java.util.List; 25 26 import static android.databinding.tool.reflection.Callable.CAN_BE_INVALIDATED; 27 import static android.databinding.tool.reflection.Callable.DYNAMIC; 28 import static android.databinding.tool.reflection.Callable.STATIC; 29 30 public abstract class ModelClass { toJavaCode()31 public abstract String toJavaCode(); 32 33 /** 34 * @return whether this ModelClass represents an array. 35 */ isArray()36 public abstract boolean isArray(); 37 38 /** 39 * For arrays, lists, and maps, this returns the contained value. For other types, null 40 * is returned. 41 * 42 * @return The component type for arrays, the value type for maps, and the element type 43 * for lists. 44 */ getComponentType()45 public abstract ModelClass getComponentType(); 46 47 /** 48 * @return Whether or not this ModelClass can be treated as a List. This means 49 * it is a java.util.List, or one of the Sparse*Array classes. 50 */ isList()51 public boolean isList() { 52 for (ModelClass listType : ModelAnalyzer.getInstance().getListTypes()) { 53 if (listType != null) { 54 if (listType.isAssignableFrom(this)) { 55 return true; 56 } 57 } 58 } 59 return false; 60 } 61 62 /** 63 * @return whether or not this ModelClass can be considered a Map or not. 64 */ isMap()65 public boolean isMap() { 66 return ModelAnalyzer.getInstance().getMapType().isAssignableFrom(erasure()); 67 } 68 69 /** 70 * @return whether or not this ModelClass is a java.lang.String. 71 */ isString()72 public boolean isString() { 73 return ModelAnalyzer.getInstance().getStringType().equals(this); 74 } 75 76 /** 77 * @return whether or not this ModelClass represents a Reference type. 78 */ isNullable()79 public abstract boolean isNullable(); 80 81 /** 82 * @return whether or not this ModelClass represents a primitive type. 83 */ isPrimitive()84 public abstract boolean isPrimitive(); 85 86 /** 87 * @return whether or not this ModelClass represents a Java boolean 88 */ isBoolean()89 public abstract boolean isBoolean(); 90 91 /** 92 * @return whether or not this ModelClass represents a Java char 93 */ isChar()94 public abstract boolean isChar(); 95 96 /** 97 * @return whether or not this ModelClass represents a Java byte 98 */ isByte()99 public abstract boolean isByte(); 100 101 /** 102 * @return whether or not this ModelClass represents a Java short 103 */ isShort()104 public abstract boolean isShort(); 105 106 /** 107 * @return whether or not this ModelClass represents a Java int 108 */ isInt()109 public abstract boolean isInt(); 110 111 /** 112 * @return whether or not this ModelClass represents a Java long 113 */ isLong()114 public abstract boolean isLong(); 115 116 /** 117 * @return whether or not this ModelClass represents a Java float 118 */ isFloat()119 public abstract boolean isFloat(); 120 121 /** 122 * @return whether or not this ModelClass represents a Java double 123 */ isDouble()124 public abstract boolean isDouble(); 125 126 /** 127 * @return whether or not this has type parameters 128 */ isGeneric()129 public abstract boolean isGeneric(); 130 131 /** 132 * @return a list of Generic type paramters for the class. For example, if the class 133 * is List<T>, then the return value will be a list containing T. null is returned 134 * if this is not a generic type 135 */ getTypeArguments()136 public abstract List<ModelClass> getTypeArguments(); 137 138 /** 139 * @return whether this is a type variable. For example, in List<T>, T is a type variable. 140 * However, List<String>, String is not a type variable. 141 */ isTypeVar()142 public abstract boolean isTypeVar(); 143 144 /** 145 * @return whether this is a wildcard type argument or not. 146 */ isWildcard()147 public abstract boolean isWildcard(); 148 149 /** 150 * @return whether or not this ModelClass is java.lang.Object and not a primitive or subclass. 151 */ isObject()152 public boolean isObject() { 153 return ModelAnalyzer.getInstance().getObjectType().equals(this); 154 } 155 156 /** 157 * @return whether or not this ModelClass is an interface 158 */ isInterface()159 public abstract boolean isInterface(); 160 161 /** 162 * @return whether or not his is a ViewDataBinding subclass. 163 */ isViewDataBinding()164 public boolean isViewDataBinding() { 165 return ModelAnalyzer.getInstance().getViewDataBindingType().isAssignableFrom(this); 166 } 167 168 /** 169 * @return whether or not this ModelClass type extends ViewStub. 170 */ extendsViewStub()171 public boolean extendsViewStub() { 172 return ModelAnalyzer.getInstance().getViewStubType().isAssignableFrom(this); 173 } 174 175 /** 176 * @return whether or not this is an Observable type such as ObservableMap, ObservableList, 177 * or Observable. 178 */ isObservable()179 public boolean isObservable() { 180 ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance(); 181 return modelAnalyzer.getObservableType().isAssignableFrom(this) || 182 modelAnalyzer.getObservableListType().isAssignableFrom(this) || 183 modelAnalyzer.getObservableMapType().isAssignableFrom(this); 184 185 } 186 187 /** 188 * @return whether or not this is an ObservableField, or any of the primitive versions 189 * such as ObservableBoolean and ObservableInt 190 */ isObservableField()191 public boolean isObservableField() { 192 ModelClass erasure = erasure(); 193 for (ModelClass observableField : ModelAnalyzer.getInstance().getObservableFieldTypes()) { 194 if (observableField.isAssignableFrom(erasure)) { 195 return true; 196 } 197 } 198 return false; 199 } 200 201 /** 202 * @return whether or not this ModelClass represents a void 203 */ isVoid()204 public abstract boolean isVoid(); 205 206 /** 207 * When this is a boxed type, such as Integer, this will return the unboxed value, 208 * such as int. If this is not a boxed type, this is returned. 209 * 210 * @return The unboxed type of the class that this ModelClass represents or this if it isn't a 211 * boxed type. 212 */ unbox()213 public abstract ModelClass unbox(); 214 215 /** 216 * When this is a primitive type, such as boolean, this will return the boxed value, 217 * such as Boolean. If this is not a primitive type, this is returned. 218 * 219 * @return The boxed type of the class that this ModelClass represents or this if it isn't a 220 * primitive type. 221 */ box()222 public abstract ModelClass box(); 223 224 /** 225 * Returns whether or not the type associated with <code>that</code> can be assigned to 226 * the type associated with this ModelClass. If this and that only require boxing or unboxing 227 * then true is returned. 228 * 229 * @param that the ModelClass to compare. 230 * @return true if <code>that</code> requires only boxing or if <code>that</code> is an 231 * implementation of or subclass of <code>this</code>. 232 */ isAssignableFrom(ModelClass that)233 public abstract boolean isAssignableFrom(ModelClass that); 234 235 /** 236 * Returns an array containing all public methods on the type represented by this ModelClass 237 * with the name <code>name</code> and can take the passed-in types as arguments. This will 238 * also work if the arguments match VarArgs parameter. 239 * 240 * @param name The name of the method to find. 241 * @param args The types that the method should accept. 242 * @param staticOnly Whether only static methods should be returned or both instance methods 243 * and static methods are valid. 244 * 245 * @return An array containing all public methods with the name <code>name</code> and taking 246 * <code>args</code> parameters. 247 */ getMethods(String name, List<ModelClass> args, boolean staticOnly)248 public ModelMethod[] getMethods(String name, List<ModelClass> args, boolean staticOnly) { 249 ModelMethod[] methods = getDeclaredMethods(); 250 ArrayList<ModelMethod> matching = new ArrayList<ModelMethod>(); 251 for (ModelMethod method : methods) { 252 if (method.isPublic() && (!staticOnly || method.isStatic()) && 253 name.equals(method.getName()) && method.acceptsArguments(args)) { 254 matching.add(method); 255 } 256 } 257 return matching.toArray(new ModelMethod[matching.size()]); 258 } 259 260 /** 261 * Returns all public instance methods with the given name and number of parameters. 262 * 263 * @param name The name of the method to find. 264 * @param numParameters The number of parameters that the method should take 265 * @return An array containing all public methods with the given name and number of parameters. 266 */ getMethods(String name, int numParameters)267 public ModelMethod[] getMethods(String name, int numParameters) { 268 ModelMethod[] methods = getDeclaredMethods(); 269 ArrayList<ModelMethod> matching = new ArrayList<ModelMethod>(); 270 for (ModelMethod method : methods) { 271 if (method.isPublic() && !method.isStatic() && 272 name.equals(method.getName()) && 273 method.getParameterTypes().length == numParameters) { 274 matching.add(method); 275 } 276 } 277 return matching.toArray(new ModelMethod[matching.size()]); 278 } 279 280 /** 281 * Returns the public method with the name <code>name</code> with the parameters that 282 * best match args. <code>staticOnly</code> governs whether a static or instance method 283 * will be returned. If no matching method was found, null is returned. 284 * 285 * @param name The method name to find 286 * @param args The arguments that the method should accept 287 * @param staticOnly true if the returned method must be static or false if it does not 288 * matter. 289 */ getMethod(String name, List<ModelClass> args, boolean staticOnly)290 public ModelMethod getMethod(String name, List<ModelClass> args, boolean staticOnly) { 291 ModelMethod[] methods = getMethods(name, args, staticOnly); 292 L.d("looking methods for %s. static only ? %s . method count: %d", name, staticOnly, 293 methods.length); 294 for (ModelMethod method : methods) { 295 L.d("method: %s, %s", method.getName(), method.isStatic()); 296 } 297 if (methods.length == 0) { 298 return null; 299 } 300 ModelMethod bestMethod = methods[0]; 301 for (int i = 1; i < methods.length; i++) { 302 if (methods[i].isBetterArgMatchThan(bestMethod, args)) { 303 bestMethod = methods[i]; 304 } 305 } 306 return bestMethod; 307 } 308 309 /** 310 * If this represents a class, the super class that it extends is returned. If this 311 * represents an interface, the interface that this extends is returned. 312 * <code>null</code> is returned if this is not a class or interface, such as an int, or 313 * if it is java.lang.Object or an interface that does not extend any other type. 314 * 315 * @return The class or interface that this ModelClass extends or null. 316 */ getSuperclass()317 public abstract ModelClass getSuperclass(); 318 319 /** 320 * @return A String representation of the class or interface that this represents, not 321 * including any type arguments. 322 */ getCanonicalName()323 public String getCanonicalName() { 324 return erasure().toJavaCode(); 325 } 326 327 /** 328 * @return The class or interface name of this type or the primitive type if it isn't a 329 * reference type. 330 */ getSimpleName()331 public String getSimpleName() { 332 final String canonicalName = getCanonicalName(); 333 final int dotIndex = canonicalName.lastIndexOf('.'); 334 if (dotIndex >= 0) { 335 return canonicalName.substring(dotIndex + 1); 336 } 337 return canonicalName; 338 } 339 340 /** 341 * Returns this class type without any generic type arguments. 342 * @return this class type without any generic type arguments. 343 */ erasure()344 public abstract ModelClass erasure(); 345 346 /** 347 * Since when this class is available. Important for Binding expressions so that we don't 348 * call non-existing APIs when setting UI. 349 * 350 * @return The SDK_INT where this method was added. If it is not a framework method, should 351 * return 1. 352 */ getMinApi()353 public int getMinApi() { 354 return SdkUtil.getMinApi(this); 355 } 356 357 /** 358 * Returns the JNI description of the method which can be used to lookup it in SDK. 359 * @see TypeUtil 360 */ getJniDescription()361 public abstract String getJniDescription(); 362 363 /** 364 * Returns a list of all abstract methods in the type. 365 */ getAbstractMethods()366 public List<ModelMethod> getAbstractMethods() { 367 ArrayList<ModelMethod> abstractMethods = new ArrayList<ModelMethod>(); 368 ModelMethod[] methods = getDeclaredMethods(); 369 for (ModelMethod method : methods) { 370 if (method.isAbstract()) { 371 abstractMethods.add(method); 372 } 373 } 374 return abstractMethods; 375 } 376 377 /** 378 * Returns the getter method or field that the name refers to. 379 * @param name The name of the field or the body of the method name -- can be name(), 380 * getName(), or isName(). 381 * @param staticOnly Whether this should look for static methods and fields or instance 382 * versions 383 * @return the getter method or field that the name refers to or null if none can be found. 384 */ findGetterOrField(String name, boolean staticOnly)385 public Callable findGetterOrField(String name, boolean staticOnly) { 386 if ("length".equals(name) && isArray()) { 387 return new Callable(Type.FIELD, name, null, 388 ModelAnalyzer.getInstance().loadPrimitive("int"), 0, 0); 389 } 390 String capitalized = StringUtils.capitalize(name); 391 String[] methodNames = { 392 "get" + capitalized, 393 "is" + capitalized, 394 name 395 }; 396 for (String methodName : methodNames) { 397 ModelMethod[] methods = getMethods(methodName, new ArrayList<ModelClass>(), staticOnly); 398 for (ModelMethod method : methods) { 399 if (method.isPublic() && (!staticOnly || method.isStatic()) && 400 !method.getReturnType(Arrays.asList(method.getParameterTypes())).isVoid()) { 401 int flags = DYNAMIC; 402 if (method.isStatic()) { 403 flags |= STATIC; 404 } 405 if (method.isBindable()) { 406 flags |= CAN_BE_INVALIDATED; 407 } else { 408 // if method is not bindable, look for a backing field 409 final ModelField backingField = getField(name, true, method.isStatic()); 410 L.d("backing field for method %s is %s", method.getName(), 411 backingField == null ? "NOT FOUND" : backingField.getName()); 412 if (backingField != null && backingField.isBindable()) { 413 flags |= CAN_BE_INVALIDATED; 414 } 415 } 416 final ModelMethod setterMethod = findSetter(method, name); 417 final String setterName = setterMethod == null ? null : setterMethod.getName(); 418 final Callable result = new Callable(Callable.Type.METHOD, methodName, 419 setterName, method.getReturnType(null), method.getParameterTypes().length, 420 flags); 421 return result; 422 } 423 } 424 } 425 426 // could not find a method. Look for a public field 427 ModelField publicField = null; 428 if (staticOnly) { 429 publicField = getField(name, false, true); 430 } else { 431 // first check non-static 432 publicField = getField(name, false, false); 433 if (publicField == null) { 434 // check for static 435 publicField = getField(name, false, true); 436 } 437 } 438 if (publicField == null) { 439 return null; 440 } 441 ModelClass fieldType = publicField.getFieldType(); 442 int flags = 0; 443 String setterFieldName = name; 444 if (publicField.isStatic()) { 445 flags |= STATIC; 446 } 447 if (!publicField.isFinal()) { 448 setterFieldName = null; 449 flags |= DYNAMIC; 450 } 451 if (publicField.isBindable()) { 452 flags |= CAN_BE_INVALIDATED; 453 } 454 return new Callable(Callable.Type.FIELD, name, setterFieldName, fieldType, 0, flags); 455 } 456 findInstanceGetter(String name)457 public ModelMethod findInstanceGetter(String name) { 458 String capitalized = StringUtils.capitalize(name); 459 String[] methodNames = { 460 "get" + capitalized, 461 "is" + capitalized, 462 name 463 }; 464 for (String methodName : methodNames) { 465 ModelMethod[] methods = getMethods(methodName, new ArrayList<ModelClass>(), false); 466 for (ModelMethod method : methods) { 467 if (method.isPublic() && !method.isStatic() && 468 !method.getReturnType(Arrays.asList(method.getParameterTypes())).isVoid()) { 469 return method; 470 } 471 } 472 } 473 return null; 474 } 475 getField(String name, boolean allowPrivate, boolean isStatic)476 private ModelField getField(String name, boolean allowPrivate, boolean isStatic) { 477 ModelField[] fields = getDeclaredFields(); 478 for (ModelField field : fields) { 479 boolean nameMatch = name.equals(field.getName()) || 480 name.equals(stripFieldName(field.getName())); 481 if (nameMatch && field.isStatic() == isStatic && 482 (allowPrivate || field.isPublic())) { 483 return field; 484 } 485 } 486 return null; 487 } 488 findSetter(ModelMethod getter, String originalName)489 private ModelMethod findSetter(ModelMethod getter, String originalName) { 490 final String capitalized = StringUtils.capitalize(originalName); 491 final String[] possibleNames; 492 if (originalName.equals(getter.getName())) { 493 possibleNames = new String[] { originalName, "set" + capitalized }; 494 } else if (getter.getName().startsWith("is")){ 495 possibleNames = new String[] { "set" + capitalized, "setIs" + capitalized }; 496 } else { 497 possibleNames = new String[] { "set" + capitalized }; 498 } 499 for (String name : possibleNames) { 500 List<ModelMethod> methods = findMethods(name, getter.isStatic()); 501 if (methods != null) { 502 ModelClass param = getter.getReturnType(null); 503 for (ModelMethod method : methods) { 504 ModelClass[] parameterTypes = method.getParameterTypes(); 505 if (parameterTypes != null && parameterTypes.length == 1 && 506 parameterTypes[0].equals(param) && 507 method.isStatic() == getter.isStatic()) { 508 return method; 509 } 510 } 511 } 512 } 513 return null; 514 } 515 516 /** 517 * Finds public methods that matches the given name exactly. These may be resolved into 518 * listener methods during Expr.resolveListeners. 519 */ findMethods(String name, boolean staticOnly)520 public List<ModelMethod> findMethods(String name, boolean staticOnly) { 521 ModelMethod[] methods = getDeclaredMethods(); 522 ArrayList<ModelMethod> matching = new ArrayList<ModelMethod>(); 523 for (ModelMethod method : methods) { 524 if (method.getName().equals(name) && (!staticOnly || method.isStatic()) && 525 method.isPublic()) { 526 matching.add(method); 527 } 528 } 529 if (matching.isEmpty()) { 530 return null; 531 } 532 return matching; 533 } 534 isIncomplete()535 public boolean isIncomplete() { 536 if (isTypeVar() || isWildcard()) { 537 return true; 538 } 539 List<ModelClass> typeArgs = getTypeArguments(); 540 if (typeArgs != null) { 541 for (ModelClass typeArg : typeArgs) { 542 if (typeArg.isIncomplete()) { 543 return true; 544 } 545 } 546 } 547 return false; 548 } 549 getDeclaredFields()550 protected abstract ModelField[] getDeclaredFields(); 551 getDeclaredMethods()552 protected abstract ModelMethod[] getDeclaredMethods(); 553 stripFieldName(String fieldName)554 private static String stripFieldName(String fieldName) { 555 // TODO: Make this configurable through IntelliJ 556 if (fieldName.length() > 2) { 557 final char start = fieldName.charAt(2); 558 if (fieldName.startsWith("m_") && Character.isJavaIdentifierStart(start)) { 559 return Character.toLowerCase(start) + fieldName.substring(3); 560 } 561 } 562 if (fieldName.length() > 1) { 563 final char start = fieldName.charAt(1); 564 final char fieldIdentifier = fieldName.charAt(0); 565 final boolean strip; 566 if (fieldIdentifier == '_') { 567 strip = true; 568 } else if (fieldIdentifier == 'm' && Character.isJavaIdentifierStart(start) && 569 !Character.isLowerCase(start)) { 570 strip = true; 571 } else { 572 strip = false; // not mUppercase format 573 } 574 if (strip) { 575 return Character.toLowerCase(start) + fieldName.substring(2); 576 } 577 } 578 return fieldName; 579 } 580 } 581