| /* |
| * 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.maddyhome.idea.copyright.psi; |
| |
| import com.intellij.lang.Commenter; |
| import com.intellij.lang.LanguageCommenters; |
| import com.intellij.openapi.command.WriteCommandAction; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.fileTypes.FileType; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.*; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.util.IncorrectOperationException; |
| import com.maddyhome.idea.copyright.CopyrightManager; |
| import com.maddyhome.idea.copyright.CopyrightProfile; |
| import com.maddyhome.idea.copyright.options.LanguageOptions; |
| import com.maddyhome.idea.copyright.util.FileTypeUtil; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.util.ArrayList; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.TreeSet; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import java.util.regex.PatternSyntaxException; |
| |
| public abstract class UpdatePsiFileCopyright extends AbstractUpdateCopyright { |
| private final CopyrightProfile myOptions; |
| |
| protected UpdatePsiFileCopyright(Project project, Module module, VirtualFile root, CopyrightProfile options) { |
| super(project, module, root, options); |
| myOptions = options; |
| |
| PsiManager manager = PsiManager.getInstance(project); |
| file = manager.findFile(root); |
| FileType type = FileTypeUtil.getInstance().getFileTypeByFile(root); |
| langOpts = CopyrightManager.getInstance(project).getOptions().getMergedOptions(type.getName()); |
| } |
| |
| @Override |
| public void prepare() { |
| if (file == null) { |
| logger.info("No file for root: " + getRoot()); |
| return; |
| } |
| |
| if (accept()) { |
| scanFile(); |
| } |
| } |
| |
| @Override |
| public void complete() throws Exception { |
| complete(true); |
| } |
| |
| public void complete(boolean allowReplacement) throws Exception { |
| if (file == null) { |
| logger.info("No file for root: " + getRoot()); |
| return; |
| } |
| |
| if (accept()) { |
| processActions(allowReplacement); |
| } |
| } |
| |
| protected boolean accept() { |
| return !(file instanceof PsiPlainTextFile); |
| } |
| |
| protected abstract void scanFile(); |
| |
| protected void checkComments(PsiElement first, PsiElement last, boolean commentHere) { |
| List<PsiComment> comments = new ArrayList<PsiComment>(); |
| collectComments(first, last, comments); |
| checkComments(last, commentHere, comments); |
| } |
| |
| protected void collectComments(PsiElement first, PsiElement last, List<PsiComment> comments) { |
| if (first == last && first instanceof PsiComment) { |
| comments.add((PsiComment)first); |
| return; |
| } |
| PsiElement elem = first; |
| while (elem != last && elem != null) { |
| if (elem instanceof PsiComment) { |
| comments.add((PsiComment)elem); |
| logger.debug("found comment"); |
| } |
| |
| elem = getNextSibling(elem); |
| } |
| } |
| |
| protected void checkComments(PsiElement last, boolean commentHere, List<PsiComment> comments) { |
| try { |
| final String keyword = myOptions.getKeyword(); |
| final LinkedHashSet<CommentRange> found = new LinkedHashSet<CommentRange>(); |
| Document doc = null; |
| if (!StringUtil.isEmpty(keyword)) { |
| Pattern pattern = Pattern.compile(keyword, Pattern.CASE_INSENSITIVE); |
| doc = FileDocumentManager.getInstance().getDocument(getFile().getVirtualFile()); |
| for (int i = 0; i < comments.size(); i++) { |
| PsiComment comment = comments.get(i); |
| String text = comment.getText(); |
| Matcher match = pattern.matcher(text); |
| if (match.find()) { |
| found.add(getLineCopyrightComments(comments, doc, i, comment)); |
| } |
| } |
| } |
| |
| // Default insertion point to just before user chosen marker (package, import, class) |
| PsiElement point = last; |
| if (commentHere && !comments.isEmpty() && langOpts.isRelativeBefore()) { |
| // Insert before first comment within this section of code. |
| point = comments.get(0); |
| } |
| |
| if (commentHere && found.size() == 1) { |
| CommentRange range = found.iterator().next(); |
| // Is the comment in the right place? |
| if (langOpts.isRelativeBefore() && range.getFirst() == comments.get(0) || |
| !langOpts.isRelativeBefore() && range.getLast() == comments.get(comments.size() - 1)) { |
| // Check to see if current copyright comment matches new one. |
| String newComment = getCommentText("", ""); |
| resetCommentText(); |
| String oldComment = doc.getCharsSequence() |
| .subSequence(range.getFirst().getTextRange().getStartOffset(), range.getLast().getTextRange().getEndOffset()).toString().trim(); |
| if (!StringUtil.isEmptyOrSpaces(myOptions.getAllowReplaceKeyword()) && |
| !oldComment.contains(myOptions.allowReplaceKeyword)) { |
| return; |
| } |
| if (newComment.trim().equals(oldComment)) { |
| if (!getLanguageOptions().isAddBlankAfter()) { |
| // TODO - do we need option to remove blank line after? |
| return; // Nothing to do since the comment is the same |
| } |
| PsiElement next = getNextSibling(range.getLast()); |
| if (next instanceof PsiWhiteSpace && countNewline(next.getText()) > 1) { |
| return; |
| } |
| point = range.getFirst(); |
| } |
| else if (!newComment.isEmpty()) { |
| int start = range.getFirst().getTextRange().getStartOffset(); |
| int end = range.getLast().getTextRange().getEndOffset(); |
| addAction(new CommentAction(CommentAction.ACTION_REPLACE, start, end)); |
| |
| return; |
| } |
| } |
| } |
| |
| for (CommentRange range : found) { |
| // Remove the old copyright |
| int start = range.getFirst().getTextRange().getStartOffset(); |
| int end = range.getLast().getTextRange().getEndOffset(); |
| // If this is the only comment then remove the whitespace after unless there is none before |
| if (range.getFirst() == comments.get(0) && range.getLast() == comments.get(comments.size() - 1)) { |
| int startLen = 0; |
| if (getPreviousSibling(range.getFirst()) instanceof PsiWhiteSpace) { |
| startLen = countNewline(getPreviousSibling(range.getFirst()).getText()); |
| } |
| int endLen = 0; |
| if (getNextSibling(range.getLast()) instanceof PsiWhiteSpace) { |
| endLen = countNewline(getNextSibling(range.getLast()).getText()); |
| } |
| if (startLen == 1 && getPreviousSibling(range.getFirst()).getTextRange().getStartOffset() > 0) { |
| start = getPreviousSibling(range.getFirst()).getTextRange().getStartOffset(); |
| } |
| else if (endLen > 0) { |
| end = getNextSibling(range.getLast()).getTextRange().getEndOffset(); |
| } |
| } |
| // If this is the last comment then remove the whitespace before the comment |
| else if (range.getLast() == comments.get(comments.size() - 1)) { |
| if (getPreviousSibling(range.getFirst()) instanceof PsiWhiteSpace && |
| countNewline(getPreviousSibling(range.getFirst()).getText()) > 1) { |
| start = getPreviousSibling(range.getFirst()).getTextRange().getStartOffset(); |
| } |
| } |
| // If this is the first or middle comment then remove the whitespace after the comment |
| else if (getNextSibling(range.getLast()) instanceof PsiWhiteSpace) { |
| end = getNextSibling(range.getLast()).getTextRange().getEndOffset(); |
| } |
| |
| addAction(new CommentAction(CommentAction.ACTION_DELETE, start, end)); |
| } |
| |
| // Finally add the comment if user chose this section. |
| if (commentHere) { |
| String suffix = "\n"; |
| if (point != last && getPreviousSibling(point) != null && getPreviousSibling(point) instanceof PsiWhiteSpace) { |
| suffix = getPreviousSibling(point).getText(); |
| if (countNewline(suffix) == 1) { |
| suffix = '\n' + suffix; |
| } |
| } |
| if (point != last && getPreviousSibling(point) == null) { |
| suffix = "\n\n"; |
| } |
| if (getLanguageOptions().isAddBlankAfter() && countNewline(suffix) == 1) { |
| suffix += "\n"; |
| } |
| String prefix = ""; |
| if (getPreviousSibling(point) != null) { |
| if (getPreviousSibling(point) instanceof PsiComment) { |
| prefix = "\n\n"; |
| } |
| if (getPreviousSibling(point) instanceof PsiWhiteSpace && |
| getPreviousSibling(getPreviousSibling(point)) != null && |
| getPreviousSibling(getPreviousSibling(point)) instanceof PsiComment) { |
| String ws = getPreviousSibling(point).getText(); |
| int cnt = countNewline(ws); |
| if (cnt == 1) { |
| prefix = "\n"; |
| } |
| } |
| } |
| |
| int pos = 0; |
| if (point != null) { |
| final TextRange textRange = point.getTextRange(); |
| if (textRange != null) { |
| pos = textRange.getStartOffset(); |
| } |
| } |
| addAction(new CommentAction(pos, prefix, suffix)); |
| } |
| } |
| catch (PatternSyntaxException ignore) { |
| } |
| catch (Exception e) { |
| logger.error(e); |
| } |
| } |
| |
| private static CommentRange getLineCopyrightComments(List<PsiComment> comments, Document doc, int i, PsiComment comment) { |
| PsiElement firstComment = comment; |
| PsiElement lastComment = comment; |
| final Commenter commenter = LanguageCommenters.INSTANCE.forLanguage(PsiUtilCore.findLanguageFromElement(comment)); |
| if (isLineComment(commenter, comment, doc)) { |
| int sline = doc.getLineNumber(comment.getTextRange().getStartOffset()); |
| int eline = doc.getLineNumber(comment.getTextRange().getEndOffset()); |
| for (int j = i - 1; j >= 0; j--) { |
| PsiComment cmt = comments.get(j); |
| |
| if (isLineComment(commenter, cmt, doc) && doc.getLineNumber(cmt.getTextRange().getEndOffset()) == sline - 1) { |
| firstComment = cmt; |
| sline = doc.getLineNumber(cmt.getTextRange().getStartOffset()); |
| } |
| else { |
| break; |
| } |
| } |
| for (int j = i + 1; j < comments.size(); j++) { |
| PsiComment cmt = comments.get(j); |
| if (isLineComment(commenter, cmt, doc) && doc.getLineNumber(cmt.getTextRange().getStartOffset()) == eline + 1) { |
| lastComment = cmt; |
| eline = doc.getLineNumber(cmt.getTextRange().getEndOffset()); |
| } |
| else { |
| break; |
| } |
| } |
| } |
| return new CommentRange(firstComment, lastComment); |
| } |
| |
| private static boolean isLineComment(Commenter commenter, PsiComment comment, Document doc) { |
| final String lineCommentPrefix = commenter.getLineCommentPrefix(); |
| if (lineCommentPrefix != null) { |
| return comment.getText().startsWith(lineCommentPrefix); |
| } |
| final TextRange textRange = comment.getTextRange(); |
| return doc.getLineNumber(textRange.getStartOffset()) == doc.getLineNumber(textRange.getEndOffset()); |
| } |
| |
| protected PsiFile getFile() { |
| return file; |
| } |
| |
| protected LanguageOptions getLanguageOptions() { |
| return langOpts; |
| } |
| |
| protected void addAction(CommentAction action) { |
| actions.add(action); |
| } |
| |
| protected PsiElement getPreviousSibling(PsiElement element) { |
| return element == null ? null : element.getPrevSibling(); |
| } |
| |
| protected PsiElement getNextSibling(PsiElement element) { |
| return element == null ? null : element.getNextSibling(); |
| } |
| |
| protected void processActions(final boolean allowReplacement) throws IncorrectOperationException { |
| new WriteCommandAction.Simple(file.getProject(), "Update copyright") { |
| @Override |
| protected void run() throws Throwable { |
| Document doc = FileDocumentManager.getInstance().getDocument(getRoot()); |
| if (doc != null) { |
| PsiDocumentManager.getInstance(file.getProject()).doPostponedOperationsAndUnblockDocument(doc); |
| for (CommentAction action : actions) { |
| int start = action.getStart(); |
| int end = action.getEnd(); |
| |
| switch (action.getType()) { |
| case CommentAction.ACTION_INSERT: |
| String comment = getCommentText(action.getPrefix(), action.getSuffix()); |
| if (!comment.isEmpty()) { |
| doc.insertString(start, comment); |
| } |
| break; |
| case CommentAction.ACTION_REPLACE: |
| if (allowReplacement) doc.replaceString(start, end, getCommentText("", "")); |
| break; |
| case CommentAction.ACTION_DELETE: |
| if (allowReplacement) doc.deleteString(start, end); |
| break; |
| } |
| } |
| PsiDocumentManager.getInstance(getProject()).commitDocument(doc); |
| } |
| } |
| }.execute(); |
| } |
| |
| private static class CommentRange { |
| public CommentRange(PsiElement first, PsiElement last) { |
| this.first = first; |
| this.last = last; |
| } |
| |
| public PsiElement getFirst() { |
| return first; |
| } |
| |
| public PsiElement getLast() { |
| return last; |
| } |
| |
| private final PsiElement first; |
| private final PsiElement last; |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| CommentRange that = (CommentRange)o; |
| |
| if (first != null ? !first.equals(that.first) : that.first != null) return false; |
| if (last != null ? !last.equals(that.last) : that.last != null) return false; |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = first != null ? first.hashCode() : 0; |
| result = 31 * result + (last != null ? last.hashCode() : 0); |
| return result; |
| } |
| } |
| |
| protected static class CommentAction implements Comparable<CommentAction> { |
| public static final int ACTION_INSERT = 1; |
| public static final int ACTION_REPLACE = 2; |
| public static final int ACTION_DELETE = 3; |
| |
| public CommentAction(int pos, String prefix, String suffix) { |
| type = ACTION_INSERT; |
| start = pos; |
| end = pos; |
| this.prefix = prefix; |
| this.suffix = suffix; |
| } |
| |
| public CommentAction(int type, int start, int end) { |
| this.type = type; |
| this.start = start; |
| this.end = end; |
| } |
| |
| public int getType() { |
| return type; |
| } |
| |
| public int getStart() { |
| return start; |
| } |
| |
| public int getEnd() { |
| return end; |
| } |
| |
| public String getPrefix() { |
| return prefix; |
| } |
| |
| public String getSuffix() { |
| return suffix; |
| } |
| |
| @Override |
| public int compareTo(@NotNull CommentAction object) { |
| int s = object.getStart(); |
| int diff = s - start; |
| if (diff == 0) { |
| diff = type == ACTION_INSERT ? 1 : -1; |
| } |
| |
| return diff; |
| } |
| |
| private final int type; |
| private final int start; |
| private final int end; |
| private String prefix = null; |
| private String suffix = null; |
| } |
| |
| private final PsiFile file; |
| private final LanguageOptions langOpts; |
| private final TreeSet<CommentAction> actions = new TreeSet<CommentAction>(); |
| |
| private static final Logger logger = Logger.getInstance(UpdatePsiFileCopyright.class.getName()); |
| } |