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