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 */
022package org.granite.generator.as3.reflect;
023
024import java.lang.annotation.Annotation;
025import java.lang.reflect.Array;
026import java.lang.reflect.Method;
027import java.lang.reflect.Modifier;
028import java.util.ArrayList;
029import java.util.Arrays;
030import java.util.List;
031import java.util.Map;
032
033public 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}