/** * Copyright (C) 2009-2012 the original author or authors. * See the notice.md file distributed with this work for additional * information regarding copyright ownership. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.fusesource.restygwt.rebind; import static org.fusesource.restygwt.rebind.BaseSourceCreator.DEBUG; import static org.fusesource.restygwt.rebind.BaseSourceCreator.ERROR; import static org.fusesource.restygwt.rebind.BaseSourceCreator.INFO; import static org.fusesource.restygwt.rebind.BaseSourceCreator.TRACE; import static org.fusesource.restygwt.rebind.BaseSourceCreator.WARN; import java.lang.reflect.Constructor; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.fusesource.restygwt.client.AbstractJsonEncoderDecoder; import org.fusesource.restygwt.client.AbstractNestedJsonEncoderDecoder; import org.fusesource.restygwt.client.Json; import org.fusesource.restygwt.client.Json.Style; import org.fusesource.restygwt.client.ObjectEncoderDecoder; import com.google.gwt.core.ext.BadPropertyValueException; import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JParameterizedType; import com.google.gwt.core.ext.typeinfo.JPrimitiveType; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.json.client.JSONValue; import com.google.gwt.xml.client.Document; /** * * @author <a href="http://hiramchirino.com">Hiram Chirino</a> */ public class JsonEncoderDecoderInstanceLocator implements EncoderDecoderLocator { public static final String JSON_ENCODER_DECODER_CLASS = AbstractJsonEncoderDecoder.class.getName(); public static final String JSON_NESTED_ENCODER_DECODER_CLASS = AbstractNestedJsonEncoderDecoder.class.getName(); public static final String JSON_CLASS = Json.class.getName(); public static final String CUSTOM_SERIALIZER_GENERATORS = "org.fusesource.restygwt.restyjsonserializergenerator"; public final JClassType STRING_TYPE; public final JClassType JSON_VALUE_TYPE; public final JClassType DOCUMENT_TYPE; public final JClassType MAP_TYPE; public final JClassType SET_TYPE; public final JClassType LIST_TYPE; public final JClassType COLLECTION_TYPE; public final HashMap<JType, String> builtInEncoderDecoders = new HashMap<JType, String>(); public final JsonSerializerGenerators customGenerators = new JsonSerializerGenerators(); public final GeneratorContext context; public final TreeLogger logger; public JsonEncoderDecoderInstanceLocator(GeneratorContext context, TreeLogger logger) throws UnableToCompleteException { this.context = context; this.logger = logger; this.STRING_TYPE = find(String.class); this.JSON_VALUE_TYPE = find(JSONValue.class); this.DOCUMENT_TYPE = find(Document.class); this.MAP_TYPE = find(Map.class); this.SET_TYPE = find(Set.class); this.LIST_TYPE = find(List.class); this.COLLECTION_TYPE = find(Collection.class); builtInEncoderDecoders.put(JPrimitiveType.BOOLEAN, JSON_ENCODER_DECODER_CLASS + ".BOOLEAN"); builtInEncoderDecoders.put(JPrimitiveType.BYTE, JSON_ENCODER_DECODER_CLASS + ".BYTE"); builtInEncoderDecoders.put(JPrimitiveType.CHAR, JSON_ENCODER_DECODER_CLASS + ".CHAR"); builtInEncoderDecoders.put(JPrimitiveType.SHORT, JSON_ENCODER_DECODER_CLASS + ".SHORT"); builtInEncoderDecoders.put(JPrimitiveType.INT, JSON_ENCODER_DECODER_CLASS + ".INT"); builtInEncoderDecoders.put(JPrimitiveType.LONG, JSON_ENCODER_DECODER_CLASS + ".LONG"); builtInEncoderDecoders.put(JPrimitiveType.FLOAT, JSON_ENCODER_DECODER_CLASS + ".FLOAT"); builtInEncoderDecoders.put(JPrimitiveType.DOUBLE, JSON_ENCODER_DECODER_CLASS + ".DOUBLE"); builtInEncoderDecoders.put(find(Boolean.class), JSON_ENCODER_DECODER_CLASS + ".BOOLEAN"); builtInEncoderDecoders.put(find(Byte.class), JSON_ENCODER_DECODER_CLASS + ".BYTE"); builtInEncoderDecoders.put(find(Character.class), JSON_ENCODER_DECODER_CLASS + ".CHAR"); builtInEncoderDecoders.put(find(Short.class), JSON_ENCODER_DECODER_CLASS + ".SHORT"); builtInEncoderDecoders.put(find(Integer.class), JSON_ENCODER_DECODER_CLASS + ".INT"); builtInEncoderDecoders.put(find(Long.class), JSON_ENCODER_DECODER_CLASS + ".LONG"); builtInEncoderDecoders.put(find(Float.class), JSON_ENCODER_DECODER_CLASS + ".FLOAT"); builtInEncoderDecoders.put(find(Double.class), JSON_ENCODER_DECODER_CLASS + ".DOUBLE"); builtInEncoderDecoders.put(find(BigDecimal.class), JSON_ENCODER_DECODER_CLASS + ".BIG_DECIMAL"); builtInEncoderDecoders.put(find(BigInteger.class), JSON_ENCODER_DECODER_CLASS + ".BIG_INTEGER"); builtInEncoderDecoders.put(STRING_TYPE, JSON_ENCODER_DECODER_CLASS + ".STRING"); builtInEncoderDecoders.put(DOCUMENT_TYPE, JSON_ENCODER_DECODER_CLASS + ".DOCUMENT"); builtInEncoderDecoders.put(JSON_VALUE_TYPE, JSON_ENCODER_DECODER_CLASS + ".JSON_VALUE"); builtInEncoderDecoders.put(find(Date.class), JSON_ENCODER_DECODER_CLASS + ".DATE"); builtInEncoderDecoders.put(find(Object.class), ObjectEncoderDecoder.class.getName() + ".INSTANCE"); fillInCustomGenerators(context, logger); } @SuppressWarnings("unchecked") private void fillInCustomGenerators(GeneratorContext context, TreeLogger logger) { try { List<String> classNames = context.getPropertyOracle().getConfigurationProperty(CUSTOM_SERIALIZER_GENERATORS).getValues(); for (String name: classNames) { try { Class<? extends RestyJsonSerializerGenerator> clazz = (Class<? extends RestyJsonSerializerGenerator>) Class.forName(name); Constructor<? extends RestyJsonSerializerGenerator> constructor = clazz.getDeclaredConstructor(); RestyJsonSerializerGenerator generator = constructor.newInstance(); customGenerators.addGenerator(generator, context.getTypeOracle()); } catch (Exception e) { logger.log(WARN, "Could not access class: " + name, e); } } } catch (BadPropertyValueException ignore) {} } private JClassType find(Class<?> type) throws UnableToCompleteException { return find(type.getName()); } private JClassType find(String type) throws UnableToCompleteException { return RestServiceGenerator.find(logger, context, type); } private String getEncoderDecoder(JType type, TreeLogger logger) throws UnableToCompleteException { String rc = builtInEncoderDecoders.get(type); if (rc == null) { JClassType ct = type.isClass() == null? type.isInterface() : type.isClass(); if (ct != null && !isCollectionType(ct)) { JsonEncoderDecoderClassCreator generator = new JsonEncoderDecoderClassCreator(logger, context, ct); return generator.create() + ".INSTANCE"; } } return rc; } private String getCustomEncoderDecoder(JType type) { RestyJsonSerializerGenerator restyGenerator = customGenerators.findGenerator(type); if (restyGenerator == null) { return null; } Class<? extends JsonEncoderDecoderClassCreator> clazz = restyGenerator.getGeneratorClass(); try { Constructor<? extends JsonEncoderDecoderClassCreator> constructor = clazz.getDeclaredConstructor(TreeLogger.class, GeneratorContext.class, JClassType.class); JsonEncoderDecoderClassCreator generator = constructor.newInstance(logger, context, type); return generator.create() + ".INSTANCE"; } catch (Exception e) { logger.log(WARN, "Could not access class: " + clazz, e); return null; } } /* (non-Javadoc) * @see org.fusesource.restygwt.rebind.EncoderDecoderLocator#hasCustomEncoderDecoder(com.google.gwt.core.ext.typeinfo.JType) */ @Override public boolean hasCustomEncoderDecoder(JType type) { return getCustomEncoderDecoder(type) != null; } /* (non-Javadoc) * @see org.fusesource.restygwt.rebind.EncoderDecoderLocator#encodeExpression(com.google.gwt.core.ext.typeinfo.JType, java.lang.String, org.fusesource.restygwt.client.Json.Style) */ @Override public String encodeExpression(JType type, String expression, Style style) throws UnableToCompleteException { return encodeDecodeExpression(type, expression, style, "encode", JSON_ENCODER_DECODER_CLASS + ".toJSON", JSON_ENCODER_DECODER_CLASS + ".toJSON", JSON_ENCODER_DECODER_CLASS + ".toJSON", JSON_ENCODER_DECODER_CLASS + ".toJSON"); } /* (non-Javadoc) * @see org.fusesource.restygwt.rebind.EncoderDecoderLocator#decodeExpression(com.google.gwt.core.ext.typeinfo.JType, java.lang.String, org.fusesource.restygwt.client.Json.Style) */ @Override public String decodeExpression(JType type, String expression, Style style) throws UnableToCompleteException { return encodeDecodeExpression(type, expression, style, "decode", JSON_ENCODER_DECODER_CLASS + ".toMap", JSON_ENCODER_DECODER_CLASS + ".toSet", JSON_ENCODER_DECODER_CLASS + ".toList", JSON_ENCODER_DECODER_CLASS + ".toArray"); } private String encodeDecodeExpression(JType type, String expression, Style style, String encoderMethod, String mapMethod, String setMethod, String listMethod, String arrayMethod) throws UnableToCompleteException { String customEncoderDecoder = getCustomEncoderDecoder(type); if (customEncoderDecoder != null) { return customEncoderDecoder + "." + encoderMethod + "(" + expression + ")"; } String encoderDecoder = getEncoderDecoder(type, logger); if (encoderDecoder != null) { return encoderDecoder + "." + encoderMethod + "(" + expression + ")"; } // TODO enum have an encodeDecoder now - should be obsolete code below if (null != type.isEnum()) { if (encoderMethod.equals("encode")) { return encodeDecodeExpression(STRING_TYPE, expression + ".name()", style, encoderMethod, mapMethod, setMethod, listMethod, arrayMethod); } return type.getQualifiedSourceName() + ".valueOf(" + encodeDecodeExpression(STRING_TYPE, expression, style, encoderMethod, mapMethod, setMethod, listMethod, arrayMethod) + ")"; } JClassType clazz = type.isClassOrInterface(); if (isCollectionType(clazz)) { JClassType[] types = getTypes(type); String[] coders = isMapEncoderDecoder( clazz, types, style ); if ( coders != null ){ String keyEncoderDecoder = coders[ 1 ]; encoderDecoder = coders[ 0 ]; if (encoderDecoder != null && keyEncoderDecoder != null) { return mapMethod + "(" + expression + ", " + keyEncoderDecoder + ", " + encoderDecoder + ", " + JSON_CLASS + ".Style." + style.name() + ")"; } else if (encoderDecoder != null) { return mapMethod + "(" + expression + ", " + encoderDecoder + ", " + JSON_CLASS + ".Style." + style.name() + ")"; } } encoderDecoder = isSetEncoderDecoder(clazz, types, style); if (encoderDecoder != null) { return setMethod + "(" + expression + ", " + encoderDecoder + ")"; } encoderDecoder = isListEncoderDecoder(clazz, types, style); if (encoderDecoder != null) { return listMethod + "(" + expression + ", " + encoderDecoder + ")"; } encoderDecoder = isCollectionEncoderDecoder(clazz, types, style); if (encoderDecoder != null) { return listMethod + "(" + expression + ", " + encoderDecoder + ")"; } } encoderDecoder = isArrayEncoderDecoder(type, style); if (encoderDecoder != null) { if (encoderMethod.equals("encode")) { return arrayMethod + "(" + expression + ", " + encoderDecoder + ")"; } else if (type.isArray().getComponentType().isPrimitive() == JPrimitiveType.BYTE) { return arrayMethod + "(" + expression + ", " + encoderDecoder + ")"; } return arrayMethod + "(" + expression + ", " + encoderDecoder + ", new " + type.isArray().getComponentType().getQualifiedSourceName() + "[" + JSON_ENCODER_DECODER_CLASS + ".getSize(" + expression + ")])"; } error("Do not know how to encode/decode " + type); return null; } protected String[] isMapEncoderDecoder(JClassType clazz, JClassType[] types, Style style) throws UnableToCompleteException { String encoderDecoder; if (clazz.isAssignableTo(MAP_TYPE)) { if (types.length != 2) { error("Map must define two and only two type parameters"); } String keyEncoderDecoder = getNestedEncoderDecoder(types[0], style); encoderDecoder = getNestedEncoderDecoder(types[1], style); return new String[]{ encoderDecoder, keyEncoderDecoder }; } return null; } String getNestedEncoderDecoder( JType type, Style style ) throws UnableToCompleteException{ String result = getEncoderDecoder(type, logger); if ( result != null ){ return result; } JClassType clazz = type.isClassOrInterface(); if (isCollectionType(clazz)) { JClassType[] types = getTypes(type); String[] coders = isMapEncoderDecoder( clazz, types, style ); if ( coders != null ){ String keyEncoderDecoder = coders[ 1 ]; result = coders[ 0 ]; if (result != null && keyEncoderDecoder != null) { return JSON_NESTED_ENCODER_DECODER_CLASS + ".mapEncoderDecoder( " + keyEncoderDecoder + ", " + result + ", " + JSON_CLASS + ".Style." + style.name() + " )"; } else if (result != null) { return JSON_NESTED_ENCODER_DECODER_CLASS + ".mapEncoderDecoder( " + result + ", " + JSON_CLASS + ".Style." + style.name() + " )"; } } result = isListEncoderDecoder( clazz, types, style ); if( result != null ){ return JSON_NESTED_ENCODER_DECODER_CLASS + ".listEncoderDecoder( " + result + " )"; } result = isSetEncoderDecoder( clazz, types, style ); if( result != null ){ return JSON_NESTED_ENCODER_DECODER_CLASS + ".setEncoderDecoder( " + result + " )"; } result = isCollectionEncoderDecoder( clazz, types, style ); if ( result != null ){ return JSON_NESTED_ENCODER_DECODER_CLASS + ".collectionEncoderDecoder( " + result + " )"; } } result = isArrayEncoderDecoder(type, style); if( result != null ){ return JSON_NESTED_ENCODER_DECODER_CLASS + ".arrayEncoderDecoder( " + result + " )"; } return null; } protected String isArrayEncoderDecoder( JType type, Style style ) throws UnableToCompleteException { if (type.isArray() != null){ JType componentType = type.isArray().getComponentType(); if (componentType.isArray() != null) { error("Multi-dimensional arrays are not yet supported"); } String encoderDecoder = getNestedEncoderDecoder( componentType, style ); debug("type encoder for: " + componentType + " is " + encoderDecoder); return encoderDecoder; } return null; } protected String isSetEncoderDecoder( JClassType clazz, JClassType[] types, Style style ) throws UnableToCompleteException { if (clazz.isAssignableTo(SET_TYPE)) { if (types.length != 1) { error("Set must define one and only one type parameter"); } String encoderDecoder = getNestedEncoderDecoder( types[0], style ); debug("type encoder for: " + types[0] + " is " + encoderDecoder); return encoderDecoder; } return null; } protected String isListEncoderDecoder( JClassType clazz, JClassType[] types, Style style) throws UnableToCompleteException { if (clazz.isAssignableTo(LIST_TYPE)) { if (types.length != 1) { error("List must define one and only one type parameter"); } String encoderDecoder = getNestedEncoderDecoder( types[0], style ); debug("type encoder for: " + types[0] + " is " + encoderDecoder); return encoderDecoder; } return null; } protected String isCollectionEncoderDecoder(JClassType clazz, JClassType[] types, Style style) throws UnableToCompleteException { if (clazz.isAssignableTo(COLLECTION_TYPE)) { if (types.length != 1) { error("Collection must define one and only one type parameter"); } String encoderDecoder = getNestedEncoderDecoder(types[0], style); debug("type encoder for: " + types[0] + " is " + encoderDecoder); return encoderDecoder; } return null; } protected JClassType[] getTypes(JType type) throws UnableToCompleteException { JClassType[] types = getTypesHelper(type); if (types == null) { JClassType superType = type.isClassOrInterface(); while (types == null) { superType = superType.getSuperclass(); if (superType == null) { break; } types = getTypesHelper(superType); } if (types == null) { error("Collection types must be parameterized: " + type); } } return types; } protected JClassType[] getTypesHelper(JType type) { JParameterizedType parameterizedType = type.isParameterized(); if (parameterizedType == null || parameterizedType.getTypeArgs() == null) { return null; } JClassType[] types = parameterizedType.getTypeArgs(); return types; } @Override public boolean isCollectionType(JClassType clazz) { return clazz != null && (clazz.isAssignableTo(SET_TYPE) || clazz.isAssignableTo(LIST_TYPE) || clazz.isAssignableTo(MAP_TYPE) || clazz.isAssignableTo(COLLECTION_TYPE)); } protected void error(String msg) throws UnableToCompleteException { logger.log(ERROR, msg); throw new UnableToCompleteException(); } protected void warn(String msg) throws UnableToCompleteException { logger.log(WARN, msg); throw new UnableToCompleteException(); } protected void info(String msg) { logger.log(INFO, msg); } protected void debug(String msg) { logger.log(DEBUG, msg); } protected void trace(String msg) { logger.log(TRACE, msg); } @Override public JClassType getListType() { return LIST_TYPE; } }