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    
023    package org.granite.generator.javafx;
024    
025    import java.lang.reflect.GenericArrayType;
026    import java.lang.reflect.ParameterizedType;
027    import java.lang.reflect.Type;
028    import java.lang.reflect.TypeVariable;
029    import java.lang.reflect.WildcardType;
030    import java.util.HashMap;
031    import java.util.HashSet;
032    import java.util.List;
033    import java.util.Map;
034    import java.util.Set;
035    import java.util.SortedMap;
036    import java.util.SortedSet;
037    
038    import org.granite.generator.as3.As3TypeFactory;
039    import org.granite.generator.as3.ClientType;
040    import org.granite.generator.as3.PropertyType;
041    import org.granite.generator.util.GenericTypeUtil;
042    import org.granite.util.ClassUtil;
043    
044    /**
045     * @author Franck WOLFF
046     */
047    public 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    }