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    }