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.java;
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 DefaultJavaTypeFactory implements As3TypeFactory {
048
049    ///////////////////////////////////////////////////////////////////////////
050    // Fields.
051
052    private final Map<String, ClientType> simpleJava2JavaType = new HashMap<String, ClientType>();
053    private final Map<String, ClientType> propertyJava2JavaType = new HashMap<String, ClientType>();
054    
055    
056    ///////////////////////////////////////////////////////////////////////////
057    // Constructors.
058
059    public DefaultJavaTypeFactory() {
060        simpleJava2JavaType.put(buildCacheKey(Boolean.TYPE), JavaType.BOOLEAN);
061        simpleJava2JavaType.put(buildCacheKey(Integer.TYPE), JavaType.INT);
062        simpleJava2JavaType.put(buildCacheKey(Long.TYPE), JavaType.LONG);
063        simpleJava2JavaType.put(buildCacheKey(Float.TYPE), JavaType.FLOAT);
064        simpleJava2JavaType.put(buildCacheKey(Double.TYPE), JavaType.DOUBLE);
065        simpleJava2JavaType.put(buildCacheKey(String.class), JavaType.STRING);
066        
067        propertyJava2JavaType.put(buildCacheKey(Boolean.TYPE), JavaType.BOOLEAN);
068        propertyJava2JavaType.put(buildCacheKey(Integer.TYPE), JavaType.INT);
069        propertyJava2JavaType.put(buildCacheKey(Long.TYPE), JavaType.LONG);
070        propertyJava2JavaType.put(buildCacheKey(Float.TYPE), JavaType.FLOAT);
071        propertyJava2JavaType.put(buildCacheKey(Double.TYPE), JavaType.DOUBLE);
072        propertyJava2JavaType.put(buildCacheKey(String.class), JavaType.STRING);
073    }
074
075    ///////////////////////////////////////////////////////////////////////////
076    // Fields.
077
078    @Override
079        public void configure(boolean externalizeLong, boolean externalizeBigInteger, boolean externalizeBigDecimal) {
080        }
081    
082    private static final String buildCacheKey(Type jType) {
083        return buildCacheKey(jType, null, null);
084    }
085    private static final String buildCacheKey(Type jType, Class<?> declaringClass, ParameterizedType[] declaringTypes) {
086                String key = jType.toString();
087                if (declaringClass != null)
088                        key += "::" + declaringClass.toString();
089                if (declaringTypes != null) {
090                        for (ParameterizedType dt : declaringTypes)
091                                key += "::" + dt.toString();
092                }
093                return key;
094    }
095
096        @Override
097        public ClientType getClientType(Type jType, Class<?> declaringClass, ParameterizedType[] declaringTypes, PropertyType propertyType) {
098                String key = buildCacheKey(jType, declaringClass, declaringTypes);
099                
100                ClientType javaType = getFromCache(key, propertyType);
101                
102        if (javaType == null) {
103                if (jType instanceof GenericArrayType) {
104                        Type componentType = ((GenericArrayType)jType).getGenericComponentType();
105                        javaType = getClientType(componentType, declaringClass, declaringTypes, PropertyType.SIMPLE).toArrayType();
106                }
107                else if (jType instanceof Class<?> && ((Class<?>)jType).isArray()) {
108                        javaType = getClientType(((Class<?>)jType).getComponentType(), declaringClass, declaringTypes, PropertyType.SIMPLE).toArrayType();
109                }
110                else {
111                        Set<String> imports = new HashSet<String>();
112                        Class<?> jClass = ClassUtil.classOfType(jType);
113                        String genericType = "";
114                        if (jType instanceof ParameterizedType)
115                                genericType = buildGenericTypeName((ParameterizedType)jType, declaringClass, declaringTypes, propertyType, imports);
116                        
117                if (propertyType.isProperty() && List.class.isAssignableFrom(jClass)) {
118                    javaType = createJavaType(jType, declaringClass, declaringTypes, "org.granite.client.persistence.collection.PersistentList" + genericType, propertyType);
119                }
120                else if (propertyType.isProperty() && SortedSet.class.isAssignableFrom(jClass)) {
121                    javaType = createJavaType(jType, declaringClass, declaringTypes, "org.granite.client.persistence.collection.PersistentSortedSet" + genericType, propertyType);
122                }
123                else if (propertyType.isProperty() && Set.class.isAssignableFrom(jClass)) {
124                    javaType = createJavaType(jType, declaringClass, declaringTypes, "org.granite.client.persistence.collection.PersistentSet" + genericType, propertyType);
125                }
126                else if (propertyType.isProperty() && SortedMap.class.isAssignableFrom(jClass)) {
127                    javaType = createJavaType(jType, declaringClass, declaringTypes, "org.granite.client.persistence.collection.PersistentSortedMap" + genericType, propertyType);
128                }
129                else if (propertyType.isProperty() && Map.class.isAssignableFrom(jClass)) {
130                    javaType = createJavaType(jType, declaringClass, declaringTypes, "org.granite.client.persistence.collection.PersistentMap" + genericType, propertyType);
131                }
132                else if (jClass.getName().equals("com.google.appengine.api.datastore.Key")) {
133                        javaType = JavaType.STRING;
134                    }
135                    else if (jClass.getName().equals("org.springframework.data.domain.Page")) {
136                        javaType = new JavaType("org.granite.tide.data.model", "Page" + genericType, null);
137                    }
138                    else if (jClass.getName().equals("org.springframework.data.domain.Pageable")) {
139                        javaType = JavaType.PAGE_INFO;
140                    }
141                    else if (jClass.getName().equals("org.springframework.data.domain.Sort")) {
142                        javaType = JavaType.SORT_INFO;
143                    }
144                    else {
145                        javaType = createJavaType(jType, declaringClass, declaringTypes, null, propertyType);
146                    }
147                    if (!imports.isEmpty())
148                        javaType.addImports(imports);
149                }
150                
151            putInCache(key, propertyType, javaType);
152        }
153        
154        return javaType;
155    }
156
157        @Override
158        public ClientType getAs3Type(Class<?> jType) {
159                return getClientType(jType, null, null, PropertyType.SIMPLE);
160        }
161
162    protected JavaType createJavaType(Type jType, Class<?> declaringClass, ParameterizedType[] declaringTypes, String propertyImplTypeName, PropertyType propertyType) {
163        Set<String> imports = new HashSet<String>();
164        
165        Class<?> jClass = ClassUtil.classOfType(jType);
166        String name = jClass.getSimpleName();
167        if (jClass.isMemberClass())
168                name = jClass.getEnclosingClass().getSimpleName() + '$' + jClass.getSimpleName();
169        else if (jType instanceof ParameterizedType) {
170                ParameterizedType type = (ParameterizedType)jType;
171                name += buildGenericTypeName(type, declaringClass, declaringTypes, propertyType, imports);
172        }
173        
174        JavaType javaType = new JavaType(ClassUtil.getPackageName(jClass), name, propertyImplTypeName, null);
175        javaType.addImports(imports);
176        return javaType;
177    }
178
179    protected ClientType getFromCache(String key, PropertyType propertyType) {
180        if (key == null)
181            throw new NullPointerException("jType must be non null");
182        if (propertyType == PropertyType.PROPERTY)
183            return propertyJava2JavaType.get(key);
184        return simpleJava2JavaType.get(key);
185    }
186
187    protected void putInCache(String key, PropertyType propertyType, ClientType javaType) {
188        if (key == null || javaType == null)
189            throw new NullPointerException("jType and JavaType must be non null");
190        if (propertyType == PropertyType.PROPERTY)
191            propertyJava2JavaType.put(key, javaType);
192        else
193            simpleJava2JavaType.put(key, javaType);
194    }
195    
196    private String buildGenericTypeName(ParameterizedType type, Class<?> declaringClass, ParameterizedType[] declaringTypes, PropertyType propertyType, Set<String> imports) {
197                StringBuilder sb = new StringBuilder("<");
198                boolean first = true;
199                for (Type ata : type.getActualTypeArguments()) {
200                        if (first)
201                                first = false;
202                        else
203                                sb.append(", ");
204                        if (ata instanceof TypeVariable<?>) {
205                                Type resolved = GenericTypeUtil.resolveTypeVariable(ata, declaringClass, declaringTypes);
206                                if (resolved instanceof TypeVariable<?>)
207                                        sb.append("?");
208                                else {
209                                        sb.append(ClassUtil.classOfType(resolved).getSimpleName());
210                                        imports.add(getClientType(resolved, declaringClass, declaringTypes, propertyType).getQualifiedName());
211                                }
212                        }
213                        else if (ata instanceof WildcardType) {
214                                sb.append("?");
215                                if (((WildcardType)ata).getLowerBounds().length > 0) {
216                                        String bounds = "";
217                                for (Type t : ((WildcardType)ata).getLowerBounds()) {
218                                        Type resolved = GenericTypeUtil.resolveTypeVariable(t, declaringClass, declaringTypes);
219                                        if (resolved instanceof TypeVariable<?>) {
220                                                bounds = "";
221                                                break;
222                                        }
223                                        if (bounds.length() > 0)
224                                                bounds = bounds + ", ";
225                                        bounds = bounds + ClassUtil.classOfType(resolved).getSimpleName();
226                                        imports.add(getClientType(resolved, declaringClass, declaringTypes, propertyType).getQualifiedName());
227                                }
228                                if (bounds.length() > 0)
229                                        sb.append(" super ").append(bounds);
230                                }
231                                if (((WildcardType)ata).getUpperBounds().length > 0) {
232                                        String bounds = "";
233                                for (Type t : ((WildcardType)ata).getUpperBounds()) {
234                                        Type resolved = GenericTypeUtil.resolveTypeVariable(t, declaringClass, declaringTypes);
235                                        if (resolved instanceof TypeVariable<?>) {
236                                                bounds = "";
237                                                break;
238                                        }
239                                        if (bounds.length() > 0)
240                                                bounds = bounds + ", ";
241                                        bounds = bounds + ClassUtil.classOfType(resolved).getSimpleName();
242                                        imports.add(getClientType(resolved, declaringClass, declaringTypes, propertyType).getQualifiedName());
243                                }
244                                if (bounds.length() > 0)
245                                        sb.append(" extends ").append(bounds);
246                                }
247                        }
248                        else {
249                                sb.append(ClassUtil.classOfType(ata).getSimpleName());
250                                imports.add(getClientType(ata, declaringClass, declaringTypes, propertyType).getQualifiedName());
251                        }
252                }
253                sb.append(">");
254                return sb.toString();
255    }
256}