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