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 */
16package android.databinding.tool.reflection.annotation;
17
18import android.databinding.tool.reflection.ModelAnalyzer;
19import android.databinding.tool.reflection.ModelClass;
20import android.databinding.tool.reflection.TypeUtil;
21import android.databinding.tool.util.L;
22
23import java.util.ArrayList;
24import java.util.HashMap;
25import java.util.Map;
26
27import javax.annotation.processing.Messager;
28import javax.annotation.processing.ProcessingEnvironment;
29import javax.lang.model.element.AnnotationMirror;
30import javax.lang.model.element.Element;
31import javax.lang.model.element.TypeElement;
32import javax.lang.model.type.DeclaredType;
33import javax.lang.model.type.TypeKind;
34import javax.lang.model.type.TypeMirror;
35import javax.lang.model.util.Elements;
36import javax.lang.model.util.Types;
37import javax.tools.Diagnostic;
38
39public class AnnotationAnalyzer extends ModelAnalyzer {
40
41    public static final Map<String, TypeKind> PRIMITIVE_TYPES;
42    static {
43        PRIMITIVE_TYPES = new HashMap<String, TypeKind>();
44        PRIMITIVE_TYPES.put("boolean", TypeKind.BOOLEAN);
45        PRIMITIVE_TYPES.put("byte", TypeKind.BYTE);
46        PRIMITIVE_TYPES.put("short", TypeKind.SHORT);
47        PRIMITIVE_TYPES.put("char", TypeKind.CHAR);
48        PRIMITIVE_TYPES.put("int", TypeKind.INT);
49        PRIMITIVE_TYPES.put("long", TypeKind.LONG);
50        PRIMITIVE_TYPES.put("float", TypeKind.FLOAT);
51        PRIMITIVE_TYPES.put("double", TypeKind.DOUBLE);
52    }
53
54    public final ProcessingEnvironment mProcessingEnv;
55
56    public AnnotationAnalyzer(ProcessingEnvironment processingEnvironment) {
57        mProcessingEnv = processingEnvironment;
58        setInstance(this);
59        L.setClient(new L.Client() {
60            @Override
61            public void printMessage(Diagnostic.Kind kind, String message, Element element) {
62                Messager messager = mProcessingEnv.getMessager();
63                if (element != null) {
64                    messager.printMessage(kind, message, element);
65                } else {
66                    messager.printMessage(kind, message);
67                }
68            }
69        });
70    }
71
72    public static AnnotationAnalyzer get() {
73        return (AnnotationAnalyzer) getInstance();
74    }
75
76    @Override
77    public AnnotationClass loadPrimitive(String className) {
78        TypeKind typeKind = PRIMITIVE_TYPES.get(className);
79        if (typeKind == null) {
80            return null;
81        } else {
82            Types typeUtils = getTypeUtils();
83            return new AnnotationClass(typeUtils.getPrimitiveType(typeKind));
84        }
85    }
86
87    @Override
88    public AnnotationClass findClass(String className, Map<String, String> imports) {
89        className = className.trim();
90        int numDimensions = 0;
91        while (className.endsWith("[]")) {
92            numDimensions++;
93            className = className.substring(0, className.length() - 2);
94        }
95        AnnotationClass primitive = loadPrimitive(className);
96        if (primitive != null) {
97            return addDimension(primitive.mTypeMirror, numDimensions);
98        }
99        int templateOpenIndex = className.indexOf('<');
100        DeclaredType declaredType;
101        if (templateOpenIndex < 0) {
102            TypeElement typeElement = getTypeElement(className, imports);
103            if (typeElement == null) {
104                return null;
105            }
106            declaredType = (DeclaredType) typeElement.asType();
107        } else {
108            int templateCloseIndex = className.lastIndexOf('>');
109            String paramStr = className.substring(templateOpenIndex + 1, templateCloseIndex);
110
111            String baseClassName = className.substring(0, templateOpenIndex);
112            TypeElement typeElement = getTypeElement(baseClassName, imports);
113            if (typeElement == null) {
114                L.e("cannot find type element for %s", baseClassName);
115                return null;
116            }
117
118            ArrayList<String> templateParameters = splitTemplateParameters(paramStr);
119            TypeMirror[] typeArgs = new TypeMirror[templateParameters.size()];
120            for (int i = 0; i < typeArgs.length; i++) {
121                final AnnotationClass clazz = findClass(templateParameters.get(i), imports);
122                if (clazz == null) {
123                    L.e("cannot find type argument for %s in %s", templateParameters.get(i),
124                            baseClassName);
125                    return null;
126                }
127                typeArgs[i] = clazz.mTypeMirror;
128            }
129            Types typeUtils = getTypeUtils();
130            declaredType = typeUtils.getDeclaredType(typeElement, typeArgs);
131        }
132        return addDimension(declaredType, numDimensions);
133    }
134
135    private AnnotationClass addDimension(TypeMirror type, int numDimensions) {
136        while (numDimensions > 0) {
137            type = getTypeUtils().getArrayType(type);
138            numDimensions--;
139        }
140        return new AnnotationClass(type);
141    }
142
143    private TypeElement getTypeElement(String className, Map<String, String> imports) {
144        Elements elementUtils = getElementUtils();
145        final boolean hasDot = className.indexOf('.') >= 0;
146        if (!hasDot && imports != null) {
147            // try the imports
148            String importedClass = imports.get(className);
149            if (importedClass != null) {
150                className = importedClass;
151            }
152        }
153        if (className.indexOf('.') < 0) {
154            // try java.lang.
155            String javaLangClass = "java.lang." + className;
156            try {
157                TypeElement javaLang = elementUtils.getTypeElement(javaLangClass);
158                if (javaLang != null) {
159                    return javaLang;
160                }
161            } catch (Exception e) {
162                // try the normal way
163            }
164        }
165        try {
166            TypeElement typeElement = elementUtils.getTypeElement(className);
167            if (typeElement == null && hasDot && imports != null) {
168                int lastDot = className.lastIndexOf('.');
169                TypeElement parent = getTypeElement(className.substring(0, lastDot), imports);
170                if (parent == null) {
171                    return null;
172                }
173                String name = parent.getQualifiedName() + "." + className.substring(lastDot + 1);
174                return getTypeElement(name, null);
175            }
176            return typeElement;
177        } catch (Exception e) {
178            return null;
179        }
180    }
181
182    private ArrayList<String> splitTemplateParameters(String templateParameters) {
183        ArrayList<String> list = new ArrayList<String>();
184        int index = 0;
185        int openCount = 0;
186        StringBuilder arg = new StringBuilder();
187        while (index < templateParameters.length()) {
188            char c = templateParameters.charAt(index);
189            if (c == ',' && openCount == 0) {
190                list.add(arg.toString());
191                arg.delete(0, arg.length());
192            } else if (!Character.isWhitespace(c)) {
193                arg.append(c);
194                if (c == '<') {
195                    openCount++;
196                } else if (c == '>') {
197                    openCount--;
198                }
199            }
200            index++;
201        }
202        list.add(arg.toString());
203        return list;
204    }
205
206    @Override
207    public ModelClass findClass(Class classType) {
208        return findClass(classType.getCanonicalName(), null);
209    }
210
211    public Types getTypeUtils() {
212        return mProcessingEnv.getTypeUtils();
213    }
214
215    public Elements getElementUtils() {
216        return mProcessingEnv.getElementUtils();
217    }
218
219    public ProcessingEnvironment getProcessingEnv() {
220        return mProcessingEnv;
221    }
222
223    @Override
224    public TypeUtil createTypeUtil() {
225        return new AnnotationTypeUtil(this);
226    }
227}
228