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    package org.granite.generator.as3.reflect;
024    
025    import java.lang.annotation.Annotation;
026    import java.lang.reflect.Field;
027    import java.lang.reflect.Method;
028    import java.lang.reflect.Modifier;
029    import java.lang.reflect.ParameterizedType;
030    import java.lang.reflect.Type;
031    import java.net.URL;
032    import java.util.ArrayList;
033    import java.util.Collection;
034    import java.util.Collections;
035    import java.util.HashMap;
036    import java.util.HashSet;
037    import java.util.LinkedHashMap;
038    import java.util.List;
039    import java.util.Map;
040    import java.util.Set;
041    import java.util.SortedMap;
042    import java.util.TreeMap;
043    
044    import org.granite.generator.as3.As3Type;
045    import org.granite.generator.as3.ClientType;
046    import org.granite.generator.as3.reflect.JavaMethod.MethodType;
047    import org.granite.messaging.annotations.Exclude;
048    import org.granite.messaging.annotations.Include;
049    import org.granite.tide.annotations.TideEvent;
050    import org.granite.util.ClassUtil;
051    import org.granite.util.ClassUtil.DeclaredAnnotation;
052    import org.granite.util.PropertyDescriptor;
053    
054    /**
055     * @author Franck WOLFF
056     */
057    public class JavaBean extends JavaAbstractType {
058    
059        ///////////////////////////////////////////////////////////////////////////
060        // Fields.
061    
062        protected final Set<JavaImport> imports = new HashSet<JavaImport>();
063    
064        protected final JavaType superclass;
065        protected final ClientType clientSuperclass;
066    
067        protected final List<JavaInterface> interfaces;
068        protected final List<JavaProperty> interfacesProperties;
069    
070        protected final Map<String, JavaProperty> properties;
071        protected final JavaProperty uid;
072        protected final List<JavaProperty> lazyProperties;
073    
074        ///////////////////////////////////////////////////////////////////////////
075        // Constructor.
076    
077        public JavaBean(JavaTypeFactory provider, Class<?> type, URL url) {
078            super(provider, type, url);
079    
080            // Find superclass (controller filtered).
081            this.superclass = provider.getJavaTypeSuperclass(type);
082            if (this.superclass == null && type.isAnnotationPresent(TideEvent.class))
083                    clientSuperclass = new As3Type("org.granite.tide.events", "AbstractTideEvent");
084            else
085                    clientSuperclass = null;
086    
087            // Find implemented interfaces (filtered by the current transformer).
088            this.interfaces = Collections.unmodifiableList(provider.getJavaTypeInterfaces(type));
089    
090            // Collect bean properties.
091            Map<String, JavaProperty> properties = new LinkedHashMap<String, JavaProperty>();
092            
093            if (this.superclass == null) {
094                    // Collect bean properties of non generated superclasses if necessary
095                    // Example: com.myapp.AbstractEntity extends org.springframework.data.jpa.model.AbstractPersistable<T>
096                    // gsup collects the superclass parameterized type so the type parameter can be translated to an actual type later
097                    List<Class<?>> superclasses = new ArrayList<Class<?>>();
098                    Type gsup = type.getGenericSuperclass();
099                    Class<?> c = type.getSuperclass();
100                    while (c.getGenericSuperclass() != null && !c.getName().equals(Object.class.getName())) {
101                            superclasses.add(0, c);
102                            c = c.getSuperclass();
103                    }
104                    
105                    for (Class<?> sc : superclasses)
106                            properties.putAll(initProperties(sc, gsup instanceof ParameterizedType ? (ParameterizedType)gsup : null));
107            }
108            
109            properties.putAll(initProperties());
110    
111            this.properties = Collections.unmodifiableMap(properties);
112    
113            List<JavaProperty> tmpLazyProperties = new ArrayList<JavaProperty>();
114            for (JavaProperty property : properties.values()) {
115                if (provider.isLazy(property))
116                    tmpLazyProperties.add(property);
117            }
118            this.lazyProperties = (tmpLazyProperties.isEmpty() ? null : Collections.unmodifiableList(tmpLazyProperties));
119    
120            // Collect properties from superclasses.
121            Map<String, JavaProperty> allProperties = new HashMap<String, JavaProperty>(this.properties);
122            for (JavaType supertype = this.superclass; supertype instanceof JavaBean; supertype = ((JavaBean)supertype).superclass)
123                    allProperties.putAll(((JavaBean)supertype).properties);
124    
125            // Collect properties from interfaces.
126            Map<String, JavaProperty> iPropertyMap = new HashMap<String, JavaProperty>();
127            addImplementedInterfacesProperties(interfaces, iPropertyMap, allProperties);
128            this.interfacesProperties = getSortedUnmodifiableList(iPropertyMap.values());
129    
130            // Find uid (if any).
131            JavaProperty tmpUid = null;
132            for (JavaProperty property : properties.values()) {
133                if (provider.isUid(property)) {
134                    tmpUid = property;
135                    break;
136                }
137            }
138            this.uid = tmpUid;
139    
140            // Collect imports.
141            if (superclass != null)
142                addToImports(provider.getJavaImport(superclass.getType()));
143            for (JavaInterface interfaze : interfaces)
144                addToImports(provider.getJavaImport(interfaze.getType()));
145            for (JavaProperty property : properties.values())
146                addToImports(provider.getJavaImports(property.getClientType(), true));
147        }
148    
149        private void addImplementedInterfacesProperties(List<JavaInterface> interfaces, Map<String, JavaProperty> iPropertyMap, Map<String, JavaProperty> allProperties) {
150            for (JavaInterface interfaze : interfaces) {
151                    for (JavaProperty property : interfaze.getProperties()) {
152                        String name = property.getName();
153                        if (!iPropertyMap.containsKey(name) && !allProperties.containsKey(name))
154                            iPropertyMap.put(name, property);
155                    }
156                    addImplementedInterfacesProperties(interfaze.interfaces, iPropertyMap, allProperties);
157            }
158            }
159    
160            ///////////////////////////////////////////////////////////////////////////
161        // Properties.
162    
163        public Set<JavaImport> getImports() {
164            return imports;
165        }
166        protected void addToImports(JavaImport javaImport) {
167            if (javaImport != null)
168                imports.add(javaImport);
169        }
170        protected void addToImports(Set<JavaImport> javaImports) {
171            if (javaImports != null)
172                imports.addAll(javaImports);
173        }
174    
175        public boolean hasSuperclass() {
176            return superclass != null;
177        }
178        public JavaType getSuperclass() {
179            return superclass;
180        }
181        public ClientType getAs3Superclass() {
182            return clientSuperclass;
183        }
184        public ClientType getClientSuperclass() {
185            return clientSuperclass;
186        }
187    
188        public boolean hasInterfaces() {
189            return interfaces != null && !interfaces.isEmpty();
190        }
191        public List<JavaInterface> getInterfaces() {
192            return interfaces;
193        }
194    
195        public boolean hasInterfacesProperties() {
196            return interfacesProperties != null && !interfacesProperties.isEmpty();
197        }
198        public List<JavaProperty> getInterfacesProperties() {
199            return interfacesProperties;
200        }
201    
202        public Collection<JavaProperty> getProperties() {
203            return properties.values();
204        }
205        
206        public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
207            return type.isAnnotationPresent(annotation);
208        }
209    
210        public boolean hasUid() {
211            return uid != null;
212        }
213        public JavaProperty getUid() {
214            return uid;
215        }
216        
217        public boolean isLazy(JavaProperty property) {
218            return lazyProperties != null && lazyProperties.contains(property);
219        }
220        
221        public boolean hasEnumProperty() {
222            for (JavaProperty property : properties.values()) {
223                    if (property.isEnum())
224                            return true;
225            }
226            return false;
227        }
228    
229        ///////////////////////////////////////////////////////////////////////////
230        // Utilities.
231    
232        protected SortedMap<String, JavaProperty> initProperties() {
233            return initProperties(type, null);
234        }
235        
236        protected SortedMap<String, JavaProperty> initProperties(Class<?> type, ParameterizedType parentGenericType) {
237            PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(type);
238            SortedMap<String, JavaProperty> propertyMap = new TreeMap<String, JavaProperty>();
239    
240            // Standard declared fields.
241            for (Field field : type.getDeclaredFields()) {
242                if (!Modifier.isStatic(field.getModifiers()) &&
243                    !Modifier.isTransient(field.getModifiers()) &&
244                    !"jdoDetachedState".equals(field.getName()) &&  // Specific for JDO statically enhanced classes
245                    !field.isAnnotationPresent(Exclude.class)) {
246    
247                    String name = field.getName();
248                    JavaMethod readMethod = null;
249                    JavaMethod writeMethod = null;
250                    
251                    if (field.getType().isMemberClass() && !field.getType().isEnum())
252                            throw new UnsupportedOperationException("Inner classes are not supported (except enums): " + field.getType());
253    
254                    if (propertyDescriptors != null) {
255                        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
256                            if (name.equals(propertyDescriptor.getName())) {
257                                if (propertyDescriptor.getReadMethod() != null)
258                                    readMethod = new JavaMethod(propertyDescriptor.getReadMethod(), MethodType.GETTER);
259                                if (propertyDescriptor.getWriteMethod() != null)
260                                    writeMethod = new JavaMethod(propertyDescriptor.getWriteMethod(), MethodType.SETTER);
261                                break;
262                            }
263                        }
264                    }
265    
266                    JavaFieldProperty property = new JavaFieldProperty(provider, field, readMethod, writeMethod, parentGenericType);
267                    propertyMap.put(name, property);
268                }
269            }
270    
271            // Getter annotated by @ExternalizedProperty.
272            if (propertyDescriptors != null) {
273                for (PropertyDescriptor property : propertyDescriptors) {
274                    Method getter = property.getReadMethod();
275                    if (getter != null &&
276                            getter.getDeclaringClass().equals(type) &&
277                        !propertyMap.containsKey(property.getName())) {
278                        
279                            DeclaredAnnotation<Include> annotation = ClassUtil.getAnnotation(getter, Include.class);
280                            if (annotation == null || (annotation.declaringClass != type && !annotation.declaringClass.isInterface()))
281                                    continue;
282    
283                        JavaMethod readMethod = new JavaMethod(getter, MethodType.GETTER);
284                        JavaMethodProperty methodProperty = new JavaMethodProperty(provider, property.getName(), readMethod, null, parentGenericType);
285                        propertyMap.put(property.getName(), methodProperty);
286                    }
287                }
288            }
289    
290            return propertyMap;
291        }
292    }