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}