/* * ==================================================================== * Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://svnkit.com/license.html * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.tmatesoft.svn.core.internal.wc; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import org.tmatesoft.svn.core.wc.SVNDiffOptions; import de.regnis.q.sequence.QSequenceDifferenceBlock; import de.regnis.q.sequence.core.QSequenceException; import de.regnis.q.sequence.line.QSequenceLine; import de.regnis.q.sequence.line.QSequenceLineCache; import de.regnis.q.sequence.line.QSequenceLineMedia; import de.regnis.q.sequence.line.QSequenceLineRAData; import de.regnis.q.sequence.line.QSequenceLineResult; import de.regnis.q.sequence.line.simplifier.QSequenceLineDummySimplifier; import de.regnis.q.sequence.line.simplifier.QSequenceLineEOLUnifyingSimplifier; import de.regnis.q.sequence.line.simplifier.QSequenceLineSimplifier; import de.regnis.q.sequence.line.simplifier.QSequenceLineTeeSimplifier; import de.regnis.q.sequence.line.simplifier.QSequenceLineWhiteSpaceReducingSimplifier; import de.regnis.q.sequence.line.simplifier.QSequenceLineWhiteSpaceSkippingSimplifier; /** * @version 1.3 * @author TMate Software Ltd. */ public class FSMergerBySequence { // Constants ============================================================== public static final String DEFAULT_EOL = System.getProperty("line.separator"); public static final int NOT_MODIFIED = 0; public static final int MERGED = 4; public static final int CONFLICTED = 2; // Fields ================================================================= private final byte[] myConflictStart; private final byte[] myConflictSeparator; private final byte[] myConflictEnd; private final byte[] myOriginalMarker; // Setup ================================================================== public FSMergerBySequence(byte[] conflictStart, byte[] conflictSeparator, byte[] conflictEnd) { this(conflictStart, conflictSeparator, conflictEnd, null); } public FSMergerBySequence(byte[] conflictStart, byte[] conflictSeparator, byte[] conflictEnd, byte[] originalMarker) { myConflictStart = conflictStart; myConflictSeparator = conflictSeparator; myConflictEnd = conflictEnd; myOriginalMarker = originalMarker; } // Accessing ============================================================== public int merge(QSequenceLineRAData baseData, QSequenceLineRAData localData, QSequenceLineRAData latestData, SVNDiffOptions options, OutputStream result, SVNDiffConflictChoiceStyle style) throws IOException { style = style == null ? SVNDiffConflictChoiceStyle.CHOOSE_MODIFIED_LATEST : style; final QSequenceLineResult localResult; final QSequenceLineResult latestResult; final QSequenceLineTeeSimplifier mySimplifer = createSimplifier(options); try { localResult = QSequenceLineMedia.createBlocks(baseData, localData, mySimplifer); latestResult = QSequenceLineMedia.createBlocks(baseData, latestData, mySimplifer); } catch (QSequenceException ex) { throw new IOException(ex.getMessage()); } try { final QSequenceLineCache baseLines = localResult.getLeftCache(); final QSequenceLineCache localLines = localResult.getRightCache(); final QSequenceLineCache latestLines = latestResult.getRightCache(); final FSMergerBySequenceList local = new FSMergerBySequenceList(localResult.getBlocks()); final FSMergerBySequenceList latest = new FSMergerBySequenceList(latestResult.getBlocks()); final List transformedLocalLines = transformLocalLines(localResult.getBlocks(), localLines); int baseLineIndex = -1; boolean conflict = false; boolean merged = false; boolean chooseOnlyConflicts = style == SVNDiffConflictChoiceStyle.CHOOSE_ONLY_CONFLICTS; OutputStream realResult = result; if (chooseOnlyConflicts) { result = SVNFileUtil.DUMMY_OUT; } while (local.hasCurrent() || latest.hasCurrent()) { if (local.hasCurrent() && latest.hasCurrent() && isEqualChange(local.current(), latest.current(), localLines, latestLines)) { baseLineIndex = appendLines(result, local.current(), localLines, baseLineIndex, transformedLocalLines); local.forward(); latest.forward(); continue; } if (local.hasCurrent() && latest.hasCurrent()) { final QSequenceDifferenceBlock localStartBlock = local.current(); final QSequenceDifferenceBlock latestStartBlock = latest.current(); if (checkConflict(local, latest, localLines, latestLines, baseLines.getLineCount())) { if (style == SVNDiffConflictChoiceStyle.CHOOSE_LATEST) { baseLineIndex = createConflict(result, localStartBlock, local.current(), latestStartBlock, latest.current(), localLines, latestLines, baseLineIndex, transformedLocalLines, style); local.forward(); latest.forward(); merged = true; } else if (style == SVNDiffConflictChoiceStyle.CHOOSE_MODIFIED) { baseLineIndex = createConflict(result, localStartBlock, local.current(), latestStartBlock, latest.current(), localLines, latestLines, baseLineIndex, transformedLocalLines, style); local.forward(); latest.forward(); merged = true; } else if (style == SVNDiffConflictChoiceStyle.CHOOSE_MODIFIED_LATEST) { baseLineIndex = createConflict(result, localStartBlock, local.current(), latestStartBlock, latest.current(), localLines, latestLines, baseLineIndex, transformedLocalLines, style); local.forward(); latest.forward(); conflict = true; } else if (style == SVNDiffConflictChoiceStyle.CHOOSE_ONLY_CONFLICTS) { baseLineIndex = createOnlyConflictWithContext(realResult, localStartBlock, local.current(), latestStartBlock, latest.current(), localLines, latestLines, baseLines); local.forward(); latest.forward(); conflict = true; } continue; } } if (local.hasCurrent() && isBefore(local.current(), latest.hasCurrent() ? latest.current() : null)) { baseLineIndex = appendLines(result, local.current(), localLines, baseLineIndex, transformedLocalLines); local.forward(); merged = true; continue; } if (latest.hasCurrent()) { baseLineIndex = appendLines(result, latest.current(), latestLines, baseLineIndex, transformedLocalLines); latest.forward(); merged = true; } } appendTransformedLocalLines(baseLineIndex, baseLines.getLineCount(), transformedLocalLines, result); if (conflict) { return CONFLICTED; } else if (merged) { return MERGED; } else { return NOT_MODIFIED; } } finally { latestResult.close(); localResult.close(); } } // Utils ================================================================== private List transformLocalLines(List blocks, QSequenceLineCache localLines) throws IOException { final List transformedLocalLines = new ArrayList(); final FSMergerBySequenceList blockList = new FSMergerBySequenceList(blocks); int localIndex = 0; int baseIndex = 0; for (;localIndex < localLines.getLineCount();) { final int baseTo; if (blockList.hasCurrent()) { final QSequenceDifferenceBlock block = blockList.current(); baseTo = block.getLeftFrom() -1; } else { baseTo = Integer.MAX_VALUE; } while (localIndex < localLines.getLineCount() && baseIndex <= baseTo) { transformedLocalLines.add(localLines.getLine(localIndex)); localIndex++; baseIndex++; } if (blockList.hasCurrent()) { for (int index = 0; index < blockList.current().getLeftSize(); index++) { transformedLocalLines.add(null); } baseIndex += blockList.current().getLeftSize(); localIndex += blockList.current().getRightSize(); blockList.forward(); } } return transformedLocalLines; } private boolean isBefore(QSequenceDifferenceBlock block1, QSequenceDifferenceBlock block2) { return block1 != null && (block2 == null || block1.getLeftTo() < block2.getLeftFrom()); } private boolean intersect(QSequenceDifferenceBlock block1, QSequenceDifferenceBlock block2, int baseLineCount) { final int from1 = block1.getLeftFrom(); final int from2 = block2.getLeftFrom(); final int to1 = block1.getLeftTo(); final int to2 = block2.getLeftTo(); if (to1 < from1) { if (to2 < from2) { return from1 == from2; } if (from1 == baseLineCount && to2 >= baseLineCount - 1) { return true; } return from1 >= from2 && from1 <= to2; } else if (to2 < from2) { if (from2 == baseLineCount && to1 >= baseLineCount - 1) { return true; } return from2 >= from1 && from2 <= to1; } else { return (from1 >= from2 && from1 <= to2) || (from2 >= from1 && from2 <= to1); } } private int appendLines(OutputStream result, QSequenceDifferenceBlock block, QSequenceLineCache changedLines, int baseLineIndex, List transformedLocalLines) throws IOException { appendTransformedLocalLines(baseLineIndex, block.getLeftFrom(), transformedLocalLines, result); for (int changedLineIndex = block.getRightFrom(); changedLineIndex <= block.getRightTo(); changedLineIndex++) { writeLine(result, changedLines.getLine(changedLineIndex)); } return block.getLeftTo(); } private boolean isEqualChange(QSequenceDifferenceBlock localBlock, QSequenceDifferenceBlock latestBlock, QSequenceLineCache localLines, QSequenceLineCache latestLines) throws IOException { if (localBlock.getLeftFrom() != latestBlock.getLeftFrom() || localBlock.getLeftTo() != latestBlock.getLeftTo()) { return false; } if (localBlock.getRightTo() - localBlock.getRightFrom() != latestBlock.getRightTo() - latestBlock.getRightFrom()) { return false; } for (int index = 0; index < localBlock.getRightTo() - localBlock.getRightFrom() + 1; index++) { final QSequenceLine localLine = localLines.getLine(localBlock.getRightFrom() + index); final QSequenceLine latestLine = latestLines.getLine(latestBlock.getRightFrom() + index); if (!localLine.equals(latestLine)) { return false; } } return true; } private boolean checkConflict(FSMergerBySequenceList localChanges, FSMergerBySequenceList latestChanges, QSequenceLineCache localLines, QSequenceLineCache latestLines, int baseLineCount) throws IOException { boolean conflict = false; while (intersect(localChanges.current(), latestChanges.current(), baseLineCount) && !isEqualChange(localChanges.current(), latestChanges.current(), localLines, latestLines)) { conflict = true; if (localChanges.current().getLeftTo() <= latestChanges.current().getLeftTo()) { if (localChanges.hasNext() && intersect(localChanges.peekNext(), latestChanges.current(), baseLineCount)) { localChanges.forward(); } else { break; } } else { if (latestChanges.hasNext() && intersect(localChanges.current(), latestChanges.peekNext(), baseLineCount)) { latestChanges.forward(); } else { break; } } } return conflict; } private int createConflict(OutputStream result, QSequenceDifferenceBlock localStart, QSequenceDifferenceBlock localEnd, QSequenceDifferenceBlock latestStart, QSequenceDifferenceBlock latestEnd, QSequenceLineCache localLines, QSequenceLineCache latestLines, int baseLineIndex, List transformedLocalLines, SVNDiffConflictChoiceStyle style) throws IOException { final int minBaseFrom = Math.min(localStart.getLeftFrom(), latestStart.getLeftFrom()); final int maxBaseTo = Math.max(localEnd.getLeftTo(), latestEnd.getLeftTo()); appendTransformedLocalLines(baseLineIndex, minBaseFrom, transformedLocalLines, result); final int localFrom = Math.max(0, localStart.getRightFrom() - (localStart.getLeftFrom() - minBaseFrom)); final int localTo = Math.min(localLines.getLineCount() - 1, localEnd.getRightTo() + (maxBaseTo - localEnd.getLeftTo())); final int latestFrom = Math.max(0, latestStart.getRightFrom() - (latestStart.getLeftFrom() - minBaseFrom)); final int latestTo = Math.min(latestLines.getLineCount() - 1, latestEnd.getRightTo() + (maxBaseTo - latestEnd.getLeftTo())); if (style == SVNDiffConflictChoiceStyle.CHOOSE_MODIFIED_LATEST) { writeBytesAndEol(result, myConflictStart); } if (style == SVNDiffConflictChoiceStyle.CHOOSE_MODIFIED || style == SVNDiffConflictChoiceStyle.CHOOSE_MODIFIED_LATEST) { for (int index = localFrom; index <= localTo; index++) { writeLine(result, localLines.getLine(index)); } } if (style == SVNDiffConflictChoiceStyle.CHOOSE_MODIFIED_LATEST) { writeBytesAndEol(result, myConflictSeparator); } if (style == SVNDiffConflictChoiceStyle.CHOOSE_LATEST || style == SVNDiffConflictChoiceStyle.CHOOSE_MODIFIED_LATEST) { for (int index = latestFrom; index <= latestTo; index++) { writeLine(result, latestLines.getLine(index)); } } if (style == SVNDiffConflictChoiceStyle.CHOOSE_MODIFIED_LATEST) { writeBytesAndEol(result, myConflictEnd); } return maxBaseTo; } private int createOnlyConflictWithContext(OutputStream result, QSequenceDifferenceBlock localStart, QSequenceDifferenceBlock localEnd, QSequenceDifferenceBlock latestStart, QSequenceDifferenceBlock latestEnd, QSequenceLineCache localLines, QSequenceLineCache latestLines, QSequenceLineCache baseLines) throws IOException { final int minBaseFrom = Math.min(localStart.getLeftFrom(), latestStart.getLeftFrom()); final int maxBaseTo = Math.max(localEnd.getLeftTo(), latestEnd.getLeftTo()); final int localFrom = Math.max(0, localStart.getRightFrom() - (localStart.getLeftFrom() - minBaseFrom)); final int localTo = Math.min(localLines.getLineCount() - 1, localEnd.getRightTo() + (maxBaseTo - localEnd.getLeftTo())); final int latestFrom = Math.max(0, latestStart.getRightFrom() - (latestStart.getLeftFrom() - minBaseFrom)); final int latestTo = Math.min(latestLines.getLineCount() - 1, latestEnd.getRightTo() + (maxBaseTo - latestEnd.getLeftTo())); //local changes block writeBytes(result, myConflictStart); int localLinesNum = localTo - localFrom + 1; String localContext = null; int localStartFrom = localFrom + 1; if (localLinesNum > 1) { localContext = " (" + localStartFrom + "," + localLinesNum + ")"; } else { localContext = " (" + localStartFrom + ")"; } writeBytesAndEol(result, localContext.getBytes()); for (int index = localFrom; index <= localTo; index++) { writeLine(result, localLines.getLine(index)); } //original block writeBytes(result, myOriginalMarker); int originalLinesNum = maxBaseTo - minBaseFrom + 1; String originalContext = null; int originalStartFrom = minBaseFrom + 1; if (originalLinesNum > 1) { originalContext = " (" + originalStartFrom + "," + originalLinesNum + ")"; } else { originalContext = " (" + originalStartFrom + ")"; } writeBytesAndEol(result, originalContext.getBytes()); for (int index = minBaseFrom; index <= maxBaseTo; index++) { writeLine(result, baseLines.getLine(index)); } //conflict separator writeBytesAndEol(result, myConflictSeparator); //server changes block for (int index = latestFrom; index <= latestTo; index++) { writeLine(result, latestLines.getLine(index)); } writeBytes(result, myConflictEnd); int latestLinesNum = latestTo - latestFrom + 1; String latestContext = null; int latestStartFrom = latestFrom + 1; if (latestLinesNum > 1) { latestContext = " (" + latestStartFrom + "," + latestLinesNum + ")"; } else { latestContext = " (" + latestStartFrom + ")"; } writeBytesAndEol(result, latestContext.getBytes()); return maxBaseTo; } private void appendTransformedLocalLines(int baseLineIndex, int to, List transformedLocalLines, OutputStream result) throws IOException { for (baseLineIndex++; baseLineIndex < to; baseLineIndex++) { final QSequenceLine sequenceLine = (QSequenceLine)transformedLocalLines.get(baseLineIndex); if (sequenceLine == null) { throw new IOException("Can not merge: sequence line is null for this base index"); } writeLine(result, sequenceLine); } } private void writeLine(OutputStream os, QSequenceLine line) throws IOException { final byte[] bytes = line.getContentBytes(); if (bytes.length == 0) { return; } os.write(bytes); } private void writeBytesAndEol(OutputStream os, final byte[] bytes) throws IOException { if (bytes.length > 0) { os.write(bytes); os.write(DEFAULT_EOL.getBytes()); } } private void writeBytes(OutputStream os, final byte[] bytes) throws IOException { if (bytes.length > 0) { os.write(bytes); } } private QSequenceLineTeeSimplifier createSimplifier(SVNDiffOptions options) { final QSequenceLineSimplifier eolSimplifier = options != null && options.isIgnoreEOLStyle() ? (QSequenceLineSimplifier)new QSequenceLineEOLUnifyingSimplifier() : (QSequenceLineSimplifier)new QSequenceLineDummySimplifier(); QSequenceLineSimplifier spaceSimplifier = new QSequenceLineDummySimplifier(); if (options != null) { if (options.isIgnoreAllWhitespace()) { spaceSimplifier = new QSequenceLineWhiteSpaceSkippingSimplifier(); } else if (options.isIgnoreAmountOfWhitespace()) { spaceSimplifier = new QSequenceLineWhiteSpaceReducingSimplifier(); } } return new QSequenceLineTeeSimplifier(eolSimplifier, spaceSimplifier); } }