/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * Granite Data Services is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * Granite Data Services is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA, or see <http://www.gnu.org/licenses/>. */ package org.granite.generator.javafx; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import org.granite.generator.as3.As3TypeFactory; import org.granite.generator.as3.ClientType; import org.granite.generator.as3.PropertyType; import org.granite.generator.util.GenericTypeUtil; import org.granite.tide.data.Lazy; import org.granite.util.ClassUtil; /** * @author Franck WOLFF */ public class DefaultJavaFXTypeFactory implements As3TypeFactory { /////////////////////////////////////////////////////////////////////////// // Fields. protected final Map<String, ClientType> simpleJava2JavaFXType = new HashMap<String, ClientType>(); protected final Map<String, ClientType> propertyJava2JavaFXType = new HashMap<String, ClientType>(); protected final Map<String, ClientType> readOnlyPropertyJava2JavaFXType = new HashMap<String, ClientType>(); /////////////////////////////////////////////////////////////////////////// // Constructors. public DefaultJavaFXTypeFactory() { simpleJava2JavaFXType.put(buildCacheKey(Boolean.TYPE), JavaFXType.BOOLEAN); simpleJava2JavaFXType.put(buildCacheKey(Integer.TYPE), JavaFXType.INT); simpleJava2JavaFXType.put(buildCacheKey(Long.TYPE), JavaFXType.LONG); simpleJava2JavaFXType.put(buildCacheKey(Float.TYPE), JavaFXType.FLOAT); simpleJava2JavaFXType.put(buildCacheKey(Double.TYPE), JavaFXType.DOUBLE); simpleJava2JavaFXType.put(buildCacheKey(String.class), JavaFXType.STRING); simpleJava2JavaFXType.put(buildCacheKey(Lazy.class), JavaFXType.LAZY); propertyJava2JavaFXType.put(buildCacheKey(Boolean.TYPE), JavaFXType.BOOLEAN_PROPERTY); propertyJava2JavaFXType.put(buildCacheKey(Double.TYPE), JavaFXType.DOUBLE_PROPERTY); propertyJava2JavaFXType.put(buildCacheKey(Float.TYPE), JavaFXType.FLOAT_PROPERTY); propertyJava2JavaFXType.put(buildCacheKey(Long.TYPE), JavaFXType.LONG_PROPERTY); propertyJava2JavaFXType.put(buildCacheKey(Integer.TYPE), JavaFXType.INT_PROPERTY); propertyJava2JavaFXType.put(buildCacheKey(String.class), JavaFXType.STRING_PROPERTY); readOnlyPropertyJava2JavaFXType.put(buildCacheKey(Boolean.TYPE), JavaFXType.BOOLEAN_READONLY_PROPERTY); readOnlyPropertyJava2JavaFXType.put(buildCacheKey(Double.TYPE), JavaFXType.DOUBLE_READONLY_PROPERTY); readOnlyPropertyJava2JavaFXType.put(buildCacheKey(Float.TYPE), JavaFXType.FLOAT_READONLY_PROPERTY); readOnlyPropertyJava2JavaFXType.put(buildCacheKey(Long.TYPE), JavaFXType.LONG_READONLY_PROPERTY); readOnlyPropertyJava2JavaFXType.put(buildCacheKey(Integer.TYPE), JavaFXType.INT_READONLY_PROPERTY); readOnlyPropertyJava2JavaFXType.put(buildCacheKey(String.class), JavaFXType.STRING_READONLY_PROPERTY); } /////////////////////////////////////////////////////////////////////////// // Fields. @Override public void configure(boolean externalizeLong, boolean externalizeBigInteger, boolean externalizeBigDecimal) { } protected static final String buildCacheKey(Type jType) { return buildCacheKey(jType, null, null); } protected static final String buildCacheKey(Type jType, Class<?> declaringClass, ParameterizedType[] declaringTypes) { String key = jType.toString(); if (declaringClass != null) key += "::" + declaringClass.toString(); if (declaringTypes != null) { for (ParameterizedType dt : declaringTypes) key += "::" + dt.toString(); } return key; } @Override public ClientType getClientType(Type jType, Class<?> declaringClass, ParameterizedType[] declaringTypes, PropertyType propertyType) { String key = buildCacheKey(jType, declaringClass, declaringTypes); ClientType javafxType = getFromCache(key, propertyType); if (javafxType == null) { if (propertyType == PropertyType.SIMPLE && jType instanceof GenericArrayType) { Type componentType = ((GenericArrayType)jType).getGenericComponentType(); javafxType = getClientType(componentType, declaringClass, declaringTypes, PropertyType.SIMPLE).toArrayType(); } else if (propertyType == PropertyType.SIMPLE && jType instanceof Class<?> && ((Class<?>)jType).isArray()) { javafxType = getClientType(((Class<?>)jType).getComponentType(), declaringClass, declaringTypes, PropertyType.SIMPLE).toArrayType(); } else { Set<String> imports = new HashSet<String>(); Class<?> jClass = ClassUtil.classOfType(jType); String genericType = ""; if (jType instanceof ParameterizedType) genericType = buildGenericTypeName((ParameterizedType)jType, declaringClass, declaringTypes, propertyType, imports); if (propertyType.isProperty() && List.class.isAssignableFrom(jClass)) { imports.add("org.granite.client.javafx.persistence.collection.FXPersistentCollections"); javafxType = new JavaFXType("javafx.collections", "ObservableList" + genericType, "javafx.beans.property.ReadOnlyListProperty" + genericType, "javafx.beans.property.ReadOnlyListWrapper" + genericType, "FXPersistentCollections.readOnlyObservablePersistentList", null, true); } else if (propertyType.isProperty() && SortedSet.class.isAssignableFrom(jClass)) { imports.add("org.granite.client.javafx.persistence.collection.FXPersistentCollections"); javafxType = new JavaFXType("javafx.collections", "ObservableSet" + genericType, "javafx.beans.property.ReadOnlySetProperty" + genericType, "javafx.beans.property.ReadOnlySetWrapper" + genericType, "FXPersistentCollections.readOnlyObservablePersistentSortedSet", null, true); } else if (propertyType.isProperty() && Set.class.isAssignableFrom(jClass)) { imports.add("org.granite.client.javafx.persistence.collection.FXPersistentCollections"); javafxType = new JavaFXType("javafx.collections", "ObservableSet" + genericType, "javafx.beans.property.ReadOnlySetProperty" + genericType, "javafx.beans.property.ReadOnlySetWrapper" + genericType, "FXPersistentCollections.readOnlyObservablePersistentSet", null, true); } else if (propertyType.isProperty() && SortedMap.class.isAssignableFrom(jClass)) { imports.add("org.granite.client.javafx.persistence.collection.FXPersistentCollections"); javafxType = new JavaFXType("javafx.collections", "ObservableMap" + genericType, "javafx.beans.property.ReadOnlyMapProperty" + genericType, "javafx.beans.property.ReadOnlyMapWrapper" + genericType, "FXPersistentCollections.readOnlyObservablePersistentSortedMap", null, true); } else if (propertyType.isProperty() && Map.class.isAssignableFrom(jClass)) { imports.add("org.granite.client.javafx.persistence.collection.FXPersistentCollections"); javafxType = new JavaFXType("javafx.collections", "ObservableMap" + genericType, "javafx.beans.property.ReadOnlyMapProperty" + genericType, "javafx.beans.property.ReadOnlyMapWrapper" + genericType, "FXPersistentCollections.readOnlyObservablePersistentMap", null, true); } else if (jClass.getName().equals("com.google.appengine.api.datastore.Key")) { javafxType = JavaFXType.STRING; } else if (jClass.getName().equals("org.springframework.data.domain.Page")) { javafxType = new JavaFXType("org.granite.tide.data.model", "Page" + genericType, null); } else if (jClass.getName().equals("org.springframework.data.domain.Pageable")) { javafxType = JavaFXType.PAGE_INFO; } else if (jClass.getName().equals("org.springframework.data.domain.Sort")) { javafxType = JavaFXType.SORT_INFO; } else { javafxType = createJavaFXType(jType, declaringClass, declaringTypes, propertyType); } if (!imports.isEmpty()) javafxType.addImports(imports); } putInCache(key, propertyType, javafxType); } return javafxType; } @Override public ClientType getAs3Type(Class<?> jType) { return getClientType(jType, null, null, PropertyType.SIMPLE); } protected JavaFXType createJavaFXType(Type jType, Class<?> declaringClass, ParameterizedType[] declaringTypes, PropertyType propertyType) { Set<String> imports = new HashSet<String>(); Class<?> jClass = ClassUtil.classOfType(jType); String name = getSimpleName(jClass); if (jType instanceof ParameterizedType) { ParameterizedType type = (ParameterizedType)jType; name += buildGenericTypeName(type, declaringClass, declaringTypes, propertyType, imports); } JavaFXType javaFXType = null; if (propertyType == PropertyType.PROPERTY) { javaFXType = new JavaFXType(ClassUtil.getPackageName(jClass), name, "javafx.beans.property.ObjectProperty<" + name + ">", "javafx.beans.property.SimpleObjectProperty<" + name + ">", null); } else if (propertyType == PropertyType.READONLY_PROPERTY) { javaFXType = new JavaFXType(ClassUtil.getPackageName(jClass), name, "javafx.beans.property.ReadOnlyObjectProperty<" + name + ">", "javafx.beans.property.ReadOnlyObjectWrapper<" + name + ">", null, null, true); } else javaFXType = new JavaFXType(ClassUtil.getPackageName(jClass), name, null); javaFXType.addImports(imports); return javaFXType; } protected ClientType getFromCache(String key, PropertyType propertyType) { if (key == null) throw new NullPointerException("key must be non null"); if (propertyType == PropertyType.PROPERTY) return propertyJava2JavaFXType.get(key); else if (propertyType == PropertyType.READONLY_PROPERTY) return readOnlyPropertyJava2JavaFXType.get(key); return simpleJava2JavaFXType.get(key); } protected void putInCache(String key, PropertyType propertyType, ClientType javafxType) { if (key == null || javafxType == null) throw new NullPointerException("key and javafxType must be non null"); if (propertyType == PropertyType.PROPERTY) propertyJava2JavaFXType.put(key, javafxType); else if (propertyType == PropertyType.READONLY_PROPERTY) readOnlyPropertyJava2JavaFXType.put(key, javafxType); else simpleJava2JavaFXType.put(key, javafxType); } private String buildGenericTypeName(ParameterizedType type, Class<?> declaringClass, ParameterizedType[] declaringTypes, PropertyType propertyType, Set<String> imports) { StringBuilder sb = new StringBuilder("<"); boolean first = true; for (Type ata : type.getActualTypeArguments()) { if (first) first = false; else sb.append(", "); if (ata instanceof TypeVariable<?>) { Type resolved = GenericTypeUtil.resolveTypeVariable(ata, declaringClass, declaringTypes); if (resolved instanceof TypeVariable<?>) sb.append("?"); else { sb.append(ClassUtil.classOfType(resolved).getSimpleName()); imports.add(getClientType(resolved, declaringClass, declaringTypes, propertyType).getQualifiedName()); } } else if (ata instanceof WildcardType) { sb.append("?"); if (((WildcardType)ata).getLowerBounds().length > 0) { String bounds = ""; for (Type t : ((WildcardType)ata).getLowerBounds()) { Type resolved = GenericTypeUtil.resolveTypeVariable(t, declaringClass, declaringTypes); if (resolved instanceof TypeVariable<?>) { bounds = ""; break; } if (bounds.length() > 0) bounds = bounds + ", "; bounds = bounds + ClassUtil.classOfType(resolved).getSimpleName(); imports.add(getClientType(resolved, declaringClass, declaringTypes, propertyType).getQualifiedName()); } if (bounds.length() > 0) sb.append(" super ").append(bounds); } if (((WildcardType)ata).getUpperBounds().length > 0) { String bounds = ""; for (Type t : ((WildcardType)ata).getUpperBounds()) { Type resolved = GenericTypeUtil.resolveTypeVariable(t, declaringClass, declaringTypes); if (resolved instanceof TypeVariable<?>) { bounds = ""; break; } if (bounds.length() > 0) bounds = bounds + ", "; bounds = bounds + ClassUtil.classOfType(resolved).getSimpleName(); imports.add(getClientType(resolved, declaringClass, declaringTypes, propertyType).getQualifiedName()); } if (bounds.length() > 0) sb.append(" extends ").append(bounds); } } else if (ata instanceof ParameterizedType) { sb.append(ClassUtil.classOfType(ata).getSimpleName()); imports.add(getClientType(ata, declaringClass, declaringTypes, propertyType).getQualifiedName()); sb.append(buildGenericTypeName((ParameterizedType)ata, declaringClass, declaringTypes, propertyType, imports)); } else { sb.append(getSimpleName(ClassUtil.classOfType(ata))); imports.add(getClientType(ata, declaringClass, declaringTypes, propertyType).getQualifiedName()); } } sb.append(">"); return sb.toString(); } private static final String getSimpleName(Class<?> jClass) { String name = jClass.getSimpleName(); if (jClass.isMemberClass()) name = jClass.getEnclosingClass().getSimpleName() + '$' + jClass.getSimpleName(); return name; } }