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 }