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.net.URL;
031    import java.util.ArrayList;
032    import java.util.Collection;
033    import java.util.Collections;
034    import java.util.HashSet;
035    import java.util.LinkedHashMap;
036    import java.util.List;
037    import java.util.Map;
038    import java.util.Set;
039    
040    import org.granite.generator.as3.reflect.JavaMethod.MethodType;
041    import org.granite.messaging.amf.io.util.externalizer.annotation.IgnoredProperty;
042    import org.granite.messaging.service.annotations.IgnoredMethod;
043    import org.granite.messaging.service.annotations.RemoteDestination;
044    
045    /**
046     * @author Franck WOLFF
047     */
048    public class JavaRemoteDestination extends JavaAbstractType {
049    
050            // /////////////////////////////////////////////////////////////////////////
051            // Fields.
052    
053            protected final Set<JavaImport> imports = new HashSet<JavaImport>();
054            protected final JavaType superclass;
055            protected final List<JavaMethod> methods;
056        protected final Map<String, JavaMethodProperty> properties;
057            protected final String destinationName;
058            protected final String channelId;
059            
060            // /////////////////////////////////////////////////////////////////////////
061            // Constructor.
062    
063            public JavaRemoteDestination(JavaTypeFactory provider, Class<?> type, URL url) {
064                    super(provider, type, url);
065    
066                    // Find superclass (controller filtered).
067                    this.superclass = provider.getJavaTypeSuperclass(type);
068    
069                    // Collect methods.
070                    this.methods = Collections.unmodifiableList(initMethods());
071    
072            // Collect bean properties.
073            this.properties = Collections.unmodifiableMap(initProperties());
074    
075                    // Collect imports.
076                    if (superclass != null)
077                            addToImports(provider.getJavaImport(superclass.getType()));
078                    
079                    RemoteDestination rd = type.getAnnotation(RemoteDestination.class);
080                    if (rd != null) {
081                            destinationName = rd.id();
082                            channelId = rd.channel();
083                    }
084                    else {
085                            destinationName = null;
086                            channelId = null;
087                    }
088            }
089    
090            // /////////////////////////////////////////////////////////////////////////
091            // Properties.
092    
093            public Set<JavaImport> getImports() {
094                    return imports;
095            }
096    
097            protected void addToImports(JavaImport javaImport) {
098                    if (javaImport != null)
099                            imports.add(javaImport);
100            }
101    
102            public boolean hasSuperclass() {
103                    return superclass != null;
104            }
105    
106            public JavaType getSuperclass() {
107                    return superclass;
108            }
109            
110        public Collection<JavaMethod> getMethods() {
111            return methods;
112        }
113            
114        public Collection<JavaMethodProperty> getProperties() {
115            return properties.values();
116        }
117    
118            public String getDestinationName() {
119                    return destinationName;
120            }
121    
122            public String getChannelId() {
123                    return channelId;
124            }
125            
126            public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
127                    return type.isAnnotationPresent(annotation);
128            }
129    
130            // /////////////////////////////////////////////////////////////////////////
131            // Utilities.
132    
133            protected List<JavaMethod> initMethods() {
134                    List<JavaMethod> methodMap = new ArrayList<JavaMethod>();
135    
136                    // Get all methods for interfaces: normally, even if it is possible in Java
137                    // to override a method into a inherited interface, there is no meaning
138                    // to do so (we just ignore potential compilation issues with generated AS3
139                    // classes for this case since it is always possible to remove the method
140                    // re-declaration in the child interface).
141                    Method[] methods = null;
142                    if (type.isInterface())
143                            methods = type.getMethods();
144                    else
145                            methods = type.getDeclaredMethods();
146                    
147                    for (Method method : methods) {
148                            if (Modifier.isPublic(method.getModifiers()) &&
149                                    !Modifier.isStatic(method.getModifiers()) &&
150                                    !method.isAnnotationPresent(IgnoredMethod.class) &&
151                                    !((method.getName().startsWith("get") || method.getName().startsWith("is")) && method.getParameterTypes().length == 0) &&
152                                    !(method.getName().startsWith("set") && method.getParameterTypes().length == 1 && method.getReturnType() == void.class)) {
153                                    for (Class<?> clazz : method.getParameterTypes()) {
154                                            if (clazz.isMemberClass() && !clazz.isEnum()) {
155                                                    throw new UnsupportedOperationException(
156                                                            "Inner classes are not supported (except enums): " + clazz
157                                                    );
158                                            }
159                                            addToImports(provider.getJavaImport(clazz));
160                                    }
161    
162                                    methodMap.add(new JavaMethod(method, MethodType.OTHER, this.provider));
163                            }
164                    }
165    
166                    return methodMap;
167            }
168    
169            protected Map<String, JavaMethodProperty> initProperties() {
170            Map<String, JavaMethodProperty> propertyMap = new LinkedHashMap<String, JavaMethodProperty>();
171    
172                    // Get all methods for interfaces: normally, even if it is possible in Java
173                    // to override a method into a inherited interface, there is no meaning
174                    // to do so (we just ignore potential compilation issues with generated AS3
175                    // classes for this case since it is always possible to remove the method
176                    // re-declaration in the child interface).
177                    Method[] methods = null;
178                    if (type.isInterface())
179                            methods = type.getMethods();
180                    else
181                            methods = type.getDeclaredMethods();
182                    
183                    List<Object[]> tmp = new ArrayList<Object[]>();
184                    
185                    for (Method method : methods) {
186                            if (Modifier.isPublic(method.getModifiers()) &&
187                                    !Modifier.isStatic(method.getModifiers()) &&
188                                    !method.isAnnotationPresent(IgnoredProperty.class) &&
189                                    (((method.getName().startsWith("get") || method.getName().startsWith("is")) && method.getParameterTypes().length == 0) ||
190                                      (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && method.getReturnType() == void.class))) {
191                                    
192                                            for (Class<?> clazz : method.getParameterTypes())
193                                                    addToImports(provider.getJavaImport(clazz));
194                                            addToImports(provider.getJavaImport(method.getReturnType()));
195                                            
196                                            String propertyName = Introspector.decapitalize(method.getName().startsWith("is") ? method.getName().substring(2) : method.getName().substring(3));
197            
198                                            Object[] property = null;
199                                            for (Object[] mp : tmp) {
200                                                    if (mp[0].equals(propertyName)) {
201                                                            property = mp;
202                                                            break;
203                                                    }
204                                            }
205                                            if (property == null) {
206                                                    property = new Object[] { propertyName, null, null };
207                                                    tmp.add(property);
208                                            }
209                                            if (method.getName().startsWith("set"))
210                                                    property[2] = method;
211                                            else
212                                                    property[1] = method;
213                            }
214                    }
215                    
216                    for (Object[] property : tmp) {
217                            JavaMethod readMethod = property[1] != null ? new JavaMethod((Method)property[1], MethodType.GETTER) : null;
218                            JavaMethod writeMethod = property[2] != null ? new JavaMethod((Method)property[2], MethodType.SETTER) : null;
219                            propertyMap.put((String)property[0], new JavaMethodProperty(provider, (String)property[0], readMethod, writeMethod));
220                    }
221                    
222                    return propertyMap;
223            }
224    
225            
226            public JavaInterface convertToJavaInterface() {
227                    return new JavaInterface(getProvider(), getType(), getUrl());
228            }
229    }