/* * 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.openapi.diff.impl; import com.intellij.CommonBundle; import com.intellij.openapi.diff.impl.string.DiffString; import com.intellij.openapi.diff.ex.DiffFragment; import com.intellij.openapi.diff.impl.highlighting.FragmentSide; import com.intellij.openapi.diff.impl.highlighting.Util; import com.intellij.openapi.diff.impl.processing.DiffCorrection; import com.intellij.openapi.diff.impl.processing.Formatting; import com.intellij.openapi.diff.impl.processing.Word; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.TextRange; import com.intellij.util.diff.Diff; import com.intellij.util.diff.FilesTooBigForDiffException; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.jetbrains.annotations.TestOnly; public abstract class ComparisonPolicy { public static final ComparisonPolicy DEFAULT = new DefaultPolicy(); public static final ComparisonPolicy TRIM_SPACE = new TrimSpacePolicy(); public static final ComparisonPolicy IGNORE_SPACE = new IgnoreSpacePolicy(); public static final ComparisonPolicy[] COMPARISON_POLICIES = new ComparisonPolicy[]{DEFAULT, IGNORE_SPACE, TRIM_SPACE}; private final String myName; protected ComparisonPolicy(final String name) { myName = name; } public String getName() { return myName; } @Nonnull public DiffFragment[] buildFragments(@Nonnull DiffString[] strings1, @Nonnull DiffString[] strings2) throws FilesTooBigForDiffException { DiffFragmentBuilder builder = new DiffFragmentBuilder(strings1, strings2); Object[] wrappers1 = getWrappers(strings1); Object[] wrappers2 = getWrappers(strings2); Diff.Change change = Diff.buildChanges(wrappers1, wrappers2); return builder.buildFragments(Util.concatEquals(change, wrappers1, wrappers2)); } @Nonnull public DiffFragment[] buildDiffFragmentsFromLines(@Nonnull DiffString[] lines1, @Nonnull DiffString[] lines2) throws FilesTooBigForDiffException { DiffFragmentBuilder builder = new DiffFragmentBuilder(lines1, lines2); Object[] wrappers1 = getLineWrappers(lines1); Object[] wrappers2 = getLineWrappers(lines2); Diff.Change change = Diff.buildChanges(wrappers1, wrappers2); return builder.buildFragments(change); } @Nonnull public DiffFragment createFragment(@Nullable DiffString text1, @Nullable DiffString text2) { text1 = toNull(text1); text2 = toNull(text2); if (text1 == null && text2 == null) return new DiffFragment(DiffString.EMPTY, DiffString.EMPTY); DiffFragment result = new DiffFragment(text1, text2); if (text1 != null && text2 != null) { result.setModified(!getWrapper(text1).equals(getWrapper(text2))); } return result; } @Nonnull public abstract DiffFragment createFragment(@Nonnull Word word1, @Nonnull Word word2); @Nonnull protected abstract Object[] getWrappers(@Nonnull DiffString[] strings); @Nonnull protected abstract Object[] getLineWrappers(@Nonnull DiffString[] lines); @Nonnull private Object getWrapper(@Nonnull DiffString text) { return getWrappers(new DiffString[]{text})[0]; } private static class DefaultPolicy extends ComparisonPolicy { public DefaultPolicy() { super(CommonBundle.message("comparison.policy.default.name")); } @Nonnull @Override protected Object[] getWrappers(@Nonnull DiffString[] strings) { return strings; } @Nonnull @Override protected Object[] getLineWrappers(@Nonnull DiffString[] lines) { return lines; } @Nonnull @Override public DiffFragment createFragment(@Nonnull Word word1, @Nonnull Word word2) { return createFragment(word1.getText(), word2.getText()); } @SuppressWarnings({"HardCodedStringLiteral"}) public String toString() { return "DEFAULT"; } } private static class TrimSpacePolicy extends ComparisonPolicy { public TrimSpacePolicy() { super(CommonBundle.message("comparison.policy.trim.space.name")); } @Nonnull @Override protected Object[] getLineWrappers(@Nonnull DiffString[] lines) { return trimStrings(lines); } @Nonnull @Override public DiffFragment createFragment(@Nonnull Word word1, @Nonnull Word word2) { DiffString text1 = word1.getText(); DiffString text2 = word2.getText(); if (word1.isWhitespace() && word2.isWhitespace() && word1.atEndOfLine() && word2.atEndOfLine()) { return DiffFragment.unchanged(text1, text2); } return createFragment(text1, text2); } @Nonnull @Override protected Object[] getWrappers(@Nonnull DiffString[] strings) { Object[] result = new Object[strings.length]; boolean atBeginning = true; for (int i = 0; i < strings.length; i++) { DiffString string = strings[i]; DiffString wrapper = atBeginning ? string.trimLeading() : string; if (wrapper.endsWith('\n')) { atBeginning = true; wrapper = wrapper.trimTrailing(); } else { atBeginning = false; } result[i] = wrapper; } return result; } @SuppressWarnings({"HardCodedStringLiteral"}) public String toString() { return "TRIM"; } } private static class IgnoreSpacePolicy extends ComparisonPolicy implements DiffCorrection.FragmentProcessor<DiffCorrection.FragmentsCollector> { public IgnoreSpacePolicy() { super(CommonBundle.message("comparison.policy.ignore.spaces.name")); } @Nonnull @Override protected Object[] getLineWrappers(@Nonnull DiffString[] lines) { Object[] result = new Object[lines.length]; for (int i = 0; i < lines.length; i++) { DiffString line = lines[i]; result[i] = getWrapper(line); } return result; } @Nonnull @Override public DiffFragment[] buildFragments(@Nonnull DiffString[] strings1, @Nonnull DiffString[] strings2) throws FilesTooBigForDiffException { DiffFragment[] fragments = super.buildFragments(strings1, strings2); DiffCorrection.FragmentsCollector collector = new DiffCorrection.FragmentsCollector(); collector.processAll(fragments, this); return collector.toArray(); } @Nonnull private static Object getWrapper(@Nonnull DiffString line) { return line.skipSpaces(); } @Nonnull @Override public DiffFragment createFragment(@Nonnull Word word1, @Nonnull Word word2) { DiffString text1 = word1.getText(); DiffString text2 = word2.getText(); return word1.isWhitespace() && word2.isWhitespace() ? DiffFragment.unchanged(text1, text2) : createFragment(text1, text2); } @Nonnull @Override public DiffFragment createFragment(DiffString text1, DiffString text2) { DiffString toCompare1 = toNotNull(text1); DiffString toCompare2 = toNotNull(text2); if (getWrapper(toCompare1).equals(getWrapper(toCompare2))) { return DiffFragment.unchanged(toCompare1, toCompare2); } return new DiffFragment(text1, text2); } @Nonnull @Override protected Object[] getWrappers(@Nonnull DiffString[] strings) { return trimStrings(strings); } @SuppressWarnings({"HardCodedStringLiteral"}) public String toString() { return "IGNORE"; } @Override public void process(@Nonnull DiffFragment fragment, @Nonnull DiffCorrection.FragmentsCollector collector) { if (fragment.isEqual()) { collector.add(fragment); return; } if (fragment.isOneSide()) { FragmentSide side = FragmentSide.chooseSide(fragment); DiffString text = side.getText(fragment); DiffString trimed = text.trim(); if (trimed.isEmpty()) { collector.add(side.createFragment(text, DiffString.EMPTY, false)); return; } } collector.add(fragment); } } @Nullable private static DiffString toNull(@Nullable DiffString text1) { return text1 == null || text1.isEmpty() ? null : text1; } @Nonnull private static DiffString toNotNull(@Nullable DiffString text) { return text == null ? DiffString.EMPTY : text; } @Nonnull protected Object[] trimStrings(@Nonnull DiffString[] strings) { Object[] result = new Object[strings.length]; for (int i = 0; i < strings.length; i++) { DiffString string = strings[i]; result[i] = string.trim(); } return result; } public boolean isEqual(@Nonnull DiffFragment fragment) { if (fragment.isOneSide()) return false; Object[] wrappers = getLineWrappers(new DiffString[]{fragment.getText1(), fragment.getText2()}); return Comparing.equal(wrappers[0], wrappers[1]); } @Nonnull public Word createFormatting(@Nonnull DiffString text, @Nonnull TextRange textRange) { return new Formatting(text, textRange); } public static ComparisonPolicy[] getAllInstances() { return COMPARISON_POLICIES; } @Nonnull @TestOnly protected Object[] getWrappers(@Nonnull String[] lines) { DiffString[] unsafeStrings = new DiffString[lines.length]; for (int i = 0; i < lines.length; i++) { unsafeStrings[i] = DiffString.createNullable(lines[i]); } return getWrappers(unsafeStrings); } @Nonnull @TestOnly protected Object[] getLineWrappers(@Nonnull String[] lines) { DiffString[] unsafeStrings = new DiffString[lines.length]; for (int i = 0; i < lines.length; i++) { unsafeStrings[i] = DiffString.createNullable(lines[i]); } return getLineWrappers(unsafeStrings); } }