View Javadoc
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 }