aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java
blob: e4ba1ca341b490691716dc2df65e5b44cf6c12fb (plain) (blame)
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
package ftbsc.lll.processor.containers;

import ftbsc.lll.exceptions.TargetNotFoundException;
import ftbsc.lll.mapper.data.ClassData;
import ftbsc.lll.processor.annotations.Find;
import ftbsc.lll.processor.annotations.Patch;
import ftbsc.lll.processor.ProcessorOptions;

import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.lang.annotation.Annotation;
import java.util.function.Function;

import static ftbsc.lll.processor.utils.ASTUtils.*;

/**
 * Container for information about a class.
 * Used internally for efficiency reasons.
 * @since 0.5.0
 */
public class ClassContainer {
   /**
    * The {@link ClassData} for the class represented by this container.
    */
   public final ClassData data;

   /**
    * The {@link TypeElement} corresponding to the class.
    * May only be null intentionally i.e. when the associated element is
    * an anonymous class or a child of an anonymous class.
    */
   public final TypeElement elem;

   /**
    * Private constructor, called from {@link #from(Annotation, Function, String[], ProcessorOptions)}.
    * @param fqn the fully-qualified name of the target class
    * @param innerNames an array of Strings containing the path to the inner class, may be null
    * @param options the {@link ProcessorOptions} to be used
    */
   private ClassContainer(String fqn, String[] innerNames, ProcessorOptions options) {
      // find and validate
      TypeElement elem = options.env.getElementUtils().getTypeElement(fqn);

      if(elem == null)
         throw new TargetNotFoundException("class", fqn);

      StringBuilder fqnBuilder = new StringBuilder(
         internalNameFromType(elem.asType(), options.env).replace('/', '.')
      );

      if(innerNames != null) {
         boolean skip = false;
         for(String inner : innerNames) {
            if(inner != null) fqnBuilder.append('$').append(inner);
            if(skip) continue;
            if(shouldValidate(inner)) {
               elem = elem
                  .getEnclosedElements()
                  .stream()
                  .filter(e -> e instanceof TypeElement)
                  .map(e -> (TypeElement) e)
                  .filter(e -> e.getSimpleName().contentEquals(inner))
                  .findFirst()
                  .orElse(null);
            } else {
               options.env.getMessager().printMessage(
                  Diagnostic.Kind.WARNING,
                  String.format(
                     "Anonymous class %s$%s and its children cannot be verified by the processor!",
                     fqnBuilder,
                     inner
                  )
               );
               elem = null;
               skip = true;
               continue;
            }
            if(elem == null)
               throw new TargetNotFoundException("class", inner);
         }
      }

      this.data = getClassData(fqnBuilder.toString(), options.mapper);
      this.elem = elem;
   }

   /**
    * Safely extracts a {@link Class} from an annotation and gets its fully qualified name.
    * @param ann the annotation containing the class
    * @param classFunction the annotation function returning the class
    * @param innerNames a string containing the inner class name or nothing
    * @param options the {@link ProcessorOptions} to be used
    * @param <T> the type of the annotation carrying the information
    * @return the fully qualified name of the given class
    * @since 0.5.0
    */
   public static <T extends Annotation> ClassContainer from(
      T ann,
      Function<T, Class<?>> classFunction,
      String[] innerNames,
      ProcessorOptions options
   ) {
      String fqn = getTypeFromAnnotation(ann, classFunction, options.env).toString();
      String[] inner = innerNames != null && innerNames.length != 0
         ? String.join("$", innerNames).split("\\$")
         : null;
      return new ClassContainer(fqn, inner, options);
   }

   /**
    * Safely extracts a {@link Class} from an annotation and gets its fully qualified name.
    * @param cl the {@link TypeElement} representing the class
    * @param options the {@link ProcessorOptions} to be used
    * @return the fully qualified name of the given class
    * @since 0.6.0
    */
   public static ClassContainer from(TypeElement cl, ProcessorOptions options) {
      return new ClassContainer(cl.getQualifiedName().toString(), null, options);
   }

   /**
    * Finds and builds a {@link ClassContainer} based on information contained
    * within {@link Patch} or a {@link Find} annotations, else returns a fallback.
    * @param fallback the {@link ClassContainer} it falls back on
    * @param p the {@link Patch} annotation to get info from
    * @param f the {@link Find} annotation to get info from
    * @param options the {@link ProcessorOptions} to be used
    * @return the built {@link ClassContainer} or the fallback if not enough information was present
    * @since 0.5.0
    */
   public static ClassContainer findOrFallback(ClassContainer fallback, Patch p, Find f, ProcessorOptions options) {
      if(f == null) return ClassContainer.from(p, Patch::value, p.inner(), options);
      ClassContainer cl = ClassContainer.from(f, Find::value, f.inner(), options);
      return cl.data.name.equals("java/lang/Object") ? fallback : cl;
   }
}