package com.hypnoticocelot.jaxrs.doclet.parser;
import com.google.common.base.Predicate;
import com.hypnoticocelot.jaxrs.doclet.DocletOptions;
import com.hypnoticocelot.jaxrs.doclet.model.Model;
import com.hypnoticocelot.jaxrs.doclet.model.Property;
import com.hypnoticocelot.jaxrs.doclet.translator.Translator;
import com.sun.javadoc.*;
import java.util.*;
import static com.google.common.collect.Collections2.filter;
public class ApiModelParser {
private final DocletOptions options;
private final Translator translator;
private final Type rootType;
private final Set<Model> models;
public ApiModelParser(DocletOptions options, Translator translator, Type rootType) {
this.options = options;
this.translator = translator;
this.rootType = rootType;
this.models = new LinkedHashSet<Model>();
}
public Set<Model> parse() {
parseModel(rootType);
return models;
}
private void parseModel(Type type) {
boolean isPrimitive = /* type.isPrimitive()? || */ AnnotationHelper.isPrimitive(type);
boolean isJavaxType = type.qualifiedTypeName().startsWith("javax.");
boolean isBaseObject = type.qualifiedTypeName().equals("java.lang.Object");
boolean isTypeToTreatAsOpaque = options.getTypesToTreatAsOpaque().contains(type.qualifiedTypeName());
ClassDoc classDoc = type.asClassDoc();
if (isPrimitive || isJavaxType || isBaseObject || isTypeToTreatAsOpaque || classDoc == null || alreadyStoredType(type)) {
return;
}
Map<String, Type> types = findReferencedTypes(classDoc);
Map<String, Property> elements = findReferencedElements(types);
if (!elements.isEmpty()) {
models.add(new Model(translator.typeName(type).value(), elements));
parseNestedModels(types.values());
}
}
private Map<String, Type> findReferencedTypes(ClassDoc classDoc) {
Map<String, Type> elements = new HashMap<String, Type>();
FieldDoc[] fieldDocs = classDoc.fields();
if (fieldDocs != null) {
for (FieldDoc field : fieldDocs) {
String name = translator.fieldName(field).value();
if (name != null && !elements.containsKey(name)) {
elements.put(name, field.type());
}
}
}
MethodDoc[] methodDocs = classDoc.methods();
if (methodDocs != null) {
for (MethodDoc method : methodDocs) {
String name = translator.methodName(method).value();
if (name != null && !elements.containsKey(name)) {
elements.put(name, method.returnType());
}
}
}
return elements;
}
private Map<String, Property> findReferencedElements(Map<String, Type> types) {
Map<String, Property> elements = new HashMap<String, Property>();
for (Map.Entry<String, Type> entry : types.entrySet()) {
String typeName = entry.getKey();
Type type = entry.getValue();
ClassDoc typeClassDoc = type.asClassDoc();
Type containerOf = parseParameterisedTypeOf(type);
String containerTypeOf = containerOf == null ? null : translator.typeName(containerOf).value();
String propertyName = translator.typeName(type).value();
Property property;
if (typeClassDoc != null && typeClassDoc.isEnum()) {
property = new Property(typeClassDoc.enumConstants(), null);
} else {
property = new Property(propertyName, null, containerTypeOf);
}
elements.put(typeName, property);
}
return elements;
}
private void parseNestedModels(Collection<Type> types) {
for (Type type : types) {
parseModel(type);
Type pt = parseParameterisedTypeOf(type);
if (pt != null) {
parseModel(pt);
}
}
}
private Type parseParameterisedTypeOf(Type type) {
Type result = null;
ParameterizedType pt = type.asParameterizedType();
if (pt != null) {
Type[] typeArgs = pt.typeArguments();
if (typeArgs != null && typeArgs.length > 0) {
result = typeArgs[0];
}
}
return result;
}
private boolean alreadyStoredType(final Type type) {
return filter(models, new Predicate<Model>() {
@Override
public boolean apply(Model model) {
return model.getId().equals(translator.typeName(type).value());
}
}).size() > 0;
}
}