001    /**
002     *   GRANITE DATA SERVICES
003     *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004     *
005     *   This file is part of the Granite Data Services Platform.
006     *
007     *   Granite Data Services is free software; you can redistribute it and/or
008     *   modify it under the terms of the GNU Lesser General Public
009     *   License as published by the Free Software Foundation; either
010     *   version 2.1 of the License, or (at your option) any later version.
011     *
012     *   Granite Data Services is distributed in the hope that it will be useful,
013     *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015     *   General Public License for more details.
016     *
017     *   You should have received a copy of the GNU Lesser General Public
018     *   License along with this library; if not, write to the Free Software
019     *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020     *   USA, or see <http://www.gnu.org/licenses/>.
021     */
022    package org.granite.generator.as3.reflect;
023    
024    import java.lang.annotation.Annotation;
025    import java.lang.reflect.Array;
026    import java.lang.reflect.Method;
027    import java.lang.reflect.Modifier;
028    import java.util.ArrayList;
029    import java.util.Arrays;
030    import java.util.List;
031    import java.util.Map;
032    
033    public class ValidatableBean {
034            
035            private final Class<?> type;
036            private final String metaAnnotationName;
037            private final List<String> specialAnnotationNames;
038            private final Map<String, String> nameConversions;
039            
040            public ValidatableBean(Class<?> type, String metaAnnotationName, List<String> specialAnnotationNames, Map<String, String> nameConversions) {
041                    this.type = type;
042                    this.metaAnnotationName = metaAnnotationName;
043                    this.specialAnnotationNames = specialAnnotationNames;
044                    this.nameConversions = nameConversions;
045            }
046            
047            public void buildConstraints(Map<String, JavaProperty> properties, Map<JavaProperty, List<JavaConstraint>> constraints) {
048                    Class<? extends Annotation> metaAnnotationClass = loadMetaAnnotationClass(type, metaAnnotationName);
049                    if (metaAnnotationClass == null)
050                            return;
051                
052                // Collect validation annotations
053                for (JavaProperty property : properties.values()) {
054                    List<JavaConstraint> javaConstraints = new ArrayList<JavaConstraint>();
055                    
056                    List<Annotation> constraintAnnotations = new ArrayList<Annotation>();
057                    for (Annotation annotation : property.getDeclaredAnnotations()) {
058                                    Class<? extends Annotation> annotationClass = annotation.annotationType();
059                                    
060                                    if (annotationClass.isAnnotationPresent(metaAnnotationClass) || specialAnnotationNames.contains(annotationClass.getName()))
061                                            constraintAnnotations.add(annotation);
062                                    else {
063            
064                                            // (Spec 2.2) "...the bean validation provider treats regular annotations
065                                            // (annotations not annotated by @Constraint) whose value element has a
066                                            // return type of an array of constraint annotations in a special way.
067                                            // Each element in the value array are processed by the Bean Validation
068                                            // implementation as regular constraint annotations."
069            
070                                            Method value = null;
071                                            try {
072                                                    value = annotationClass.getMethod("value");
073                                            }
074                                            catch (NoSuchMethodException e) {
075                                            }
076                                            
077                                            if (value != null && value.getReturnType().isArray() &&
078                                                    value.getReturnType().getComponentType().isAnnotation() &&
079                                                    value.getReturnType().getComponentType().isAnnotationPresent(metaAnnotationClass)) {
080                                                    
081                                                    try {
082                                                    Annotation[] annotationList = (Annotation[])value.invoke(annotation);
083                                                    constraintAnnotations.addAll(Arrays.asList(annotationList));
084                                                    }
085                                                    catch (Exception e) {
086                                                            // should never happen...
087                                                    }
088                                            }
089                                    }
090                    }
091                    
092                            for (Annotation constraint : constraintAnnotations) {
093                                    List<String[]> attributes = new ArrayList<String[]>();
094            
095                                    for (Method attribute : constraint.annotationType().getDeclaredMethods()) {
096                                            if (Modifier.isPublic(attribute.getModifiers()) &&
097                                                    !Modifier.isStatic(attribute.getModifiers()) &&
098                                                    attribute.getParameterTypes().length == 0) {
099                                                    
100                                                    Object value = null;
101                                                    try {
102                                                            value = attribute.invoke(constraint);
103                                                    }
104                                                    catch (Exception e) {
105                                                            continue;
106                                                    }
107                                                    
108                                                    if (value != null && (!value.getClass().isArray() || Array.getLength(value) > 0))
109                                                            attributes.add(new String[]{attribute.getName(), escape(value), attribute.getReturnType().getName()});
110                                            }
111                                    }
112                                    
113                                    String constraintName = constraint.annotationType().getName();
114                                    if (nameConversions.containsKey(constraintName))
115                                            constraintName = nameConversions.get(constraintName);
116                                    String packageName = constraintName.indexOf(".") > 0 ? constraintName.substring(0, constraintName.lastIndexOf(".")) : "";
117                                    constraintName = constraintName.indexOf(".") > 0 ? constraintName.substring(constraintName.lastIndexOf(".")+1) : constraintName;
118                                    if (nameConversions.containsKey(packageName))
119                                            packageName = nameConversions.get(packageName);
120                                    
121                                    javaConstraints.add(new JavaConstraint(packageName, constraintName, attributes));
122                            }
123                    
124                    if (!javaConstraints.isEmpty())
125                            constraints.put(property, javaConstraints);
126                }
127            }
128        
129        @SuppressWarnings("unchecked")
130            private static Class<? extends Annotation> loadMetaAnnotationClass(Class<?> type, String metaAnnotationName) {
131                    try {
132                            return (Class<? extends Annotation>)type.getClassLoader().loadClass(metaAnnotationName);
133                    }
134                    catch (Exception e) {
135                            return null;
136                    }
137        }
138            
139            private static String escape(Object value) {
140                    
141                    if (value.getClass().isArray()) {
142                            StringBuilder sb = new StringBuilder();
143                            
144                            final int length = Array.getLength(value);
145                            boolean first = true;
146                            for (int i = 0; i < length; i++) {
147                                    Object item = Array.get(value, i);
148                                    if (item == null)
149                                            continue;
150    
151                                    if (first)
152                                            first = false;
153                                    else
154                                            sb.append(", ");
155                                    
156                                    sb.append(escape(item, true));
157                            }
158                            
159                            return sb.toString();
160                    }
161                    
162                    return escape(value, false);
163            }
164            
165            private static String escape(Object value, boolean array) {
166                    if (value instanceof Class<?>)
167                            return ((Class<?>)value).getName();
168                    
169                    if (value.getClass().isEnum())
170                            return ((Enum<?>)value).name();
171                    
172                    value = value.toString().replace("&", "&amp;").replace("\"", "&quot;");
173                    if (array)
174                            value = ((String)value).replace(",", ",,");
175                    return (String)value;
176            }
177    }