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