/* * 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. */ /* * DataConsFieldNameRenamer.java * Created: Sep 24, 2007 * By: Joseph Wong */ package org.openquark.cal.compiler; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.openquark.cal.compiler.IdentifierInfo.DataConsFieldName; import org.openquark.cal.compiler.IdentifierOccurrence.Binding; import org.openquark.cal.compiler.IdentifierOccurrence.Reference; import org.openquark.cal.compiler.IdentifierOccurrenceFinder.FinderState; import org.openquark.cal.compiler.IdentifierResolver.SymbolTable; import org.openquark.cal.compiler.IdentifierResolver.VisitorArgument; import org.openquark.cal.compiler.IdentifierResolver.SymbolTable.LocalScope; import org.openquark.cal.compiler.IdentifierResolver.SymbolTable.TopLevelScope; /** * This class encapsulates the renaming logic for handling data constructor field names. * <p> * <h4>Some background</h4> * A data constructor field name can be associated with multiple data constructors when it appears * in a case alternative containing more than one data constructor, e.g. * <pre> * case foo of * (DC1|DC2|DC3) {field1=bar, field2} -> ... * </pre> * Both field1 and field2 are associated with DC1, DC2, and DC3. * <p> * When renaming such a field, care must be taken to ensure that all data constructors linked * by such case alternatives must have the named field renamed <i>together</i> in order to preserve * semantics. * <p> * The renaming logic works in two passes: * <ol> * <li> * The first pass goes through all modules in which the data constructor is visible, and attempts * to find case expressions containing alternatives involving the data constructor field. All other * data constructors appearing in those particular alternatives are recorded. * <li> * The second pass determines where are all the occurrences of the field name to be renamed, * including any identically-named fields in sibling data constructors identified in the first pass. * </ol> * The renaming algorithm takes the information gathered by the two passes to produce a set of * required source modifications. * * @author Joseph Wong */ final class DataConsFieldNameRenamer { /** * This first pass goes through all modules in which the data constructor is visible, and * attempts to find case expressions containing alternatives involving the data constructor * field. All other data constructors appearing in those particular alternatives are recorded. * * @author Joseph Wong */ private static final class AllAffectedDataConsCollector extends IdentifierOccurrenceFinder<Void> { /** * The target data constructor field to be renamed. */ private final IdentifierInfo.DataConsFieldName target; /** * A set for collecting all affected data constructors related to the target via * multi-data-constructor case alternatives. */ private final Set<IdentifierInfo.TopLevel.DataCons> affectedDataConsSet = new HashSet<IdentifierInfo.TopLevel.DataCons>(); /** * Constructs an instance of this class. * @param currentModuleName the name of the module associated with the source being visited. * @param target the target data constructor field to be renamed. */ AllAffectedDataConsCollector(final ModuleName currentModuleName, final IdentifierInfo.DataConsFieldName target) { super(currentModuleName); if (target == null) { throw new NullPointerException(); } this.target = target; } /** * @return a set of all affected data constructors related to the target via * multi-data-constructor case alternatives. */ Set<IdentifierInfo.TopLevel.DataCons> getAffectedDataConsSet() { return affectedDataConsSet; } /** * {@inheritDoc} */ @Override protected void handleLocalVariableBinding(final Binding<IdentifierInfo.Local> binding, final LocalScope scope) { if (binding instanceof Binding.PunnedTextualDataConsFieldName<?>) { final Binding.PunnedTextualDataConsFieldName<?> punnedBinding = (Binding.PunnedTextualDataConsFieldName<?>)binding; if (target.getFieldName().getCalSourceForm().equals(punnedBinding.getIdentifierInfo().getVarName())) { boolean overlapsWithTarget = false; for (final Binding<IdentifierInfo.TopLevel.DataCons> dataConsNameBinding : punnedBinding.getDataConsNameBindings()) { if (target.getAssociatedDataConstructors().contains(dataConsNameBinding.getIdentifierInfo())) { overlapsWithTarget = true; } } if (overlapsWithTarget) { for (final Binding<IdentifierInfo.TopLevel.DataCons> dataConsNameBinding : punnedBinding.getDataConsNameBindings()) { affectedDataConsSet.add(dataConsNameBinding.getIdentifierInfo()); } } } } super.handleLocalVariableBinding(binding, scope); } /** * {@inheritDoc} */ @Override protected void handleDataConsFieldNameReference(final Reference.DataConsFieldName reference, final List<Binding<DataConsFieldName>> bindings, final SymbolTable scope) { if (target.getFieldName().equals(reference.getIdentifierInfo().getFieldName())) { boolean overlapsWithTarget = false; for (final Reference.Qualifiable<IdentifierInfo.TopLevel.DataCons> dataConsNameOccurrence : reference.getDataConsOccurrences()) { if (target.getAssociatedDataConstructors().contains(dataConsNameOccurrence.getIdentifierInfo())) { overlapsWithTarget = true; } } if (overlapsWithTarget) { for (final Reference.Qualifiable<IdentifierInfo.TopLevel.DataCons> dataConsNameOccurrence : reference.getDataConsOccurrences()) { affectedDataConsSet.add(dataConsNameOccurrence.getIdentifierInfo()); } } } super.handleDataConsFieldNameReference(reference, bindings, scope); } } /** * This second pass determines where are all the occurrences of the field name to be renamed, * including any identically-named fields in sibling data constructors identified in the first * pass. * * @author Joseph Wong */ private static final class RenameableOccurrenceCollector extends IdentifierOccurrenceFinder<Void> { /** * A set of all affected data constructors related to the target via multi-data-constructor * case alternatives. Can *not* be null. */ private final Set<IdentifierInfo.TopLevel.DataCons> affectedDataConsSet; /** * The original name of the target field to be renamed. */ private final FieldName oldName; /** * A list of the occurrences of the target to be renamed. */ private final List<IdentifierOccurrence<?>> occurrencesToRename = new ArrayList<IdentifierOccurrence<?>>(); /** * @return a list of the occurrences of the target to be renamed. */ List<IdentifierOccurrence<?>> getOccurrencesToRename() { return occurrencesToRename; } /** * Constructs an instance of this class. * @param currentModuleName the name of the module associated with the source being visited. * @param affectedDataConsSet * a set of all affected data constructors related to the target via * multi-data-constructor case alternatives. Can *not* be null. * @param oldName the original name of the target field to be renamed. */ RenameableOccurrenceCollector(final ModuleName currentModuleName, final Set<IdentifierInfo.TopLevel.DataCons> affectedDataConsSet, final FieldName oldName) { super(currentModuleName); if (affectedDataConsSet == null || oldName == null) { throw new NullPointerException(); } this.affectedDataConsSet = affectedDataConsSet; this.oldName = oldName; } /** * {@inheritDoc} */ @Override protected void handleDataConsFieldNameBinding(final Binding<IdentifierInfo.DataConsFieldName> binding, final TopLevelScope scope) { for (final IdentifierInfo.TopLevel.DataCons associatedDataConsInfo : binding.getIdentifierInfo().getAssociatedDataConstructors()) { if (affectedDataConsSet.contains(associatedDataConsInfo)) { if (binding.getIdentifierInfo().getFieldName().equals(oldName)) { occurrencesToRename.add(binding); break; // if we allow the loop to continue, the occurrence may be added more than once! } } } super.handleDataConsFieldNameBinding(binding, scope); } /** * {@inheritDoc} */ @Override protected void handleLocalVariableBinding(final Binding<IdentifierInfo.Local> binding, final LocalScope scope) { if (binding instanceof Binding.PunnedTextualDataConsFieldName<?>) { final Binding.PunnedTextualDataConsFieldName<?> punnedBinding = (Binding.PunnedTextualDataConsFieldName<?>)binding; for (final Binding<IdentifierInfo.TopLevel.DataCons> associatedDataCons : punnedBinding.getDataConsNameBindings()) { if (affectedDataConsSet.contains(associatedDataCons.getIdentifierInfo())) { if (punnedBinding.getIdentifierInfo().getVarName().equals(oldName.getCalSourceForm())) { occurrencesToRename.add(binding); break; // if we allow the loop to continue, the occurrence may be added more than once! } } } } super.handleLocalVariableBinding(binding, scope); } /** * {@inheritDoc} */ @Override protected void handleDataConsFieldNameReference(final Reference.DataConsFieldName reference, final List<Binding<DataConsFieldName>> bindings, final SymbolTable scope) { for (final Reference.Qualifiable<IdentifierInfo.TopLevel.DataCons> associatedDataCons : reference.getDataConsOccurrences()) { if (affectedDataConsSet.contains(associatedDataCons.getIdentifierInfo())) { if (reference.getIdentifierInfo().getFieldName().equals(oldName)) { occurrencesToRename.add(reference); break; // if we allow the loop to continue, the occurrence may be added more than once! } } } super.handleDataConsFieldNameReference(reference, bindings, scope); } } /** Private constructor. */ private DataConsFieldNameRenamer() {} /** * Calculates the set of all affected data constructors related to the target via * multi-data-constructor case alternatives in the specified module. * @param oldName the original name of the target field. * @param moduleTypeInfo the module type info. * @param moduleDefn the corresponding module source model. * @return the set of all affected data constructors. */ static Set<IdentifierInfo.TopLevel.DataCons> getAffectedDataConsSetFromModule(final IdentifierInfo.DataConsFieldName oldName, final ModuleTypeInfo moduleTypeInfo, final SourceModel.ModuleDefn moduleDefn) { final ModuleName moduleName = moduleTypeInfo.getModuleName(); final AllAffectedDataConsCollector collector = new AllAffectedDataConsCollector(moduleName, oldName); moduleDefn.accept(collector, VisitorArgument.make(SymbolTable.makeRoot(moduleName, IdentifierResolver.makeContext(moduleTypeInfo)), FinderState.make())); return collector.getAffectedDataConsSet(); } /** * Runs the renaming logic for a data constructor field name and returns the necessary modifications in * a {@link SourceModifier}. * * @param moduleTypeInfo ModuleTypeInfo for the module to process * @param affectedDataConsSet * @param oldName the identifier being renamed * @param newName the name name * @param messageLogger CompilerMessageLogger for logging failures * @return a SourceModifier that will apply the renaming to the source. */ static SourceModifier getSourceModifier(final ModuleTypeInfo moduleTypeInfo, final String sourceText, final Set<IdentifierInfo.TopLevel.DataCons> affectedDataConsSet, final FieldName oldName, final FieldName newName, final CompilerMessageLogger messageLogger) { final SourceModifier sourceModifier = new SourceModifier(); // if the module is a sourceless module, then there is not much we can do with it. if (sourceText.length() == 0) { return sourceModifier; } final ModuleName moduleName = moduleTypeInfo.getModuleName(); final SourceModel.ModuleDefn moduleDefn = SourceModelUtilities.TextParsing.parseModuleDefnIntoSourceModel(sourceText, messageLogger); if (moduleDefn == null) { return sourceModifier; } final RenameableOccurrenceCollector collector = new RenameableOccurrenceCollector(moduleName, affectedDataConsSet, oldName); moduleDefn.accept(collector, VisitorArgument.make(SymbolTable.makeRoot(moduleName, IdentifierResolver.makeContext(moduleTypeInfo)), FinderState.make())); for (final IdentifierOccurrence<?> occurrenceToRename : collector.getOccurrencesToRename()) { final String newNameString = newName.getCalSourceForm(); if (occurrenceToRename instanceof Binding.PunnedTextualDataConsFieldName<?>) { final Binding.PunnedTextualDataConsFieldName<?> punnedBinding = (Binding.PunnedTextualDataConsFieldName<?>)occurrenceToRename; // need to unpun the pattern sourceModifier.addSourceModification(IdentifierRenamer.makeReplaceText(sourceText, occurrenceToRename.getSourceRange(), newNameString + " = " + punnedBinding.getIdentifierInfo().getVarName())); } else if (occurrenceToRename instanceof Reference.DataConsFieldName.PunnedOrdinal && oldName instanceof FieldName.Ordinal && !(newName instanceof FieldName.Ordinal)) { // if this is an ordinal->textual field name renaming, and the original ordinal name appears in a punned context // it needs to be unpunned and bound to the wildcard pattern, e.g. {#3} is renamed to {foo = _} sourceModifier.addSourceModification(IdentifierRenamer.makeReplaceText(sourceText, occurrenceToRename.getSourceRange(), newNameString + " = _")); } else { sourceModifier.addSourceModification(IdentifierRenamer.makeReplaceText(sourceText, occurrenceToRename.getSourceRange(), newNameString)); } } return sourceModifier; } }