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