/** * Copyright © 2006-2016 Web Cohesion (info@webcohesion.com) * * 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 com.webcohesion.enunciate.modules.jackson.model.types; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.webcohesion.enunciate.javac.decorations.Annotations; import com.webcohesion.enunciate.javac.decorations.DecoratedProcessingEnvironment; import com.webcohesion.enunciate.javac.decorations.TypeMirrorDecorator; import com.webcohesion.enunciate.javac.decorations.type.DecoratedTypeMirror; import com.webcohesion.enunciate.metadata.rs.TypeHint; import com.webcohesion.enunciate.modules.jackson.EnunciateJacksonContext; import com.webcohesion.enunciate.modules.jackson.model.TypeDefinition; import com.webcohesion.enunciate.modules.jackson.model.adapters.AdapterType; import com.webcohesion.enunciate.modules.jackson.model.util.JacksonUtil; import com.webcohesion.enunciate.modules.jackson.model.util.MapType; import com.webcohesion.enunciate.util.TypeHintUtils; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.*; import javax.lang.model.util.SimpleTypeVisitor6; import java.util.LinkedList; import java.util.concurrent.Callable; import static com.webcohesion.enunciate.javac.decorations.type.TypeMirrorUtils.getComponentType; /** * Utility visitor for discovering the json types of type mirrors. * * @author Ryan Heaton */ public class JsonTypeVisitor extends SimpleTypeVisitor6<JsonType, JsonTypeVisitor.Context> { @Override protected JsonType defaultAction(TypeMirror typeMirror, Context context) { return KnownJsonType.OBJECT; } @Override public JsonType visitPrimitive(PrimitiveType primitiveType, Context context) { if (context.isInArray() && (primitiveType.getKind() == TypeKind.BYTE)) { //special case for byte[] return KnownJsonType.STRING; //todo: make sure this is correct serialization of byte[]. } else { JsonPrimitiveType jsonType = new JsonPrimitiveType(primitiveType); return context.isInArray() || context.isInCollection() ? new JsonArrayType(jsonType) : jsonType; } } @Override public JsonType visitDeclared(DeclaredType declaredType, Context context) { JsonType jsonType = null; Element declaredElement = declaredType.asElement(); String fqn = declaredElement instanceof TypeElement ? ((TypeElement) declaredElement).getQualifiedName().toString() : declaredType.toString(); if (context.getStack().contains(fqn)) { //break out of recursive loop. DecoratedProcessingEnvironment env = context.getContext().getContext().getProcessingEnvironment(); if (((DecoratedTypeMirror) TypeMirrorDecorator.decorate(declaredType, env)).isCollection()) { return KnownJsonType.ARRAY; } else { return KnownJsonType.OBJECT; } } context.getStack().push(fqn); try { TypeHint typeHint = declaredElement.getAnnotation(TypeHint.class); if (typeHint != null) { TypeMirror hint = TypeHintUtils.getTypeHint(typeHint, context.getContext().getContext().getProcessingEnvironment(), null); if (hint != null) { jsonType = hint.accept(this, new Context(context.context, false, false, context.stack)); } } final JsonSerialize serializeInfo = declaredElement.getAnnotation(JsonSerialize.class); if (serializeInfo != null) { DecoratedProcessingEnvironment env = context.getContext().getContext().getProcessingEnvironment(); DecoratedTypeMirror using = Annotations.mirrorOf(new Callable<Class<?>>() { @Override public Class<?> call() throws Exception { return serializeInfo.using(); } }, env, JsonSerializer.None.class); if (using != null) { //custom serializer; just say it's an object. jsonType = KnownJsonType.OBJECT; } DecoratedTypeMirror as = Annotations.mirrorOf(new Callable<Class<?>>() { @Override public Class<?> call() throws Exception { return serializeInfo.as(); } }, env, Void.class); if (as != null) { jsonType = (JsonType) as.accept(this, new Context(context.context, false, false, context.stack)); } } AdapterType adapterType = JacksonUtil.findAdapterType(declaredElement, context.getContext()); if (adapterType != null) { adapterType.getAdaptingType().accept(this, new Context(context.context, false, false, context.stack)); } else { MapType mapType = MapType.findMapType(declaredType, context.getContext()); if (mapType != null) { JsonType keyType = mapType.getKeyType().accept(this, new Context(context.getContext(), false, false, context.stack)); JsonType valueType = mapType.getValueType().accept(this, new Context(context.getContext(), false, false, context.stack)); jsonType = new JsonMapType(keyType, valueType); } else { DecoratedProcessingEnvironment env = context.getContext().getContext().getProcessingEnvironment(); TypeMirror componentType = getComponentType((DecoratedTypeMirror) TypeMirrorDecorator.decorate(declaredType, env), env); if (componentType != null) { return componentType.accept(this, new Context(context.context, false, true, context.stack)); } else { switch (declaredElement.getKind()) { case ENUM: case CLASS: JsonType knownType = context.getContext().getKnownType(declaredElement); if (knownType != null) { jsonType = knownType; } else { //type not known, not specified. Last chance: look for the type definition. TypeDefinition typeDefinition = context.getContext().findTypeDefinition(declaredElement); if (typeDefinition != null) { jsonType = new JsonClassType(typeDefinition); } } break; case INTERFACE: if (context.isInCollection()) { jsonType = KnownJsonType.OBJECT; } break; } } } } if (jsonType == null) { jsonType = super.visitDeclared(declaredType, context); } return context.isInArray() || context.isInCollection() ? new JsonArrayType(jsonType) : jsonType; } finally { context.getStack().pop(); } } @Override public JsonType visitArray(ArrayType arrayType, Context context) { return arrayType.getComponentType().accept(this, new Context(context.context, true, false, context.stack)); } @Override public JsonType visitTypeVariable(TypeVariable typeVariable, Context context) { TypeMirror bound = typeVariable.getUpperBound(); if (bound == null) { return context.isInArray() || context.isInCollection() ? new JsonArrayType(KnownJsonType.OBJECT) : KnownJsonType.OBJECT; } else { JsonType jsonType = bound.accept(this, new Context(context.context, false, false, context.stack)); return context.isInArray() || context.isInCollection() ? new JsonArrayType(jsonType) : jsonType; } } @Override public JsonType visitWildcard(WildcardType wildcardType, Context context) { TypeMirror bound = wildcardType.getExtendsBound(); if (bound == null) { return context.isInArray() || context.isInCollection() ? new JsonArrayType(KnownJsonType.OBJECT) : KnownJsonType.OBJECT; } else { JsonType jsonType = bound.accept(this, new Context(context.context, false, false, context.stack)); return context.isInArray() || context.isInCollection() ? new JsonArrayType(jsonType) : jsonType; } } public static class Context { private final EnunciateJacksonContext context; private final boolean inArray; private final boolean inCollection; private final LinkedList<String> stack; public Context(EnunciateJacksonContext context, boolean inArray, boolean inCollection, LinkedList<String> stack) { this.context = context; this.inArray = inArray; this.inCollection = inCollection; this.stack = stack; } public EnunciateJacksonContext getContext() { return context; } public boolean isInArray() { return inArray; } public boolean isInCollection() { return inCollection; } public LinkedList<String> getStack() { return stack; } } }