diff --git a/pom.xml b/pom.xml index f237bea..8e019dd 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.github.markusbernhardt xml-doclet - 1.0.6-SNAPSHOT + 2.0.0-SNAPSHOT jar XML Doclet @@ -20,7 +20,9 @@ UTF-8 true true - 1.7 + 11 + 1.2.0 + 2.3.1 @@ -52,13 +54,26 @@ - - com.sun - tools - ${java.version} - system - ${java.home}/../lib/tools.jar - + + javax.xml.bind + jaxb-api + ${jaxb.version} + + + com.sun.xml.bind + jaxb-impl + ${jaxb.version} + + + org.glassfish.jaxb + jaxb-runtime + ${jaxb.version} + + + javax.activation + javax.activation-api + ${javax.activation.version} + commons-cli commons-cli @@ -91,7 +106,7 @@ org.jvnet.jaxb2.maven2 maven-jaxb2-plugin - 0.12.3 + 0.14.0 generate-sources diff --git a/src/main/java/com/github/markusbernhardt/xmldoclet/JavadocTransformer.java b/src/main/java/com/github/markusbernhardt/xmldoclet/JavadocTransformer.java new file mode 100644 index 0000000..d0d151a --- /dev/null +++ b/src/main/java/com/github/markusbernhardt/xmldoclet/JavadocTransformer.java @@ -0,0 +1,539 @@ +package com.github.markusbernhardt.xmldoclet; + +import com.github.markusbernhardt.xmldoclet.xjc.Root; +import com.github.markusbernhardt.xmldoclet.xjc.TagInfo; +import com.github.markusbernhardt.xmldoclet.xjc.TypeInfo; +import com.github.markusbernhardt.xmldoclet.xjc.Wildcard; +import com.sun.source.doctree.BlockTagTree; +import com.sun.source.doctree.DocCommentTree; +import com.sun.source.doctree.DocTree; +import com.sun.source.util.DocTrees; +import java.io.Externalizable; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.QualifiedNameable; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.JavaFileObject; +import jdk.javadoc.doclet.DocletEnvironment; + +public class JavadocTransformer { + + private final DocletEnvironment environment; + private final DocTrees docTrees; + private final Elements elementUtils; + private final Types typeUtils; + private final TypeMirror objectTypeMirror; + private final TypeElement errorTypeElement; + private final TypeElement exceptionTypeElement; + private final TypeElement externalizableTypeElement; + private final TypeElement serializableTypeElement; + + public JavadocTransformer(DocletEnvironment environment) { + this.environment = environment; + this.docTrees = environment.getDocTrees(); + this.elementUtils = environment.getElementUtils(); + this.typeUtils = environment.getTypeUtils(); + this.objectTypeMirror = elementUtils.getTypeElement(Object.class.getCanonicalName()).asType(); + this.errorTypeElement = elementUtils.getTypeElement(Error.class.getCanonicalName()); + this.exceptionTypeElement = elementUtils.getTypeElement(Exception.class.getCanonicalName()); + this.externalizableTypeElement = elementUtils.getTypeElement(Externalizable.class.getCanonicalName()); + this.serializableTypeElement = elementUtils.getTypeElement(Serializable.class.getCanonicalName()); + } + + public Root transform() { + final Root xmlRoot = new Root(); + transformElements(xmlRoot, environment.getSpecifiedElements()); + return xmlRoot; + } + + private void transformElements(Root xmlRoot, Collection elements) { + for (Element element : elements) { + transformElement(xmlRoot, element); + } + } + + private void transformElement(Root xmlRoot, Element element) { + if (element instanceof PackageElement) { + transformPackageElement(xmlRoot, (PackageElement) element); + } + if (element instanceof TypeElement) { + transformTypeElement(xmlRoot, (TypeElement) element); + } + } + + private void transformPackageElement(Root xmlRoot, PackageElement packageElement) { + final com.github.markusbernhardt.xmldoclet.xjc.Package xmlPackage = new com.github.markusbernhardt.xmldoclet.xjc.Package(); + xmlPackage.setName(packageElement.getQualifiedName().toString()); + transformJavadoc(packageElement, xmlPackage::setComment, xmlPackage::getTag); + xmlRoot.getPackage().add(xmlPackage); + transformElements(xmlRoot, packageElement.getEnclosedElements()); + } + + private void transformTypeElement(Root xmlRoot, TypeElement typeElement) { + if (environment.getFileKind(typeElement) != JavaFileObject.Kind.SOURCE) { + return; + } + if (!environment.isIncluded(typeElement)) { + return; + } + final PackageElement packageElement = getEnclosingPackage(typeElement); + if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) { + final com.github.markusbernhardt.xmldoclet.xjc.Annotation xmlAnnotation = new com.github.markusbernhardt.xmldoclet.xjc.Annotation(); + setNames(typeElement, packageElement, xmlAnnotation::setName, xmlAnnotation::setQualified); + xmlAnnotation.setScope(getScope(typeElement.getModifiers())); + xmlAnnotation.setIncluded(environment.isIncluded(typeElement)); + transformJavadoc(typeElement, xmlAnnotation::setComment, xmlAnnotation::getTag); + xmlAnnotation.getAnnotation().addAll(transformAnnotationMirrors(typeElement.getAnnotationMirrors())); + for (Element enclosedElement : typeElement.getEnclosedElements()) { + if (!environment.isIncluded(enclosedElement)) { + continue; + } + if (enclosedElement.getKind() == ElementKind.METHOD) { + final ExecutableElement methodElement = (ExecutableElement) enclosedElement; + xmlAnnotation.getElement().add(transformAnnotationElement(methodElement, xmlAnnotation.getQualified())); + } + } + getXmlPackage(xmlRoot, packageElement).getAnnotation().add(xmlAnnotation); + } + if (typeElement.getKind() == ElementKind.ENUM) { + final com.github.markusbernhardt.xmldoclet.xjc.Enum xmlEnum = new com.github.markusbernhardt.xmldoclet.xjc.Enum(); + setNames(typeElement, packageElement, xmlEnum::setName, xmlEnum::setQualified); + xmlEnum.setScope(getScope(typeElement.getModifiers())); + xmlEnum.setIncluded(environment.isIncluded(typeElement)); + transformJavadoc(typeElement, xmlEnum::setComment, xmlEnum::getTag); + xmlEnum.setClazz(transformTypeMirror(typeElement.getSuperclass())); + xmlEnum.getInterface().addAll(transformTypeMirrors(typeElement.getInterfaces())); + xmlEnum.getAnnotation().addAll(transformAnnotationMirrors(typeElement.getAnnotationMirrors())); + for (Element enclosedElement : typeElement.getEnclosedElements()) { + if (!environment.isIncluded(enclosedElement)) { + continue; + } + if (enclosedElement.getKind() == ElementKind.ENUM_CONSTANT) { + final VariableElement constantElement = (VariableElement) enclosedElement; + xmlEnum.getConstant().add(transformEnumConstant(constantElement)); + } + } + getXmlPackage(xmlRoot, packageElement).getEnum().add(xmlEnum); + } + if (typeElement.getKind() == ElementKind.INTERFACE) { + final com.github.markusbernhardt.xmldoclet.xjc.Interface xmlInterface = new com.github.markusbernhardt.xmldoclet.xjc.Interface(); + setNames(typeElement, packageElement, xmlInterface::setName, xmlInterface::setQualified); + xmlInterface.setScope(getScope(typeElement.getModifiers())); + xmlInterface.setIncluded(environment.isIncluded(typeElement)); + transformJavadoc(typeElement, xmlInterface::setComment, xmlInterface::getTag); + xmlInterface.getGeneric().addAll(transformTypeParameters(typeElement.getTypeParameters())); + xmlInterface.getInterface().addAll(transformTypeMirrors(typeElement.getInterfaces())); + xmlInterface.getAnnotation().addAll(transformAnnotationMirrors(typeElement.getAnnotationMirrors())); + for (Element enclosedElement : typeElement.getEnclosedElements()) { + if (!environment.isIncluded(enclosedElement)) { + continue; + } + if (enclosedElement.getKind() == ElementKind.FIELD) { + final VariableElement fieldElement = (VariableElement) enclosedElement; + xmlInterface.getField().add(transformFieldElement(fieldElement, xmlInterface.getQualified())); + } + if (enclosedElement.getKind() == ElementKind.METHOD) { + final ExecutableElement methodElement = (ExecutableElement) enclosedElement; + xmlInterface.getMethod().add(transformMethodElement(methodElement, xmlInterface.getQualified())); + } + } + getXmlPackage(xmlRoot, packageElement).getInterface().add(xmlInterface); + } + if (typeElement.getKind() == ElementKind.CLASS) { + final com.github.markusbernhardt.xmldoclet.xjc.Class xmlClass = new com.github.markusbernhardt.xmldoclet.xjc.Class(); + setNames(typeElement, packageElement, xmlClass::setName, xmlClass::setQualified); + xmlClass.setScope(getScope(typeElement.getModifiers())); + setFlag(typeElement, Modifier.ABSTRACT, xmlClass::setAbstract); + xmlClass.setError(environment.getTypeUtils().isAssignable(typeElement.asType(), errorTypeElement.asType())); + xmlClass.setException(environment.getTypeUtils().isAssignable(typeElement.asType(), exceptionTypeElement.asType())); + xmlClass.setExternalizable(environment.getTypeUtils().isAssignable(typeElement.asType(), externalizableTypeElement.asType())); + xmlClass.setIncluded(environment.isIncluded(typeElement)); + xmlClass.setSerializable(environment.getTypeUtils().isAssignable(typeElement.asType(), serializableTypeElement.asType())); + transformJavadoc(typeElement, xmlClass::setComment, xmlClass::getTag); + xmlClass.getGeneric().addAll(transformTypeParameters(typeElement.getTypeParameters())); + xmlClass.setClazz(transformTypeMirror(typeElement.getSuperclass())); + xmlClass.getInterface().addAll(transformTypeMirrors(typeElement.getInterfaces())); + xmlClass.getAnnotation().addAll(transformAnnotationMirrors(typeElement.getAnnotationMirrors())); + for (Element enclosedElement : typeElement.getEnclosedElements()) { + if (!environment.isIncluded(enclosedElement)) { + continue; + } + if (enclosedElement.getKind() == ElementKind.FIELD) { + final VariableElement fieldElement = (VariableElement) enclosedElement; + xmlClass.getField().add(transformFieldElement(fieldElement, xmlClass.getQualified())); + } + if (enclosedElement.getKind() == ElementKind.CONSTRUCTOR) { + final ExecutableElement constructorElement = (ExecutableElement) enclosedElement; + xmlClass.getConstructor().add(transformConstructorElement(constructorElement, xmlClass.getQualified())); + } + if (enclosedElement.getKind() == ElementKind.METHOD) { + final ExecutableElement methodElement = (ExecutableElement) enclosedElement; + xmlClass.getMethod().add(transformMethodElement(methodElement, xmlClass.getQualified())); + } + } + getXmlPackage(xmlRoot, packageElement).getClazz().add(xmlClass); + } + transformElements(xmlRoot, typeElement.getEnclosedElements()); + } + + private com.github.markusbernhardt.xmldoclet.xjc.AnnotationElement transformAnnotationElement(ExecutableElement annotationElement, String qualified) { + final com.github.markusbernhardt.xmldoclet.xjc.AnnotationElement xmlElement = new com.github.markusbernhardt.xmldoclet.xjc.AnnotationElement(); + xmlElement.setName(annotationElement.getSimpleName().toString()); + xmlElement.setQualified(qualified + "." + xmlElement.getName()); + if (annotationElement.getDefaultValue() != null) { + xmlElement.setDefault(String.valueOf(annotationElement.getDefaultValue().getValue())); + } + xmlElement.setType(transformTypeMirror(annotationElement.getReturnType())); + return xmlElement; + } + + private com.github.markusbernhardt.xmldoclet.xjc.EnumConstant transformEnumConstant(VariableElement constantElement) { + final com.github.markusbernhardt.xmldoclet.xjc.EnumConstant xmlConstant = new com.github.markusbernhardt.xmldoclet.xjc.EnumConstant(); + xmlConstant.setName(constantElement.getSimpleName().toString()); + transformJavadoc(constantElement, xmlConstant::setComment, xmlConstant::getTag); + xmlConstant.getAnnotation().addAll(transformAnnotationMirrors(constantElement.getAnnotationMirrors())); + return xmlConstant; + } + + private com.github.markusbernhardt.xmldoclet.xjc.Field transformFieldElement(VariableElement variableElement, String qualified) { + final com.github.markusbernhardt.xmldoclet.xjc.Field xmlField = new com.github.markusbernhardt.xmldoclet.xjc.Field(); + xmlField.setName(variableElement.getSimpleName().toString()); + xmlField.setQualified(qualified + "." + xmlField.getName()); + xmlField.setScope(getScope(variableElement.getModifiers())); + setFlag(variableElement, Modifier.VOLATILE, xmlField::setVolatile); + setFlag(variableElement, Modifier.TRANSIENT, xmlField::setTransient); + setFlag(variableElement, Modifier.STATIC, xmlField::setStatic); + setFlag(variableElement, Modifier.FINAL, xmlField::setFinal); + xmlField.setType(transformTypeMirror(variableElement.asType())); + transformJavadoc(variableElement, xmlField::setComment, xmlField::getTag); + final Object constantValue = variableElement.getConstantValue(); + if (constantValue != null) { + xmlField.setConstant(elementUtils.getConstantExpression(constantValue)); + } + xmlField.getAnnotation().addAll(transformAnnotationMirrors(variableElement.getAnnotationMirrors())); + return xmlField; + } + + private com.github.markusbernhardt.xmldoclet.xjc.Constructor transformConstructorElement(ExecutableElement constructorElement, String qualified) { + final com.github.markusbernhardt.xmldoclet.xjc.Constructor xmlConstructor = new com.github.markusbernhardt.xmldoclet.xjc.Constructor(); + xmlConstructor.setName(constructorElement.getEnclosingElement().getSimpleName().toString()); + xmlConstructor.setSignature(getSignature(constructorElement)); + xmlConstructor.setQualified(qualified); + xmlConstructor.setScope(getScope(constructorElement.getModifiers())); + setFlag(constructorElement, Modifier.FINAL, xmlConstructor::setFinal); + xmlConstructor.setIncluded(environment.isIncluded(constructorElement)); + setFlag(constructorElement, Modifier.NATIVE, xmlConstructor::setNative); + setFlag(constructorElement, Modifier.SYNCHRONIZED, xmlConstructor::setSynchronized); + setFlag(constructorElement, Modifier.STATIC, xmlConstructor::setStatic); + xmlConstructor.setVarArgs(constructorElement.isVarArgs()); + transformJavadoc(constructorElement, xmlConstructor::setComment, xmlConstructor::getTag); + xmlConstructor.getParameter().addAll(transformParameters(constructorElement)); + xmlConstructor.getException().addAll(transformExceptions(constructorElement)); + xmlConstructor.getAnnotation().addAll(transformAnnotationMirrors(constructorElement.getAnnotationMirrors())); + return xmlConstructor; + } + + private com.github.markusbernhardt.xmldoclet.xjc.Method transformMethodElement(ExecutableElement methodElement, String qualified) { + final com.github.markusbernhardt.xmldoclet.xjc.Method xmlMethod = new com.github.markusbernhardt.xmldoclet.xjc.Method(); + xmlMethod.setName(methodElement.getSimpleName().toString()); + xmlMethod.setSignature(getSignature(methodElement)); + xmlMethod.setQualified(qualified + "." + xmlMethod.getName()); + xmlMethod.setScope(getScope(methodElement.getModifiers())); + setFlag(methodElement, Modifier.ABSTRACT, xmlMethod::setAbstract); + setFlag(methodElement, Modifier.FINAL, xmlMethod::setFinal); + xmlMethod.setIncluded(environment.isIncluded(methodElement)); + setFlag(methodElement, Modifier.NATIVE, xmlMethod::setNative); + setFlag(methodElement, Modifier.SYNCHRONIZED, xmlMethod::setSynchronized); + setFlag(methodElement, Modifier.STATIC, xmlMethod::setStatic); + xmlMethod.setVarArgs(methodElement.isVarArgs()); + transformJavadoc(methodElement, xmlMethod::setComment, xmlMethod::getTag); + xmlMethod.getParameter().addAll(transformParameters(methodElement)); + xmlMethod.setReturn(transformTypeMirror(methodElement.getReturnType())); + xmlMethod.getException().addAll(transformExceptions(methodElement)); + xmlMethod.getAnnotation().addAll(transformAnnotationMirrors(methodElement.getAnnotationMirrors())); + return xmlMethod; + } + + private List transformExceptions(ExecutableElement executableElement) { + return transformTypeMirrors(executableElement.getThrownTypes()); + } + + private List transformParameters(ExecutableElement executableElement) { + return executableElement.getParameters().stream() + .map(this::transformParameter) + .collect(Collectors.toList()); + } + + private com.github.markusbernhardt.xmldoclet.xjc.MethodParameter transformParameter(VariableElement methodParameter) { + final com.github.markusbernhardt.xmldoclet.xjc.MethodParameter xmlParameter = new com.github.markusbernhardt.xmldoclet.xjc.MethodParameter(); + xmlParameter.setName(methodParameter.getSimpleName().toString()); + xmlParameter.setType(transformTypeMirror(methodParameter.asType())); + xmlParameter.getAnnotation().addAll(transformAnnotationMirrors(methodParameter.getAnnotationMirrors())); + return xmlParameter; + } + + private List transformAnnotationMirrors(List annotationMirrors) { + return annotationMirrors.stream() + .map(this::transformAnnotationMirror) + .collect(Collectors.toList()); + } + + private com.github.markusbernhardt.xmldoclet.xjc.AnnotationInstance transformAnnotationMirror(AnnotationMirror annotationMirror) { + final com.github.markusbernhardt.xmldoclet.xjc.AnnotationInstance xmlAnnotation = new com.github.markusbernhardt.xmldoclet.xjc.AnnotationInstance(); + setNames(annotationMirror.getAnnotationType().asElement(), xmlAnnotation::setName, xmlAnnotation::setQualified); + xmlAnnotation.getArgument().addAll(transformAnnotationValues(annotationMirror.getElementValues())); + return xmlAnnotation; + } + + private List transformAnnotationValues(Map annotationValues) { + return annotationValues.entrySet().stream() + .map(entry -> transformAnnotationValue(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + } + + private com.github.markusbernhardt.xmldoclet.xjc.AnnotationArgument transformAnnotationValue(ExecutableElement element, AnnotationValue annotationValue) { + final com.github.markusbernhardt.xmldoclet.xjc.AnnotationArgument xmlArgument = new com.github.markusbernhardt.xmldoclet.xjc.AnnotationArgument(); + xmlArgument.setName(element.getSimpleName().toString()); + final TypeMirror type = element.getReturnType(); + xmlArgument.setType(transformTypeMirror(type)); + final Object value = annotationValue.getValue(); + if (type instanceof ArrayType) { + final ArrayType arrayType = (ArrayType) type; + final TypeMirror componentType = arrayType.getComponentType(); + xmlArgument.setPrimitive(componentType instanceof PrimitiveType); + xmlArgument.setArray(true); + if (value instanceof List) { + final List list = (List) value; + for (Object item : list) { + if (item instanceof AnnotationValue) { + final AnnotationValue annotationValueItem = (AnnotationValue) item; +// transformAnnotationSingleValueCompatibleMode(xmlArgument, annotationValueItem.getValue(), componentType); + transformAnnotationSingleValue(xmlArgument, annotationValueItem.getValue(), componentType); + } + } + } + } else { + xmlArgument.setPrimitive(type instanceof PrimitiveType); + xmlArgument.setArray(false); + transformAnnotationSingleValue(xmlArgument, value, type); + } + return xmlArgument; + } + + // TODO remove this method + @Deprecated + private void transformAnnotationSingleValueCompatibleMode(com.github.markusbernhardt.xmldoclet.xjc.AnnotationArgument xmlArgument, Object value, TypeMirror type) { + if (value instanceof VariableElement) { + // compatible mode + final VariableElement variableElement = (VariableElement) value; + final String qualifiedName = variableElement.getEnclosingElement() + "." + variableElement; + xmlArgument.getValue().add(qualifiedName); + } else { + transformAnnotationSingleValue(xmlArgument, value, type); + } + } + + private void transformAnnotationSingleValue(com.github.markusbernhardt.xmldoclet.xjc.AnnotationArgument xmlArgument, Object value, TypeMirror type) { + if (type instanceof PrimitiveType) { + if (value instanceof Character) { + final Character character = (Character) value; + xmlArgument.getValue().add(String.valueOf((long) character)); + } else { + xmlArgument.getValue().add(String.valueOf(value)); + } + } else if (value instanceof String) { + xmlArgument.getValue().add(String.valueOf(value)); + } else if (value instanceof TypeMirror) { + final TypeMirror typeMirror = (TypeMirror) value; + xmlArgument.getValue().add(typeMirror.toString()); + } else if (value instanceof VariableElement) { + final VariableElement variableElement = (VariableElement) value; + xmlArgument.getValue().add(variableElement.getSimpleName().toString()); + } else if (value instanceof AnnotationMirror) { + final AnnotationMirror annotationMirror = (AnnotationMirror) value; + xmlArgument.getAnnotation().add(transformAnnotationMirror(annotationMirror)); + } else if (value instanceof List) { + // already handled by calling method + } + } + + private List transformTypeParameters(List typeParameterElements) { + return typeParameterElements.stream() + .map(this::transformTypeParameter) + .collect(Collectors.toList()); + } + + private com.github.markusbernhardt.xmldoclet.xjc.TypeParameter transformTypeParameter(TypeParameterElement typeParameterElement) { + final com.github.markusbernhardt.xmldoclet.xjc.TypeParameter xmlTypeParameter = new com.github.markusbernhardt.xmldoclet.xjc.TypeParameter(); + xmlTypeParameter.setName(typeParameterElement.getSimpleName().toString()); + final List bounds = typeParameterElement.getBounds().stream() + .filter(bound -> !typeUtils.isSameType(bound, objectTypeMirror)) + .map(TypeMirror::toString) + .collect(Collectors.toList()); + xmlTypeParameter.getBound().addAll(bounds); + return xmlTypeParameter; + } + + private void transformJavadoc(Element element, Consumer commentSetter, Supplier> tagsGetter) { + final DocCommentTree docCommentTree = docTrees.getDocCommentTree(element); + if (docCommentTree != null) { + final String fullBody = docCommentTree.getFullBody().stream() + .map(Object::toString) + .collect(Collectors.joining()); + if (!fullBody.isEmpty()) { + commentSetter.accept(fullBody); + } + final List tags = tagsGetter.get(); + for (DocTree blockTag : docCommentTree.getBlockTags()) { + if (blockTag instanceof BlockTagTree) { + final BlockTagTree blockTagTree = (BlockTagTree) blockTag; + final String[] parts = blockTagTree.toString().split(" ", 2); + if (parts.length == 2) { + final TagInfo tag = new TagInfo(); + tag.setName(parts[0]); + tag.setText(parts[1]); + tags.add(tag); + } + } + } + } + } + + private PackageElement getEnclosingPackage(Element element) { + Objects.requireNonNull(element); + return element instanceof PackageElement + ? (PackageElement) element + : getEnclosingPackage(element.getEnclosingElement()); + } + + private static String getScope(Set modifiers) { + if (modifiers.contains(Modifier.PUBLIC)) return "public"; + if (modifiers.contains(Modifier.PROTECTED)) return "protected"; + if (modifiers.contains(Modifier.PRIVATE)) return "private"; + return ""; + } + + private static String getSignature(ExecutableElement executableElement) { + final StringBuilder sb = new StringBuilder(); + sb.append("("); + final List parameters = executableElement.getParameters(); + final Iterator iterator = parameters.iterator(); + while (iterator.hasNext()) { + final VariableElement variableElement = iterator.next(); + if (iterator.hasNext()) { + sb.append(variableElement.asType().toString()); + sb.append(", "); + } else { + if (variableElement.asType() instanceof ArrayType && executableElement.isVarArgs()) { + final ArrayType arrayType = (ArrayType) variableElement.asType(); + sb.append(arrayType.getComponentType().toString()); + sb.append("..."); + } else { + sb.append(variableElement.asType().toString()); + } + } + } + sb.append(")"); + return sb.toString(); + } + + private List transformTypeMirrorIfNonNull(TypeMirror typeMirror) { + return typeMirror != null + ? Collections.singletonList(transformTypeMirror(typeMirror)) + : Collections.emptyList(); + } + + private List transformTypeMirrors(List typeMirrors) { + return typeMirrors.stream() + .map(this::transformTypeMirror) + .collect(Collectors.toList()); + } + + private TypeInfo transformTypeMirror(TypeMirror typeMirror) { + final TypeInfo xmlTypeInfo = new TypeInfo(); + if (typeMirror instanceof DeclaredType) { + xmlTypeInfo.setQualified(typeUtils.erasure(typeMirror).toString()); + final DeclaredType declaredType = (DeclaredType) typeMirror; + final List generic = declaredType.getTypeArguments().stream() + .map(this::transformTypeMirror) + .collect(Collectors.toList()); + xmlTypeInfo.getGeneric().addAll(generic); + } else if (typeMirror instanceof ArrayType) { + final ArrayType arrayType = (ArrayType) typeMirror; + final TypeInfo typeInfo = transformTypeMirror(arrayType.getComponentType()); + typeInfo.setDimension((typeInfo.getDimension() != null ? typeInfo.getDimension() : "") + "[]"); + return typeInfo; + } else if (typeMirror instanceof WildcardType) { + xmlTypeInfo.setQualified("?"); + final WildcardType wildcardType = (WildcardType) typeMirror; + final Wildcard wildcard = new Wildcard(); + wildcard.getExtendsBound().addAll(transformTypeMirrorIfNonNull(wildcardType.getExtendsBound())); + wildcard.getSuperBound().addAll(transformTypeMirrorIfNonNull(wildcardType.getSuperBound())); + xmlTypeInfo.setWildcard(wildcard); + } else { + xmlTypeInfo.setQualified(typeMirror.toString()); + } + return xmlTypeInfo; + } + + private static void setFlag(Element element, Modifier modifier, Consumer flagSetter) { + flagSetter.accept(element.getModifiers().contains(modifier)); + } + + private com.github.markusbernhardt.xmldoclet.xjc.Package getXmlPackage(Root xmlRoot, PackageElement packageElement) { + final com.github.markusbernhardt.xmldoclet.xjc.Package xmlPackage = xmlRoot.getPackage().stream() + .filter(p -> Objects.equals(p.getName(), packageElement.getQualifiedName().toString())) + .findFirst() + .orElse(null); + if (xmlPackage == null) { + final com.github.markusbernhardt.xmldoclet.xjc.Package newXmlPackage = new com.github.markusbernhardt.xmldoclet.xjc.Package(); + newXmlPackage.setName(packageElement.getQualifiedName().toString()); + xmlRoot.getPackage().add(newXmlPackage); + return newXmlPackage; + } else { + return xmlPackage; + } + } + + private void setNames(QualifiedNameable qualifiedNameable, PackageElement packageElement, Consumer nameSetter, Consumer qualifiedSetter) { + setNames(qualifiedNameable.getQualifiedName().toString(), packageElement, nameSetter, qualifiedSetter); + } + + private void setNames(Element element, Consumer nameSetter, Consumer qualifiedSetter) { + setNames(element.toString(), getEnclosingPackage(element), nameSetter, qualifiedSetter); + } + + private void setNames(String qualifiedName, PackageElement packageElement, Consumer nameSetter, Consumer qualifiedSetter) { + final String packageName = packageElement.getQualifiedName().toString(); + final String packagePrefix = packageName.isEmpty() ? "" : packageName + "."; + final String name = qualifiedName.substring(packagePrefix.length()); + nameSetter.accept(name); + qualifiedSetter.accept(qualifiedName); + } + +} diff --git a/src/main/java/com/github/markusbernhardt/xmldoclet/NewXmlDoclet.java b/src/main/java/com/github/markusbernhardt/xmldoclet/NewXmlDoclet.java new file mode 100644 index 0000000..7f18a8c --- /dev/null +++ b/src/main/java/com/github/markusbernhardt/xmldoclet/NewXmlDoclet.java @@ -0,0 +1,194 @@ +package com.github.markusbernhardt.xmldoclet; + +import com.github.markusbernhardt.xmldoclet.xjc.Annotation; +import com.github.markusbernhardt.xmldoclet.xjc.Class; +import com.github.markusbernhardt.xmldoclet.xjc.Enum; +import com.github.markusbernhardt.xmldoclet.xjc.Interface; +import com.github.markusbernhardt.xmldoclet.xjc.Package; +import com.github.markusbernhardt.xmldoclet.xjc.Root; +import java.io.BufferedOutputStream; +import java.io.CharArrayWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.function.Consumer; +import javax.lang.model.SourceVersion; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import jdk.javadoc.doclet.Doclet; +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.doclet.Reporter; + + +public class NewXmlDoclet implements Doclet { + + /** + * For tests. + */ + public static Root root; + + private String directory; + private String encoding; + private boolean dryrun; + private String filename; + + @Override + public void init(Locale locale, Reporter reporter) { + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + @Override + public Set getSupportedOptions() { + return new LinkedHashSet<>(Arrays.asList( + new SingleArgumentOption( + Arrays.asList("-d"), + "directory", + "Destination directory for output file.\nDefault: .", + argument -> this.directory = argument + ), + new SingleArgumentOption( + Arrays.asList("-docencoding"), + "encoding", + "Encoding of the output file.\nDefault: UTF8", + argument -> this.encoding = argument + ), + new FlagOption( + Arrays.asList("-dryrun"), + "Parse javadoc, but don't write output file.\nDefault: false", + () -> this.dryrun = true + ), + new SingleArgumentOption( + Arrays.asList("-filename"), + "filename", + "Name of the output file.\nDefault: javadoc.xml", + argument -> this.filename = argument + ) + )); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + @Override + public boolean run(DocletEnvironment environment) { + final Root xmlRoot = new JavadocTransformer(environment).transform(); + root = xmlRoot; + save(xmlRoot); + return true; + } + + private void save(Root xmlRoot) { + ClassLoader originalClassLoader = null; + try { + originalClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); + if (dryrun) { + return; + } + sort(xmlRoot); + final JAXBContext context = JAXBContext.newInstance(Root.class); + final CharArrayWriter writer = new CharArrayWriter(); + final Marshaller marshaller = context.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + if (encoding != null) { + marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding); + } + final String name = (directory != null ? directory + File.separator : "") + (filename != null ? filename : "javadoc.xml"); + try (OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(name))) { + marshaller.marshal(xmlRoot, outputStream); + } + marshaller.marshal(xmlRoot, writer); + } catch (JAXBException | IOException e) { + throw new RuntimeException(e); + } finally { + if (originalClassLoader != null) { + Thread.currentThread().setContextClassLoader(originalClassLoader); + } + } + } + + public static void sort(Root xmlRoot) { + xmlRoot.getPackage().sort(Comparator.comparing(Package::getName)); + for (Package pkg : xmlRoot.getPackage()) { + pkg.getAnnotation().sort(Comparator.comparing(Annotation::getQualified)); + pkg.getEnum().sort(Comparator.comparing(Enum::getQualified)); + pkg.getInterface().sort(Comparator.comparing(Interface::getQualified)); + pkg.getClazz().sort(Comparator.comparing(Class::getQualified)); + } + } + + private static class FlagOption extends StandardOption { + public FlagOption(List names, String description, Runnable processor) { + super(names, null, description, 0, arguments -> processor.run()); + } + } + + private static class SingleArgumentOption extends StandardOption { + public SingleArgumentOption(List names, String parameter, String description, Consumer processor) { + super(names, parameter, description, 1, arguments -> processor.accept(arguments.get(0))); + } + } + + private static class StandardOption implements Option { + + private final List names; + private final String parameters; + private final String description; + private final int argumentCount; + private final Consumer> processor; + + public StandardOption(List names, String parameters, String description, int argumentCount, Consumer> processor) { + this.names = names; + this.parameters = parameters; + this.description = description; + this.argumentCount = argumentCount; + this.processor = processor; + } + + @Override + public int getArgumentCount() { + return argumentCount; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public Kind getKind() { + return Kind.STANDARD; + } + + @Override + public List getNames() { + return names; + } + + @Override + public String getParameters() { + return parameters; + } + + @Override + public boolean process(String option, List arguments) { + processor.accept(arguments); + return true; + } + } + +} diff --git a/src/main/java/com/github/markusbernhardt/xmldoclet/XmlDoclet.java b/src/main/java/com/github/markusbernhardt/xmldoclet/XmlDoclet.java index 99b029e..d7094c2 100644 --- a/src/main/java/com/github/markusbernhardt/xmldoclet/XmlDoclet.java +++ b/src/main/java/com/github/markusbernhardt/xmldoclet/XmlDoclet.java @@ -158,6 +158,8 @@ public static void save(CommandLine commandLine, Root root) { if (commandLine.hasOption("dryrun")) { return; } + // for easier comparison between original and new version + NewXmlDoclet.sort(root); FileOutputStream fileOutputStream = null; BufferedOutputStream bufferedOutputStream = null; diff --git a/src/test/java/com/github/markusbernhardt/xmldoclet/AbstractTestParent.java b/src/test/java/com/github/markusbernhardt/xmldoclet/AbstractTestParent.java index 4a0b392..4cea1c8 100644 --- a/src/test/java/com/github/markusbernhardt/xmldoclet/AbstractTestParent.java +++ b/src/test/java/com/github/markusbernhardt/xmldoclet/AbstractTestParent.java @@ -3,8 +3,19 @@ import java.io.File; import java.io.OutputStream; import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.tools.DocumentationTool; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; +import javax.xml.bind.JAXB; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,7 +72,7 @@ public class AbstractTestParent { * Additional Arguments. * @return XStream compatible data structure */ - public Root executeJavadoc(String extendedClassPath, String[] sourcePaths, String[] packages, String[] sourceFiles, + public Root oldExecuteJavadoc(String extendedClassPath, String[] sourcePaths, String[] packages, String[] sourceFiles, String[] subPackages, String[] additionalArguments) { try { OutputStream errors = new LoggingOutputStream(log, LoggingLevelEnum.ERROR); @@ -130,9 +141,69 @@ public Root executeJavadoc(String extendedClassPath, String[] sourcePaths, Strin log.error("doclet error", e); } + // for debugging +// System.out.println(marshalJAXB(XmlDoclet.root)); return XmlDoclet.root; } + public Root executeJavadoc(String extendedClassPath, String[] sourcePaths, String[] packages, String[] sourceFiles, + String[] subPackages, String[] additionalArguments) { + try { + final DocumentationTool documentationTool = ToolProvider.getSystemDocumentationTool(); + final StandardJavaFileManager fileManager = documentationTool.getStandardFileManager(null, null, null); + final ArrayList compilationUnits = new ArrayList<>(); + + // sourcePaths + if (sourcePaths != null) { + fileManager.setLocation( + StandardLocation.SOURCE_PATH, + Stream.of(sourcePaths).map(File::new).collect(Collectors.toList()) + ); + } + + // subPackages + if (subPackages != null) { + for (String pkg : subPackages) { + final Iterable javaFileObjects = fileManager.list( + StandardLocation.SOURCE_PATH, + pkg, + Collections.singleton(JavaFileObject.Kind.SOURCE), + true + ); + javaFileObjects.forEach(compilationUnits::add); + } + } + + // sourceFiles + if (sourceFiles != null) { + fileManager.getJavaFileObjects(sourceFiles).forEach(compilationUnits::add); + } + + // additionalArguments + final List options = Stream + .concat( + Stream.of("-private"), + Stream.of(additionalArguments != null ? additionalArguments : new String[]{}) + ) + .collect(Collectors.toList()); + + final DocumentationTool.DocumentationTask task = documentationTool.getTask(null, fileManager, null, NewXmlDoclet.class, options, compilationUnits); + task.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // for debugging +// System.out.println(marshalJAXB(NewXmlDoclet.root)); + return NewXmlDoclet.root; + } + + private static String marshalJAXB(Object jaxbObject) { + final StringWriter writer = new StringWriter(); + JAXB.marshal(jaxbObject, writer); + return writer.toString(); + } + /** * Helper method to concat strings. *