/** * Copyright 2011-2017 Asakusa Framework Team. * * 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.asakusafw.operator.util; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.tools.Diagnostic; import com.asakusafw.operator.CompileEnvironment; import com.asakusafw.operator.model.OperatorDescription.Document; import com.asakusafw.operator.model.OperatorDescription.ExternalDocument; import com.asakusafw.operator.model.OperatorDescription.ParameterReference; import com.asakusafw.operator.model.OperatorDescription.Reference; import com.asakusafw.operator.model.OperatorDescription.ReferenceDocument; import com.asakusafw.operator.model.OperatorDescription.SpecialReference; import com.asakusafw.operator.model.OperatorDescription.TextDocument; import com.asakusafw.utils.java.model.syntax.DocBlock; import com.asakusafw.utils.java.model.syntax.DocElement; import com.asakusafw.utils.java.model.syntax.DocText; import com.asakusafw.utils.java.model.syntax.Javadoc; import com.asakusafw.utils.java.model.syntax.SimpleName; import com.asakusafw.utils.java.model.util.Models; import com.asakusafw.utils.java.parser.javadoc.JavadocConverter; import com.asakusafw.utils.java.parser.javadoc.JavadocParseException; /** * Common helper methods about documentation comments. */ public class JavadocHelper { private final CompileEnvironment environment; private final Map<String, List<List<DocElement>>> blocks = new HashMap<>(); private ExecutableElement executable; /** * Creates a new instance. * @param environment current environment * @throws IllegalArgumentException if some parameters were {@code null} */ public JavadocHelper(CompileEnvironment environment) { this.environment = Objects.requireNonNull(environment, "environment must not be null"); //$NON-NLS-1$ } /** * Put a documented element. * @param element the documented element * @throws IllegalArgumentException if some parameters were {@code null} */ public void put(Element element) { Objects.requireNonNull(element, "element must not be null"); //$NON-NLS-1$ if (this.executable == null && (element.getKind() == ElementKind.METHOD || element.getKind() == ElementKind.CONSTRUCTOR)) { this.executable = (ExecutableElement) element; } Javadoc javadoc = parseJavadoc(element); boolean sawSummary = false; for (DocBlock block : javadoc.getBlocks()) { String tag = block.getTag(); sawSummary |= tag.isEmpty(); appendBlock(tag, block.getElements()); } if (sawSummary == false) { appendBlock("", Collections.emptyList()); //$NON-NLS-1$ } } private Javadoc parseJavadoc(Element element) { assert environment != null; assert element != null; String comment = environment.getProcessingEnvironment().getElementUtils().getDocComment(element); try { return parseJavadoc(comment); } catch (JavadocParseException e) { environment.getProcessingEnvironment().getMessager().printMessage(Diagnostic.Kind.WARNING, e.getMessage(), element); return Models.getModelFactory().newJavadoc(Collections.emptyList()); } } private static Javadoc parseJavadoc(String comment) throws JavadocParseException { if (comment == null) { return Models.getModelFactory().newJavadoc(Collections.emptyList()); } String string = comment; if (comment.startsWith("/**") == false) { //$NON-NLS-1$ string = "/**" + string; //$NON-NLS-1$ } if (comment.endsWith("*/") == false) { //$NON-NLS-1$ string = string + "*/"; //$NON-NLS-1$ } return new JavadocConverter(Models.getModelFactory()).convert(string, 0); } private void appendBlock(String tag, List<? extends DocElement> elements) { assert tag != null; assert elements != null; List<List<DocElement>> list = blocks.get(tag); if (list == null) { list = new ArrayList<>(); blocks.put(tag, list); } list.add(new ArrayList<>(elements)); } /** * Returns the document elements for the description. * @param document description * @return related elements, or an empty list if they are not found * @throws IllegalArgumentException if some parameters were {@code null} */ public List<? extends DocElement> get(Document document) { Objects.requireNonNull(document, "document must not be null"); //$NON-NLS-1$ switch (document.getKind()) { case TEXT: return text(((TextDocument) document).getText()); case REFERENCE: return reference(((ReferenceDocument) document).getReference()); case EXTERNAL: return external(((ExternalDocument) document).getElement()); default: throw new AssertionError(document.getKind()); } } /** * Returns the parameter document elements. * @param name parameter name * @return related elements, or an empty list if the parameter does not exist * @throws IllegalArgumentException if some parameters were {@code null} */ public List<? extends DocElement> getParameter(String name) { Objects.requireNonNull(name, "name must not be null"); //$NON-NLS-1$ List<List<DocElement>> params = blocks.get("@param"); //$NON-NLS-1$ if (params == null) { return Collections.emptyList(); } for (List<DocElement> param : params) { if (param.isEmpty()) { continue; } if (hasName(param.get(0), name)) { return param.subList(1, param.size()); } } return Collections.emptyList(); } /** * Returns the type parameter document elements. * @param name type parameter name * @return related elements, or an empty list if the type parameter does not exist * @throws IllegalArgumentException if some parameters were {@code null} */ public List<? extends DocElement> getTypeParameter(String name) { Objects.requireNonNull(name, "name must not be null"); //$NON-NLS-1$ List<List<DocElement>> params = blocks.get("@param"); //$NON-NLS-1$ if (params == null) { return Collections.emptyList(); } for (List<DocElement> param : params) { if (param.size() < 3) { continue; } if (hasName(param.get(0), "<") //$NON-NLS-1$ && hasName(param.get(1), name) && hasName(param.get(2), ">")) { //$NON-NLS-1$ return param.subList(3, param.size()); } } return Collections.emptyList(); } private static boolean hasName(DocElement element, String name) { assert element != null; assert name != null; switch (element.getModelKind()) { case SIMPLE_NAME: return name.equals(((SimpleName) element).getToken()); case DOC_TEXT: return name.equals(((DocText) element).getString()); default: return false; } } private List<? extends DocElement> text(String text) { Javadoc javadoc; try { javadoc = parseJavadoc(text); } catch (JavadocParseException e) { environment.getProcessingEnvironment().getMessager().printMessage(Diagnostic.Kind.WARNING, e.getMessage(), executable); return Collections.emptyList(); } if (javadoc.getBlocks().isEmpty()) { return Collections.emptyList(); } return javadoc.getBlocks().get(0).getElements(); } private List<? extends DocElement> reference(Reference reference) { switch (reference.getKind()) { case METHOD: return getBlock(""); //$NON-NLS-1$ case RETURN: return getBlock("@return"); //$NON-NLS-1$ case PARAMETER: return getParameter(((ParameterReference) reference).getLocation()); case SPECIAL: return text(((SpecialReference) reference).getInfo()); default: throw new AssertionError(reference.getKind()); } } private List<? extends DocElement> getParameter(int location) { if (executable == null || location >= executable.getParameters().size()) { return Collections.emptyList(); } String name = executable.getParameters().get(location).getSimpleName().toString(); return getParameter(name); } private List<? extends DocElement> external(Element element) { Javadoc javadoc = parseJavadoc(element); List<? extends DocBlock> elementBlocks = javadoc.getBlocks(); if (elementBlocks.isEmpty()) { return Collections.emptyList(); } DocBlock first = elementBlocks.get(0); if (first.getTag().isEmpty()) { return first.getElements(); } return Collections.emptyList(); } private List<? extends DocElement> getBlock(String tag) { assert tag != null; List<List<DocElement>> list = blocks.get(tag); if (list == null) { return Collections.emptyList(); } return list.get(0); } }