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.javafx;
022
023import java.lang.reflect.GenericArrayType;
024import java.lang.reflect.ParameterizedType;
025import java.lang.reflect.Type;
026import java.lang.reflect.TypeVariable;
027import java.lang.reflect.WildcardType;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.List;
031import java.util.Map;
032import java.util.Set;
033import java.util.SortedMap;
034import java.util.SortedSet;
035
036import org.granite.generator.as3.As3TypeFactory;
037import org.granite.generator.as3.ClientType;
038import org.granite.generator.as3.PropertyType;
039import org.granite.generator.util.GenericTypeUtil;
040import org.granite.util.ClassUtil;
041
042/**
043 * @author Franck WOLFF
044 */
045public class DefaultJavaFXTypeFactory implements As3TypeFactory {
046
047    ///////////////////////////////////////////////////////////////////////////
048    // Fields.
049
050    private final Map<String, ClientType> simpleJava2JavaFXType = new HashMap<String, ClientType>();
051    private final Map<String, ClientType> propertyJava2JavaFXType = new HashMap<String, ClientType>();
052    private final Map<String, ClientType> readOnlyPropertyJava2JavaFXType = new HashMap<String, ClientType>();
053    
054    
055    ///////////////////////////////////////////////////////////////////////////
056    // Constructors.
057
058    public DefaultJavaFXTypeFactory() {
059        simpleJava2JavaFXType.put(buildCacheKey(Boolean.TYPE), JavaFXType.BOOLEAN);
060        simpleJava2JavaFXType.put(buildCacheKey(Integer.TYPE), JavaFXType.INT);
061        simpleJava2JavaFXType.put(buildCacheKey(Long.TYPE), JavaFXType.LONG);
062        simpleJava2JavaFXType.put(buildCacheKey(Float.TYPE), JavaFXType.FLOAT);
063        simpleJava2JavaFXType.put(buildCacheKey(Double.TYPE), JavaFXType.DOUBLE);
064        simpleJava2JavaFXType.put(buildCacheKey(String.class), JavaFXType.STRING);
065        
066        propertyJava2JavaFXType.put(buildCacheKey(Boolean.TYPE), JavaFXType.BOOLEAN_PROPERTY);
067        propertyJava2JavaFXType.put(buildCacheKey(Double.TYPE), JavaFXType.DOUBLE_PROPERTY);
068        propertyJava2JavaFXType.put(buildCacheKey(Float.TYPE), JavaFXType.FLOAT_PROPERTY);
069        propertyJava2JavaFXType.put(buildCacheKey(Long.TYPE), JavaFXType.LONG_PROPERTY);
070        propertyJava2JavaFXType.put(buildCacheKey(Integer.TYPE), JavaFXType.INT_PROPERTY);
071        propertyJava2JavaFXType.put(buildCacheKey(String.class), JavaFXType.STRING_PROPERTY);
072        
073        readOnlyPropertyJava2JavaFXType.put(buildCacheKey(Boolean.TYPE), JavaFXType.BOOLEAN_READONLY_PROPERTY);
074        readOnlyPropertyJava2JavaFXType.put(buildCacheKey(Double.TYPE), JavaFXType.DOUBLE_READONLY_PROPERTY);
075        readOnlyPropertyJava2JavaFXType.put(buildCacheKey(Float.TYPE), JavaFXType.FLOAT_READONLY_PROPERTY);
076        readOnlyPropertyJava2JavaFXType.put(buildCacheKey(Long.TYPE), JavaFXType.LONG_READONLY_PROPERTY);
077        readOnlyPropertyJava2JavaFXType.put(buildCacheKey(Integer.TYPE), JavaFXType.INT_READONLY_PROPERTY);
078        readOnlyPropertyJava2JavaFXType.put(buildCacheKey(String.class), JavaFXType.STRING_READONLY_PROPERTY);
079    }
080
081    ///////////////////////////////////////////////////////////////////////////
082    // Fields.
083
084    @Override
085        public void configure(boolean externalizeLong, boolean externalizeBigInteger, boolean externalizeBigDecimal) {
086        }
087    
088    private static final String buildCacheKey(Type jType) {
089        return buildCacheKey(jType, null, null);
090    }
091    private static final String buildCacheKey(Type jType, Class<?> declaringClass, ParameterizedType[] declaringTypes) {
092                String key = jType.toString();
093                if (declaringClass != null)
094                        key += "::" + declaringClass.toString();
095                if (declaringTypes != null) {
096                        for (ParameterizedType dt : declaringTypes)
097                                key += "::" + dt.toString();
098                }
099                return key;
100    }
101
102        @Override
103        public ClientType getClientType(Type jType, Class<?> declaringClass, ParameterizedType[] declaringTypes, PropertyType propertyType) {
104                String key = buildCacheKey(jType, declaringClass, declaringTypes);
105                
106                ClientType javafxType = getFromCache(key, propertyType);
107                
108        if (javafxType == null) {
109                if (jType instanceof GenericArrayType) {
110                        Type componentType = ((GenericArrayType)jType).getGenericComponentType();
111                        javafxType = getClientType(componentType, declaringClass, declaringTypes, PropertyType.SIMPLE).toArrayType();
112                }
113                else if (jType instanceof Class<?> && ((Class<?>)jType).isArray()) {
114                        javafxType = getClientType(((Class<?>)jType).getComponentType(), declaringClass, declaringTypes, PropertyType.SIMPLE).toArrayType();
115                }
116                else {
117                        Set<String> imports = new HashSet<String>();
118                        Class<?> jClass = ClassUtil.classOfType(jType);
119                        String genericType = "";
120                        if (jType instanceof ParameterizedType)
121                                genericType = buildGenericTypeName((ParameterizedType)jType, declaringClass, declaringTypes, propertyType, imports);
122                        
123                    if (propertyType.isProperty() && List.class.isAssignableFrom(jClass)) {
124                        imports.add("org.granite.client.persistence.collection.javafx.FXPersistentCollections");
125                        javafxType = new JavaFXType("javafx.collections", "ObservableList" + genericType, "javafx.beans.property.ReadOnlyListProperty" + genericType, "javafx.beans.property.ReadOnlyListWrapper" + genericType, "FXPersistentCollections.readOnlyObservablePersistentList", null, true);
126                    }
127                else if (propertyType.isProperty() && SortedSet.class.isAssignableFrom(jClass)) {
128                    imports.add("org.granite.client.persistence.collection.javafx.FXPersistentCollections");
129                    javafxType = new JavaFXType("javafx.collections", "ObservableSet" + genericType, "javafx.beans.property.ReadOnlySetProperty" + genericType, "javafx.beans.property.ReadOnlySetWrapper" + genericType, "FXPersistentCollections.readOnlyObservablePersistentSortedSet", null, true);
130                }
131                    else if (propertyType.isProperty() && Set.class.isAssignableFrom(jClass)) {
132                        imports.add("org.granite.client.persistence.collection.javafx.FXPersistentCollections");
133                        javafxType = new JavaFXType("javafx.collections", "ObservableSet" + genericType, "javafx.beans.property.ReadOnlySetProperty" + genericType, "javafx.beans.property.ReadOnlySetWrapper" + genericType, "FXPersistentCollections.readOnlyObservablePersistentSet", null, true);
134                    }
135                else if (propertyType.isProperty() && SortedMap.class.isAssignableFrom(jClass)) {
136                    imports.add("org.granite.client.persistence.collection.javafx.FXPersistentCollections");
137                    javafxType = new JavaFXType("javafx.collections", "ObservableMap" + genericType, "javafx.beans.property.ReadOnlyMapProperty" + genericType, "javafx.beans.property.ReadOnlyMapWrapper" + genericType, "FXPersistentCollections.readOnlyObservablePersistentSortedMap", null, true);
138                }
139                    else if (propertyType.isProperty() && Map.class.isAssignableFrom(jClass)) {
140                        imports.add("org.granite.client.persistence.collection.javafx.FXPersistentCollections");
141                        javafxType = new JavaFXType("javafx.collections", "ObservableMap" + genericType, "javafx.beans.property.ReadOnlyMapProperty" + genericType, "javafx.beans.property.ReadOnlyMapWrapper" + genericType, "FXPersistentCollections.readOnlyObservablePersistentMap", null, true);
142                    }
143                    else if (jClass.getName().equals("com.google.appengine.api.datastore.Key")) {
144                        javafxType = JavaFXType.STRING;
145                    }
146                    else if (jClass.getName().equals("org.springframework.data.domain.Page")) {
147                        javafxType = new JavaFXType("org.granite.tide.data.model", "Page" + genericType, null);
148                    }
149                    else if (jClass.getName().equals("org.springframework.data.domain.Pageable")) {
150                        javafxType = JavaFXType.PAGE_INFO;
151                    }
152                    else if (jClass.getName().equals("org.springframework.data.domain.Sort")) {
153                        javafxType = JavaFXType.SORT_INFO;
154                    }
155                    else {
156                        javafxType = createJavaFXType(jType, declaringClass, declaringTypes, propertyType);
157                    }
158                    if (!imports.isEmpty())
159                        javafxType.addImports(imports);
160                }
161                
162            putInCache(key, propertyType, javafxType);
163        }
164        
165        return javafxType;
166    }
167
168        @Override
169        public ClientType getAs3Type(Class<?> jType) {
170                return getClientType(jType, null, null, PropertyType.SIMPLE);
171        }
172
173    protected JavaFXType createJavaFXType(Type jType, Class<?> declaringClass, ParameterizedType[] declaringTypes, PropertyType propertyType) {
174        Set<String> imports = new HashSet<String>();
175        
176        Class<?> jClass = ClassUtil.classOfType(jType);
177        String name = jClass.getSimpleName();
178        if (jClass.isMemberClass())
179                name = jClass.getEnclosingClass().getSimpleName() + '$' + jClass.getSimpleName();
180        else if (jType instanceof ParameterizedType) {
181                ParameterizedType type = (ParameterizedType)jType;
182                name += buildGenericTypeName(type, declaringClass, declaringTypes, propertyType, imports);
183        }
184        
185        JavaFXType javaFXType = null;
186        if (propertyType == PropertyType.PROPERTY) {
187                javaFXType = new JavaFXType(ClassUtil.getPackageName(jClass), name, 
188                                "javafx.beans.property.ObjectProperty<" + name + ">", "javafx.beans.property.SimpleObjectProperty<" + name + ">", null);
189        }
190        else if (propertyType == PropertyType.READONLY_PROPERTY) {
191                javaFXType = new JavaFXType(ClassUtil.getPackageName(jClass), name, 
192                                "javafx.beans.property.ReadOnlyObjectProperty<" + name + ">", "javafx.beans.property.ReadOnlyObjectWrapper<" + name + ">", null, null, true);
193        }
194        else
195                javaFXType = new JavaFXType(ClassUtil.getPackageName(jClass), name, null);
196        javaFXType.addImports(imports);
197        return javaFXType;
198    }
199
200    protected ClientType getFromCache(String key, PropertyType propertyType) {
201        if (key == null)
202            throw new NullPointerException("jType must be non null");
203        if (propertyType == PropertyType.PROPERTY)
204                return propertyJava2JavaFXType.get(key);
205        else if (propertyType == PropertyType.READONLY_PROPERTY)
206                return readOnlyPropertyJava2JavaFXType.get(key);
207        return simpleJava2JavaFXType.get(key);
208    }
209
210    protected void putInCache(String key, PropertyType propertyType, ClientType javafxType) {
211        if (key == null || javafxType == null)
212            throw new NullPointerException("jType and JavaFXType must be non null");
213        if (propertyType == PropertyType.PROPERTY)
214                propertyJava2JavaFXType.put(key, javafxType);
215        else if (propertyType == PropertyType.READONLY_PROPERTY)
216                readOnlyPropertyJava2JavaFXType.put(key, javafxType);
217        else
218                simpleJava2JavaFXType.put(key, javafxType);
219    }
220    
221    private String buildGenericTypeName(ParameterizedType type, Class<?> declaringClass, ParameterizedType[] declaringTypes, PropertyType propertyType, Set<String> imports) {
222                StringBuilder sb = new StringBuilder("<");
223                boolean first = true;
224                for (Type ata : type.getActualTypeArguments()) {
225                        if (first)
226                                first = false;
227                        else
228                                sb.append(", ");
229                        if (ata instanceof TypeVariable<?>) {
230                                Type resolved = GenericTypeUtil.resolveTypeVariable(ata, declaringClass, declaringTypes);
231                                if (resolved instanceof TypeVariable<?>)
232                                        sb.append("?");
233                                else {
234                                        sb.append(ClassUtil.classOfType(resolved).getSimpleName());
235                                        imports.add(getClientType(resolved, declaringClass, declaringTypes, propertyType).getQualifiedName());
236                                }
237                        }
238                        else if (ata instanceof WildcardType) {
239                                sb.append("?");
240                                if (((WildcardType)ata).getLowerBounds().length > 0) {
241                                        String bounds = "";
242                                for (Type t : ((WildcardType)ata).getLowerBounds()) {
243                                        Type resolved = GenericTypeUtil.resolveTypeVariable(t, declaringClass, declaringTypes);
244                                        if (resolved instanceof TypeVariable<?>) {
245                                                bounds = "";
246                                                break;
247                                        }
248                                        if (bounds.length() > 0)
249                                                bounds = bounds + ", ";
250                                        bounds = bounds + ClassUtil.classOfType(resolved).getSimpleName();
251                                        imports.add(getClientType(resolved, declaringClass, declaringTypes, propertyType).getQualifiedName());
252                                }
253                                if (bounds.length() > 0)
254                                        sb.append(" super ").append(bounds);
255                                }
256                                if (((WildcardType)ata).getUpperBounds().length > 0) {
257                                        String bounds = "";
258                                for (Type t : ((WildcardType)ata).getUpperBounds()) {
259                                        Type resolved = GenericTypeUtil.resolveTypeVariable(t, declaringClass, declaringTypes);
260                                        if (resolved instanceof TypeVariable<?>) {
261                                                bounds = "";
262                                                break;
263                                        }
264                                        if (bounds.length() > 0)
265                                                bounds = bounds + ", ";
266                                        bounds = bounds + ClassUtil.classOfType(resolved).getSimpleName();
267                                        imports.add(getClientType(resolved, declaringClass, declaringTypes, propertyType).getQualifiedName());
268                                }
269                                if (bounds.length() > 0)
270                                        sb.append(" extends ").append(bounds);
271                                }
272                        }
273                        else {
274                                sb.append(ClassUtil.classOfType(ata).getSimpleName());
275                                imports.add(getClientType(ata, declaringClass, declaringTypes, propertyType).getQualifiedName());
276                        }
277                }
278                sb.append(">");
279                return sb.toString();
280    }
281}