Skip to content

Package: ClassParser

ClassParser

nameinstructionbranchcomplexitylinemethod
ClassParser(JsonbContext)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
checkPropertyNameClash(List, Class)
M: 78 C: 0
0%
M: 16 C: 0
0%
M: 9 C: 0
0%
M: 16 C: 0
0%
M: 1 C: 0
0%
getSortedParentProperties(ClassModel, JsonbAnnotatedElement, Map)
M: 78 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 14 C: 0
0%
M: 1 C: 0
0%
isGetter(Method)
M: 17 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
isPropertyMethod(Method)
M: 10 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
isSetter(Method)
M: 13 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
isSpecialCaseMethod(Class, Method)
M: 41 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 9 C: 0
0%
M: 1 C: 0
0%
lambda$parseFields$3(JsonbAnnotatedElement)
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
lambda$parseProperties$0(ClassModel, Property)
M: 8 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
lambda$parseProperties$1(JsonbCreator, PropertyModel)
M: 28 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
lambda$registerMethod$2(JsonbAnnotatedElement, String)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
lowerFirstLetter(String)
M: 39 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 10 C: 0
0%
M: 1 C: 0
0%
mergeProperty(Property, PropertyModel, JsonbAnnotatedElement)
M: 45 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 14 C: 0
0%
M: 1 C: 0
0%
mergePropertyModels(List)
M: 75 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 14 C: 0
0%
M: 1 C: 0
0%
parseClassAndInterfaceMethods(JsonbAnnotatedElement, Map)
M: 30 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 6 C: 0
0%
M: 1 C: 0
0%
parseFields(JsonbAnnotatedElement, Map)
M: 43 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 9 C: 0
0%
M: 1 C: 0
0%
parseIfaceMethodAnnotations(Class, JsonbAnnotatedElement, Map)
M: 109 C: 0
0%
M: 22 C: 0
0%
M: 12 C: 0
0%
M: 23 C: 0
0%
M: 1 C: 0
0%
parseMethods(Class, JsonbAnnotatedElement, Map)
M: 64 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 12 C: 0
0%
M: 1 C: 0
0%
parseProperties(ClassModel, JsonbAnnotatedElement)
M: 88 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 21 C: 0
0%
M: 1 C: 0
0%
registerMethod(String, Method, JsonbAnnotatedElement, Map)
M: 19 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
selectMostSpecificNonDefaultMethod(Method, Method)
M: 16 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
toPropertyMethod(String)
M: 11 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0 which is available at
6: * http://www.eclipse.org/legal/epl-2.0,
7: * or the Eclipse Distribution License v. 1.0 which is available at
8: * http://www.eclipse.org/org/documents/edl-v10.php.
9: *
10: * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
11: */
12:
13: package org.eclipse.yasson.internal;
14:
15: import java.lang.annotation.Annotation;
16: import java.lang.reflect.Field;
17: import java.lang.reflect.Method;
18: import java.lang.reflect.Modifier;
19: import java.security.AccessController;
20: import java.security.PrivilegedAction;
21: import java.util.ArrayList;
22: import java.util.HashMap;
23: import java.util.List;
24: import java.util.Map;
25: import java.util.Objects;
26: import java.util.stream.Collectors;
27:
28: import jakarta.json.bind.JsonbException;
29: import jakarta.json.bind.config.PropertyVisibilityStrategy;
30:
31: import org.eclipse.yasson.internal.model.ClassModel;
32: import org.eclipse.yasson.internal.model.CreatorModel;
33: import org.eclipse.yasson.internal.model.JsonbAnnotatedElement;
34: import org.eclipse.yasson.internal.model.JsonbCreator;
35: import org.eclipse.yasson.internal.model.Property;
36: import org.eclipse.yasson.internal.model.PropertyModel;
37: import org.eclipse.yasson.internal.properties.MessageKeys;
38: import org.eclipse.yasson.internal.properties.Messages;
39:
40: /**
41: * Created a class internal model.
42: */
43: class ClassParser {
44:
45: private static final String IS_PREFIX = "is";
46:
47: private static final String GET_PREFIX = "get";
48:
49: private static final String SET_PREFIX = "set";
50:
51: private final JsonbContext jsonbContext;
52:
53: ClassParser(JsonbContext jsonbContext) {
54: this.jsonbContext = jsonbContext;
55: }
56:
57: /**
58: * Parse class fields and getters setters. Merge to java bean like properties.
59: */
60: void parseProperties(ClassModel classModel, JsonbAnnotatedElement<Class<?>> classElement) {
61: final Map<String, Property> classProperties = new HashMap<>();
62: parseFields(classElement, classProperties);
63: parseClassAndInterfaceMethods(classElement, classProperties);
64:
65: //add sorted properties from parent, if they are not overridden in current class
66: //parent properties are by default first by alphabet, than properties from a subclass
67: final List<PropertyModel> sortedParentProperties = getSortedParentProperties(classModel, classElement, classProperties);
68:
69: List<PropertyModel> classPropertyModels = classProperties.values().stream()
70: .map(property -> new PropertyModel(classModel, property, jsonbContext))
71: .collect(Collectors.toList());
72:
73: //check for collision on same property read name
74: List<PropertyModel> unsortedMerged = new ArrayList<>(sortedParentProperties.size() + classPropertyModels.size());
75: unsortedMerged.addAll(sortedParentProperties);
76: unsortedMerged.addAll(classPropertyModels);
77: checkPropertyNameClash(unsortedMerged, classModel.getType());
78:
79: mergePropertyModels(classPropertyModels);
80:
81: List<PropertyModel> sortedPropertyModels = new ArrayList<>(sortedParentProperties.size() + classPropertyModels.size());
82: sortedPropertyModels.addAll(sortedParentProperties);
83: sortedPropertyModels.addAll(jsonbContext.getConfigProperties().getPropertyOrdering()
84: .orderProperties(classPropertyModels, classModel));
85:
86: //reference property to creator parameter by name to merge configuration in runtime
87: JsonbCreator creator = classModel.getClassCustomization().getCreator();
88:• if (creator != null) {
89: sortedPropertyModels.forEach(propertyModel -> {
90:• for (CreatorModel creatorModel : creator.getParams()) {
91:• if (creatorModel.getName().equals(propertyModel.getPropertyName())) {
92: creatorModel.getCustomization().setPropertyModel(propertyModel);
93: }
94: }
95: });
96: }
97:
98: classModel.setProperties(sortedPropertyModels);
99:
100: }
101:
102: private static void mergePropertyModels(List<PropertyModel> unsortedMerged) {
103: PropertyModel[] clone = unsortedMerged.toArray(new PropertyModel[0]);
104:• for (int i = 0; i < clone.length; i++) {
105:• for (int j = i + 1; j < clone.length; j++) {
106: PropertyModel firstPropertyModel = clone[i];
107: PropertyModel secondPropertyModel = clone[j];
108:• if (firstPropertyModel.equals(secondPropertyModel)) {
109: // Need to merge two properties
110: unsortedMerged.remove(firstPropertyModel);
111: unsortedMerged.remove(secondPropertyModel);
112:• if (!firstPropertyModel.isReadable() && !firstPropertyModel.isWritable()) {
113: unsortedMerged.add(secondPropertyModel);
114:• } else if (!secondPropertyModel.isReadable() && !secondPropertyModel.isWritable()) {
115: unsortedMerged.add(firstPropertyModel);
116: } else {
117: unsortedMerged.add(new PropertyModel(firstPropertyModel, secondPropertyModel));
118: }
119: }
120: }
121: }
122: }
123:
124: private void parseClassAndInterfaceMethods(JsonbAnnotatedElement<Class<?>> classElement,
125: Map<String, Property> classProperties) {
126: Class<?> concreteClass = classElement.getElement();
127: parseMethods(concreteClass, classElement, classProperties);
128:• for (Class<?> ifc : jsonbContext.getAnnotationIntrospector().collectInterfaces(concreteClass)) {
129: parseIfaceMethodAnnotations(ifc, classElement, classProperties);
130: }
131: }
132:
133: private void parseIfaceMethodAnnotations(Class<?> ifc,
134: JsonbAnnotatedElement<Class<?>> classElement,
135: Map<String, Property> classProperties) {
136: Method[] declaredMethods = AccessController.doPrivileged((PrivilegedAction<Method[]>) ifc::getDeclaredMethods);
137:• for (Method method : declaredMethods) {
138: final String methodName = method.getName();
139:• if (!isPropertyMethod(method)) {
140: continue;
141: }
142: String propertyName = toPropertyMethod(methodName);
143:
144: Property property = classProperties.get(propertyName);
145:
146:• if (method.isDefault()) {
147: // Interface provides default implementation
148:• if (property == null) {
149: // the property does not yet exists : create it from scratch
150: property = registerMethod(propertyName, method, classElement, classProperties);
151: } else {
152: // property already exists, take care not overriding already parsed implementation
153:• if (isSetter(method)) {
154:• if (property.getSetter() == null) {
155: property.setSetter(method);
156: }
157: } else {
158:• if (property.getGetter() == null) {
159: property.setGetter(method);
160: }
161: }
162: }
163: }
164:
165:• if (property == null) {
166: //May happen for classes which both extend a class with some method and implement interface with same method.
167: continue;
168: }
169:• JsonbAnnotatedElement<Method> methodElement = isGetter(method)
170: ? property.getGetterElement() : property.getSetterElement();
171: //Only push iface annotations if not overridden on impl classes
172:• for (Annotation ann : method.getDeclaredAnnotations()) {
173:• if (methodElement.getAnnotation(ann.annotationType()).isEmpty()) {
174: methodElement.putAnnotation(ann, true, null);
175: }
176: }
177: }
178: }
179:
180: private Property registerMethod(String propertyName,
181: Method method,
182: JsonbAnnotatedElement<Class<?>> classElement,
183: Map<String, Property> classProperties) {
184: Property property = classProperties.computeIfAbsent(propertyName, n -> new Property(n, classElement));
185:• if (isSetter(method)) {
186: property.setSetter(method);
187: } else {
188: property.setGetter(method);
189: }
190:
191: return property;
192: }
193:
194: private void parseMethods(Class<?> clazz,
195: JsonbAnnotatedElement<Class<?>> classElement,
196: Map<String, Property> classProperties) {
197: Method[] declaredMethods = AccessController.doPrivileged((PrivilegedAction<Method[]>) clazz::getDeclaredMethods);
198:• for (Method method : declaredMethods) {
199: String name = method.getName();
200: //isBridge method filters out methods inherited from interfaces
201:• boolean isAccessorMethod = ClassMultiReleaseExtension.isSpecialAccessorMethod(method, classProperties)
202:• || isPropertyMethod(method);
203:• if (!isAccessorMethod || method.isBridge() || isSpecialCaseMethod(clazz, method)) {
204: continue;
205: }
206:• final String propertyName = ClassMultiReleaseExtension.shouldTransformToPropertyName(method)
207: ? toPropertyMethod(name)
208: : name;
209:
210: registerMethod(propertyName, method, classElement, classProperties);
211: }
212: }
213:
214: /**
215: * Filter out certain methods that get forcibly added to some classes.
216: * For example the public groovy.lang.MetaClass X.getMetaClass() method from Groovy classes
217: */
218: private static boolean isSpecialCaseMethod(Class<?> clazz, Method m) {
219:• if (!Modifier.isPublic(m.getModifiers()) || Modifier.isStatic(m.getModifiers()) || m.isSynthetic()) {
220: return false;
221: }
222: // Groovy objects will have public groovy.lang.MetaClass X.getMetaClass()
223: // which causes an infinite loop in serialization
224:• if (m.getName().equals("getMetaClass")
225:• && m.getReturnType().getCanonicalName().equals("groovy.lang.MetaClass")) {
226: return true;
227: }
228: // WELD proxy objects will have 'public org.jboss.weld
229:• if (m.getName().equals("getMetadata")
230:• && m.getReturnType().getCanonicalName().equals("org.jboss.weld.proxy.WeldClientProxy$Metadata")) {
231: return true;
232: }
233: return false;
234: }
235:
236: private static boolean isGetter(Method m) {
237:• return (m.getName().startsWith(GET_PREFIX) || m.getName().startsWith(IS_PREFIX)) && m.getParameterCount() == 0;
238: }
239:
240: private static boolean isSetter(Method m) {
241:• return m.getName().startsWith(SET_PREFIX) && m.getParameterCount() == 1;
242: }
243:
244: private static String toPropertyMethod(String name) {
245:• return lowerFirstLetter(name.substring(name.startsWith(IS_PREFIX) ? 2 : 3));
246: }
247:
248: private static String lowerFirstLetter(String name) {
249: Objects.requireNonNull(name);
250:• if (name.length() == 0) {
251: //methods named get() or set()
252: return name;
253: }
254:• if (name.length() > 1
255:• && Character.isUpperCase(name.charAt(1))
256:• && Character.isUpperCase(name.charAt(0))) {
257: return name;
258: }
259: char[] chars = name.toCharArray();
260: chars[0] = Character.toLowerCase(chars[0]);
261: return new String(chars);
262: }
263:
264: private static boolean isPropertyMethod(Method m) {
265:• return isGetter(m) || isSetter(m);
266: }
267:
268: private static void parseFields(JsonbAnnotatedElement<Class<?>> classElement, Map<String, Property> classProperties) {
269: Field[] declaredFields = AccessController.doPrivileged(
270: (PrivilegedAction<Field[]>) () -> classElement.getElement().getDeclaredFields());
271:• for (Field field : declaredFields) {
272: final String name = field.getName();
273:• if (field.isSynthetic()) {
274: continue;
275: }
276: final Property property = new Property(name, classElement);
277: property.setField(field);
278: classProperties.put(name, property);
279: }
280: }
281:
282: private static void checkPropertyNameClash(List<PropertyModel> collectedProperties, Class<?> cls) {
283: final List<PropertyModel> checkedProperties = new ArrayList<>();
284:• for (PropertyModel collectedPropertyModel : collectedProperties) {
285:• for (PropertyModel checkedPropertyModel : checkedProperties) {
286:• if ((checkedPropertyModel.getReadName().equals(collectedPropertyModel.getReadName())
287:• && checkedPropertyModel.isReadable() //
288:• && collectedPropertyModel.isReadable())
289:• || (checkedPropertyModel.getWriteName().equals(collectedPropertyModel.getWriteName())
290:• && checkedPropertyModel.isWritable() //
291:• && collectedPropertyModel.isWritable())) {
292: throw new JsonbException(
293: Messages.getMessage(MessageKeys.PROPERTY_NAME_CLASH, checkedPropertyModel.getPropertyName(),
294: collectedPropertyModel.getPropertyName(), cls.getName()));
295: }
296: }
297: checkedProperties.add(collectedPropertyModel);
298: }
299: }
300:
301: /**
302: * Merges current class properties with parent class properties.
303: * If javabean property is declared in more than one inheritance levels,
304: * merge field, getters and setters of that property.
305: * <p>
306: * For example BaseClass contains field foo and getter getFoo. In BaseExtensions there is a setter setFoo.
307: * All three will be merged for BaseExtension.
308: * <p>
309: * Such property is sorted based on where its getter or field is located.
310: */
311: private List<PropertyModel> getSortedParentProperties(ClassModel classModel,
312: JsonbAnnotatedElement<Class<?>> classElement,
313: Map<String, Property> classProperties) {
314: List<PropertyModel> sortedProperties = new ArrayList<>();
315: //Pull properties from parent
316:• if (classModel.getParentClassModel() != null) {
317:• for (PropertyModel parentProp : classModel.getParentClassModel().getSortedProperties()) {
318: final Property current = classProperties.get(parentProp.getPropertyName());
319: //don't replace overridden properties
320:• if (current == null) {
321: sortedProperties.add(parentProp);
322: } else {
323: //merge
324: final Property merged = mergeProperty(current, parentProp, classElement);
325: PropertyVisibilityStrategy propertyVisibilityStrategy = classModel.getClassCustomization()
326: .getPropertyVisibilityStrategy();
327:
328:• if (PropertyModel.isPropertyReadable(current.getField(), current.getGetter(), propertyVisibilityStrategy)) {
329: classProperties.replace(current.getName(), merged);
330: } else {
331: sortedProperties.add(new PropertyModel(classModel, merged, jsonbContext));
332: classProperties.remove(current.getName());
333: }
334:
335: }
336: }
337: }
338: return sortedProperties;
339: }
340:
341: /**
342: * Select the correct method to use. The correct method is the most specific
343: * method which is not a default one:
344: * <ul>
345: * <li> if current is not defined, returns parent;</li>
346: * <li> if parent is not defined, returns current;</li>
347: * <li> if current is a default method and parent is not, returns parent;</li>
348: * <ul>
349: * <li><i>By definition, it is not possible to make a choice betweentwo default
350: * methods. <br/>Here, the most specific is selected, but a concrete
351: * implementation MUST eventually be provided as the source code won't even
352: * compile if such a method does not exist</i></li>
353: * </ul>
354: * <li> returns current otherwise</li>
355: * </ul>
356: *
357: * @param current current 'child' implementation
358: * @param parent parent implementation
359: * @return effective method to register as getter or setter
360: */
361: private static Method selectMostSpecificNonDefaultMethod(Method current, Method parent) {
362: return (
363:• current != null ? (
364:• parent != null && current.isDefault()
365:• && !parent.isDefault() ? parent : current) : parent);
366: }
367:
368: private static Property mergeProperty(Property current,
369: PropertyModel parentProp,
370: JsonbAnnotatedElement<Class<?>> classElement) {
371:• Field field = current.getField() != null
372: ? current.getField() : parentProp.getField();
373: Method getter = selectMostSpecificNonDefaultMethod(current.getGetter(),
374: parentProp.getGetter());
375: Method setter = selectMostSpecificNonDefaultMethod(current.getSetter(),
376: parentProp.getSetter());
377:
378: Property merged = new Property(parentProp.getPropertyName(), classElement);
379:• if (field != null) {
380: merged.setField(field);
381: }
382:• if (getter != null) {
383: merged.setGetter(getter);
384: }
385:• if (setter != null) {
386: merged.setSetter(setter);
387: }
388: return merged;
389: }
390: }