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
023package org.granite.generator.as3.reflect;
024
025import java.lang.annotation.Annotation;
026import java.lang.reflect.Field;
027import java.lang.reflect.Method;
028import java.lang.reflect.Modifier;
029import java.lang.reflect.ParameterizedType;
030import java.lang.reflect.Type;
031import java.net.URL;
032import java.util.ArrayList;
033import java.util.Collection;
034import java.util.Collections;
035import java.util.HashMap;
036import java.util.HashSet;
037import java.util.LinkedHashMap;
038import java.util.List;
039import java.util.Map;
040import java.util.Set;
041import java.util.SortedMap;
042import java.util.TreeMap;
043
044import org.granite.generator.as3.As3Type;
045import org.granite.generator.as3.ClientType;
046import org.granite.generator.as3.reflect.JavaMethod.MethodType;
047import org.granite.messaging.annotations.Exclude;
048import org.granite.messaging.annotations.Include;
049import org.granite.tide.annotations.TideEvent;
050import org.granite.util.ClassUtil;
051import org.granite.util.ClassUtil.DeclaredAnnotation;
052import org.granite.util.PropertyDescriptor;
053
054/**
055 * @author Franck WOLFF
056 */
057public 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}