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