/* * 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. */ /* * SearchManager.java * Created: Sep 20, 2007 * By: Joseph Wong */ package org.openquark.cal.compiler; import java.util.Collections; import java.util.List; import java.util.Set; 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.SourceModel.FunctionDefn; /** * This immutable class encapsulates logic for performing searches on source code and performing structured * source code navigation. The functionality is based on source model traversal, implemented by * subclasses of {@link IdentifierOccurrenceFinder}. * * @see IdentifierOccurrenceFinder * @see IdentifierOccurrenceCollector * * @author Joseph Wong * @author James Wright * @author Greg McClement */ /* * @history * * This class contains search-related functionality formerly implemented in the SourceMetricsManager by James Wright * and Greg McClement. */ public final class SearchManager { /** * A visitor class that traverses a source model, and collects only those occurrences that refer * to a particular named target. * * @author Joseph Wong */ private static final class IdentifierMatchesCollector extends IdentifierOccurrenceCollector<Void> { /** * The target to look for. */ private final IdentifierOccurrence<?> target; /** * Constructs an instance of this class. * @param currentModuleName the name of the module associated with the source being visited. * @param target the target to look for. * @param shouldCollectDeclaredBindings whether to collect declared bindings of the target. * @param shouldCollectReferences whether to collect references to the target. */ private IdentifierMatchesCollector(final ModuleName currentModuleName, final IdentifierOccurrence<?> target, final boolean shouldCollectDeclaredBindings, final boolean shouldCollectReferences) { super(currentModuleName, shouldCollectDeclaredBindings, false, shouldCollectReferences, shouldCollectReferences, false); this.target = target; } /** * {@inheritDoc} */ @Override protected boolean matchesDesiredIdentifiers(final IdentifierOccurrence<?> occurrence) { final IdentifierInfo targetIdentifierInfo = target.getIdentifierInfo(); final IdentifierInfo occurrenceIdentifierInfo = occurrence.getIdentifierInfo(); if (targetIdentifierInfo instanceof IdentifierInfo.DataConsFieldName) { // the target may be a data cons field name associated with multiple data cons... a non-empty overlap // with the occurrence in terms of the associated data constructors is enough to cause a match if (occurrenceIdentifierInfo instanceof IdentifierInfo.DataConsFieldName) { final IdentifierInfo.DataConsFieldName targetFieldInfo = (IdentifierInfo.DataConsFieldName)targetIdentifierInfo; final IdentifierInfo.DataConsFieldName occurrenceFieldInfo = (IdentifierInfo.DataConsFieldName)occurrenceIdentifierInfo; // first we need to check that the field names are actually equal if (targetFieldInfo.getFieldName().getCalSourceForm().equals(occurrenceFieldInfo.getFieldName().getCalSourceForm())) { final Set<IdentifierInfo.TopLevel.DataCons> targetDataConsList = targetFieldInfo.getAssociatedDataConstructors(); for (final IdentifierInfo.TopLevel.DataCons occurrenceDataCons : occurrenceFieldInfo.getAssociatedDataConstructors()) { if (targetDataConsList.contains(occurrenceDataCons)) { // the occurrence is associated with a data cons that is also associated with the target, so we declare a match return true; } } } } } return targetIdentifierInfo.equals(occurrenceIdentifierInfo); } } /** * A visitor class that traverses a source model, and collects only those occurrences that contain * a particular source position. * * @author Joseph Wong */ private static class PositionDirectedOccurrenceCollector extends IdentifierOccurrenceCollector<Void> { /** * The source position to look for. */ private final SourcePosition targetPosition; /** * Constructs an instance of this class. * @param currentModuleName the name of the module associated with the source being visited. * @param targetPosition the source position to look for. */ PositionDirectedOccurrenceCollector(final ModuleName currentModuleName, final SourcePosition targetPosition) { super(currentModuleName, true, true, true, true, true); if (targetPosition == null) { throw new NullPointerException(); } this.targetPosition = targetPosition; } /** * {@inheritDoc} */ @Override public Void visit_FunctionDefn_Algebraic(final FunctionDefn.Algebraic algebraic, final VisitorArgument<FinderState> arg) { // optimization: only visit an algebraic function if it contains the target position if (algebraic.getSourceRange().containsPosition(targetPosition)) { return super.visit_FunctionDefn_Algebraic(algebraic, arg); } else { return null; } } /** * {@inheritDoc} */ @Override protected boolean matchesDesiredIdentifiers(final IdentifierOccurrence<? extends IdentifierInfo> occurrence) { return occurrence.getSourceRange() != null && occurrence.getSourceRange().containsPosition(targetPosition); } } /** * A visitor class that traverses a source model, and finds the occurences that are * after the given position. * * @author Greg McClement */ public class FindOccurrencesFollowingPositionCollector extends IdentifierOccurrenceCollector<Void> { /** * The source position to look after. */ private final SourcePosition targetPosition; /** * Constructs an instance of this class. * @param currentModuleName the name of the module associated with the source being visited. */ public FindOccurrencesFollowingPositionCollector( final ModuleName currentModuleName, final SourcePosition targetPosition) { super(currentModuleName, true, true, true, true, true); if (targetPosition == null) { throw new NullPointerException(); } this.targetPosition = targetPosition; } /** * Checks whether the occurrence should be filtered out because it does not match certain criteria. * The default implementation returns true (no filtering). * @param occurrence the occurrence to be checked. * @return true if it should be kept, false if it should be filtered out. */ protected boolean matchesDesiredIdentifiers(final IdentifierOccurrence<?> occurrence) { return occurrence.getSourceRange() != null && occurrence.getSourceRange().getStartSourcePosition().isAfter(targetPosition); } } /** * The module container. */ private final ModuleContainer moduleContainer; /** * Constructs an instance of this class. * @param moduleContainer the module container. */ public SearchManager(final ModuleContainer moduleContainer) { if (moduleContainer == null) { throw new NullPointerException(); } this.moduleContainer = moduleContainer; } /** * Creates a source position for use with the API in this class. * @param line the line number. * @param column the column number. * @param moduleName the module name. Can be null. * @return a source position. */ public static SourcePosition makeSourcePosition(final int line, final int column, final ModuleName moduleName) { return new SourcePosition(line, column, moduleName); } /** * Finds the identifier occurrence at the specified source position. * @param moduleName the name of the module to process. * @param sourcePosition the source position to look for. * @param sourcePositionToTheLeft the source position immediately to the left. Can be null if there is none * (e.g. start of line, or is in a bit of modification not currently represented in the source model) * @param logger a message logger. * @return the most specific occurrence containing the source position, or null if none could be found. */ public IdentifierOccurrence<?> findSymbolAt(final ModuleName moduleName, final SourcePosition sourcePosition, final SourcePosition sourcePositionToTheLeft, final CompilerMessageLogger logger) { final PositionDirectedOccurrenceCollector collector = new PositionDirectedOccurrenceCollector(moduleName, sourcePosition); final boolean visitOK = visitModule(moduleName, collector, logger); if (!visitOK) { return null; } if (collector.hasCollectedOccurrences()) { return getMostSpecificOccurrence(collector); } else { // the collector could not find anything, so perhaps the source position is on a whitespace or a punctuation // we should try the position immediately to the left, if we're not at the first column already if (sourcePositionToTheLeft != null) { final PositionDirectedOccurrenceCollector collectorForPosToTheLeft = new PositionDirectedOccurrenceCollector(moduleName, sourcePositionToTheLeft); final boolean secondVisitOK = visitModule(moduleName, collectorForPosToTheLeft, logger); if (!secondVisitOK) { return null; } return getMostSpecificOccurrence(collectorForPosToTheLeft); } else { return null; } } } /** * Finds the symbols after the specified source position. * @param moduleName the name of the module to process. * @param line the line of the position. * @param column the column of the position. * @param logger a message logger. * @return list of symbols after the given position, or null if none could be found. */ public List<IdentifierOccurrence<?>> findSymbolsAfter(final ModuleName moduleName, final int line, final int column, final CompilerMessageLogger logger) { return findSymbolsAfter(moduleName, new SourcePosition(line, column, moduleName), logger); } /** * Finds the symbols after the specified source position. * @param moduleName the name of the module to process. * @param sourcePosition the source position to look for. * @param logger a message logger. * @return list of symbols after the given position, or null if none could be found. */ public List<IdentifierOccurrence<?>> findSymbolsAfter(final ModuleName moduleName, final SourcePosition sourcePosition, final CompilerMessageLogger logger) { final FindOccurrencesFollowingPositionCollector collector = new FindOccurrencesFollowingPositionCollector(moduleName, sourcePosition); final boolean visitOK = visitModule(moduleName, collector, logger); if (!visitOK) { return null; } return collector.getCollectedOccurrences(); } /** * Finds the definition of the identifier appearing in the given occurrence. * @param targetModuleName the module defining the target. * @param target an occurrence of the target. * @param logger a message logger. * @return the definition of the identifier. */ public IdentifierOccurrence<?> findDefinition(final ModuleName targetModuleName, final IdentifierOccurrence<?> target, final CompilerMessageLogger logger) { if (target.getIdentifierInfo() instanceof IdentifierInfo.Local.Parameter.InstanceMethodCALDoc) { // an instance method's CALDoc-declared argument's definition is itself... we do this check specially since // the IdentifierInfo.Local.Parameter.InstanceMethodCALDoc is currently non unique // todo-jowong when the identifier info is fixed to be unique in the module, we probably don't need this special case return target; } else { final boolean shouldCollectDeclaredBindings = true; final boolean shouldCollectExpressions = false; final IdentifierOccurrenceCollector<Void> collector = new IdentifierMatchesCollector(targetModuleName, target, shouldCollectDeclaredBindings, shouldCollectExpressions); final boolean visitOK = visitModule(targetModuleName, collector, logger); if (!visitOK) { return null; } return getMostSpecificOccurrence(collector); } } /** * Finds all occurrences in the given module of the identifier appearing in the given occurrence. * @param currentModuleName the module to process. * @param target an occurrence of the target. * @param logger a message logger. * @return all occurrences in the same module. */ public List<IdentifierOccurrence<?>> findOccurrencesInCurrentModule(final ModuleName currentModuleName, final IdentifierOccurrence<?> target, final CompilerMessageLogger logger) { if (target.getIdentifierInfo() instanceof IdentifierInfo.Local.Parameter.InstanceMethodCALDoc) { // an instance method's CALDoc-declared argument's definition is itself... we do this check specially since // the IdentifierInfo.Local.Parameter.InstanceMethodCALDoc is currently non unique // todo-jowong when the identifier info is fixed to be unique in the module, we probably don't need this special case return Collections.<IdentifierOccurrence<?>>singletonList(target); } else { final boolean shouldCollectDeclaredBindings = true; final boolean shouldCollectExpressions = true; final IdentifierOccurrenceCollector<Void> collector = new IdentifierMatchesCollector(currentModuleName, target, shouldCollectDeclaredBindings, shouldCollectExpressions); final boolean visitOK = visitModule(currentModuleName, collector, logger); if (!visitOK) { return null; } return collector.getCollectedOccurrences(); } } /** * Internal helper method to visit a module with a {@link IdentifierOccurrenceFinder}. * @param moduleName the name of the module to visit. * @param visitor the visitor. * @param logger a message logger for logging parser errors. * @return true if the module was visited successfully; false if the module could not be visited * (e.g. the module does not exist, or contains syntax errors). */ private boolean visitModule(final ModuleName moduleName, final IdentifierOccurrenceFinder<?> visitor, final CompilerMessageLogger logger) { final MessageLogger loggerForParsingErrors = new MessageLogger(); final SourceModel.ModuleDefn moduleDefn = moduleContainer.getSourceModel(moduleName, false, loggerForParsingErrors); if (moduleDefn == null) { augmentAndCopyLoggerMessages(moduleName, loggerForParsingErrors, logger); return false; } // todo-jowong get rid of the reliance on module type info when possible (when visibility check for empty context is fixed) final ModuleTypeInfo homeModuleTypeInfo = moduleContainer.getModuleTypeInfo(moduleName); if (homeModuleTypeInfo == null){ return false; } moduleDefn.accept(visitor, VisitorArgument.make(SymbolTable.makeRoot(moduleName, IdentifierResolver.makeContext(homeModuleTypeInfo)), FinderState.make())); return true; } /** * Retrieves the most specific occurrence from an {@link IdentifierOccurrenceCollector} - the * first occurrence that does not container another occurrence. * @param collector the collector to process. * @return the most specific occurrence. */ private IdentifierOccurrence<?> getMostSpecificOccurrence(final IdentifierOccurrenceCollector<?> collector) { final List<IdentifierOccurrence<?>> collectedOccurrences = collector.getCollectedOccurrences(); // return the first occurrence that does not contain any other occurrence for (final IdentifierOccurrence<?> occurrence : collectedOccurrences) { boolean containsAnother = false; for (final IdentifierOccurrence<?> other : collectedOccurrences) { if (occurrence != other && occurrence.getSourceRange().contains(other.getSourceRange())) { containsAnother = true; break; } } if (!containsAnother) { return occurrence; } } return null; } /** * Augment each message in the given logger with the given module name. * @param moduleName the module name to use. * @param inputLogger the input logger. * @param outputLogger the output logger. */ private void augmentAndCopyLoggerMessages(final ModuleName moduleName, final CompilerMessageLogger inputLogger, final CompilerMessageLogger outputLogger) { // Copy messages, adding source names to each SourcePosition for (final CompilerMessage compilerMessage : inputLogger.getCompilerMessages()) { final SourceRange sourceRange = compilerMessage.getSourceRange(); if(sourceRange != null) { final SourceRange augmentedRange = getAugmentedRange(sourceRange, moduleName); outputLogger.logMessage(new CompilerMessage(augmentedRange, compilerMessage.getMessageKind())); } else { outputLogger.logMessage(compilerMessage); } } } /** * Returns a source range the same as the given one except that the module name is updated. * @param sourceRange the source range to add the module name to * @param moduleName the module name to add to the source range * @return a new source range that is the same as the given one except that the module name is updated. */ private static SourceRange getAugmentedRange(final SourceRange sourceRange, final ModuleName moduleName){ final SourcePosition augmentedStartPosition = new SourcePosition(sourceRange.getStartLine(), sourceRange.getStartColumn(), moduleName); final SourcePosition augmentedEndPosition = new SourcePosition(sourceRange.getEndLine(), sourceRange.getEndColumn(), moduleName); return new SourceRange(augmentedStartPosition, augmentedEndPosition); } }