001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2014 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                                for (ClientType annotationType : javaMethod.getClientAnnotationTypes())
195                                        addToImports(provider.getJavaImports(annotationType, false));
196                                
197                                Class<?> clazz = javaMethod.getReturnType();
198                                if (clazz.isMemberClass() && !clazz.isEnum()) {
199                                        throw new UnsupportedOperationException(
200                                                "Inner classes are not supported (except enums): " + clazz
201                                        );
202                                }
203                                if (!clazz.equals(Void.class) && !clazz.equals(void.class))
204                                        addToImports(provider.getJavaImports(javaMethod.getClientReturnType(), false));
205                                
206                                methodList.add(javaMethod);
207                        }
208                }
209                
210                Collections.sort(methodList, new Comparator<JavaMethod>() {
211                        @Override
212                        public int compare(JavaMethod m1, JavaMethod m2) {
213                                if (m1.getName().equals(m2.getName())) {
214                                        if (m1.getMember().getDeclaringClass() != m2.getMember().getDeclaringClass()) {
215                                                if (m1.getMember().getDeclaringClass().isAssignableFrom(m2.getMember().getDeclaringClass()))
216                                                        return -1;
217                                                if (m2.getMember().getDeclaringClass().isAssignableFrom(m1.getMember().getDeclaringClass()))
218                                                        return 1;
219                                        }
220                                        
221                                        if (m1.getParameterTypes().length < m2.getParameterTypes().length)
222                                                return -1;
223                                        else if (m1.getParameterTypes().length > m2.getParameterTypes().length)
224                                                return 1;
225                                        
226                                        for (int i = 0; i < m1.getParameterTypes().length; i++) {
227                                                if (m1.getParameterTypes()[i] == m2.getParameterTypes()[i])
228                                                        continue;
229                                                if (m1.getParameterTypes()[i].isAssignableFrom(m2.getParameterTypes()[i]))
230                                                        return -1;
231                                                else if (m2.getParameterTypes()[i].isAssignableFrom(m1.getParameterTypes()[i]))
232                                                        return 1;
233                                                return m1.getParameterTypes()[i].toString().compareTo(m2.getParameterTypes()[i].toString());
234                                        }
235                                }
236                                
237                                return m1.getName().compareTo(m2.getName());
238                        }
239                });
240
241                return methodList;
242        }
243        
244        protected boolean shouldGenerateMethod(Method method) {
245                if (!Modifier.isPublic(method.getModifiers())
246                        || Modifier.isStatic(method.getModifiers())
247                        || method.isAnnotationPresent(IgnoredMethod.class))
248                        return false;
249                
250                return true;
251        }       
252
253        protected Map<String, JavaMethodProperty> initProperties() {
254        Map<String, JavaMethodProperty> propertyMap = new LinkedHashMap<String, JavaMethodProperty>();
255
256                // Get all methods for interfaces: normally, even if it is possible in Java
257                // to override a method into a inherited interface, there is no meaning
258                // to do so (we just ignore potential compilation issues with generated AS3
259                // classes for this case since it is always possible to remove the method
260                // re-declaration in the child interface).
261                Method[] methods = null;
262                if (type.isInterface())
263                        methods = type.getMethods();
264                else
265                        methods = type.getDeclaredMethods();
266                
267                List<Object[]> tmp = new ArrayList<Object[]>();
268                
269                for (Method method : methods) {
270                        if (shouldGenerateProperty(method)) {                           
271                                for (Class<?> clazz : method.getParameterTypes())
272                                        addToImports(provider.getJavaImport(clazz));
273                                addToImports(provider.getJavaImport(method.getReturnType()));
274                                
275                                String propertyName = Introspector.decapitalize(method.getName().startsWith("is") ? method.getName().substring(2) : method.getName().substring(3));
276
277                                Object[] property = null;
278                                for (Object[] mp : tmp) {
279                                        if (mp[0].equals(propertyName)) {
280                                                property = mp;
281                                                break;
282                                        }
283                                }
284                                if (property == null) {
285                                        property = new Object[] { propertyName, null, null };
286                                        tmp.add(property);
287                                }
288                                if (method.getName().startsWith("set"))
289                                        property[2] = method;
290                                else
291                                        property[1] = method;
292                        }
293                }
294                
295                for (Object[] property : tmp) {
296                        JavaMethod readMethod = property[1] != null ? new JavaMethod((Method)property[1], MethodType.GETTER) : null;
297                        JavaMethod writeMethod = property[2] != null ? new JavaMethod((Method)property[2], MethodType.SETTER) : null;
298                        propertyMap.put((String)property[0], new JavaMethodProperty(provider, (String)property[0], readMethod, writeMethod));
299                }
300                
301                return propertyMap;
302        }
303        
304        protected boolean shouldGenerateProperty(Method method) {
305                return false;
306        }       
307
308        
309        public JavaInterface convertToJavaInterface() {
310                return new JavaInterface(getProvider(), getType(), getUrl());
311        }
312        
313        
314        public static Method[] filterOverrides(Method[] methods, ParameterizedType[] declaringTypes) {
315                List<Method> filteredMethods = new ArrayList<Method>();
316                for (Method method : methods) {
317                        // Apply generic information
318                        Type[] paramTypes = new Type[method.getGenericParameterTypes().length];
319                        for (int i = 0; i < method.getGenericParameterTypes().length; i++)
320                                paramTypes[i] = GenericTypeUtil.resolveTypeVariable(method.getGenericParameterTypes()[i], method.getDeclaringClass(), declaringTypes);
321                        
322                        // Lookup an override in subinterfaces
323                        boolean foundOverride = false;
324                        for (Method m : methods) {
325                                if (method == m)
326                                        continue;
327                                if (m.getName().equals(method.getName()) && m.getParameterTypes().length == paramTypes.length && method.getDeclaringClass().isAssignableFrom(m.getDeclaringClass())) {
328                                        boolean same = true;
329                                        for (int i = 0; i < paramTypes.length; i++) {
330                                                if (!ClassUtil.classOfType(paramTypes[i]).equals(ClassUtil.classOfType(m.getParameterTypes()[i]))) {
331                                                        same = false;
332                                                        break;
333                                                }
334                                        }
335                                        if (same) {
336                                                foundOverride = true;
337                                                break;
338                                        }
339                                }
340                        }
341                        if (!foundOverride)
342                                filteredMethods.add(method);
343                }
344                return filteredMethods.toArray(new Method[filteredMethods.size()]);
345        }
346}