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