1 package org.pojomatic.internal;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.util.ArrayList;
6 import java.util.EnumMap;
7 import java.util.LinkedHashSet;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Set;
11
12 import org.objectweb.asm.ClassReader;
13 import org.objectweb.asm.ClassVisitor;
14 import org.objectweb.asm.FieldVisitor;
15 import org.objectweb.asm.MethodVisitor;
16 import org.objectweb.asm.Opcodes;
17 import org.pojomatic.PropertyElement;
18
19 class PropertyClassVisitor extends ClassVisitor {
20
21 private final Map<PropertyRole, Map<String, PropertyElement>> fieldsMap;
22 private final Map<PropertyRole, Map<String, PropertyElement>> methodsMap;
23 private final Map<PropertyRole, List<PropertyElement>> sortedProperties = makeProperties();
24
25 PropertyClassVisitor(
26 Map<PropertyRole, Map<String, PropertyElement>> fieldsMap,
27 Map<PropertyRole, Map<String, PropertyElement>> methodsMap) {
28 super(Opcodes.ASM7);
29 this.fieldsMap = fieldsMap;
30 this.methodsMap = methodsMap;
31
32 }
33
34 static PropertyClassVisitor visitClass(
35 Class<?> clazz,
36 Map<PropertyRole, Map<String, PropertyElement>> fieldsMap,
37 Map<PropertyRole, Map<String, PropertyElement>> methodsMap) {
38 String classPath = clazz.getName().replace(".", "/") + ".class";
39 ClassLoader classLoader = clazz.getClassLoader();
40 if (classLoader == null) {
41 return null;
42 }
43 try (InputStream stream = classLoader.getResourceAsStream(classPath)) {
44 ClassReader classReader = new ClassReader(stream);
45 PropertyClassVisitor propertyClassVisitor = new PropertyClassVisitor(fieldsMap, methodsMap);
46 classReader.accept(propertyClassVisitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
47 verifyAllPropertiesFound(clazz, fieldsMap, methodsMap, propertyClassVisitor);
48 return propertyClassVisitor;
49 } catch (IOException e) {
50 return null;
51 }
52 }
53
54 private static void verifyAllPropertiesFound(Class<?> clazz,
55 Map<PropertyRole, Map<String, PropertyElement>> fieldsMap,
56 Map<PropertyRole, Map<String, PropertyElement>> methodsMap,
57 PropertyClassVisitor propertyClassVisitor) {
58 for (PropertyRole role: PropertyRole.values()) {
59 List<PropertyElement> sortedProperties = propertyClassVisitor.getSortedProperties().get(role);
60 Map<String, PropertyElement> fields = fieldsMap.get(role);
61 Map<String, PropertyElement> methods = methodsMap.get(role);
62 if (fields.size() + methods.size() != sortedProperties.size()) {
63 throwReflectionMissmatch(clazz, fields, methods, sortedProperties);
64 }
65 }
66 }
67
68 private static void throwReflectionMissmatch(
69 Class<?> clazz,
70 Map<String, PropertyElement> fields,
71 Map<String, PropertyElement> methods,
72 List<PropertyElement> sortedProperties) {
73 Set<PropertyElement> expectedProperties = new LinkedHashSet<>();
74 expectedProperties.addAll(fields.values());
75 expectedProperties.addAll(methods.values());
76 expectedProperties.removeAll(sortedProperties);
77 StringBuilder message = new StringBuilder("In class ").append(clazz.getName()).append(", properties ");
78 boolean seenOne = false;
79 for (PropertyElement property: expectedProperties) {
80 if (seenOne) {
81 message.append(", ");
82 }
83 seenOne = true;
84 message.append(property);
85 }
86 message.append(" were found in reflection, but not when visiting the bytecode");
87 throw new IllegalStateException(message.toString());
88 }
89
90 @Override
91 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
92 if ((access & Opcodes.ACC_STATIC )== 0) {
93 for (PropertyRole role: PropertyRole.values()) {
94 PropertyElement propertyElement = fieldsMap.get(role).get(name);
95 if (propertyElement != null) {
96 sortedProperties.get(role).add(propertyElement);
97 }
98 }
99 }
100 return null;
101 }
102
103 @Override
104 public MethodVisitor visitMethod(int access, String name, String desc,
105 String signature, String[] exceptions) {
106 if (desc.startsWith("()") && ((access & (Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC)) == 0)) {
107 for (PropertyRole role: PropertyRole.values()) {
108 PropertyElement propertyElement = methodsMap.get(role).get(name);
109 if (propertyElement != null) {
110 sortedProperties.get(role).add(propertyElement);
111 }
112 }
113 }
114 return null;
115 }
116
117 public Map<PropertyRole, List<PropertyElement>> getSortedProperties() {
118 return sortedProperties;
119 };
120
121 private static Map<PropertyRole, List<PropertyElement>> makeProperties() {
122 Map<PropertyRole, List<PropertyElement>> properties =
123 new EnumMap<>(PropertyRole.class);
124 for (PropertyRole role : PropertyRole.values()) {
125 properties.put(role, new ArrayList<PropertyElement>());
126 }
127 return properties;
128 }
129 }