blob: aaf47cfb0a7d5960423d34f1d1d62e83a5b72071 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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.intellij.psi.impl.source.codeStyle.javadoc;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.lang.ASTNode;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.LanguageLevelProjectExtension;
import com.intellij.openapi.util.text.LineTokenizer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author max
*/
public class CommentFormatter {
private static final Logger LOG = Logger.getInstance(
"#com.intellij.psi.impl.source.codeStyle.javadoc.CommentFormatter");
private final CodeStyleSettings mySettings;
private final JDParser myParser;
private final Project myProject;
public CommentFormatter(@NotNull Project project) {
mySettings = CodeStyleSettingsManager.getSettings(project);
myParser = new JDParser(mySettings, LanguageLevelProjectExtension.getInstance(project).getLanguageLevel());
myProject = project;
}
public CodeStyleSettings getSettings() {
return mySettings;
}
public JDParser getParser() {
return myParser;
}
public void processComment(@Nullable ASTNode element) {
if (!getSettings().ENABLE_JAVADOC_FORMATTING) return;
PsiElement psiElement = SourceTreeToPsiMap.treeElementToPsi(element);
processElementComment(psiElement);
}
private void processElementComment(@Nullable PsiElement psiElement) {
if (psiElement instanceof PsiClass) {
String newCommentText = formatClassComment((PsiClass)psiElement);
replaceDocComment(newCommentText, (PsiDocCommentOwner)psiElement);
}
else if (psiElement instanceof PsiMethod) {
String newCommentText = formatMethodComment((PsiMethod)psiElement);
replaceDocComment(newCommentText, (PsiDocCommentOwner)psiElement);
}
else if (psiElement instanceof PsiField) {
String newCommentText = formatFieldComment((PsiField)psiElement);
replaceDocComment(newCommentText, (PsiDocCommentOwner)psiElement);
}
else if (psiElement instanceof PsiDocComment) {
processElementComment(psiElement.getParent());
}
}
private void replaceDocComment(@Nullable String newCommentText, @NotNull final PsiDocCommentOwner psiDocCommentOwner) {
final PsiDocComment oldComment = psiDocCommentOwner.getDocComment();
if (newCommentText != null) newCommentText = stripSpaces(newCommentText);
if (newCommentText == null || oldComment == null || newCommentText.equals(oldComment.getText())) {
return;
}
try {
PsiComment newComment = JavaPsiFacade.getInstance(myProject).getElementFactory().createCommentFromText(
newCommentText, null);
final ASTNode oldNode = oldComment.getNode();
final ASTNode newNode = newComment.getNode();
assert oldNode != null && newNode != null;
final ASTNode parent = oldNode.getTreeParent();
parent.replaceChild(oldNode, newNode); //important to replace with tree operation to avoid resolve and repository update
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
private static String stripSpaces(String text) {
String[] lines = LineTokenizer.tokenize(text.toCharArray(), false);
StringBuilder buf = new StringBuilder(text.length());
for (int i = 0; i < lines.length; i++) {
if (i > 0) buf.append('\n');
buf.append(rTrim(lines[i]));
}
return buf.toString();
}
private static String rTrim(String text) {
int idx = text.length();
while (idx > 0) {
if (!Character.isWhitespace(text.charAt(idx-1))) break;
idx--;
}
return text.substring(0, idx);
}
@Nullable
private String formatClassComment(@NotNull PsiClass psiClass) {
final String info = getOrigCommentInfo(psiClass);
if (info == null) return null;
JDComment comment = getParser().parse(info, new JDClassComment(this));
return comment.generate(getIndent(psiClass));
}
@Nullable
private String formatMethodComment(@NotNull PsiMethod psiMethod) {
final String info = getOrigCommentInfo(psiMethod);
if (info == null) return null;
JDComment comment = getParser().parse(info, new JDMethodComment(this));
return comment.generate(getIndent(psiMethod));
}
@Nullable
private String formatFieldComment(@NotNull PsiField psiField) {
final String info = getOrigCommentInfo(psiField);
if (info == null) return null;
JDComment comment = getParser().parse(info, new JDComment(this));
return comment.generate(getIndent(psiField));
}
/**
* Returns the original comment info of the specified element or null
*
* @param element the specified element
* @return text chunk
*/
@Nullable
private static String getOrigCommentInfo(PsiDocCommentOwner element) {
StringBuilder sb = new StringBuilder();
PsiElement e = element.getFirstChild();
if (!(e instanceof PsiComment)) {
// no comments for this element
return null;
}
else {
boolean first = true;
while (true) {
if (e instanceof PsiDocComment) {
PsiComment cm = (PsiComment)e;
String text = cm.getText();
if (text.startsWith("//")) {
if (!first) sb.append('\n');
sb.append(text.substring(2).trim());
}
else if (text.startsWith("/*")) {
if (text.charAt(2) == '*') {
text = text.substring(3, Math.max(3, text.length() - 2));
}
else {
text = text.substring(2, Math.max(2, text.length() - 2));
}
sb.append(text);
}
}
else if (!(e instanceof PsiWhiteSpace) && !(e instanceof PsiComment)) {
break;
}
first = false;
e = e.getNextSibling();
}
return sb.toString();
}
}
/**
* Computes indentation of PsiClass, PsiMethod and PsiField elements after formatting
* @param element PsiClass or PsiMethod or PsiField
* @return indentation size
*/
private int getIndentSpecial(@NotNull PsiElement element) {
assert(element instanceof PsiClass ||
element instanceof PsiField ||
element instanceof PsiMethod);
int indentSize = mySettings.getIndentSize(JavaFileType.INSTANCE);
boolean doNotIndentTopLevelClassMembers = mySettings.getCommonSettings(JavaLanguage.INSTANCE).DO_NOT_INDENT_TOP_LEVEL_CLASS_MEMBERS;
int indent = 0;
PsiClass top = PsiUtil.getTopLevelClass(element);
while (top != null && !element.isEquivalentTo(top)) {
if (doNotIndentTopLevelClassMembers && element.getParent().isEquivalentTo(top)) {
break;
}
element = element.getParent();
indent += indentSize;
}
return indent;
}
/**
* Used while formatting javadocs. We need precise element indentation after formatting to wrap comments correctly.
* Used only for PsiClass, PsiMethod and PsiFields.
* @return indent which would be used for the given element when it's formatted according to the current code style settings
*/
@NotNull
private String getIndent(@NotNull PsiElement element) {
return StringUtil.repeatSymbol(' ', getIndentSpecial(element));
}
}