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