Java tutorial
/** * 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); } }