| /* |
| * Copyright 2000-2009 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.diff.impl.incrementalMerge; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.diff.impl.string.DiffString; |
| import com.intellij.openapi.diff.ex.DiffFragment; |
| import com.intellij.openapi.diff.impl.ComparisonPolicy; |
| import com.intellij.openapi.diff.impl.DiffUtil; |
| import com.intellij.openapi.diff.impl.highlighting.FragmentSide; |
| import com.intellij.openapi.diff.impl.splitter.LineBlocks; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.HashSet; |
| import com.intellij.util.diff.FilesTooBigForDiffException; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| public class ChangeList { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.incrementalMerge.ChangeList"); |
| public static final Comparator<Change> CHANGE_ORDER = new SimpleChange.ChangeOrder(FragmentSide.SIDE1); |
| |
| private final Project myProject; |
| private final Document[] myDocuments = new Document[2]; |
| private final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); |
| private ArrayList<Change> myChanges; |
| private ArrayList<Change> myAppliedChanges; |
| |
| public ChangeList(@NotNull Document base, @NotNull Document version, Project project) { |
| myDocuments[0] = base; |
| myDocuments[1] = version; |
| myProject = project; |
| } |
| |
| public void addListener(Listener listener) { |
| myListeners.add(listener); |
| } |
| |
| public void removeListener(Listener listener) { |
| LOG.assertTrue(myListeners.remove(listener)); |
| } |
| |
| public void setChanges(@NotNull ArrayList<Change> changes) { |
| if (myChanges != null) { |
| HashSet<Change> newChanges = new HashSet<Change>(changes); |
| LOG.assertTrue(newChanges.size() == changes.size()); |
| for (Iterator<Change> iterator = myChanges.iterator(); iterator.hasNext();) { |
| Change oldChange = iterator.next(); |
| if (!newChanges.contains(oldChange)) { |
| iterator.remove(); |
| oldChange.onRemovedFromList(); |
| } |
| } |
| } |
| for (Change change : changes) { |
| LOG.assertTrue(change.isValid()); |
| } |
| myChanges = new ArrayList<Change>(changes); |
| myAppliedChanges = new ArrayList<Change>(); |
| } |
| |
| public Project getProject() { return myProject; } |
| |
| @NotNull |
| public List<Change> getChanges() { |
| return new ArrayList<Change>(myChanges); |
| } |
| |
| public static ChangeList build(Document base, Document version, Project project) throws FilesTooBigForDiffException { |
| ChangeList result = new ChangeList(base, version, project); |
| ArrayList<Change> changes = result.buildChanges(); |
| Collections.sort(changes, CHANGE_ORDER); |
| result.setChanges(changes); |
| return result; |
| } |
| |
| public void setMarkup(final Editor base, final Editor version) { |
| Editor[] editors = {base, version}; |
| for (Change change : myChanges) { |
| change.addMarkup(editors); |
| } |
| } |
| |
| public void updateMarkup() { |
| for (Change change : myChanges) { |
| change.updateMarkup(); |
| } |
| } |
| |
| @NotNull |
| public Document getDocument(@NotNull FragmentSide side) { |
| return myDocuments[side.getIndex()]; |
| } |
| |
| private ArrayList<Change> buildChanges() throws FilesTooBigForDiffException { |
| Document base = getDocument(FragmentSide.SIDE1); |
| DiffString[] baseLines = DiffUtil.convertToLines(base.getText()); |
| Document version = getDocument(FragmentSide.SIDE2); |
| DiffString[] versionLines = DiffUtil.convertToLines(version.getText()); |
| DiffFragment[] fragments = ComparisonPolicy.DEFAULT.buildDiffFragmentsFromLines(baseLines, versionLines); |
| final ArrayList<Change> result = new ArrayList<Change>(); |
| new DiffFragmentsEnumerator(fragments) { |
| protected void process(DiffFragment fragment) { |
| if (fragment.isEqual()) return; |
| Context context = getContext(); |
| TextRange range1 = context.createRange(FragmentSide.SIDE1); |
| TextRange range2 = context.createRange(FragmentSide.SIDE2); |
| result.add(new SimpleChange(ChangeType.fromDiffFragment(context.getFragment()), range1, range2, ChangeList.this)); |
| } |
| }.execute(); |
| return result; |
| } |
| |
| public Change getChange(int index) { |
| return myChanges.get(index); |
| } |
| |
| private abstract static class DiffFragmentsEnumerator { |
| @NotNull private final DiffFragment[] myFragments; |
| @NotNull private final Context myContext; |
| |
| private DiffFragmentsEnumerator(@NotNull DiffFragment[] fragments) { |
| myContext = new Context(); |
| myFragments = fragments; |
| } |
| |
| public void execute() { |
| for (DiffFragment fragment : myFragments) { |
| myContext.myFragment = fragment; |
| process(fragment); |
| DiffString text1 = fragment.getText1(); |
| DiffString text2 = fragment.getText2(); |
| myContext.myStarts[0] += StringUtil.length(text1); |
| myContext.myStarts[1] += StringUtil.length(text2); |
| myContext.myLines[0] += countLines(text1); |
| myContext.myLines[1] += countLines(text2); |
| } |
| } |
| |
| private static int countLines(@Nullable DiffString text) { |
| if (text == null) return 0; |
| return StringUtil.countNewLines(text); |
| } |
| |
| @NotNull |
| protected Context getContext() { |
| return myContext; |
| } |
| |
| protected abstract void process(DiffFragment fragment); |
| } |
| |
| public static class Context { |
| private DiffFragment myFragment; |
| private final int[] myStarts = {0, 0}; |
| private final int[] myLines = {0, 0}; |
| |
| public DiffFragment getFragment() { |
| return myFragment; |
| } |
| |
| public int getStart(@NotNull FragmentSide side) { |
| return myStarts[side.getIndex()]; |
| } |
| |
| public int getEnd(@NotNull FragmentSide side) { |
| return getStart(side) + StringUtil.length(side.getText(myFragment)); |
| } |
| |
| @NotNull |
| public TextRange createRange(@NotNull FragmentSide side) { |
| return new TextRange(getStart(side), getEnd(side)); |
| } |
| } |
| |
| public int getCount() { |
| return myChanges.size(); |
| } |
| |
| public LineBlocks getLineBlocks() { |
| ArrayList<Change> changes = new ArrayList<Change>(myChanges); |
| changes.addAll(myAppliedChanges); |
| return LineBlocks.fromChanges(changes); |
| } |
| |
| public void remove(@NotNull Change change) { |
| if (change.getType().isApplied()) { |
| LOG.assertTrue(myAppliedChanges.remove(change), change); |
| } |
| else { |
| LOG.assertTrue(myChanges.remove(change), change); |
| } |
| change.onRemovedFromList(); |
| fireOnChangeRemoved(); |
| } |
| |
| public void apply(@NotNull Change change) { |
| LOG.assertTrue(myChanges.remove(change), change); |
| myAppliedChanges.add(change); |
| fireOnChangeApplied(); |
| } |
| |
| private void fireOnChangeRemoved() { |
| for (Listener listener : myListeners) { |
| listener.onChangeRemoved(this); |
| } |
| } |
| |
| void fireOnChangeApplied() { |
| for (Listener listener : myListeners) { |
| listener.onChangeApplied(this); |
| } |
| } |
| |
| public interface Listener { |
| void onChangeRemoved(ChangeList source); |
| void onChangeApplied(ChangeList source); |
| } |
| } |