001/* 002 GRANITE DATA SERVICES 003 Copyright (C) 2011 GRANITE DATA SERVICES S.A.S. 004 005 This file is part of Granite Data Services. 006 007 Granite Data Services is free software; you can redistribute it and/or modify 008 it under the terms of the GNU Library General Public License as published by 009 the Free Software Foundation; either version 2 of the License, or (at your 010 option) any later version. 011 012 Granite Data Services is distributed in the hope that it will be useful, but 013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License 015 for more details. 016 017 You should have received a copy of the GNU Library General Public License 018 along with this library; if not, see <http://www.gnu.org/licenses/>. 019 020 021 SLSB: This class and all the modifications to use it are marked with the 'SLSB' tag. 022 */ 023 024package org.granite.generator.as3.reflect; 025 026import java.beans.Introspector; 027import java.lang.annotation.Annotation; 028import java.lang.reflect.Method; 029import java.lang.reflect.Modifier; 030import java.lang.reflect.ParameterizedType; 031import java.lang.reflect.Type; 032import java.net.URL; 033import java.util.ArrayList; 034import java.util.Collection; 035import java.util.Collections; 036import java.util.Comparator; 037import java.util.HashSet; 038import java.util.LinkedHashMap; 039import java.util.List; 040import java.util.Map; 041import java.util.Set; 042 043import org.granite.generator.as3.ClientType; 044import org.granite.generator.as3.reflect.JavaMethod.MethodType; 045import org.granite.generator.util.GenericTypeUtil; 046import org.granite.messaging.service.annotations.IgnoredMethod; 047import org.granite.messaging.service.annotations.RemoteDestination; 048import org.granite.util.ClassUtil; 049 050/** 051 * @author Franck WOLFF 052 */ 053public class JavaRemoteDestination extends JavaAbstractType { 054 055 // ///////////////////////////////////////////////////////////////////////// 056 // Fields. 057 058 protected final Set<JavaImport> imports = new HashSet<JavaImport>(); 059 protected final JavaType superclass; 060 protected final List<JavaMethod> methods; 061 protected final Map<String, JavaMethodProperty> properties; 062 protected final String destinationName; 063 protected final String channelId; 064 065 // ///////////////////////////////////////////////////////////////////////// 066 // Constructor. 067 068 public JavaRemoteDestination(JavaTypeFactory provider, Class<?> type, URL url) { 069 super(provider, type, url); 070 071 // Find superclass (controller filtered). 072 this.superclass = provider.getJavaTypeSuperclass(type); 073 074 // Collect methods. 075 this.methods = Collections.unmodifiableList(initMethods()); 076 077 // Collect bean properties. 078 this.properties = Collections.unmodifiableMap(initProperties()); 079 080 // Collect imports. 081 if (superclass != null) 082 addToImports(provider.getJavaImport(superclass.getType())); 083 084 RemoteDestination rd = type.getAnnotation(RemoteDestination.class); 085 if (rd != null) { 086 destinationName = rd.id(); 087 channelId = rd.channel(); 088 } 089 else { 090 destinationName = null; 091 channelId = null; 092 } 093 } 094 095 // ///////////////////////////////////////////////////////////////////////// 096 // Properties. 097 098 public Set<JavaImport> getImports() { 099 return imports; 100 } 101 102 protected void addToImports(JavaImport javaImport) { 103 if (javaImport != null) 104 imports.add(javaImport); 105 } 106 107 protected void addToImports(Set<JavaImport> javaImports) { 108 if (javaImports != null) 109 imports.addAll(javaImports); 110 } 111 112 public boolean hasSuperclass() { 113 return superclass != null; 114 } 115 116 public JavaType getSuperclass() { 117 return superclass; 118 } 119 120 public Collection<JavaMethod> getMethods() { 121 return methods; 122 } 123 124 public Collection<JavaMethodProperty> getProperties() { 125 return properties.values(); 126 } 127 128 public String getDestinationName() { 129 return destinationName; 130 } 131 132 public String getChannelId() { 133 return channelId; 134 } 135 136 public boolean isAnnotationPresent(Class<? extends Annotation> annotation) { 137 return type.isAnnotationPresent(annotation); 138 } 139 140 // ///////////////////////////////////////////////////////////////////////// 141 // Utilities. 142 143 protected List<JavaMethod> initMethods() { 144 List<JavaMethod> methodList = new ArrayList<JavaMethod>(); 145 146 // Get all methods for interfaces: normally, even if it is possible in Java 147 // to override a method into a inherited interface, there is no meaning 148 // to do so (we just ignore potential compilation issues with generated AS3 149 // classes for this case since it is always possible to remove the method 150 // re-declaration in the child interface). 151 Method[] methods = null; 152 ParameterizedType[] declaringTypes = GenericTypeUtil.getDeclaringTypes(type); 153 154 if (type.isInterface()) 155 methods = filterOverrides(type.getMethods(), declaringTypes); 156 else 157 methods = type.getDeclaredMethods(); 158 159 for (Method method : methods) { 160 if (shouldGenerateMethod(method)) { 161 JavaMethod javaMethod = new JavaMethod(method, MethodType.OTHER, this.provider, declaringTypes); 162 163 for (Class<?> clazz : javaMethod.getParameterTypes()) { 164 if (clazz.isMemberClass() && !clazz.isEnum()) { 165 throw new UnsupportedOperationException( 166 "Inner classes are not supported (except enums): " + clazz 167 ); 168 } 169 } 170 for (ClientType paramType : javaMethod.getClientParameterTypes()) 171 addToImports(provider.getJavaImports(paramType, false)); 172 173 Class<?> clazz = javaMethod.getReturnType(); 174 if (clazz.isMemberClass() && !clazz.isEnum()) { 175 throw new UnsupportedOperationException( 176 "Inner classes are not supported (except enums): " + clazz 177 ); 178 } 179 if (!clazz.equals(Void.class) && !clazz.equals(void.class)) 180 addToImports(provider.getJavaImports(javaMethod.getClientReturnType(), false)); 181 182 methodList.add(javaMethod); 183 } 184 } 185 186 Collections.sort(methodList, new Comparator<JavaMethod>() { 187 @Override 188 public int compare(JavaMethod m1, JavaMethod m2) { 189 if (m1.getName().equals(m2.getName())) { 190 if (m1.getMember().getDeclaringClass() != m2.getMember().getDeclaringClass()) { 191 if (m1.getMember().getDeclaringClass().isAssignableFrom(m2.getMember().getDeclaringClass())) 192 return -1; 193 if (m2.getMember().getDeclaringClass().isAssignableFrom(m1.getMember().getDeclaringClass())) 194 return 1; 195 } 196 197 if (m1.getParameterTypes().length < m2.getParameterTypes().length) 198 return -1; 199 else if (m1.getParameterTypes().length > m2.getParameterTypes().length) 200 return 1; 201 202 for (int i = 0; i < m1.getParameterTypes().length; i++) { 203 if (m1.getParameterTypes()[i] == m2.getParameterTypes()[i]) 204 continue; 205 if (m1.getParameterTypes()[i].isAssignableFrom(m2.getParameterTypes()[i])) 206 return -1; 207 else if (m2.getParameterTypes()[i].isAssignableFrom(m1.getParameterTypes()[i])) 208 return 1; 209 return m1.getParameterTypes()[i].toString().compareTo(m2.getParameterTypes()[i].toString()); 210 } 211 } 212 213 return m1.getName().compareTo(m2.getName()); 214 } 215 }); 216 217 return methodList; 218 } 219 220 protected boolean shouldGenerateMethod(Method method) { 221 if (!Modifier.isPublic(method.getModifiers()) 222 || Modifier.isStatic(method.getModifiers()) 223 || method.isAnnotationPresent(IgnoredMethod.class)) 224 return false; 225 226 return true; 227 } 228 229 protected Map<String, JavaMethodProperty> initProperties() { 230 Map<String, JavaMethodProperty> propertyMap = new LinkedHashMap<String, JavaMethodProperty>(); 231 232 // Get all methods for interfaces: normally, even if it is possible in Java 233 // to override a method into a inherited interface, there is no meaning 234 // to do so (we just ignore potential compilation issues with generated AS3 235 // classes for this case since it is always possible to remove the method 236 // re-declaration in the child interface). 237 Method[] methods = null; 238 if (type.isInterface()) 239 methods = type.getMethods(); 240 else 241 methods = type.getDeclaredMethods(); 242 243 List<Object[]> tmp = new ArrayList<Object[]>(); 244 245 for (Method method : methods) { 246 if (shouldGenerateProperty(method)) { 247 for (Class<?> clazz : method.getParameterTypes()) 248 addToImports(provider.getJavaImport(clazz)); 249 addToImports(provider.getJavaImport(method.getReturnType())); 250 251 String propertyName = Introspector.decapitalize(method.getName().startsWith("is") ? method.getName().substring(2) : method.getName().substring(3)); 252 253 Object[] property = null; 254 for (Object[] mp : tmp) { 255 if (mp[0].equals(propertyName)) { 256 property = mp; 257 break; 258 } 259 } 260 if (property == null) { 261 property = new Object[] { propertyName, null, null }; 262 tmp.add(property); 263 } 264 if (method.getName().startsWith("set")) 265 property[2] = method; 266 else 267 property[1] = method; 268 } 269 } 270 271 for (Object[] property : tmp) { 272 JavaMethod readMethod = property[1] != null ? new JavaMethod((Method)property[1], MethodType.GETTER) : null; 273 JavaMethod writeMethod = property[2] != null ? new JavaMethod((Method)property[2], MethodType.SETTER) : null; 274 propertyMap.put((String)property[0], new JavaMethodProperty(provider, (String)property[0], readMethod, writeMethod)); 275 } 276 277 return propertyMap; 278 } 279 280 protected boolean shouldGenerateProperty(Method method) { 281 return false; 282 } 283 284 285 public JavaInterface convertToJavaInterface() { 286 return new JavaInterface(getProvider(), getType(), getUrl()); 287 } 288 289 290 public static Method[] filterOverrides(Method[] methods, ParameterizedType[] declaringTypes) { 291 List<Method> filteredMethods = new ArrayList<Method>(); 292 for (Method method : methods) { 293 // Apply generic information 294 Type[] paramTypes = new Type[method.getGenericParameterTypes().length]; 295 for (int i = 0; i < method.getGenericParameterTypes().length; i++) 296 paramTypes[i] = GenericTypeUtil.resolveTypeVariable(method.getGenericParameterTypes()[i], method.getDeclaringClass(), declaringTypes); 297 298 // Lookup an override in subinterfaces 299 boolean foundOverride = false; 300 for (Method m : methods) { 301 if (method == m) 302 continue; 303 if (m.getName().equals(method.getName()) && m.getParameterTypes().length == paramTypes.length && method.getDeclaringClass().isAssignableFrom(m.getDeclaringClass())) { 304 boolean same = true; 305 for (int i = 0; i < paramTypes.length; i++) { 306 if (!ClassUtil.classOfType(paramTypes[i]).equals(ClassUtil.classOfType(m.getParameterTypes()[i]))) { 307 same = false; 308 break; 309 } 310 } 311 if (same) { 312 foundOverride = true; 313 break; 314 } 315 } 316 } 317 if (!foundOverride) 318 filteredMethods.add(method); 319 } 320 return filteredMethods.toArray(new Method[filteredMethods.size()]); 321 } 322}