All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jsweet.transpiler.util.JSDoc Maven / Gradle / Ivy

The newest version!
/* 
 * JSweet transpiler - http://www.jsweet.org
 * Copyright (C) 2015 CINCHEO SAS 
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *  
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.jsweet.transpiler.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;

import org.jsweet.transpiler.JSweetContext;
import org.jsweet.transpiler.model.ExtendedElement;
import org.jsweet.transpiler.model.ExtendedElementFactory;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;

/**
 * A utility class to print JSDoc comments from regular Java comments.
 * 
 * @author Renaud Pawlak
 */
public class JSDoc {

	private JSDoc() {
	}

	/**
	 * A regexp that matches JavaDoc @param expressions.
	 */
	public static final Pattern paramPattern = Pattern.compile("(\\s*@param\\s+)(\\w+)(.*)");
	/**
	 * A regexp that matches JavaDoc @author expressions.
	 */
	public static final Pattern authorPattern = Pattern.compile("(\\s*@author\\s+)(\\w+)(.*)");
	/**
	 * A regexp that matches JavaDoc @return expression.
	 */
	public static final Pattern returnPattern = Pattern.compile("(\\s*@return\\s+)(.*)");
	/**
	 * A regexp that matches JavaDoc @link expressions.
	 */
	public static final Pattern linkPattern = Pattern.compile("(\\{@link\\s+)([\\w\\.#,]+)\\s+[^}]*(\\})");

	/**
	 * Gets the JSDoc type from a Java type tree and/or type.
	 * 
	 * @param typeTree        the Java type tree
	 * @param type            the Java type
	 * @param compilationUnit TODO
	 * 
	 * @return the JSDoc type
	 */
	public static String getMappedDocType(JSweetContext context, Tree typeTree, TypeMirror type,
			CompilationUnitTree compilationUnit) {
		String qualifiedName = type.toString();
		if (typeTree instanceof ParameterizedTypeTree) {
			TypeMirror parametrizedType = Util.getType(((ParameterizedTypeTree) typeTree).getType());
			qualifiedName = parametrizedType.toString();
		}
		boolean isMapped = false;
        for (Function mapping : context.getFunctionalTypeMirrorMappings()) {
            String mapped = mapping.apply(type);
            if (mapped != null) {
                isMapped = true;
                qualifiedName = mapped;
            }
        }
		
		if (typeTree != null) {
		    if (type instanceof TypeVariable) {
		        TypeVariable typeVar = (TypeVariable) type;
		        if (typeVar.getUpperBound() == null) {
		            return "*";
		        } else {
		            return getMappedDocType(context, null, typeVar.getUpperBound(), compilationUnit);
		        }
		    }
			ExtendedElement extendedElement = ExtendedElementFactory.INSTANCE
					.create(typeTree);
			for (BiFunction mapping : context.getFunctionalTypeMappings()) {
                Object mapped = mapping.apply(extendedElement, qualifiedName);
                if (mapped instanceof String) {
                    isMapped = true;
                    qualifiedName = (String) mapped;
                } else if (mapped instanceof Tree) {
                    isMapped = true;
                    qualifiedName = getMappedDocType(context, (Tree) mapped,
                            Util.getType((Tree) mapped), compilationUnit);
                }
            }
		}
		if (context.isMappedType(qualifiedName)) {
			isMapped = true;
			qualifiedName = context.getTypeMappingTarget(qualifiedName);
		}
		if (!isMapped && !context.util.isPrimitiveOrVoid(type) && context.types.asElement(type) != null) {
			qualifiedName = context.getRootRelativeName(null, context.types.asElement(type));
		}
		if ("Array".equals(qualifiedName) && typeTree instanceof ParameterizedTypeTree) {
			Tree firstTypeArgTree = ((ParameterizedTypeTree) typeTree).getTypeArguments().get(0);
			TypeMirror firstTypeArgType = Util.getType(firstTypeArgTree);
			return getMappedDocType(context, firstTypeArgTree, firstTypeArgType, compilationUnit) + "[]";
		}
		if (typeTree instanceof ParameterizedTypeTree) {
			TypeMirror parametrizedType = Util.getType(((ParameterizedTypeTree) typeTree).getType());
			return getMappedDocType(context, ((ParameterizedTypeTree) typeTree).getType(), parametrizedType,
					compilationUnit);
		}
		if (!isMapped && context.isInterface((TypeElement) context.types.asElement(type))) {
			return "*";
		}
		return "any".equals(qualifiedName) ? "*" : qualifiedName;
	}

	/**
	 * Replaces Java links by JSDoc links in a doc text.
	 */
	private static String replaceLinks(JSweetContext context, String text) {
		Matcher linkMatcher = linkPattern.matcher(text);
		boolean result = linkMatcher.find();
		int lastMatch = 0;
		if (result) {
			StringBuffer sb = new StringBuffer();
			do {
				sb.append(text.substring(lastMatch, linkMatcher.start()));
				sb.append(linkMatcher.group(1));
				TypeElement type = context.util.getTypeElementByName(context, linkMatcher.group(2));
				sb.append(type == null ? linkMatcher.group(2)
						: getMappedDocType(context, null, type.asType(), context.util.getCompilationUnit(type)));
				sb.append(linkMatcher.group(3));
				lastMatch = linkMatcher.end();
				result = linkMatcher.find();
			} while (result);
			sb.append(text.substring(lastMatch));
			return sb.toString();
		}
		return text;
	}

	/**
	 * Adapts the JavaDoc comment for a given element to conform to JSDoc.
	 * 
	 * 

* By default, this implementation does not auto-generate comments. This * behavior can be overridden by adding a line in the comment before calling * this method. For example: * *

	 * public String adaptDocComment(Tree element, String commentText) {
	 * 	if (commentText == null) {
	 * 		return "My default comment";
	 * 	}
	 * 	super(element, commentText);
	 * }
	 * 
* * @param tree the documented element * @param commentText the comment text if any (null when no comment) * @return the adapted comment (null will remove the JavaDoc comment) */ public static String adaptDocComment(JSweetContext context, TreePath treePath, Tree tree, String commentText) { if (tree instanceof ClassTree) { MethodTree mainConstructor = null; for (Tree member : ((ClassTree) tree).getMembers()) { if (member instanceof MethodTree) { TreePath memberTreePath = TreePath.getPath(treePath, member); ExecutableElement methodElement = context.util.getElementForTreePath(memberTreePath); if (methodElement.getKind() == ElementKind.CONSTRUCTOR && ((MethodTree) member).getModifiers().getFlags().contains(Modifier.PUBLIC)) { if (mainConstructor == null || mainConstructor.getParameters().size() < ((MethodTree) member) .getParameters().size()) { mainConstructor = ((MethodTree) member); } } } } if (mainConstructor != null) { TreePath mainConstructorTreePath = TreePath.getPath(treePath, mainConstructor); String docComment = context.trees.getDocComment(mainConstructorTreePath); String author = null; if (docComment != null) { List commentLines = commentText == null ? null : new ArrayList<>(Arrays.asList(commentText.split("\n"))); // replace the class comment with the main constructor's commentText = docComment; // gets the author for further insertion if (commentLines != null) { for (String line : commentLines) { if (authorPattern.matcher(line).matches()) { author = line; break; } } } } if (commentText != null) { commentText = replaceLinks(context, commentText); List commentLines = new ArrayList<>(Arrays.asList(commentText.split("\n"))); applyForMethod(context, mainConstructor, mainConstructorTreePath, commentLines); ClassTree clazz = (ClassTree) tree; TypeElement classElement = context.util.getElementForTreePath(treePath); if (classElement.getKind() == ElementKind.ENUM) { commentLines.add(" @enum"); } if (clazz.getExtendsClause() != null) { commentLines.add(" @extends " + getMappedDocType(context, clazz.getExtendsClause(), classElement.getSuperclass(), treePath.getCompilationUnit())); } if (author != null) { commentLines.add(author); } return String.join("\n", commentLines); } } } Element element = context.util.getElementForTreePath(treePath); List commentLines = null; if (commentText == null) { if (tree instanceof MethodTree && context.hasAnnotationType(element, Override.class.getName())) { commentText = ""; commentLines = new ArrayList<>(); applyForMethod(context, (MethodTree) tree, treePath, commentLines); } } if (commentText == null) { return null; } commentText = replaceLinks(context, commentText); commentLines = new ArrayList<>(Arrays.asList(commentText.split("\n"))); if (tree instanceof MethodTree) { MethodTree method = (MethodTree) tree; if (element.getKind() != ElementKind.CONSTRUCTOR) { applyForMethod(context, method, treePath, commentLines); } else { // erase constructor comments because jsDoc uses class comments return null; } } else if (tree instanceof ClassTree) { ClassTree clazz = (ClassTree) tree; if (element.getKind() == ElementKind.ENUM) { commentLines.add(" @enum"); for (Tree def : clazz.getMembers()) { if (def instanceof VariableTree) { VariableTree var = (VariableTree) def; TreePath varTreePath = TreePath.getPath(treePath, var); VariableElement varElement = context.util.getElementForTreePath(varTreePath); if (varElement.getModifiers() != null && varElement.getModifiers().contains(Modifier.PUBLIC) && varElement.getModifiers().contains(Modifier.STATIC)) { commentLines .add("@property {" + getMappedDocType(context, var.getType(), context.util.getTypeForTreePath( TreePath.getPath(varTreePath, var.getType())), varTreePath.getCompilationUnit()) + "} " + var.getName().toString()); String varComment = context.trees.getDocComment(varTreePath); if (varComment != null) { varComment = replaceLinks(context, varComment); commentLines.addAll(new ArrayList<>(Arrays.asList(varComment.split("\n")))); } } } } } if (clazz.getExtendsClause() != null) { TreePath extendsTreePath = TreePath.getPath(treePath, clazz.getExtendsClause()); commentLines.add(" @extends " + getMappedDocType(context, clazz.getExtendsClause(), context.util.getTypeForTreePath(extendsTreePath), treePath.getCompilationUnit())); } commentLines.add(" @class"); } return String.join("\n", commentLines); } private static void applyForMethod( // JSweetContext context, // MethodTree method, // TreePath methodTreePath, // List commentLines) { ExecutableElement methodElement = context.util.getElementForTreePath(methodTreePath); Set params = new HashSet<>(); boolean hasReturn = false; for (int i = 0; i < commentLines.size(); i++) { Matcher m = paramPattern.matcher(commentLines.get(i)); if (m.matches()) { String name = m.group(2); params.add(name); VariableTree parameter = context.util.findParameter(method, name); if (parameter != null) { TreePath parameterTypeTreePath = TreePath.getPath(methodTreePath, parameter.getType()); commentLines.set(i, m.group(1) + "{" + getMappedDocType(context, parameter.getType(), context.util.getTypeForTreePath(parameterTypeTreePath), methodTreePath.getCompilationUnit()) + "} " + m.group(2) + m.group(3)); } } else if ((m = returnPattern.matcher(commentLines.get(i))).matches()) { hasReturn = true; if (method.getReturnType() != null) { TreePath returnTypeTreePath = TreePath.getPath(methodTreePath, method.getReturnType()); commentLines.set(i, m.group(1) + "{" + getMappedDocType(context, method.getReturnType(), context.util.getTypeForTreePath(returnTypeTreePath), returnTypeTreePath.getCompilationUnit()) + "} " + m.group(2)); } } } for (VariableTree parameter : method.getParameters()) { String name = parameter.getName().toString(); if (!params.contains(name)) { TreePath parameterTypeTreePath = TreePath.getPath(methodTreePath, parameter.getType()); commentLines.add(" @param {" + getMappedDocType(context, parameter.getType(), context.util.getTypeForTreePath(parameterTypeTreePath), parameterTypeTreePath.getCompilationUnit()) + "} " + name); } } if (!hasReturn && !(method.getReturnType() == null || context.util.getTypeForTreePath(TreePath.getPath(methodTreePath, method.getReturnType())) .getKind() == TypeKind.VOID) && methodElement.getKind() != ElementKind.CONSTRUCTOR) { TreePath returnTypeTreePath = TreePath.getPath(methodTreePath, method.getReturnType()); commentLines.add(" @return {" + getMappedDocType(context, method.getReturnType(), context.util.getTypeForTreePath(returnTypeTreePath), returnTypeTreePath.getCompilationUnit()) + "}"); } if (methodElement.getModifiers().contains(Modifier.PRIVATE)) { commentLines.add(" @private"); } if (methodElement.getKind() == ElementKind.CONSTRUCTOR) { commentLines.add(" @class"); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy