/**
* 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.api.impl;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.webcohesion.enunciate.EnunciateException;
import com.webcohesion.enunciate.api.datatype.DataTypeReference;
import com.webcohesion.enunciate.facets.FacetFilter;
import com.webcohesion.enunciate.javac.decorations.element.ElementUtils;
import com.webcohesion.enunciate.javac.javadoc.JavaDoc;
import com.webcohesion.enunciate.metadata.DocumentationExample;
import com.webcohesion.enunciate.modules.jackson.model.*;
import com.webcohesion.enunciate.modules.jackson.model.types.*;
import com.webcohesion.enunciate.util.TypeHintUtils;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
/**
* @author Ryan Heaton
*/
public class DataTypeExampleImpl extends ExampleImpl {
private final ObjectTypeDefinition type;
private final List<DataTypeReference.ContainerType> containers;
public DataTypeExampleImpl(ObjectTypeDefinition type) {
this(type, null);
}
public DataTypeExampleImpl(ObjectTypeDefinition typeDefinition, List<DataTypeReference.ContainerType> containers) {
this.type = typeDefinition;
this.containers = containers == null ? Collections.<DataTypeReference.ContainerType>emptyList() : containers;
}
@Override
public String getBody() {
ObjectNode node = JsonNodeFactory.instance.objectNode();
Context context = new Context();
context.stack = new LinkedList<String>();
build(node, this.type, context);
if (this.type.getContext().isWrapRootValue()) {
ObjectNode wrappedNode = JsonNodeFactory.instance.objectNode();
wrappedNode.set(this.type.getJsonRootName(), node);
node = wrappedNode;
}
JsonNode outer = node;
for (DataTypeReference.ContainerType container : this.containers) {
switch (container) {
case array:
case collection:
case list:
ArrayNode arrayNode = JsonNodeFactory.instance.arrayNode();
arrayNode.add(outer);
outer = arrayNode;
break;
case map:
ObjectNode mapNode = JsonNodeFactory.instance.objectNode();
mapNode.set("...", outer);
outer = mapNode;
break;
}
}
ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
try {
return mapper.writeValueAsString(outer);
}
catch (JsonProcessingException e) {
throw new EnunciateException(e);
}
}
private void build(ObjectNode node, ObjectTypeDefinition type, Context context) {
if (context.stack.size() > 2) {
//don't go deeper than 2 for fear of the OOM (see https://github.com/stoicflame/enunciate/issues/139).
return;
}
if (type.getTypeIdInclusion() == JsonTypeInfo.As.PROPERTY) {
if (type.getTypeIdProperty() != null) {
node.put(type.getTypeIdProperty(), "...");
}
}
FacetFilter facetFilter = type.getContext().getContext().getConfiguration().getFacetFilter();
for (Member member : type.getMembers()) {
if (!facetFilter.accept(member)) {
continue;
}
if (ElementUtils.findDeprecationMessage(member) != null) {
continue;
}
String example = null;
String example2 = null;
JsonType exampleType = null;
JavaDoc.JavaDocTagList tags = member.getJavaDoc().get("documentationExample");
if (tags != null && tags.size() > 0) {
String tag = tags.get(0).trim();
example = tag.isEmpty() ? null : tag;
example2 = example;
if (tags.size() > 1) {
tag = tags.get(1).trim();
example2 = tag.isEmpty() ? null : tag;
}
}
tags = member.getJavaDoc().get("documentationType");
if (tags != null && tags.size() > 0) {
String tag = tags.get(0).trim();
if (!tag.isEmpty()) {
TypeElement typeElement = type.getContext().getContext().getProcessingEnvironment().getElementUtils().getTypeElement(tag);
if (typeElement != null) {
exampleType = JsonTypeFactory.getJsonType(typeElement.asType(), type.getContext());
}
else {
type.getContext().getContext().getLogger().warn("Invalid documentation type %s.", tag);
}
}
}
DocumentationExample documentationExample = member.getAnnotation(DocumentationExample.class);
if (documentationExample != null) {
if (documentationExample.exclude()) {
continue;
}
example = documentationExample.value();
example = "##default".equals(example) ? null : example;
example2 = documentationExample.value2();
example2 = "##default".equals(example2) ? null : example2;
TypeMirror typeHint = TypeHintUtils.getTypeHint(documentationExample.type(), type.getContext().getContext().getProcessingEnvironment(), null);
if (typeHint != null) {
exampleType = JsonTypeFactory.getJsonType(typeHint, type.getContext());
}
}
if (context.currentIndex % 2 > 0) {
//if our index is odd, switch example 1 and example 2.
String placeholder = example2;
example2 = example;
example = placeholder;
}
if (member.getChoices().size() > 1) {
if (member.isCollectionType()) {
final ArrayNode exampleNode = JsonNodeFactory.instance.arrayNode();
for (Member choice : member.getChoices()) {
JsonType jsonType = exampleType == null ? choice.getJsonType() : exampleType;
String choiceName = choice.getName();
if ("".equals(choiceName)) {
choiceName = "...";
}
if (member.getSubtypeIdInclusion() == JsonTypeInfo.As.WRAPPER_ARRAY) {
ArrayNode wrapperNode = JsonNodeFactory.instance.arrayNode();
wrapperNode.add(choiceName);
wrapperNode.add(exampleNode(jsonType, example, example2, context));
exampleNode.add(wrapperNode);
}
else if (member.getSubtypeIdInclusion() == JsonTypeInfo.As.WRAPPER_OBJECT) {
ObjectNode wrapperNode = JsonNodeFactory.instance.objectNode();
wrapperNode.set(choiceName, exampleNode(jsonType, example, example2, context));
exampleNode.add(wrapperNode);
}
else {
JsonNode itemNode = exampleNode(jsonType, example, example2, context);
if (member.getSubtypeIdInclusion() == JsonTypeInfo.As.PROPERTY) {
if (member.getSubtypeIdProperty() != null && itemNode instanceof ObjectNode) {
((ObjectNode) itemNode).put(member.getSubtypeIdProperty(), "...");
}
}
else if (member.getSubtypeIdInclusion() == JsonTypeInfo.As.EXTERNAL_PROPERTY) {
if (member.getSubtypeIdProperty() != null) {
node.put(member.getSubtypeIdProperty(), "...");
}
}
exampleNode.add(itemNode);
}
}
node.set(member.getName(), exampleNode);
}
else {
for (Member choice : member.getChoices()) {
JsonNode exampleNode;
JsonType jsonType = exampleType == null ? choice.getJsonType() : exampleType;
String choiceName = choice.getName();
if ("".equals(choiceName)) {
choiceName = "...";
}
if (member.getSubtypeIdInclusion() == JsonTypeInfo.As.WRAPPER_ARRAY) {
ArrayNode wrapperNode = JsonNodeFactory.instance.arrayNode();
wrapperNode.add(choiceName);
wrapperNode.add(exampleNode(jsonType, example, example2, context));
exampleNode = wrapperNode;
}
else if (member.getSubtypeIdInclusion() == JsonTypeInfo.As.WRAPPER_OBJECT) {
ObjectNode wrapperNode = JsonNodeFactory.instance.objectNode();
wrapperNode.set(choiceName, exampleNode(jsonType, example, example2, context));
exampleNode = wrapperNode;
}
else {
exampleNode = exampleNode(jsonType, example, example2, context);
if (member.getSubtypeIdInclusion() == JsonTypeInfo.As.PROPERTY) {
if (member.getSubtypeIdProperty() != null && exampleNode instanceof ObjectNode) {
((ObjectNode) exampleNode).put(member.getSubtypeIdProperty(), "...");
}
}
else if (member.getSubtypeIdInclusion() == JsonTypeInfo.As.EXTERNAL_PROPERTY) {
if (member.getSubtypeIdProperty() != null) {
node.put(member.getSubtypeIdProperty(), "...");
}
}
}
node.set(member.getName(), exampleNode);
}
}
}
else {
JsonType jsonType = exampleType == null ? member.getJsonType() : exampleType;
node.set(member.getName(), exampleNode(jsonType, example, example2, context));
}
}
JsonType supertype = type.getSupertype();
if (supertype instanceof JsonClassType && ((JsonClassType)supertype).getTypeDefinition() instanceof ObjectTypeDefinition) {
build(node, (ObjectTypeDefinition) ((JsonClassType) supertype).getTypeDefinition(), context);
}
if (type.getWildcardMember() != null && ElementUtils.findDeprecationMessage(type.getWildcardMember()) == null) {
node.put("extension1", "...");
node.put("extension2", "...");
}
}
private JsonNode exampleNode(JsonType jsonType, String specifiedExample, String specifiedExample2, Context context) {
if (jsonType instanceof JsonClassType) {
TypeDefinition typeDefinition = ((JsonClassType) jsonType).getTypeDefinition();
if (typeDefinition instanceof ObjectTypeDefinition) {
ObjectNode objectNode = JsonNodeFactory.instance.objectNode();
if (!context.stack.contains(typeDefinition.getQualifiedName().toString())) {
context.stack.push(typeDefinition.getQualifiedName().toString());
try {
build(objectNode, (ObjectTypeDefinition) typeDefinition, context);
}
finally {
context.stack.pop();
}
}
return objectNode;
}
else if (typeDefinition instanceof EnumTypeDefinition) {
String example = "???";
if (specifiedExample != null) {
example = specifiedExample;
}
else {
List<EnumValue> enumValues = ((EnumTypeDefinition) typeDefinition).getEnumValues();
if (enumValues.size() > 0) {
int index = new Random().nextInt(enumValues.size());
example = enumValues.get(index).getValue();
}
}
return JsonNodeFactory.instance.textNode(example);
}
else {
return exampleNode(((SimpleTypeDefinition) typeDefinition).getBaseType(), specifiedExample, specifiedExample2, context);
}
}
else if (jsonType instanceof JsonMapType) {
ObjectNode mapNode = JsonNodeFactory.instance.objectNode();
JsonType valueType = ((JsonMapType) jsonType).getValueType();
mapNode.set("property1", exampleNode(valueType, specifiedExample, specifiedExample2, context));
Context context2 = new Context();
context2.stack = context.stack;
context2.currentIndex = 1;
mapNode.set("property2", exampleNode(valueType, specifiedExample, specifiedExample2, context2));
return mapNode;
}
else if (jsonType.isArray()) {
ArrayNode arrayNode = JsonNodeFactory.instance.arrayNode();
if (jsonType instanceof JsonArrayType) {
JsonNode componentNode = exampleNode(((JsonArrayType) jsonType).getComponentType(), specifiedExample, specifiedExample2, context);
arrayNode.add(componentNode);
Context context2 = new Context();
context2.stack = context.stack;
context2.currentIndex = 1;
JsonNode componentNode2 = exampleNode(((JsonArrayType) jsonType).getComponentType(), specifiedExample2, specifiedExample, context2);
arrayNode.add(componentNode2);
}
return arrayNode;
}
else if (jsonType.isWholeNumber()) {
Long example = 12345L;
if (specifiedExample != null) {
try {
example = Long.parseLong(specifiedExample);
}
catch (NumberFormatException e) {
this.type.getContext().getContext().getLogger().warn("\"%s\" was provided as a documentation example, but it is not a valid JSON whole number, so it will be ignored.", specifiedExample);
}
}
return JsonNodeFactory.instance.numberNode(example);
}
else if (jsonType.isNumber()) {
Double example = 12345D;
if (specifiedExample != null) {
try {
example = Double.parseDouble(specifiedExample);
}
catch (NumberFormatException e) {
this.type.getContext().getContext().getLogger().warn("\"%s\" was provided as a documentation example, but it is not a valid JSON number, so it will be ignored.", specifiedExample);
}
}
return JsonNodeFactory.instance.numberNode(example);
}
else if (jsonType.isBoolean()) {
boolean example = !"false".equals(specifiedExample);
return JsonNodeFactory.instance.booleanNode(example);
}
else if (jsonType.isString()) {
String example = specifiedExample;
if (example == null) {
example = "...";
}
return JsonNodeFactory.instance.textNode(example);
}
else {
return JsonNodeFactory.instance.objectNode();
}
}
private static class Context {
LinkedList<String> stack;
int currentIndex = 0;
}
}