001    package org.granite.generator.as3.reflect;
002    
003    import java.lang.annotation.Annotation;
004    import java.lang.reflect.Array;
005    import java.lang.reflect.Method;
006    import java.lang.reflect.Modifier;
007    import java.util.ArrayList;
008    import java.util.Arrays;
009    import java.util.List;
010    import java.util.Map;
011    
012    public 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    }