org.eclim.plugin.jdt.command.doc.CommentCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.eclim.plugin.jdt.command.doc.CommentCommand.java

Source

/**
 * Copyright (C) 2005 - 2012  Eric Van Dewoestine
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */
package org.eclim.plugin.jdt.command.doc;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;

import java.util.HashMap;
import java.util.List;

import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

import org.eclim.Services;

import org.eclim.annotation.Command;

import org.eclim.command.CommandLine;
import org.eclim.command.Options;

import org.eclim.plugin.core.command.AbstractCommand;

import org.eclim.plugin.core.preference.Preferences;

import org.eclim.plugin.core.util.ProjectUtils;

import org.eclim.plugin.jdt.util.ASTUtils;
import org.eclim.plugin.jdt.util.JavaUtils;
import org.eclim.plugin.jdt.util.MethodUtils;
import org.eclim.plugin.jdt.util.TypeInfo;
import org.eclim.plugin.jdt.util.TypeUtils;

import org.eclim.util.IOUtils;

import org.eclipse.core.resources.IProject;

import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.Signature;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.core.dom.TextElement;

import org.eclipse.jdt.core.formatter.CodeFormatter;

/**
 * Handles requests to add javadoc comments to an element.
 *
 * @author Eric Van Dewoestine
 */
@Command(name = "javadoc_comment", options = "REQUIRED p project ARG," + "REQUIRED f file ARG,"
        + "REQUIRED o offset ARG," + "OPTIONAL e encoding ARG")
public class CommentCommand extends AbstractCommand {
    private static final Pattern THROWS_PATTERN = Pattern.compile("\\s*[a-zA-Z0-9._]*\\.(\\w*)($|\\s.*)");

    private static final String INHERIT_DOC = "{" + TagElement.TAG_INHERITDOC + "}";

    @Override
    public Object execute(CommandLine commandLine) throws Exception {
        String project = commandLine.getValue(Options.PROJECT_OPTION);
        String file = commandLine.getValue(Options.FILE_OPTION);
        int offset = getOffset(commandLine);

        ICompilationUnit src = JavaUtils.getCompilationUnit(project, file);
        IJavaElement element = src.getElementAt(offset);
        // don't comment import declarations.
        if (element.getElementType() == IJavaElement.IMPORT_DECLARATION) {
            return null;
        }

        CompilationUnit cu = ASTUtils.getCompilationUnit(src, true);
        ASTNode node = ASTUtils.findNode(cu, offset, element);

        if (node != null) {
            comment(src, node, element);

            ASTUtils.commitCompilationUnit(src, cu);

            // re-grab the compilation unit + node so we can get the javadoc node w/
            // its position and length set.
            cu = ASTUtils.getCompilationUnit(src, true);
            node = ASTUtils.findNode(cu, offset, element);
            Javadoc javadoc = (node instanceof PackageDeclaration) ? ((PackageDeclaration) node).getJavadoc()
                    : ((BodyDeclaration) node).getJavadoc();
            JavaUtils.format(src, CodeFormatter.K_COMPILATION_UNIT, javadoc.getStartPosition(),
                    javadoc.getLength());
        }

        return null;
    }

    /**
     * Comment the supplied node.
     *
     * @param src The source file.
     * @param node The node to comment.
     * @param element The IJavaElement this node corresponds to.
     */
    private void comment(ICompilationUnit src, ASTNode node, IJavaElement element) throws Exception {
        Javadoc javadoc = null;
        boolean isNew = false;
        if (node instanceof PackageDeclaration) {
            javadoc = ((PackageDeclaration) node).getJavadoc();
            if (javadoc == null) {
                isNew = true;
                javadoc = node.getAST().newJavadoc();
                ((PackageDeclaration) node).setJavadoc(javadoc);
            }
        } else {
            javadoc = ((BodyDeclaration) node).getJavadoc();
            if (javadoc == null) {
                isNew = true;
                javadoc = node.getAST().newJavadoc();
                ((BodyDeclaration) node).setJavadoc(javadoc);
            }
        }

        switch (node.getNodeType()) {
        case ASTNode.PACKAGE_DECLARATION:
            commentPackage(src, javadoc, element, isNew);
            break;
        case ASTNode.ENUM_DECLARATION:
        case ASTNode.TYPE_DECLARATION:
            commentType(src, javadoc, element, isNew);
            break;
        case ASTNode.METHOD_DECLARATION:
            commentMethod(src, javadoc, element, isNew);
            break;
        default:
            commentOther(src, javadoc, element, isNew);
        }
    }

    /**
     * Comment a package declaration.
     *
     * @param src The source file.
     * @param javadoc The Javadoc.
     * @param element The IJavaElement.
     * @param isNew true if there was no previous javadoc for this element.
     */
    private void commentPackage(ICompilationUnit src, Javadoc javadoc, IJavaElement element, boolean isNew)
            throws Exception {
        IProject project = element.getJavaProject().getProject();
        String copyright = getPreferences().getValue(project, Preferences.PROJECT_COPYRIGHT_PREFERENCE);
        if (copyright != null && copyright.trim().length() > 0) {
            File file = new File(ProjectUtils.getFilePath(project, copyright));
            if (!file.exists()) {
                throw new IllegalArgumentException(Services.getMessage("project.copyright.not.found", file));
            }

            @SuppressWarnings("unchecked")
            List<TagElement> tags = javadoc.tags();
            tags.clear();

            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new FileReader(file));
                String line = null;
                while ((line = reader.readLine()) != null) {
                    addTag(javadoc, tags.size(), null, line);
                }
            } finally {
                IOUtils.closeQuietly(reader);
            }
        } else {
            commentOther(src, javadoc, element, isNew);
        }
    }

    /**
     * Comment a type declaration.
     *
     * @param src The source file.
     * @param javadoc The Javadoc.
     * @param element The IJavaElement.
     * @param isNew true if there was no previous javadoc for this element.
     */
    private void commentType(ICompilationUnit src, Javadoc javadoc, IJavaElement element, boolean isNew)
            throws Exception {
        if (element.getParent().getElementType() == IJavaElement.COMPILATION_UNIT) {
            @SuppressWarnings("unchecked")
            List<TagElement> tags = javadoc.tags();
            IProject project = element.getJavaProject().getProject();
            if (isNew) {
                addTag(javadoc, tags.size(), null, null);
                addTag(javadoc, tags.size(), null, null);
                addTag(javadoc, tags.size(), TagElement.TAG_AUTHOR, getAuthor(project));
                String version = getPreferences().getValue(project, "org.eclim.java.doc.version");
                if (version != null && !StringUtils.EMPTY.equals(version.trim())) {
                    version = StringUtils.replace(version, "\\$", "$");
                    addTag(javadoc, tags.size(), TagElement.TAG_VERSION, version);
                }
            } else {
                // check if author tag exists.
                int index = -1;
                String author = getAuthor(project);
                for (int ii = 0; ii < tags.size(); ii++) {
                    TagElement tag = (TagElement) tags.get(ii);
                    if (TagElement.TAG_AUTHOR.equals(tag.getTagName())) {
                        String authorText = tag.fragments().size() > 0
                                ? ((TextElement) tag.fragments().get(0)).getText()
                                : null;
                        // check if author tag is the same.
                        if (authorText != null && author.trim().equals(authorText.trim())) {
                            index = -1;
                            break;
                        }
                        index = ii + 1;
                    } else if (tag.getTagName() != null) {
                        if (index == -1) {
                            index = ii;
                        }
                    }
                }

                // insert author tag if it doesn't exist.
                if (index > -1) {
                    TagElement authorTag = javadoc.getAST().newTagElement();
                    TextElement authorText = javadoc.getAST().newTextElement();
                    authorText.setText(author);
                    authorTag.setTagName(TagElement.TAG_AUTHOR);

                    @SuppressWarnings("unchecked")
                    List<ASTNode> fragments = authorTag.fragments();
                    fragments.add(authorText);
                    tags.add(index, authorTag);
                }

                // add the version tag if it doesn't exist.
                boolean versionExists = false;
                for (int ii = 0; ii < tags.size(); ii++) {
                    TagElement tag = (TagElement) tags.get(ii);
                    if (TagElement.TAG_VERSION.equals(tag.getTagName())) {
                        versionExists = true;
                        break;
                    }
                }
                if (!versionExists) {
                    String version = getPreferences().getValue(project, "org.eclim.java.doc.version");
                    version = StringUtils.replace(version, "\\$", "$");
                    addTag(javadoc, tags.size(), TagElement.TAG_VERSION, version);
                }
            }
        } else {
            commentOther(src, javadoc, element, isNew);
        }
    }

    /**
     * Comment a method declaration.
     *
     * @param src The source file.
     * @param javadoc The Javadoc.
     * @param element The IJavaElement.
     * @param isNew true if there was no previous javadoc for this element.
     */
    private void commentMethod(ICompilationUnit src, Javadoc javadoc, IJavaElement element, boolean isNew)
            throws Exception {
        @SuppressWarnings("unchecked")
        List<TagElement> tags = javadoc.tags();
        IMethod method = (IMethod) element;
        IType type = method.getDeclaringType();

        boolean hasOverride = false;
        IAnnotation[] annotations = method.getAnnotations();
        for (IAnnotation annotation : annotations) {
            for (String[] result : type.resolveType(annotation.getElementName())) {
                if (result[0].equals("java.lang") && result[1].equals("Override")) {
                    hasOverride = true;
                    break;
                }
            }
        }

        if (isNew) {
            // see if method is overriding / implementing method from superclass
            IType parentType = null;
            TypeInfo[] types = TypeUtils.getSuperTypes(type);
            for (TypeInfo info : types) {
                if (MethodUtils.containsMethod(info, method)) {
                    parentType = info.getType();
                    break;
                }
            }

            // if an inherited method, add inheritDoc and @see
            if (parentType != null) {
                if (!hasOverride) {
                    addTag(javadoc, tags.size(), null, INHERIT_DOC);

                    String typeName = JavaUtils.getCompilationUnitRelativeTypeName(src, parentType);

                    StringBuffer signature = new StringBuffer();
                    signature.append(typeName).append('#')
                            .append(MethodUtils.getMinimalMethodSignature(method, null));
                    addTag(javadoc, tags.size(), TagElement.TAG_SEE, signature.toString());
                }
            } else {
                addTag(javadoc, tags.size(), null, null);
                addTag(javadoc, tags.size(), null, null);
            }
        }

        // only add/update tags if javadoc doesn't contain inheritDoc.
        boolean update = true;
        for (TagElement tag : tags) {
            if (tag.getTagName() == null && tag.fragments().size() > 0) {
                if (INHERIT_DOC.equals(tag.fragments().get(0).toString())) {
                    update = false;
                    break;
                }
            }
        }

        if (update) {
            addUpdateParamTags(javadoc, method, isNew);
            addUpdateReturnTag(javadoc, method, isNew);
            addUpdateThrowsTags(javadoc, method, isNew);
        }
    }

    /**
     * Comment everything else.
     *
     * @param src The source file.
     * @param javadoc The Javadoc.
     * @param element The IJavaElement.
     * @param isNew true if there was no previous javadoc for this element.
     */
    private void commentOther(ICompilationUnit src, Javadoc javadoc, IJavaElement element, boolean isNew)
            throws Exception {
        if (isNew) {
            addTag(javadoc, 0, null, null);
        }
    }

    /**
     * Add or update the param tags for the given method.
     *
     * @param javadoc The Javadoc instance.
     * @param method The method.
     * @param isNew true if we're adding to brand new javadocs.
     */
    private void addUpdateParamTags(Javadoc javadoc, IMethod method, boolean isNew) throws Exception {
        @SuppressWarnings("unchecked")
        List<TagElement> tags = javadoc.tags();
        String[] params = method.getParameterNames();
        if (isNew) {
            for (int ii = 0; ii < params.length; ii++) {
                addTag(javadoc, tags.size(), TagElement.TAG_PARAM, params[ii]);
            }
        } else {
            // find current params.
            int index = 0;
            HashMap<String, TagElement> current = new HashMap<String, TagElement>();
            for (int ii = 0; ii < tags.size(); ii++) {
                TagElement tag = (TagElement) tags.get(ii);
                if (TagElement.TAG_PARAM.equals(tag.getTagName())) {
                    if (current.size() == 0) {
                        index = ii;
                    }
                    Object element = tag.fragments().size() > 0 ? tag.fragments().get(0) : null;
                    if (element != null && element instanceof Name) {
                        String name = ((Name) element).getFullyQualifiedName();
                        current.put(name, tag);
                    } else {
                        current.put(String.valueOf(ii), tag);
                    }
                } else {
                    if (current.size() > 0) {
                        break;
                    }
                    if (tag.getTagName() == null) {
                        index = ii + 1;
                    }
                }
            }

            if (current.size() > 0) {
                for (int ii = 0; ii < params.length; ii++) {
                    if (current.containsKey(params[ii])) {
                        TagElement tag = (TagElement) current.get(params[ii]);
                        int currentIndex = tags.indexOf(tag);
                        if (currentIndex != ii) {
                            tags.remove(tag);
                            tags.add(index + ii, tag);
                        }
                        current.remove(params[ii]);
                    } else {
                        addTag(javadoc, index + ii, TagElement.TAG_PARAM, params[ii]);
                    }
                }

                // remove any other param tags.
                for (TagElement tag : current.values()) {
                    tags.remove(tag);
                }
            } else {
                for (int ii = 0; ii < params.length; ii++) {
                    addTag(javadoc, index + ii, TagElement.TAG_PARAM, params[ii]);
                }
            }
        }
    }

    /**
     * Add or update the return tag for the given method.
     *
     * @param javadoc The Javadoc instance.
     * @param method The method.
     * @param isNew true if we're adding to brand new javadocs.
     */
    private void addUpdateReturnTag(Javadoc javadoc, IMethod method, boolean isNew) throws Exception {
        @SuppressWarnings("unchecked")
        List<TagElement> tags = javadoc.tags();
        // get return type from element.
        if (!method.isConstructor()) {
            String returnType = Signature.getSignatureSimpleName(method.getReturnType());
            if (!"void".equals(returnType)) {
                if (isNew) {
                    addTag(javadoc, tags.size(), TagElement.TAG_RETURN, null);
                } else {
                    // search starting from the bottom since @return should be near the
                    // end.
                    int index = tags.size();
                    for (int ii = tags.size() - 1; ii >= 0; ii--) {
                        TagElement tag = (TagElement) tags.get(ii);
                        // return tag already exists?
                        if (TagElement.TAG_RETURN.equals(tag.getTagName())) {
                            index = -1;
                            break;
                        }
                        // if we hit the param tags, or the main text, insert below them.
                        if (TagElement.TAG_PARAM.equals(tag.getTagName()) || tag.getTagName() == null) {
                            index = ii + 1;
                            break;
                        }
                        index = ii;
                    }
                    if (index > -1) {
                        addTag(javadoc, index, TagElement.TAG_RETURN, null);
                    }
                }
            } else {
                // remove any return tag that may exist.
                for (int ii = tags.size() - 1; ii >= 0; ii--) {
                    TagElement tag = (TagElement) tags.get(ii);
                    // return tag already exists?
                    if (TagElement.TAG_RETURN.equals(tag.getTagName())) {
                        tags.remove(tag);
                    }
                    // if we hit the param tags, or the main text we can stop.
                    if (TagElement.TAG_PARAM.equals(tag.getTagName()) || tag.getTagName() == null) {
                        break;
                    }
                }
            }
        }
    }

    /**
     * Add or update the throws tags for the given method.
     *
     * @param javadoc The Javadoc instance.
     * @param method The method.
     * @param isNew true if we're adding to brand new javadocs.
     */
    private void addUpdateThrowsTags(Javadoc javadoc, IMethod method, boolean isNew) throws Exception {
        @SuppressWarnings("unchecked")
        List<TagElement> tags = javadoc.tags();

        // get thrown exceptions from element.
        String[] exceptions = method.getExceptionTypes();
        if (isNew && exceptions.length > 0) {
            addTag(javadoc, tags.size(), null, null);
            for (int ii = 0; ii < exceptions.length; ii++) {
                addTag(javadoc, tags.size(), TagElement.TAG_THROWS,
                        Signature.getSignatureSimpleName(exceptions[ii]));
            }
        } else {
            // get current throws tags
            HashMap<String, TagElement> current = new HashMap<String, TagElement>();
            int index = tags.size();
            for (int ii = tags.size() - 1; ii >= 0; ii--) {
                TagElement tag = (TagElement) tags.get(ii);
                if (TagElement.TAG_THROWS.equals(tag.getTagName())) {
                    index = index == tags.size() ? ii + 1 : index;
                    Name name = tag.fragments().size() > 0 ? (Name) tag.fragments().get(0) : null;
                    if (name != null) {
                        String text = name.getFullyQualifiedName();
                        String key = THROWS_PATTERN.matcher(text).replaceFirst("$1");
                        current.put(key, tag);
                    } else {
                        current.put(String.valueOf(ii), tag);
                    }
                }
                // if we hit the return tag, a param tag, or the main text we can stop.
                if (TagElement.TAG_PARAM.equals(tag.getTagName()) || TagElement.TAG_RETURN.equals(tag.getTagName())
                        || tag.getTagName() == null) {
                    break;
                }
            }

            // see what needs to be added / removed.
            for (int ii = 0; ii < exceptions.length; ii++) {
                String name = Signature.getSignatureSimpleName(exceptions[ii]);
                if (!current.containsKey(name)) {
                    addTag(javadoc, index, TagElement.TAG_THROWS, name);
                } else {
                    current.remove(name);
                }
            }

            // remove any left over thows clauses.
            for (TagElement tag : current.values()) {
                tags.remove(tag);
            }
        }
    }

    /**
     * Gets the author string.
     *
     * @return The author.
     */
    private String getAuthor(IProject project) throws Exception {
        String username = getPreferences().getValue(project.getProject(), Preferences.USERNAME_PREFERENCE);
        String email = getPreferences().getValue(project.getProject(), Preferences.USEREMAIL_PREFERENCE);

        // build the author string.
        StringBuffer author = new StringBuffer();
        if (username != null && username.trim().length() > 0) {
            author.append(username);
            if (email != null && email.trim().length() > 0) {
                author.append(" (").append(email).append(")");
            }
        } else if (email != null && email.trim().length() > 0) {
            author.append(email);
        }
        return author.toString();
    }

    /**
     * Adds a tag to the supplied list of tags.
     *
     * @param javadoc The Javadoc instance.
     * @param index The index to insert the new tag at.
     * @param name The tag name.
     * @param text The tag text.
     */
    private void addTag(Javadoc javadoc, int index, String name, String text) throws Exception {
        TagElement tag = javadoc.getAST().newTagElement();
        tag.setTagName(name);

        if (text != null) {
            TextElement textElement = javadoc.getAST().newTextElement();
            textElement.setText(text);

            @SuppressWarnings("unchecked")
            List<ASTNode> fragments = tag.fragments();
            fragments.add(textElement);
        }

        @SuppressWarnings("unchecked")
        List<TagElement> tags = javadoc.tags();
        tags.add(index, tag);
    }
}