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    }