| /* |
| * Copyright 2000-2014 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.openapi.vcs.checkin; |
| |
| import com.intellij.ide.todo.TodoFilter; |
| import com.intellij.ide.todo.TodoIndexPatternProvider; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.diff.ex.DiffFragment; |
| import com.intellij.openapi.diff.impl.ComparisonPolicy; |
| import com.intellij.openapi.diff.impl.fragments.LineFragment; |
| import com.intellij.openapi.diff.impl.highlighting.FragmentSide; |
| import com.intellij.openapi.diff.impl.processing.DiffCorrection; |
| import com.intellij.openapi.diff.impl.processing.DiffFragmentsProcessor; |
| import com.intellij.openapi.diff.impl.processing.DiffPolicy; |
| import com.intellij.openapi.diff.impl.string.DiffString; |
| import com.intellij.openapi.diff.impl.util.TextDiffTypeEnum; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Getter; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vcs.FilePath; |
| import com.intellij.openapi.vcs.VcsException; |
| import com.intellij.openapi.vcs.changes.Change; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiFileFactory; |
| import com.intellij.psi.PsiManager; |
| import com.intellij.psi.impl.search.LightIndexPatternSearch; |
| import com.intellij.psi.impl.search.TodoItemsCreator; |
| import com.intellij.psi.search.IndexPatternOccurrence; |
| import com.intellij.psi.search.PsiTodoSearchHelper; |
| import com.intellij.psi.search.TodoItem; |
| import com.intellij.psi.search.searches.IndexPatternSearch; |
| import com.intellij.util.PairConsumer; |
| import com.intellij.util.SmartList; |
| import com.intellij.util.containers.Convertor; |
| import com.intellij.util.diff.FilesTooBigForDiffException; |
| |
| import java.util.*; |
| |
| /** |
| * @author irengrig |
| * Date: 2/18/11 |
| * Time: 5:16 PM |
| */ |
| public class TodoCheckinHandlerWorker { |
| private final static Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.checkin.TodoCheckinHandler"); |
| |
| private final Collection<Change> changes; |
| private final TodoFilter myTodoFilter; |
| private final boolean myIncludePattern; |
| private final PsiManager myPsiManager; |
| private final PsiTodoSearchHelper mySearchHelper; |
| |
| private final List<TodoItem> myAddedOrEditedTodos; |
| private final List<TodoItem> myInChangedTodos; |
| private final List<Pair<FilePath, String>> mySkipped; |
| private PsiFile myPsiFile; |
| private List<TodoItem> myNewTodoItems; |
| private final MyEditedFileProcessor myEditedFileProcessor; |
| |
| |
| public TodoCheckinHandlerWorker(final Project project, final Collection<Change> changes, final TodoFilter todoFilter, |
| final boolean includePattern) { |
| this.changes = changes; |
| myTodoFilter = todoFilter; |
| myIncludePattern = includePattern; |
| myPsiManager = PsiManager.getInstance(project); |
| mySearchHelper = PsiTodoSearchHelper.SERVICE.getInstance(project); |
| myAddedOrEditedTodos = new ArrayList<TodoItem>(); |
| myInChangedTodos = new ArrayList<TodoItem>(); |
| mySkipped = new SmartList<Pair<FilePath,String>>(); |
| myEditedFileProcessor = new MyEditedFileProcessor(project, new Acceptor() { |
| @Override |
| public void skipped(Pair<FilePath, String> pair) { |
| mySkipped.add(pair); |
| } |
| |
| @Override |
| public void addedOrEdited(TodoItem todoItem) { |
| myAddedOrEditedTodos.add(todoItem); |
| } |
| |
| @Override |
| public void inChanged(TodoItem todoItem) { |
| myInChangedTodos.add(todoItem); |
| } |
| }, myTodoFilter); |
| } |
| |
| public void execute() { |
| for (Change change : changes) { |
| ProgressManager.checkCanceled(); |
| if (change.getAfterRevision() == null) continue; |
| VirtualFile afterFile = change.getAfterRevision().getFile().getVirtualFile(); |
| if (afterFile == null) { |
| afterFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(change.getAfterRevision().getFile().getIOFile()); |
| } |
| if (afterFile == null || afterFile.isDirectory() || afterFile.getFileType().isBinary()) continue; |
| myPsiFile = null; |
| |
| if (afterFile.isValid()) { |
| myPsiFile = myPsiManager.findFile(afterFile); |
| } |
| if (myPsiFile == null) { |
| mySkipped.add(Pair.create(change.getAfterRevision().getFile(), ourInvalidFile)); |
| continue; |
| } |
| |
| myNewTodoItems = new ArrayList<TodoItem>(Arrays.asList(mySearchHelper.findTodoItems(myPsiFile))); |
| applyFilterAndRemoveDuplicates(myNewTodoItems, myTodoFilter); |
| if (change.getBeforeRevision() == null) { |
| // take just all todos |
| if (myNewTodoItems.isEmpty()) continue; |
| myAddedOrEditedTodos.addAll(myNewTodoItems); |
| } |
| else { |
| myEditedFileProcessor.process(change, myNewTodoItems); |
| } |
| } |
| } |
| |
| private static void applyFilterAndRemoveDuplicates(final List<TodoItem> todoItems, final TodoFilter filter) { |
| TodoItem previous = null; |
| for (Iterator<TodoItem> iterator = todoItems.iterator(); iterator.hasNext(); ) { |
| final TodoItem next = iterator.next(); |
| if (filter != null && ! filter.contains(next.getPattern())) { |
| iterator.remove(); |
| continue; |
| } |
| if (previous != null && next.getTextRange().equals(previous.getTextRange())) { |
| iterator.remove(); |
| } else { |
| previous = next; |
| } |
| } |
| } |
| |
| private static class MyEditedFileProcessor { |
| //private String myFileText; |
| private String myBeforeContent; |
| private String myAfterContent; |
| private List<TodoItem> myOldItems; |
| private LineFragment myCurrentLineFragment; |
| private HashSet<String> myOldTodoTexts; |
| private PsiFile myBeforeFile; |
| private final PsiFileFactory myPsiFileFactory; |
| private FilePath myAfterFile; |
| private final Acceptor myAcceptor; |
| private final TodoFilter myTodoFilter; |
| |
| private MyEditedFileProcessor(final Project project, Acceptor acceptor, final TodoFilter todoFilter) { |
| myAcceptor = acceptor; |
| myTodoFilter = todoFilter; |
| myPsiFileFactory = PsiFileFactory.getInstance(project); |
| } |
| |
| public void process(final Change change, final List<TodoItem> newTodoItems) { |
| myBeforeFile = null; |
| //myFileText = null; |
| myOldItems = null; |
| myOldTodoTexts = null; |
| |
| myAfterFile = change.getAfterRevision().getFile(); |
| try { |
| myBeforeContent = change.getBeforeRevision().getContent(); |
| myAfterContent = change.getAfterRevision().getContent(); |
| if (myAfterContent == null) { |
| myAcceptor.skipped(Pair.create(myAfterFile, ourCannotLoadCurrentRevision)); |
| return; |
| } |
| if (myBeforeContent == null) { |
| myAcceptor.skipped(Pair.create(myAfterFile, ourCannotLoadPreviousRevision)); |
| return; |
| } |
| ArrayList<LineFragment> lineFragments = getLineFragments(myAfterFile.getPath(), myBeforeContent, myAfterContent); |
| for (Iterator<LineFragment> iterator = lineFragments.iterator(); iterator.hasNext(); ) { |
| ProgressManager.checkCanceled(); |
| final LineFragment next = iterator.next(); |
| final TextDiffTypeEnum type = next.getType(); |
| assert ! TextDiffTypeEnum.CONFLICT.equals(type); |
| if (type == null || TextDiffTypeEnum.DELETED.equals(type) || TextDiffTypeEnum.NONE.equals(type)) { |
| iterator.remove(); |
| } |
| } |
| final StepIntersection<TodoItem, LineFragment> intersection = |
| new StepIntersection<TodoItem, LineFragment>(TodoItemConvertor.getInstance(), LineFragmentConvertor.getInstance(), lineFragments, |
| new Getter<String>() { |
| @Override |
| public String get() { |
| return myAfterContent; |
| } |
| }); |
| |
| intersection.process(newTodoItems, new PairConsumer<TodoItem, LineFragment>() { |
| |
| @Override |
| public void consume(TodoItem todoItem, LineFragment lineFragment) { |
| ProgressManager.checkCanceled(); |
| if (myCurrentLineFragment == null || ! myCurrentLineFragment.getRange(FragmentSide.SIDE2).equals( |
| lineFragment.getRange(FragmentSide.SIDE2))) { |
| myCurrentLineFragment = lineFragment; |
| myOldTodoTexts = null; |
| } |
| final TextDiffTypeEnum type = lineFragment.getType(); |
| if (TextDiffTypeEnum.INSERT.equals(type)) { |
| myAcceptor.addedOrEdited(todoItem); |
| } else { |
| // change |
| checkEditedFragment(todoItem); |
| } |
| } |
| }); |
| } catch (VcsException e) { |
| LOG.info(e); |
| myAcceptor.skipped(Pair.create(myAfterFile, ourCannotLoadPreviousRevision)); |
| } |
| } |
| |
| private void checkEditedFragment(TodoItem newTodoItem) { |
| if (myBeforeFile == null) { |
| myBeforeFile = myPsiFileFactory.createFileFromText("old" + myAfterFile.getName(), myAfterFile.getFileType(), myBeforeContent); |
| } |
| if (myOldItems == null) { |
| final Collection<IndexPatternOccurrence> all = |
| LightIndexPatternSearch.SEARCH.createQuery(new IndexPatternSearch.SearchParameters(myBeforeFile, TodoIndexPatternProvider |
| .getInstance())).findAll(); |
| |
| final TodoItemsCreator todoItemsCreator = new TodoItemsCreator(); |
| myOldItems = new ArrayList<TodoItem>(); |
| if (all.isEmpty()) { |
| myAcceptor.addedOrEdited(newTodoItem); |
| return; |
| } |
| for (IndexPatternOccurrence occurrence : all) { |
| myOldItems.add(todoItemsCreator.createTodo(occurrence)); |
| } |
| applyFilterAndRemoveDuplicates(myOldItems, myTodoFilter); |
| } |
| if (myOldTodoTexts == null) { |
| final StepIntersection<LineFragment, TodoItem> intersection = new StepIntersection<LineFragment, TodoItem>( |
| LineFragmentConvertor.getInstance(), TodoItemConvertor.getInstance(), myOldItems, new Getter<String>() { |
| @Override |
| public String get() { |
| return myBeforeContent; |
| } |
| }); |
| myOldTodoTexts = new HashSet<String>(); |
| intersection.process(Collections.singletonList(myCurrentLineFragment), new PairConsumer<LineFragment, TodoItem>() { |
| @Override |
| public void consume(LineFragment lineFragment, TodoItem todoItem) { |
| myOldTodoTexts.add(getTodoText(todoItem, myBeforeContent)); |
| } |
| }); |
| } |
| final String text = getTodoText(newTodoItem, myAfterContent); |
| if (! myOldTodoTexts.contains(text)) { |
| myAcceptor.addedOrEdited(newTodoItem); |
| } else { |
| myAcceptor.inChanged(newTodoItem); |
| } |
| } |
| } |
| |
| interface Acceptor { |
| void skipped(Pair<FilePath, String> pair); |
| void addedOrEdited(final TodoItem todoItem); |
| void inChanged(final TodoItem todoItem); |
| } |
| |
| public List<TodoItem> getAddedOrEditedTodos() { |
| return myAddedOrEditedTodos; |
| } |
| |
| public List<TodoItem> getInChangedTodos() { |
| return myInChangedTodos; |
| } |
| |
| public List<Pair<FilePath, String>> getSkipped() { |
| return mySkipped; |
| } |
| |
| private static String getTodoText(TodoItem oldItem, final String content) { |
| final String fragment = content.substring(oldItem.getTextRange().getStartOffset(), oldItem.getTextRange().getEndOffset()); |
| return StringUtil.join(fragment.split("\\s"), " "); |
| } |
| |
| private static ArrayList<LineFragment> getLineFragments(final String fileName, String beforeContent, String afterContent) throws VcsException { |
| try { |
| DiffFragment[] woFormattingBlocks = |
| DiffPolicy.LINES_WO_FORMATTING.buildFragments(DiffString.create(beforeContent), DiffString.create(afterContent)); |
| DiffFragment[] step1lineFragments = |
| new DiffCorrection.TrueLineBlocks(ComparisonPolicy.IGNORE_SPACE).correctAndNormalize(woFormattingBlocks); |
| return new DiffFragmentsProcessor().process(step1lineFragments); |
| } catch (FilesTooBigForDiffException e) { |
| throw new VcsException("File " + fileName + " is too big and there are too many changes to build a diff", e); |
| } |
| } |
| |
| private final static String ourInvalidFile = "Invalid file (s)"; |
| private final static String ourCannotLoadPreviousRevision = "Can not load previous revision"; |
| private final static String ourCannotLoadCurrentRevision = "Can not load current revision"; |
| |
| private static class TodoItemConvertor implements Convertor<TodoItem, TextRange> { |
| private static final TodoItemConvertor ourInstance = new TodoItemConvertor(); |
| |
| public static TodoItemConvertor getInstance() { |
| return ourInstance; |
| } |
| |
| @Override |
| public TextRange convert(TodoItem o) { |
| final TextRange textRange = o.getTextRange(); |
| return new TextRange(textRange.getStartOffset(), textRange.getEndOffset() - 1); |
| } |
| } |
| |
| private static class LineFragmentConvertor implements Convertor<LineFragment, TextRange> { |
| private static final LineFragmentConvertor ourInstance = new LineFragmentConvertor(); |
| |
| public static LineFragmentConvertor getInstance() { |
| return ourInstance; |
| } |
| |
| @Override |
| public TextRange convert(LineFragment o) { |
| final TextRange textRange = o.getRange(FragmentSide.SIDE2); |
| return new TextRange(textRange.getStartOffset(), textRange.getEndOffset() - 1); |
| } |
| } |
| |
| public List<TodoItem> inOneList() { |
| final List<TodoItem> list = new ArrayList<TodoItem>(); |
| list.addAll(getAddedOrEditedTodos()); |
| list.addAll(getInChangedTodos()); |
| return list; |
| } |
| } |