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