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