3925239a98d2888f7a867a42b15871a3f3f8a75b
[idea/community.git] / platform / script-debugger / protocol / protocol-reader / src / org / jetbrains / protocolReader / FieldProcessor.java
1 package org.jetbrains.protocolReader;
2
3 import org.jetbrains.annotations.NotNull;
4 import org.jetbrains.jsonProtocol.JsonField;
5 import org.jetbrains.jsonProtocol.JsonNullable;
6 import org.jetbrains.jsonProtocol.JsonOptionalField;
7 import org.jetbrains.jsonProtocol.JsonSubtypeCasting;
8
9 import java.lang.reflect.Method;
10 import java.lang.reflect.Type;
11 import java.util.*;
12
13 final class FieldProcessor<T> {
14   private final List<FieldLoader> fieldLoaders = new ArrayList<>(2);
15   private final LinkedHashMap<Method, MethodHandler> methodHandlerMap = new LinkedHashMap<>();
16   private final List<VolatileFieldBinding> volatileFields = new ArrayList<>(2);
17   boolean lazyRead;
18   private final InterfaceReader reader;
19
20   FieldProcessor(@NotNull InterfaceReader reader, @NotNull Class<T> typeClass) {
21     this.reader = reader;
22
23     Method[] methods = typeClass.getMethods();
24     // todo sort by source location
25     Arrays.sort(methods, new Comparator<Method>() {
26       @Override
27       public int compare(@NotNull Method o1, @NotNull Method o2) {
28         return o1.getName().compareTo(o2.getName());
29       }
30     });
31
32     Package aPackage = typeClass.getPackage();
33     for (Method method : methods) {
34       Class<?> methodClass = method.getDeclaringClass();
35       // use method from super if super located in the same package
36       if (methodClass != typeClass && methodClass.getPackage() != aPackage) {
37         continue;
38       }
39
40       if (method.getParameterTypes().length != 0) {
41         throw new JsonProtocolModelParseException("No parameters expected in " + method);
42       }
43
44       try {
45         String fieldName = checkAndGetJsonFieldName(method);
46         MethodHandler methodHandler;
47
48         JsonSubtypeCasting jsonSubtypeCaseAnnotation = method.getAnnotation(JsonSubtypeCasting.class);
49         if (jsonSubtypeCaseAnnotation != null) {
50           methodHandler = processManualSubtypeMethod(method, jsonSubtypeCaseAnnotation);
51           lazyRead = true;
52         }
53         else {
54           methodHandler = processFieldGetterMethod(method, fieldName);
55         }
56         methodHandlerMap.put(method, methodHandler);
57       }
58       catch (JsonProtocolModelParseException e) {
59         throw new JsonProtocolModelParseException("Problem with method " + method, e);
60       }
61     }
62   }
63
64   private MethodHandler processFieldGetterMethod(@NotNull Method method, @NotNull String fieldName) {
65     Type genericReturnType = method.getGenericReturnType();
66     boolean nullable;
67     if (method.getAnnotation(JsonNullable.class) != null) {
68       nullable = true;
69     }
70     else if (genericReturnType == String.class || genericReturnType == Enum.class) {
71       JsonField jsonField = method.getAnnotation(JsonField.class);
72       if (jsonField != null) {
73         nullable = jsonField.optional() && !jsonField.allowAnyPrimitiveValue() && !jsonField.allowAnyPrimitiveValueAndMap();
74       }
75       else {
76         nullable = method.getAnnotation(JsonOptionalField.class) != null;
77       }
78     }
79     else {
80       nullable = false;
81     }
82
83     ValueReader fieldTypeParser = reader.getFieldTypeParser(genericReturnType, nullable, false, method);
84     if (fieldTypeParser != InterfaceReader.VOID_PARSER) {
85       fieldLoaders.add(new FieldLoader(fieldName, fieldTypeParser));
86     }
87
88     final String effectiveFieldName = fieldTypeParser == InterfaceReader.VOID_PARSER ? null : fieldName;
89     return new MethodHandler() {
90       @Override
91       void writeMethodImplementationJava(@NotNull ClassScope scope, @NotNull Method method, @NotNull TextOutput out) {
92         if (!nullable) {
93           out.append("@NotNull").newLine();
94         }
95         writeMethodDeclarationJava(out, method);
96         out.openBlock();
97         if (effectiveFieldName != null) {
98           out.append("return ").append(FieldLoader.FIELD_PREFIX).append(effectiveFieldName).semi();
99         }
100         out.closeBlock();
101       }
102     };
103   }
104
105   private MethodHandler processManualSubtypeMethod(final Method m, JsonSubtypeCasting jsonSubtypeCaseAnn) {
106     ValueReader fieldTypeParser = reader.getFieldTypeParser(m.getGenericReturnType(), false, !jsonSubtypeCaseAnn.reinterpret(), null);
107     VolatileFieldBinding fieldInfo = allocateVolatileField(fieldTypeParser, true);
108     LazyCachedMethodHandler handler = new LazyCachedMethodHandler(fieldTypeParser, fieldInfo);
109     ObjectValueReader<?> parserAsObjectValueParser = fieldTypeParser.asJsonTypeParser();
110     if (parserAsObjectValueParser != null && parserAsObjectValueParser.isSubtyping()) {
111       SubtypeCaster subtypeCaster = new SubtypeCaster(parserAsObjectValueParser.getType()) {
112         @Override
113         void writeJava(TextOutput out) {
114           out.append(m.getName()).append("()");
115         }
116       };
117       reader.subtypeCasters.add(subtypeCaster);
118     }
119     return handler;
120   }
121
122   List<VolatileFieldBinding> getVolatileFields() {
123     return volatileFields;
124   }
125
126   List<FieldLoader> getFieldLoaders() {
127     return fieldLoaders;
128   }
129
130   LinkedHashMap<Method, MethodHandler> getMethodHandlerMap() {
131     return methodHandlerMap;
132   }
133
134   private VolatileFieldBinding allocateVolatileField(final ValueReader fieldTypeParser, boolean internalType) {
135     int position = volatileFields.size();
136     FieldTypeInfo fieldTypeInfo;
137     if (internalType) {
138       fieldTypeInfo = fieldTypeParser::appendInternalValueTypeName;
139     }
140     else {
141       fieldTypeInfo = (scope, out) -> fieldTypeParser.appendFinishedValueTypeName(out);
142     }
143     VolatileFieldBinding binding = new VolatileFieldBinding(position, fieldTypeInfo);
144     volatileFields.add(binding);
145     return binding;
146   }
147
148   @NotNull
149   private static String checkAndGetJsonFieldName(@NotNull Method method) {
150     if (method.getParameterTypes().length != 0) {
151       throw new JsonProtocolModelParseException("Must have 0 parameters");
152     }
153     JsonField fieldAnnotation = method.getAnnotation(JsonField.class);
154     if (fieldAnnotation != null) {
155       String jsonLiteralName = fieldAnnotation.jsonLiteralName();
156       if (!jsonLiteralName.isEmpty()) {
157         return jsonLiteralName;
158       }
159     }
160     return method.getName();
161   }
162 }