001package org.granite.generator.as3.reflect;
002
003import java.lang.annotation.Annotation;
004import java.lang.reflect.Array;
005import java.lang.reflect.Method;
006import java.lang.reflect.Modifier;
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.List;
010import java.util.Map;
011
012public class ValidatableBean {
013        
014        private final Class<?> type;
015        private final String metaAnnotationName;
016        private final List<String> specialAnnotationNames;
017        private final Map<String, String> nameConversions;
018        
019        public ValidatableBean(Class<?> type, String metaAnnotationName, List<String> specialAnnotationNames, Map<String, String> nameConversions) {
020                this.type = type;
021                this.metaAnnotationName = metaAnnotationName;
022                this.specialAnnotationNames = specialAnnotationNames;
023                this.nameConversions = nameConversions;
024        }
025        
026        public void buildConstraints(Map<String, JavaProperty> properties, Map<JavaProperty, List<JavaConstraint>> constraints) {
027                Class<? extends Annotation> metaAnnotationClass = loadMetaAnnotationClass(type, metaAnnotationName);
028                if (metaAnnotationClass == null)
029                        return;
030            
031            // Collect validation annotations
032            for (JavaProperty property : properties.values()) {
033                List<JavaConstraint> javaConstraints = new ArrayList<JavaConstraint>();
034                
035                List<Annotation> constraintAnnotations = new ArrayList<Annotation>();
036                for (Annotation annotation : property.getDeclaredAnnotations()) {
037                                Class<? extends Annotation> annotationClass = annotation.annotationType();
038                                
039                                if (annotationClass.isAnnotationPresent(metaAnnotationClass) || specialAnnotationNames.contains(annotationClass.getName()))
040                                        constraintAnnotations.add(annotation);
041                                else {
042        
043                                        // (Spec 2.2) "...the bean validation provider treats regular annotations
044                                        // (annotations not annotated by @Constraint) whose value element has a
045                                        // return type of an array of constraint annotations in a special way.
046                                        // Each element in the value array are processed by the Bean Validation
047                                        // implementation as regular constraint annotations."
048        
049                                        Method value = null;
050                                        try {
051                                                value = annotationClass.getMethod("value");
052                                        }
053                                        catch (NoSuchMethodException e) {
054                                        }
055                                        
056                                        if (value != null && value.getReturnType().isArray() &&
057                                                value.getReturnType().getComponentType().isAnnotation() &&
058                                                value.getReturnType().getComponentType().isAnnotationPresent(metaAnnotationClass)) {
059                                                
060                                                try {
061                                                Annotation[] annotationList = (Annotation[])value.invoke(annotation);
062                                                constraintAnnotations.addAll(Arrays.asList(annotationList));
063                                                }
064                                                catch (Exception e) {
065                                                        // should never happen...
066                                                }
067                                        }
068                                }
069                }
070                
071                        for (Annotation constraint : constraintAnnotations) {
072                                List<String[]> attributes = new ArrayList<String[]>();
073        
074                                for (Method attribute : constraint.annotationType().getDeclaredMethods()) {
075                                        if (Modifier.isPublic(attribute.getModifiers()) &&
076                                                !Modifier.isStatic(attribute.getModifiers()) &&
077                                                attribute.getParameterTypes().length == 0) {
078                                                
079                                                Object value = null;
080                                                try {
081                                                        value = attribute.invoke(constraint);
082                                                }
083                                                catch (Exception e) {
084                                                        continue;
085                                                }
086                                                
087                                                if (value != null && (!value.getClass().isArray() || Array.getLength(value) > 0))
088                                                        attributes.add(new String[]{attribute.getName(), escape(value), attribute.getReturnType().getName()});
089                                        }
090                                }
091                                
092                                String constraintName = constraint.annotationType().getName();
093                                if (nameConversions.containsKey(constraintName))
094                                        constraintName = nameConversions.get(constraintName);
095                                String packageName = constraintName.indexOf(".") > 0 ? constraintName.substring(0, constraintName.lastIndexOf(".")) : "";
096                                constraintName = constraintName.indexOf(".") > 0 ? constraintName.substring(constraintName.lastIndexOf(".")+1) : constraintName;
097                                if (nameConversions.containsKey(packageName))
098                                        packageName = nameConversions.get(packageName);
099                                
100                                javaConstraints.add(new JavaConstraint(packageName, constraintName, attributes));
101                        }
102                
103                if (!javaConstraints.isEmpty())
104                        constraints.put(property, javaConstraints);
105            }
106        }
107    
108    @SuppressWarnings("unchecked")
109        private static Class<? extends Annotation> loadMetaAnnotationClass(Class<?> type, String metaAnnotationName) {
110                try {
111                        return (Class<? extends Annotation>)type.getClassLoader().loadClass(metaAnnotationName);
112                }
113                catch (Exception e) {
114                        return null;
115                }
116    }
117        
118        private static String escape(Object value) {
119                
120                if (value.getClass().isArray()) {
121                        StringBuilder sb = new StringBuilder();
122                        
123                        final int length = Array.getLength(value);
124                        boolean first = true;
125                        for (int i = 0; i < length; i++) {
126                                Object item = Array.get(value, i);
127                                if (item == null)
128                                        continue;
129
130                                if (first)
131                                        first = false;
132                                else
133                                        sb.append(", ");
134                                
135                                sb.append(escape(item, true));
136                        }
137                        
138                        return sb.toString();
139                }
140                
141                return escape(value, false);
142        }
143        
144        private static String escape(Object value, boolean array) {
145                if (value instanceof Class<?>)
146                        return ((Class<?>)value).getName();
147                
148                if (value.getClass().isEnum())
149                        return ((Enum<?>)value).name();
150                
151                value = value.toString().replace("&", "&amp;").replace("\"", "&quot;");
152                if (array)
153                        value = ((String)value).replace(",", ",,");
154                return (String)value;
155        }
156}