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("&", "&").replace("\"", """);
173 if (array)
174 value = ((String)value).replace(",", ",,");
175 return (String)value;
176 }
177 }