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
045 package org.granite.generator.as3.reflect;
046
047 import java.beans.Introspector;
048 import java.lang.annotation.Annotation;
049 import java.lang.reflect.Method;
050 import java.lang.reflect.Modifier;
051 import java.lang.reflect.ParameterizedType;
052 import java.lang.reflect.Type;
053 import java.net.URL;
054 import java.util.ArrayList;
055 import java.util.Collection;
056 import java.util.Collections;
057 import java.util.Comparator;
058 import java.util.HashSet;
059 import java.util.LinkedHashMap;
060 import java.util.List;
061 import java.util.Map;
062 import java.util.Set;
063
064 import org.granite.generator.as3.ClientType;
065 import org.granite.generator.as3.reflect.JavaMethod.MethodType;
066 import org.granite.generator.util.GenericTypeUtil;
067 import org.granite.messaging.service.annotations.IgnoredMethod;
068 import org.granite.messaging.service.annotations.RemoteDestination;
069 import org.granite.util.ClassUtil;
070
071 /**
072 * @author Franck WOLFF
073 */
074 public 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 }