blob: 84a325553bda00f8efea5e3ea864741afce3fa63 [file] [log] [blame]
/*
* 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);
}
}