aboutsummaryrefslogtreecommitdiffhomepage
path: root/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/ElementUtils.java
blob: 19f050058555285751571b15bad3834adb5274cc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
package org.checkerframework.javacutil;

/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
*/

import static com.sun.tools.javac.code.Flags.ABSTRACT;
import static com.sun.tools.javac.code.Flags.EFFECTIVELY_FINAL;
import static com.sun.tools.javac.code.Flags.FINAL;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;

import com.sun.tools.javac.code.Symbol;

/**
 * A Utility class for analyzing {@code Element}s.
 */
public class ElementUtils {

    // Class cannot be instantiated.
    private ElementUtils() { throw new AssertionError("Class ElementUtils cannot be instantiated."); }

    /**
     * Returns the innermost type element enclosing the given element
     *
     * @param elem the enclosed element of a class
     * @return  the innermost type element
     */
    public static TypeElement enclosingClass(final Element elem) {
        Element result = elem;
        while (result != null && !result.getKind().isClass()
                && !result.getKind().isInterface()) {
            /*@Nullable*/ Element encl = result.getEnclosingElement();
            result = encl;
        }
        return (TypeElement) result;
    }

    /**
     * Returns the innermost package element enclosing the given element.
     * The same effect as {@link javax.lang.model.util.Elements#getPackageOf(Element)}.
     * Returns the element itself if it is a package.
     *
     * @param elem the enclosed element of a package
     * @return the innermost package element
     *
     */
    public static PackageElement enclosingPackage(final Element elem) {
        Element result = elem;
        while (result != null && result.getKind() != ElementKind.PACKAGE) {
            /*@Nullable*/ Element encl = result.getEnclosingElement();
            result = encl;
        }
        return (PackageElement) result;
    }

    /**
     * Returns the "parent" package element for the given package element.
     * For package "A.B" it gives "A".
     * For package "A" it gives the default package.
     * For the default package it returns null;
     *
     * Note that packages are not enclosed within each other, we have to manually climb
     * the namespaces. Calling "enclosingPackage" on a package element returns the
     * package element itself again.
     *
     * @param elem the package to start from
     * @return the parent package element
     *
     */
    public static PackageElement parentPackage(final Elements e, final PackageElement elem) {
        // The following might do the same thing:
        //   ((Symbol) elt).owner;
        // TODO: verify and see whether the change is worth it.
        String fqnstart = elem.getQualifiedName().toString();
        String fqn = fqnstart;
        if (fqn != null && !fqn.isEmpty() && fqn.contains(".")) {
            fqn = fqn.substring(0, fqn.lastIndexOf('.'));
            return e.getPackageElement(fqn);
        }
        return null;
    }

    /**
     * Returns true if the element is a static element: whether it is a static
     * field, static method, or static class
     *
     * @return true if element is static
     */
    public static boolean isStatic(Element element) {
        return element.getModifiers().contains(Modifier.STATIC);
    }

    /**
     * Returns true if the element is a final element: a final field, final
     * method, or final class
     *
     * @return true if the element is final
     */
    public static boolean isFinal(Element element) {
        return element.getModifiers().contains(Modifier.FINAL);
    }

    /**
     * Returns true if the element is a effectively final element.
     *
     * @return true if the element is effectively final
     */
    public static boolean isEffectivelyFinal(Element element) {
        Symbol sym = (Symbol) element;
        if (sym.getEnclosingElement().getKind() == ElementKind.METHOD &&
                (sym.getEnclosingElement().flags() & ABSTRACT) != 0) {
            return true;
        }
        return (sym.flags() & (FINAL | EFFECTIVELY_FINAL)) != 0;
    }

    /**
     * Returns the {@code TypeMirror} for usage of Element as a value. It
     * returns the return type of a method element, the class type of a
     * constructor, or simply the type mirror of the element itself.
     *
     * @return  the type for the element used as a value
     */
    public static TypeMirror getType(Element element) {
        if (element.getKind() == ElementKind.METHOD) {
            return ((ExecutableElement)element).getReturnType();
        } else if (element.getKind() == ElementKind.CONSTRUCTOR) {
            return enclosingClass(element).asType();
        } else {
            return element.asType();
        }
    }

    /**
     * Returns the qualified name of the inner most class enclosing
     * the provided {@code Element}
     *
     * @param element
     *            an element enclosed by a class, or a
     *            {@code TypeElement}
     * @return the qualified {@code Name} of the innermost class
     *         enclosing the element
     */
    public static /*@Nullable*/ Name getQualifiedClassName(Element element) {
        if (element.getKind() == ElementKind.PACKAGE) {
            PackageElement elem = (PackageElement) element;
            return elem.getQualifiedName();
        }

        TypeElement elem = enclosingClass(element);
        if (elem == null) {
            return null;
        }

        return elem.getQualifiedName();
    }

    /**
     * Returns a verbose name that identifies the element.
     */
    public static String getVerboseName(Element elt) {
        if (elt.getKind() == ElementKind.PACKAGE ||
                elt.getKind().isClass() ||
                elt.getKind().isInterface()) {
            return getQualifiedClassName(elt).toString();
        } else {
            return getQualifiedClassName(elt) + "." + elt.toString();
        }
    }

    /**
     * Check if the element is an element for 'java.lang.Object'
     *
     * @param element   the type element
     * @return true iff the element is java.lang.Object element
     */
    public static boolean isObject(TypeElement element) {
        return element.getQualifiedName().contentEquals("java.lang.Object");
    }

    /**
     * Returns true if the element is a constant time reference
     */
    public static boolean isCompileTimeConstant(Element elt) {
        return elt != null
            && (elt.getKind() == ElementKind.FIELD || elt.getKind() == ElementKind.LOCAL_VARIABLE)
            && ((VariableElement)elt).getConstantValue() != null;
    }

    /**
     * Returns true if the element is declared in ByteCode.
     * Always return false if elt is a package.
     */
    public static boolean isElementFromByteCode(Element elt) {
        if (elt == null) {
            return false;
        }

        if (elt instanceof Symbol.ClassSymbol) {
            Symbol.ClassSymbol clss = (Symbol.ClassSymbol) elt;
            if (null != clss.classfile) {
                // The class file could be a .java file
                return clss.classfile.getName().endsWith(".class");
            } else {
                return false;
            }
        }
        return isElementFromByteCode(elt.getEnclosingElement(), elt);
    }

    /**
     * Returns true if the element is declared in ByteCode.
     * Always return false if elt is a package.
     */
    private static boolean isElementFromByteCode(Element elt, Element orig) {
        if (elt == null) {
            return false;
        }
        if (elt instanceof Symbol.ClassSymbol) {
            Symbol.ClassSymbol clss = (Symbol.ClassSymbol) elt;
            if (null != clss.classfile) {
                // The class file could be a .java file
                return (clss.classfile.getName().endsWith(".class") ||
                        clss.classfile.getName().endsWith(".class)") ||
                        clss.classfile.getName().endsWith(".class)]"));
            } else {
                return false;
            }
        }
        return isElementFromByteCode(elt.getEnclosingElement(), elt);
    }

    /**
     * Returns the field of the class
     */
    public static VariableElement findFieldInType(TypeElement type, String name) {
        for (VariableElement field: ElementFilter.fieldsIn(type.getEnclosedElements())) {
            if (field.getSimpleName().toString().equals(name)) {
                return field;
            }
        }
        return null;
    }

    public static Set<VariableElement> findFieldsInType(TypeElement type, Collection<String> names) {
        Set<VariableElement> results = new HashSet<VariableElement>();
        for (VariableElement field: ElementFilter.fieldsIn(type.getEnclosedElements())) {
            if (names.contains(field.getSimpleName().toString())) {
                results.add(field);
            }
        }
        return results;
    }

    public static boolean isError(Element element) {
        return element.getClass().getName().equals("com.sun.tools.javac.comp.Resolve$SymbolNotFoundError");
    }

    /**
     * Does the given element need a receiver for accesses?
     * For example, an access to a local variable does not require a receiver.
     *
     * @param element the element to test
     * @return whether the element requires a receiver for accesses
     */
    public static boolean hasReceiver(Element element) {
        return (element.getKind().isField() ||
                element.getKind() == ElementKind.METHOD ||
                element.getKind() == ElementKind.CONSTRUCTOR)
                && !ElementUtils.isStatic(element);
    }

    /**
     * Determine all type elements for the classes and interfaces referenced
     * in the extends/implements clauses of the given type element.
     * TODO: can we learn from the implementation of
     * com.sun.tools.javac.model.JavacElements.getAllMembers(TypeElement)?
     */
    public static List<TypeElement> getSuperTypes(Elements elements, TypeElement type) {

        List<TypeElement> superelems = new ArrayList<TypeElement>();
        if (type == null) {
            return superelems;
        }

        // Set up a stack containing type, which is our starting point.
        Deque<TypeElement> stack = new ArrayDeque<TypeElement>();
        stack.push(type);

        while (!stack.isEmpty()) {
            TypeElement current = stack.pop();

            // For each direct supertype of the current type element, if it
            // hasn't already been visited, push it onto the stack and
            // add it to our superelems set.
            TypeMirror supertypecls = current.getSuperclass();
            if (supertypecls.getKind() != TypeKind.NONE) {
                TypeElement supercls = (TypeElement) ((DeclaredType)supertypecls).asElement();
                if (!superelems.contains(supercls)) {
                    stack.push(supercls);
                    superelems.add(supercls);
                }
            }
            for (TypeMirror supertypeitf : current.getInterfaces()) {
                TypeElement superitf = (TypeElement) ((DeclaredType)supertypeitf).asElement();
                if (!superelems.contains(superitf)) {
                    stack.push(superitf);
                    superelems.add(superitf);
                }
            }
        }

        // Include java.lang.Object as implicit superclass for all classes and interfaces.
        TypeElement jlobject = elements.getTypeElement("java.lang.Object");
        if (!superelems.contains(jlobject)) {
            superelems.add(jlobject);
        }

        return Collections.<TypeElement>unmodifiableList(superelems);
    }

    /**
     * Return all fields declared in the given type or any superclass/interface.
     * TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement)
     * instead of our own getSuperTypes?
     */
    public static List<VariableElement> getAllFieldsIn(Elements elements, TypeElement type) {
        List<VariableElement> fields = new ArrayList<VariableElement>();
        fields.addAll(ElementFilter.fieldsIn(type.getEnclosedElements()));
        List<TypeElement> alltypes = getSuperTypes(elements, type);
        for (TypeElement atype : alltypes) {
            fields.addAll(ElementFilter.fieldsIn(atype.getEnclosedElements()));
        }
        return Collections.<VariableElement>unmodifiableList(fields);
    }

    /**
     * Return all methods declared in the given type or any superclass/interface.
     * Note that no constructors will be returned.
     * TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement)
     * instead of our own getSuperTypes?
     */
    public static List<ExecutableElement> getAllMethodsIn(Elements elements, TypeElement type) {
        List<ExecutableElement> meths = new ArrayList<ExecutableElement>();
        meths.addAll(ElementFilter.methodsIn(type.getEnclosedElements()));

        List<TypeElement> alltypes = getSuperTypes(elements, type);
        for (TypeElement atype : alltypes) {
            meths.addAll(ElementFilter.methodsIn(atype.getEnclosedElements()));
        }
        return Collections.<ExecutableElement>unmodifiableList(meths);
    }

    public static boolean isTypeDeclaration(Element elt) {
        switch (elt.getKind()) {
            // These tree kinds are always declarations.  Uses of the declared
            // types have tree kind IDENTIFIER.
            case ANNOTATION_TYPE:
            case CLASS:
            case ENUM:
            case INTERFACE:
            case TYPE_PARAMETER:
                return true;

            default:
                return false;
        }
    }

    /**
     * Check that a method Element matches a signature.
     *
     * Note: Matching the receiver type must be done elsewhere as
     * the Element receiver type is only populated when annotated.
     *
     * @param method the method Element
     * @param methodName the name of the method
     * @param parameters the formal parameters' Classes
     * @return true if the method matches
     */
    public static boolean matchesElement(ExecutableElement method,
            String methodName,
            Class<?> ... parameters) {

        if (!method.getSimpleName().toString().equals(methodName)) {
            return false;
        }

        if (method.getParameters().size() != parameters.length) {
            return false;
        } else {
            for (int i = 0; i < method.getParameters().size(); i++) {
                if (!method.getParameters().get(i).asType().toString().equals(
                        parameters[i].getName())) {

                    return false;
                }
            }
        }

        return true;
    }
}