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}