/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * SourceModifier.java * Creation date: (Feb 17, 2006) * By: James Wright */ package org.openquark.cal.compiler; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.openquark.util.Pair; /** * Contains a set of SourceModifications to apply or undo to source text. * * This class is a generalization of the Renamer class, which it replaces. * * @author James Wright * @author Bo Ilic */ final class SourceModifier { /** The SourceModifications to apply */ private final List<SourceModification> sourceModifications = new ArrayList<SourceModification>(); /** The SourceModifications to perform in order to undo */ private final List<SourceModification> undoModifications = new ArrayList<SourceModification>(); /** * True whenever the sourceModifications field contain potentially unsorted data. * * The standard usage pattern is to add a bunch of SourceModifications and then * call apply, at which point the list will be sorted. Afterward, there may be * subsequent calls to apply and undo without any new data having been added; in * those cases it is unnecessary to sort the list again. */ private boolean sourceModificationsNeedsSort = false; SourceModifier() { } /** * @param sourceModification SourceModification to add to the list of modifications * to apply. */ void addSourceModification(SourceModification sourceModification) { sourceModifications.add(sourceModification); sourceModificationsNeedsSort = true; } /** * Applies a list of SourceModifications to oldSourceText and returns the modified text. * The inverseModifications list is updated to contain a list of inverses of the modifications * that were performed/ * This method factors out the common functionality between apply and undo. * @param oldSourceText String to modify * @param sourceModifications List (SourceModification) of modifications to apply * @param inverseModifications List (SourceModification) This list will be updated to contain inverse * modifications of the modifications that were applied. * @return Modified text */ private static String performModifications(final String oldSourceText, List<SourceModification> sourceModifications, List<SourceModification> inverseModifications) { //the oldSourceText is a rough approximation of the size of the newSourceText... StringBuilder newSourceText = new StringBuilder (oldSourceText.length()); SourcePosition lastPositionInOld = new SourcePosition(1, 1); int lastIndexInOld = 0; int copiedUpToIndexInOld = 0; SourcePosition lastPositionInNew = new SourcePosition(1, 1); int lastIndexInNew = 0; for (final SourceModification sourceModification : sourceModifications) { SourcePosition modPositionInOld = sourceModification.getSourcePosition(); int modIndexInOld = modPositionInOld.getPosition(oldSourceText, lastPositionInOld, lastIndexInOld); if (modIndexInOld == -1) { throw new IllegalArgumentException(); } // append the stuff between the previous modification and the current modification from the old source text String interimText = oldSourceText.substring(copiedUpToIndexInOld, modIndexInOld); newSourceText.append(interimText); // Append the actual modification String newText = sourceModification.getNewText(); newSourceText.append(newText); copiedUpToIndexInOld = modIndexInOld + sourceModification.getOldText().length(); lastPositionInOld = modPositionInOld; lastIndexInOld = modIndexInOld; // Record the inverse modification if requested if (inverseModifications != null) { int modIndexInNew = lastIndexInNew + interimText.length(); SourcePosition modPositionInNew = lastPositionInNew.offsetPositionByText(interimText); inverseModifications.add(sourceModification.getInverse(modPositionInNew)); lastIndexInNew = modIndexInNew + newText.length(); lastPositionInNew = modPositionInNew.offsetPositionByText(newText); sourceModification.setNewSourcePosition(modPositionInNew); } } // Copy the last chunk of text after the final modification newSourceText.append(oldSourceText.substring(copiedUpToIndexInOld)); return newSourceText.toString(); } /** * Applies a list of SourceModifications to the given module. * @param moduleName the name of the module being modified. * @param sourceManager contains the source to modify * @param sourceModifications List (SourceModification) of modifications to apply */ private static void performModifications(ModuleName moduleName, ModuleContainer.ISourceManager2 sourceManager, List<SourceModification> sourceModifications) { // Figure out what the current offsets to the modification positions are. This assumes // no overlap of positions in the source modification. final int[] offsets = new int[sourceModifications.size()]; { int i = 0; for (final SourceModification sourceModification : sourceModifications) { offsets[i] = sourceManager.getOffset(moduleName, sourceModification.getSourcePosition()); ++i; } } // Perform the modifications int i = 0; // how much the offsets should be shifted due to source changes. int shift = 0; for (final SourceModification sourceModification : sourceModifications) { final int offset = offsets[i++]; int startIndex = offset + shift; final String oldText = sourceModification.getOldText(); final String newText = sourceModification.getNewText(); sourceManager.saveSource(moduleName, startIndex, startIndex + oldText.length(), newText); Pair<Integer, Integer> newPosition = sourceManager.getLineAndColumn(moduleName, startIndex); sourceModification.setNewSourcePosition(new SourcePosition(newPosition.fst(), newPosition.snd())); shift += newText.length() - oldText.length(); } } /** * Applies the previously-added SourceModifications to the provided sourceText. * * @param sourceText The source text to apply the modifications to * @return containing the results of applying the modifications */ String apply(String sourceText) { ensureSortedSourceModifications(); undoModifications.clear(); return performModifications(sourceText, sourceModifications, undoModifications); } /** * Applies the previously-added SourceModifications to the provided sourceText. * @param moduleName the name of the module being modified. * @param sourceManager contains the source to modify */ void apply(ModuleName moduleName, ModuleContainer.ISourceManager2 sourceManager) { ensureSortedSourceModifications(); performModifications(moduleName, sourceManager, sourceModifications); } /** * Undoes the previously-applied SourceModifications to the provided sourceText. * * The modifications must have been previously applied. An attempt to call * undo before apply will result in an IllegalArgumentException. * * @param sourceText The source text to apply the undo operation to * @return containing the results of undoing the operation */ String undo(String sourceText) { if(undoModifications.size() != sourceModifications.size()) { throw new IllegalArgumentException(); } return performModifications(sourceText, undoModifications, null); } /** * Remove all the modifications contained by this SourceModifier. */ void clearAllModifications() { sourceModifications.clear(); undoModifications.clear(); sourceModificationsNeedsSort = false; } /** * @return The number of source modifications that this SourceModifier currently * contains. */ int getNSourceModifications() { return sourceModifications.size(); } /** Ensure that the sourceModifications list is sorted by SourcePosition */ private void ensureSortedSourceModifications() { if(!sourceModificationsNeedsSort) { return; } Collections.sort(sourceModifications, SourceModification.compareByPosition); sourceModificationsNeedsSort = false; } /** {@inheritDoc} */ @Override public String toString() { return sourceModifications.toString(); } }