1 /* 2 * Copyright 2019 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.processor.view.inspector; 18 19 import androidx.annotation.NonNull; 20 import androidx.annotation.Nullable; 21 22 import com.squareup.javapoet.ClassName; 23 24 import java.util.Collection; 25 import java.util.Collections; 26 import java.util.HashMap; 27 import java.util.List; 28 import java.util.Map; 29 import java.util.NoSuchElementException; 30 import java.util.Objects; 31 import java.util.Optional; 32 33 /** 34 * Model of an inspectable class derived from annotations. 35 * 36 * This class does not use any {@code javax.lang.model} objects to facilitate building models for 37 * testing {@link InspectionCompanionGenerator}. 38 */ 39 public final class InspectableClassModel { 40 private final @NonNull ClassName mClassName; 41 private final @NonNull Map<String, Property> mPropertyMap; 42 43 /** 44 * @param className The name of the modeled class 45 */ InspectableClassModel(@onNull ClassName className)46 public InspectableClassModel(@NonNull ClassName className) { 47 mClassName = className; 48 mPropertyMap = new HashMap<>(); 49 } 50 51 @NonNull getClassName()52 public ClassName getClassName() { 53 return mClassName; 54 } 55 56 /** 57 * Add a property to the model, replacing an existing property of the same name. 58 * 59 * @param property The property to add or replace 60 */ putProperty(@onNull Property property)61 public void putProperty(@NonNull Property property) { 62 mPropertyMap.put(property.getName(), property); 63 } 64 65 /** 66 * Get a property by name. 67 * 68 * @param name The name of the property 69 * @return The property or an empty optional 70 */ 71 @NonNull getProperty(@onNull String name)72 public Optional<Property> getProperty(@NonNull String name) { 73 return Optional.ofNullable(mPropertyMap.get(name)); 74 } 75 76 /** 77 * Get all the properties defined on this model. 78 * 79 * @return An un-ordered collection of properties 80 */ 81 @NonNull getAllProperties()82 public Collection<Property> getAllProperties() { 83 return mPropertyMap.values(); 84 } 85 86 /** 87 * Represents a way to access a property, either a getter or a field. 88 */ 89 public static final class Accessor { 90 private final @NonNull String mName; 91 private final @NonNull Type mType; 92 93 /** 94 * Construct an accessor for a field. 95 * 96 * @param name The name of the field 97 * @return The new accessor 98 * @see Type#FIELD 99 */ 100 @NonNull ofField(@onNull String name)101 static Accessor ofField(@NonNull String name) { 102 return new Accessor(name, Type.FIELD); 103 } 104 105 /** 106 * Construct an accessor for a getter. 107 * 108 * @param name The name of the getter 109 * @return The new accessor 110 * @see Type#GETTER 111 */ 112 @NonNull ofGetter(@onNull String name)113 static Accessor ofGetter(@NonNull String name) { 114 return new Accessor(name, Type.GETTER); 115 } 116 Accessor(@onNull String name, @NonNull Type type)117 public Accessor(@NonNull String name, @NonNull Type type) { 118 mName = Objects.requireNonNull(name, "Accessor name must not be null"); 119 mType = Objects.requireNonNull(type, "Accessor type must not be null"); 120 } 121 122 @NonNull getName()123 public String getName() { 124 return mName; 125 } 126 127 @NonNull getType()128 public Type getType() { 129 return mType; 130 } 131 132 /** 133 * Get the invocation of this accessor. 134 * 135 * Example: {@code "getValue()"} for a getter or {@code "valueField"} for a field. 136 * 137 * @return A string representing the invocation of this accessor 138 */ 139 @NonNull invocation()140 public String invocation() { 141 switch (mType) { 142 case FIELD: 143 return mName; 144 case GETTER: 145 return String.format("%s()", mName); 146 default: 147 throw new NoSuchElementException( 148 String.format("No such accessor type %s", mType)); 149 } 150 } 151 152 public enum Type { 153 /** 154 * A property accessed by a public field. 155 * 156 * @see #ofField(String) 157 */ 158 FIELD, 159 160 /** 161 * A property accessed by a public getter method. 162 * 163 * @see #ofGetter(String) 164 */ 165 GETTER 166 } 167 } 168 169 /** 170 * Model an inspectable property 171 */ 172 public static final class Property { 173 private final @NonNull String mName; 174 private final @NonNull Accessor mAccessor; 175 private final @NonNull Type mType; 176 private boolean mAttributeIdInferrableFromR = true; 177 private int mAttributeId = 0; 178 private @Nullable List<IntEnumEntry> mIntEnumEntries; 179 private @Nullable List<IntFlagEntry> mIntFlagEntries; 180 Property(@onNull String name, @NonNull Accessor accessor, @NonNull Type type)181 public Property(@NonNull String name, @NonNull Accessor accessor, @NonNull Type type) { 182 mName = Objects.requireNonNull(name, "Name must not be null"); 183 mAccessor = Objects.requireNonNull(accessor, "Accessor must not be null"); 184 mType = Objects.requireNonNull(type, "Type must not be null"); 185 } 186 getAttributeId()187 public int getAttributeId() { 188 return mAttributeId; 189 } 190 191 /** 192 * Set the attribute ID, and mark the attribute ID as non-inferrable. 193 * 194 * @param attributeId The attribute ID for this property 195 */ setAttributeId(int attributeId)196 public void setAttributeId(int attributeId) { 197 mAttributeIdInferrableFromR = false; 198 mAttributeId = attributeId; 199 } 200 isAttributeIdInferrableFromR()201 public boolean isAttributeIdInferrableFromR() { 202 return mAttributeIdInferrableFromR; 203 } 204 setAttributeIdInferrableFromR(boolean attributeIdInferrableFromR)205 public void setAttributeIdInferrableFromR(boolean attributeIdInferrableFromR) { 206 mAttributeIdInferrableFromR = attributeIdInferrableFromR; 207 } 208 209 @NonNull getName()210 public String getName() { 211 return mName; 212 } 213 214 @NonNull getAccessor()215 public Accessor getAccessor() { 216 return mAccessor; 217 } 218 219 @NonNull getType()220 public Type getType() { 221 return mType; 222 } 223 224 /** 225 * Get the mapping for an {@code int} enumeration, if present. 226 * 227 * @return A list of mapping entries, empty if absent 228 */ 229 @NonNull getIntEnumEntries()230 public List<IntEnumEntry> getIntEnumEntries() { 231 if (mIntEnumEntries != null) { 232 return mIntEnumEntries; 233 } else { 234 return Collections.emptyList(); 235 } 236 } 237 setIntEnumEntries(@onNull List<IntEnumEntry> intEnumEntries)238 public void setIntEnumEntries(@NonNull List<IntEnumEntry> intEnumEntries) { 239 mIntEnumEntries = intEnumEntries; 240 } 241 242 /** 243 * Get the mapping of {@code int} flags, if present. 244 * 245 * @return A list of mapping entries, empty if absent 246 */ 247 @NonNull getIntFlagEntries()248 public List<IntFlagEntry> getIntFlagEntries() { 249 if (mIntFlagEntries != null) { 250 return mIntFlagEntries; 251 } else { 252 return Collections.emptyList(); 253 } 254 } 255 setIntFlagEntries(@onNull List<IntFlagEntry> intFlagEntries)256 public void setIntFlagEntries(@NonNull List<IntFlagEntry> intFlagEntries) { 257 mIntFlagEntries = intFlagEntries; 258 } 259 260 public enum Type { 261 /** Primitive or boxed {@code boolean} */ 262 BOOLEAN, 263 264 /** Primitive or boxed {@code byte} */ 265 BYTE, 266 267 /** Primitive or boxed {@code char} */ 268 CHAR, 269 270 /** Primitive or boxed {@code double} */ 271 DOUBLE, 272 273 /** Primitive or boxed {@code float} */ 274 FLOAT, 275 276 /** Primitive or boxed {@code int} */ 277 INT, 278 279 /** Primitive or boxed {@code long} */ 280 LONG, 281 282 /** Primitive or boxed {@code short} */ 283 SHORT, 284 285 /** Any other object */ 286 OBJECT, 287 288 /** 289 * A color object or packed color {@code int} or {@code long}. 290 * 291 * @see android.graphics.Color 292 * @see android.annotation.ColorInt 293 * @see android.annotation.ColorLong 294 */ 295 COLOR, 296 297 /** 298 * An {@code int} packed with a gravity specification 299 * 300 * @see android.view.Gravity 301 */ 302 GRAVITY, 303 304 /** 305 * An enumeration packed into an {@code int}. 306 * 307 * @see android.view.inspector.IntEnumMapping 308 * @see IntEnumEntry 309 */ 310 INT_ENUM, 311 312 /** 313 * Non-exclusive or partially-exclusive flags packed into an {@code int}. 314 * 315 * @see android.view.inspector.IntFlagMapping 316 * @see IntFlagEntry 317 */ 318 INT_FLAG, 319 320 /** A resource ID */ 321 RESOURCE_ID 322 } 323 } 324 325 /** 326 * Model one entry in a int enum mapping. 327 * 328 * @see android.view.inspector.IntEnumMapping 329 */ 330 public static final class IntEnumEntry { 331 private final @NonNull String mName; 332 private final int mValue; 333 IntEnumEntry(int value, @NonNull String name)334 public IntEnumEntry(int value, @NonNull String name) { 335 mName = Objects.requireNonNull(name, "Name must not be null"); 336 mValue = value; 337 } 338 339 @NonNull getName()340 public String getName() { 341 return mName; 342 } 343 getValue()344 public int getValue() { 345 return mValue; 346 } 347 } 348 349 /** 350 * Model one entry in an int flag mapping. 351 * 352 * @see android.view.inspector.IntFlagMapping 353 */ 354 public static final class IntFlagEntry { 355 private final @NonNull String mName; 356 private final int mTarget; 357 private final int mMask; 358 IntFlagEntry(int mask, int target, @NonNull String name)359 public IntFlagEntry(int mask, int target, @NonNull String name) { 360 mName = Objects.requireNonNull(name, "Name must not be null"); 361 mTarget = target; 362 mMask = mask; 363 } 364 IntFlagEntry(int target, String name)365 public IntFlagEntry(int target, String name) { 366 this(target, target, name); 367 } 368 369 @NonNull getName()370 public String getName() { 371 return mName; 372 } 373 getTarget()374 public int getTarget() { 375 return mTarget; 376 } 377 getMask()378 public int getMask() { 379 return mMask; 380 } 381 } 382 } 383