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