/*
* 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.
*/
/*
* WorkspaceSourceMetricsManager.java
* Creation date: (Apr 14, 2005)
* By: Jawright
*/
package org.openquark.cal.compiler;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openquark.cal.compiler.ClassInstanceIdentifier.UniversalRecordInstance;
import org.openquark.cal.compiler.SearchResult.Precise;
import org.openquark.cal.compiler.SearchResult.Frequency.Type;
import org.openquark.cal.compiler.SourceIdentifier.Category;
import org.openquark.cal.compiler.SourceMetricFinder.LintWarning;
import org.openquark.cal.compiler.SourceMetricFinder.SearchType;
import org.openquark.cal.compiler.SourceModel.FunctionTypeDeclaration;
import org.openquark.cal.compiler.SourceModel.InstanceDefn;
import org.openquark.cal.compiler.SourceModel.SourceElement;
import org.openquark.cal.compiler.SourceModel.TopLevelSourceElement;
import org.openquark.cal.compiler.SourceModel.InstanceDefn.InstanceTypeCons;
import org.openquark.cal.compiler.SourceModel.InstanceDefn.InstanceTypeCons.TypeCons;
import org.openquark.cal.compiler.SourceModel.TypeClassDefn.ClassMethodDefn;
import org.openquark.cal.compiler.SourceModel.TypeConstructorDefn.AlgebraicType.DataConsDefn;
import org.openquark.cal.filter.AcceptAllModulesFilter;
import org.openquark.cal.filter.AcceptAllQualifiedNamesFilter;
import org.openquark.cal.filter.ModuleFilter;
import org.openquark.cal.filter.QualifiedNameFilter;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.util.Pair;
import org.openquark.util.WildcardPatternMatcher;
/**
* This internal class encapsulates aggregated source metrics for a set of modules.
* It should be instantiated only by the CALWorkspace class. Other clients should use
* the WorkspaceSourceMetrics interface instead.
*
* This class aggregates the module-level source metric information held in modules'
* ModuleTypeInfo objects.
*
* It is not safe to concurrently modify this class.
*
* Creation date: (Apr 14, 2005)
* @author Jawright
*/
public final class SourceMetricsManager implements SourceMetrics {
/** Map from gem name to number of times the gem is referred
* to in the function bodies of functions defined in modules contained by the containing
* workspace.
*/
private final Map<QualifiedName, Integer> gemToReferenceFrequencyMap = new HashMap<QualifiedName, Integer>();
/** The containing workspace. */
private final ModuleContainer moduleContainer;
/**
* Used to provide feedback on the progress of the search.
*
* @author GMcClement
*
*/
public interface IProgressMonitor {
/**
* Called at the start of the search to provide the number of modules to be searched.
* @param numberOfModules The number of modules that are going to be searched.
*/
public void numberOfModules(int numberOfModules);
/**
* Called just before the given module is searched.
* @param module The name of the next module to be searched.
*/
public void startModule(ModuleName module);
/**
* Called when the search is completed.
*/
public void done();
/**
* Check the current activity was cancelled.
* @return the flag indicating if the activity should be cancelled.
*/
public boolean isCancelled();
/**
* Called when more results from the search are available.
* @param moreResults The additional search results that have been found.
*/
public void moreResults(Collection<? extends SearchResult> moreResults);
}
/** Not intended to be used outside of the CALWorkspace class */
public SourceMetricsManager(ModuleContainer moduleContainer) {
this.moduleContainer = moduleContainer;
}
/**
* Add in the metrics for the specified module to the aggregate collection.
* This method is only intended to be called from CALWorkspace.
* @param moduleTypeInfo ModuleTypeInfo of module to add to the metrics
*/
public synchronized void addModuleMetrics(ModuleTypeInfo moduleTypeInfo) {
// If there are no metrics being kept, then there's nothing for us to do
if (moduleTypeInfo.getModuleSourceMetrics() == null) {
return;
}
Map<QualifiedName, Integer> updateMap = moduleTypeInfo.getModuleSourceMetrics().getGemToLocalReferenceFrequencyMap();
// Add reference-frequency metrics
addFrequencyMetrics(updateMap, gemToReferenceFrequencyMap);
}
/**
* Remove the metrics for the specified module from the aggregate collection.
* This method is only intended to be called from CALWorkspace.
* @param moduleTypeInfo ModuleTypeInfo of module to add to the metrics
*/
public synchronized void removeModuleMetrics(ModuleTypeInfo moduleTypeInfo) {
// If there are no metrics being kept, then there's nothing for us to do
if (moduleTypeInfo.getModuleSourceMetrics() == null) {
return;
}
Map<QualifiedName, Integer> updateMap = moduleTypeInfo.getModuleSourceMetrics().getGemToLocalReferenceFrequencyMap();
// Remove reference-frequency metrics
removeFrequencyMetrics(updateMap, gemToReferenceFrequencyMap);
}
/**
* Aggregate the frequency data in sourceMap into destinationMap.
* If (K,V) is in sourceMap but no mapping for K exists in destinationMap, then
* (K,V) is added to destinationMap. If (K,V1) is in sourceMap and (K,V2) is
* in destinationMap, then destinationMap is updated to contain (K, V1+V2).
* @param sourceMap Map to add to destinationMap
* @param destinationMap Map to update
*/
private static <T> void addFrequencyMetrics(Map<T, Integer> sourceMap, Map<T, Integer> destinationMap) {
for (final Map.Entry<T, Integer> entry : sourceMap.entrySet()) {
Integer newValue = entry.getValue();
addFrequencyEntry(entry.getKey(), newValue.intValue(), destinationMap);
}
}
/**
* Adds (key, frequency) to destinationMap if destinationMap does not already contain
* a value for key. If destinationMap does contain a value for key, then it is replaced
* by (key, frequency + origValue).
* @param key Key to update entry for
* @param frequency Value to add to the entry for key
* @param destinationMap Map to add the entry to
*/
private static <T> void addFrequencyEntry(T key, int frequency, Map<T, Integer> destinationMap) {
Integer currentValue = destinationMap.get(key);
if (currentValue != null) {
destinationMap.put(key, Integer.valueOf(currentValue.intValue() + frequency));
} else {
destinationMap.put(key, Integer.valueOf(frequency));
}
}
/**
* Remove the frequency data in removalItems from the aggregate totals
* in targetMap.
* @param removalItems Map of values to remove from the totals in targetMap
* @param targetMap Map to update
*/
private static <T> void removeFrequencyMetrics(Map<T, Integer> removalItems, Map<T, Integer> targetMap) {
for (final Map.Entry<T, Integer> entry : removalItems.entrySet()) {
T key = entry.getKey();
Integer removalValue = entry.getValue();
Integer currentValue = targetMap.get(key);
if (currentValue != null) {
targetMap.put(key, Integer.valueOf(currentValue.intValue() - removalValue.intValue()));
if (currentValue.intValue() - removalValue.intValue() < 0) {
throw new IllegalStateException("Module-removal operation on WorkspaceSourceMetricsManager caused a negative frequency for "+key);
}
} else {
throw new IllegalStateException("Module-removal operation on WorkspaceSourceMetricsManager caused an attempt to remove missing key "+key+" from WorkspaceSourceMetricsManager tracking");
}
}
}
/**
* {@inheritDoc}
*/
public synchronized int getGemReferenceFrequency(QualifiedName gemName) {
Integer result = gemToReferenceFrequencyMap.get(gemName);
if (result != null) {
return result.intValue();
} else {
return 0;
}
}
/**
* {@inheritDoc}
*/
public String dumpCompositionalFrequencies(ModuleFilter moduleFilter, QualifiedNameFilter functionFilter, boolean traceSkippedFunctions) {
StringBuilder buffer = new StringBuilder();
Map<Pair<QualifiedName, QualifiedName>, Integer> workspaceCompositionalFrequencies = new HashMap<Pair<QualifiedName, QualifiedName>, Integer>();
for(int i = 0; i < moduleContainer.getNModules(); i++) {
ModuleTypeInfo moduleTypeInfo = moduleContainer.getNthModuleTypeInfo(i);
if(moduleFilter.acceptModule(moduleTypeInfo.getModuleName())) {
// Parse a SourceModel to process
CompilerMessageLogger logger = new MessageLogger();
SourceModel.ModuleDefn moduleDefn = moduleContainer.getSourceModel(moduleTypeInfo.getModuleName(), false, logger);
// Fold this module's frequencies into the grand total
Map<Pair<QualifiedName, QualifiedName>, Integer> compositionalFrequencies = SourceMetricFinder.computeCompositionalFrequencies(moduleDefn, moduleTypeInfo, functionFilter, traceSkippedFunctions);
addFrequencyMetrics(compositionalFrequencies, workspaceCompositionalFrequencies);
} else {
System.out.println("Skipping test module " + moduleTypeInfo.getModuleName());
}
}
// Build a string from the grand total
for (final Map.Entry<Pair<QualifiedName, QualifiedName>, Integer> entry : workspaceCompositionalFrequencies.entrySet()) {
Pair<QualifiedName, QualifiedName> key = entry.getKey();
Integer frequency = entry.getValue();
buffer.append(key.fst());
buffer.append(",");
buffer.append(key.snd());
buffer.append(",");
buffer.append(frequency.intValue());
buffer.append("\n");
}
return buffer.toString();
}
/**
* {@inheritDoc}
*/
public synchronized String dumpReferenceFrequencies(ModuleFilter moduleFilter, QualifiedNameFilter functionFilter, boolean traceSkippedFunctions) {
StringBuilder buffer = new StringBuilder();
Set<Map.Entry<QualifiedName, Integer>> dataEntries = null;
if (moduleFilter instanceof AcceptAllModulesFilter && functionFilter instanceof AcceptAllQualifiedNamesFilter) {
// If there are no filtering options selected, then we can just dump the cache
dataEntries = gemToReferenceFrequencyMap.entrySet();
} else {
Map<Pair<QualifiedName, QualifiedName>, Integer> workspaceDependeeFrequencies = new HashMap<Pair<QualifiedName, QualifiedName>, Integer>();
Map<QualifiedName, Integer> workspaceReferenceFrequencies = new HashMap<QualifiedName, Integer>();
for(int i = 0; i < moduleContainer.getNModules(); i++) {
ModuleTypeInfo moduleTypeInfo = moduleContainer.getNthModuleTypeInfo(i);
if(moduleFilter.acceptModule(moduleTypeInfo.getModuleName())) {
// Parse a SourceModel to process
SourceModel.ModuleDefn moduleDefn = moduleContainer.getSourceModel(moduleTypeInfo.getModuleName(), false, new MessageLogger());
// Fold this module's frequencies into the grand total
Map<Pair<QualifiedName, QualifiedName>, Integer> referenceFrequencies = SourceMetricFinder.computeReferenceFrequencies(moduleDefn, moduleTypeInfo, functionFilter, traceSkippedFunctions);
addFrequencyMetrics(referenceFrequencies, workspaceDependeeFrequencies);
} else {
System.out.println("Skipping test module " + moduleTypeInfo.getModuleName());
}
}
// Summarize the dependee data into reference frequency data
for (final Map.Entry<Pair<QualifiedName, QualifiedName>, Integer> entry : workspaceDependeeFrequencies.entrySet()) {
Pair<QualifiedName, QualifiedName> dependeePair = entry.getKey();
Integer frequency = entry.getValue();
QualifiedName dependee = dependeePair.fst();
addFrequencyEntry(dependee, frequency.intValue(), workspaceReferenceFrequencies);
}
dataEntries = workspaceReferenceFrequencies.entrySet();
}
// Iterate over the possibly-filtered collection and dump them out
for (final Map.Entry<QualifiedName, Integer> entry : dataEntries) {
QualifiedName name = entry.getKey();
Integer frequency = entry.getValue();
buffer.append(name.getModuleName());
buffer.append(",");
buffer.append(name.getUnqualifiedName());
buffer.append(",");
buffer.append(frequency.intValue());
buffer.append("\n");
}
return buffer.toString();
}
/**
* {@inheritDoc}
*/
public void dumpLintWarnings(ModuleFilter moduleFilter, QualifiedNameFilter functionFilter, boolean traceSkippedModulesAndFunctions,
boolean includeUnplingedPrimitiveArgs, boolean includeRedundantLambdas, boolean includeUnusedPrivates, boolean includeMismatchedWrapperPlings, boolean includeUnreferencedLetVariables) {
for(int i = 0; i < moduleContainer.getNModules(); i++) {
ModuleTypeInfo moduleTypeInfo = moduleContainer.getNthModuleTypeInfo(i);
if(moduleFilter.acceptModule(moduleTypeInfo.getModuleName())) {
// Parse a SourceModel to process
SourceModel.ModuleDefn moduleDefn = moduleContainer.getSourceModel(moduleTypeInfo.getModuleName(), false, new MessageLogger());
// Dump the warnings
List<LintWarning> warnings = SourceMetricFinder.computeLintWarnings(moduleDefn, moduleTypeInfo, functionFilter, traceSkippedModulesAndFunctions,
includeUnplingedPrimitiveArgs, includeRedundantLambdas, includeUnusedPrivates, includeMismatchedWrapperPlings, includeUnreferencedLetVariables);
for (final LintWarning lintWarning : warnings) {
System.out.println(lintWarning.toString());
}
} else if(traceSkippedModulesAndFunctions) {
System.out.println("Skipping test module " + moduleTypeInfo.getModuleName());
}
}
}
/**
* Helper method for all the different kinds of search. Walks over each
* module in the workspace, performing the specified SourceMetricFinder search
* on each module where the target entity is visible.
* @param targetEntityList List of ScopedEntities to search for
* @param searchType Type of search to perform
* @return List of SearchResults of search hits
*/
private List<SearchResult> performSearch(List<? extends ScopedEntity> targetEntityList, SourceMetricFinder.SearchType searchType, CompilerMessageLogger messageLogger) {
if(targetEntityList.isEmpty()) {
return Collections.emptyList();
}
List<SearchResult> searchResults = new ArrayList<SearchResult>();
// Process modules in sorted order to ensure that the results come
// back sorted by module name.
for (final ModuleTypeInfo moduleTypeInfo : getSortedModuleTypeInfos()) {
Set<QualifiedName> qualifiedTargets = new HashSet<QualifiedName>();
for (final ScopedEntity scopedEntity : targetEntityList) {
if(isEntityVisibleToModule(scopedEntity, moduleTypeInfo)) {
qualifiedTargets.add(scopedEntity.getName());
}
}
if(qualifiedTargets.size() == 0) {
continue;
}
List<QualifiedName> targetNames = new ArrayList<QualifiedName>(qualifiedTargets);
if(!moduleContainsHits(moduleTypeInfo, targetNames, searchType)) {
continue;
}
// Parse a SourceModel to process
MessageLogger moduleLogger = new MessageLogger();
ModuleName moduleName = moduleTypeInfo.getModuleName();
SourceModel.ModuleDefn moduleDefn = moduleContainer.getSourceModel(moduleName, false, moduleLogger);
// if the module is a sourceless module, then we need to process it differently.
if (moduleDefn == null && moduleLogger.getNErrors() == 0) {
searchResults.addAll(performSourcelessSearch(moduleTypeInfo, targetNames, searchType));
continue;
}
if(moduleDefn == null) {
// Copy messages, adding source names to each SourcePosition
for (final CompilerMessage compilerMessage : moduleLogger.getCompilerMessages()) {
SourceRange sourceRange = compilerMessage.getSourceRange();
if(sourceRange != null) {
SourceRange augmentedRange = getAugmentedRange(sourceRange, moduleName);
messageLogger.logMessage(new CompilerMessage(augmentedRange, compilerMessage.getMessageKind()));
} else {
messageLogger.logMessage(compilerMessage);
}
}
continue;
}
// Find all the hits in the module and absorb them into the full total
List<SearchResult.Precise> moduleSearchResults = SourceMetricFinder.performSearch(moduleDefn, moduleTypeInfo, searchType, targetNames);
searchResults.addAll(augmentSearchResultsWithContextLines(moduleSearchResults, moduleContainer.getModuleSource(moduleName)));
}
return Collections.unmodifiableList(searchResults);
}
/**
* Return 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 sames as the given one except that the module name is updated.
*/
private SourceRange getAugmentedRange(SourceRange sourceRange, ModuleName moduleName){
SourcePosition augmentedStartPosition = new SourcePosition(sourceRange.getStartLine(), sourceRange.getStartColumn(), moduleName);
SourcePosition augmentedEndPosition = new SourcePosition(sourceRange.getEndLine(), sourceRange.getEndColumn(), moduleName);
return new SourceRange(augmentedStartPosition, augmentedEndPosition);
}
/**
* Helper method for all the different kinds of search. Walks over each
* module in the workspace, performing the specified SourceMetricFinder
* search on each module where the target entity is visible.
*
* @param targetEntityList
* List of ScopedEntities to search for
* @param targetModuleTypeInfos The list of moduleTypeInfos to search in the order to be searched.
* @param searchType Type of search to perform
* @param monitor A callback function to receive information about the progression of the search. This may be null.
*/
private void performSearch(
List<? extends ScopedEntity> targetEntityList,
Collection<ModuleTypeInfo> targetModuleTypeInfos,
SourceMetricFinder.SearchType searchType,
IProgressMonitor monitor,
CompilerMessageLogger messageLogger) {
if (targetEntityList.isEmpty()) {
return;
}
if (monitor != null){
monitor.numberOfModules(targetModuleTypeInfos.size());
}
// Process modules in sorted order to ensure that the results come
// back sorted by module name.
for (final ModuleTypeInfo moduleTypeInfo : targetModuleTypeInfos) {
if (monitor != null){
monitor.startModule(moduleTypeInfo.getModuleName());
if (monitor.isCancelled()){
return;
}
}
Set<QualifiedName> qualifiedTargets = new HashSet<QualifiedName>();
for (final ScopedEntity scopedEntity : targetEntityList) {
if (isEntityVisibleToModule(scopedEntity, moduleTypeInfo)) {
qualifiedTargets.add(scopedEntity.getName());
}
}
if (qualifiedTargets.size() == 0) {
continue;
}
List<QualifiedName> targetNames = new ArrayList<QualifiedName>(qualifiedTargets);
if (!moduleContainsHits(moduleTypeInfo, targetNames,
searchType)) {
continue;
}
// Parse a SourceModel to process
MessageLogger moduleLogger = new MessageLogger();
ModuleName moduleName = moduleTypeInfo.getModuleName();
SourceModel.ModuleDefn moduleDefn = moduleContainer.getSourceModel(moduleName, false, moduleLogger);
// if the module is a sourceless module, then we need to process it
// differently.
if (moduleDefn == null && moduleLogger.getNErrors() == 0) {
monitor.moreResults(performSourcelessSearch(moduleTypeInfo, targetNames, searchType));
continue;
}
if (moduleDefn == null) {
// Copy messages, adding source names to each SourcePosition
for (final CompilerMessage compilerMessage : moduleLogger.getCompilerMessages()) {
final SourceRange sourceRange = compilerMessage.getSourceRange();
if (sourceRange != null) {
SourceRange augmentedSourceRange = getAugmentedRange(sourceRange, moduleName);
messageLogger.logMessage(new CompilerMessage(
augmentedSourceRange, compilerMessage.getMessageKind()));
} else {
messageLogger.logMessage(compilerMessage);
}
}
continue;
}
// Find all the hits in the module and absorb them into the full
// total
List<SearchResult.Precise> moduleSearchResults = SourceMetricFinder.performSearch(
moduleDefn, moduleTypeInfo, searchType, targetNames);
Collection<SearchResult> moreResults =
augmentSearchResultsWithContextLines(moduleSearchResults, moduleContainer.getModuleSource(moduleName));
monitor.moreResults(moreResults);
}
}
/**
* Searches a sourceless module for references, definitions, or instances, and returns a list of SearchResult
* representing the hits.
* <p>
* NOTE: this method is closely related to {@link #moduleContainsHits}. Modifying one should require modifications in the other.
*
* @param moduleTypeInfo the ModuleTypeInfo for the sourceless module to be searched.
* @param targetNames the List of QualifiedNames of the entities to search for.
* @param searchType type of search to perform.
* @return a List of the search results.
*/
private List<SearchResult.Frequency> performSourcelessSearch(ModuleTypeInfo moduleTypeInfo, List<QualifiedName> targetNames, SourceMetricFinder.SearchType searchType) {
ModuleName moduleName = moduleTypeInfo.getModuleName();
LinkedHashMap<Pair<QualifiedName, SearchResult.Frequency.Type>, Integer> results = new LinkedHashMap<Pair<QualifiedName, SearchResult.Frequency.Type>, Integer>();
if(searchType == SourceMetricFinder.SearchType.REFERENCES || searchType == SourceMetricFinder.SearchType.REFERENCES_CONSTRUCTIONS || searchType == SourceMetricFinder.SearchType.ALL) {
for(int i = 0; i < moduleTypeInfo.getNFunctions(); i++) {
Function functionEntity = moduleTypeInfo.getNthFunction(i);
for (final QualifiedName qualifiedName : targetNames) {
Map<QualifiedName, Integer> dependeeToFrequencyMap = functionEntity.getDependeeToFrequencyMap();
if(dependeeToFrequencyMap.containsKey(qualifiedName)) {
Integer frequency = dependeeToFrequencyMap.get(qualifiedName);
incrementFrequencyCountForSearchResult(results, qualifiedName, SearchResult.Frequency.Type.FUNCTION_REFERENCES, frequency.intValue());
}
}
}
for(int i = 0; i < moduleTypeInfo.getNClassInstances(); i++) {
ClassInstance classInstance = moduleTypeInfo.getNthClassInstance(i);
for(int j = 0; j < classInstance.getNInstanceMethods(); j++) {
QualifiedName resolvingFunctionName = classInstance.getInstanceMethod(j);
if (resolvingFunctionName != null) {
for (final QualifiedName qualifiedName : targetNames) {
if(resolvingFunctionName.equals(qualifiedName)) {
incrementFrequencyCountForSearchResult(results, qualifiedName, SearchResult.Frequency.Type.INSTANCE_METHOD_REFERENCES, 1);
}
}
if(searchType == SourceMetricFinder.SearchType.ALL) {
for (final QualifiedName targetName : targetNames) {
TypeClass targetClass = moduleTypeInfo.getVisibleTypeClass(targetName);
List<Set<TypeClass>> constraintList = classInstance.getSuperclassPolymorphicVarConstraints();
for(int constraintIdx = 0; constraintIdx < constraintList.size(); constraintIdx++) {
Set<TypeClass> constraints = constraintList.get(constraintIdx);
if(constraints.contains(targetClass)) {
incrementFrequencyCountForSearchResult(results, targetName, SearchResult.Frequency.Type.CLASS_CONSTRAINTS, 1);
}
}
}
}
}
}
}
}
if(searchType == SourceMetricFinder.SearchType.INSTANCES || searchType == SourceMetricFinder.SearchType.ALL) {
for(int i = 0; i < moduleTypeInfo.getNClassInstances(); i++) {
ClassInstance classInstance = moduleTypeInfo.getNthClassInstance(i);
for (final QualifiedName qualifiedName : targetNames) {
if(classInstance.getTypeClass().getName().equals(qualifiedName)) {
incrementFrequencyCountForSearchResult(results, qualifiedName, SearchResult.Frequency.Type.INSTANCES, 1);
}
}
}
}
if(searchType == SourceMetricFinder.SearchType.CLASSES || searchType == SourceMetricFinder.SearchType.ALL) {
for(int i = 0; i < moduleTypeInfo.getNClassInstances(); i++) {
ClassInstance classInstance = moduleTypeInfo.getNthClassInstance(i);
for (final QualifiedName qualifiedName : targetNames) {
TypeExpr typeExpr = classInstance.getType();
if(typeExpr.getArity() > 0 && qualifiedName.equals(CAL_Prelude.TypeConstructors.Function)) {
incrementFrequencyCountForSearchResult(results, qualifiedName, SearchResult.Frequency.Type.CLASSES, 1);
}
TypeConsApp rootTypeConsApp = typeExpr.rootTypeConsApp();
if(rootTypeConsApp == null) {
continue;
}
if(rootTypeConsApp.getName().equals(qualifiedName)) {
incrementFrequencyCountForSearchResult(results, qualifiedName, SearchResult.Frequency.Type.CLASSES, 1);
}
}
}
}
if(searchType == SourceMetricFinder.SearchType.DEFINITION || searchType == SourceMetricFinder.SearchType.ALL) {
for (final QualifiedName qualifiedName : targetNames) {
if(qualifiedName.getModuleName().equals(moduleTypeInfo.getModuleName())) {
incrementFrequencyCountForSearchResult(results, qualifiedName, SearchResult.Frequency.Type.DEFINITION, 1);
}
}
}
if(searchType == SourceMetricFinder.SearchType.ALL) {
for (final QualifiedName qualifiedName : targetNames) {
if(moduleTypeInfo.doesImportedNameOccur(qualifiedName)) {
incrementFrequencyCountForSearchResult(results, qualifiedName, SearchResult.Frequency.Type.IMPORT, 1);
}
}
}
List<SearchResult.Frequency> resultList = new ArrayList<SearchResult.Frequency>();
for (final Map.Entry<Pair<QualifiedName, Type>, Integer> entry : results.entrySet()) {
Pair<QualifiedName, SearchResult.Frequency.Type> targetNameAndType = entry.getKey();
QualifiedName qualifiedName = targetNameAndType.fst();
SearchResult.Frequency.Type type = targetNameAndType.snd();
int frequency = entry.getValue().intValue();
resultList.add(new SearchResult.Frequency(moduleName, qualifiedName, frequency, type));
}
return resultList;
}
/**
* Helper method for incrementing the frequency count for a particular kind of search hit in a sourceless module.
* @param map the map to be modified, mapping Pair<QualifiedName, SearchResult.Frequency.Type> to an Integer representing
* the frequency of that particular kind of search hit.
* @param targetName the name of the search hit's target.
* @param type the type of the search hit.
* @param freq the frequency increment.
*/
private void incrementFrequencyCountForSearchResult(LinkedHashMap<Pair<QualifiedName, SearchResult.Frequency.Type>, Integer> map, QualifiedName targetName, SearchResult.Frequency.Type type, int freq) {
Pair<QualifiedName, SearchResult.Frequency.Type> key = new Pair<QualifiedName, SearchResult.Frequency.Type>(targetName, type);
Integer value = map.get(key);
if (value == null) {
map.put(key, Integer.valueOf(freq));
} else {
map.put(key, Integer.valueOf(value.intValue() + freq));
}
}
/**
* @return The ModuleTypeInfos in the current workspace, sorted by
* module name.
*/
public SortedSet<ModuleTypeInfo> getSortedModuleTypeInfos() {
SortedSet<ModuleTypeInfo> sortedModuleTypeInfos = new TreeSet<ModuleTypeInfo>(new Comparator<ModuleTypeInfo>() {
public int compare(ModuleTypeInfo a, ModuleTypeInfo b) {
ModuleName leftModuleName = a.getModuleName();
ModuleName rightModuleName = b.getModuleName();
return leftModuleName.compareTo(rightModuleName);
}
});
for(int i = 0; i < moduleContainer.getNModules(); i++) {
sortedModuleTypeInfos.add(moduleContainer.getNthModuleTypeInfo(i));
}
return sortedModuleTypeInfos;
}
/**
* Helper function that accepts a list of SearchResults and returns a new list of search results
* with contextLines filled in. Assumes that the SearchResults have valid SourcePositions that
* refer to locations in moduleSource.
*
* @param searchResults List of SearchResults to return new, augmented versions of
* @param moduleSource String containing the source text that corresponds to the SourcePositions in
* the SearchResults of searchResults.
* @return List of SearchResults with their contextLines set based on their SourcePositions in moduleSource
*/
private static List<SearchResult> augmentSearchResultsWithContextLines(List<? extends SearchResult> searchResults, String moduleSource) {
List<SearchResult> results = new ArrayList<SearchResult>(searchResults.size());
SourcePosition previousPosition = null;
int previousIndex = 0;
for (final SearchResult searchResult : searchResults) {
if (searchResult instanceof SearchResult.Precise) {
SearchResult.Precise preciseSearchResult = (SearchResult.Precise)searchResult;
SourcePosition currentPosition = preciseSearchResult.getSourcePosition();
SourcePosition lineStart = new SourcePosition(currentPosition.getLine(), 1, currentPosition.getSourceName());
int startIndex;
int currentIndex;
if (previousPosition != null && SourcePosition.compareByPosition.compare(previousPosition, lineStart) <= 0) {
startIndex = lineStart.getPosition(moduleSource, previousPosition, previousIndex);
currentIndex = currentPosition.getPosition(moduleSource, lineStart, startIndex);
} else {
startIndex = lineStart.getPosition(moduleSource);
currentIndex = currentPosition.getPosition(moduleSource, lineStart, startIndex);
}
int endIndex = moduleSource.indexOf('\n', startIndex);
String line = moduleSource.substring(startIndex, endIndex);
previousPosition = lineStart;
previousIndex = startIndex;
results.add(new SearchResult.Precise(preciseSearchResult.getSourceRange(), preciseSearchResult.getName(), preciseSearchResult.getCategory(), line, currentIndex - startIndex, preciseSearchResult.refersToJavaSource()));
} else {
results.add(searchResult);
}
}
return results;
}
/**
* Helper function for pre-filtering modules before they have been parsed. ModuleTypeInfo doesn't contain
* enough data to tell where in a module's source text a hit occurs, but it does contain enough data to tell
* whether or not there are any hits in the module. Scanning this data is a lot faster than parsing a module
* into a SourceModel and walking it, so before we parse each module we do a quick check to see if there are
* any hits.
* <p>
* NOTE: this method is closely related to {@link #performSourcelessSearch}. Modifying one should require modifications in the other.
*
* @param moduleTypeInfo ModuleTypeInfo of module to check for hits
* @param targetNames List of QualifiedNames of search targets
* @param searchType Type of search being performed
* @return boolean true if the module contains at least one search hit for the specified search type and targets, or false otherwise.
*/
private static boolean moduleContainsHits(ModuleTypeInfo moduleTypeInfo, List<QualifiedName> targetNames, SourceMetricFinder.SearchType searchType) {
if(searchType == SourceMetricFinder.SearchType.REFERENCES || searchType == SourceMetricFinder.SearchType.REFERENCES_CONSTRUCTIONS || searchType == SourceMetricFinder.SearchType.ALL) {
for(int i = 0; i < moduleTypeInfo.getNFunctions(); i++) {
Function functionEntity = moduleTypeInfo.getNthFunction(i);
for (final QualifiedName qualifiedName : targetNames) {
// Short-circuit return as soon as we know that there is a reference to one
// of the target names in this module.
if(functionEntity.getDependeeToFrequencyMap().keySet().contains(qualifiedName)) {
return true;
}
}
}
for(int i = 0; i < moduleTypeInfo.getNClassInstances(); i++) {
ClassInstance classInstance = moduleTypeInfo.getNthClassInstance(i);
for(int j = 0; j < classInstance.getNInstanceMethods(); j++) {
QualifiedName resolvingFunctionName = classInstance.getInstanceMethod(j);
if (resolvingFunctionName != null) {
for (final QualifiedName qualifiedName : targetNames) {
// Short-circuit return as soon as we find an instance method that
// resolves to one of the target names
if(resolvingFunctionName.equals(qualifiedName)) {
return true;
}
}
if(searchType == SourceMetricFinder.SearchType.ALL) {
for (final QualifiedName targetName : targetNames) {
TypeClass targetClass = moduleTypeInfo.getVisibleTypeClass(targetName);
List<Set<TypeClass>> constraintArray = classInstance.getSuperclassPolymorphicVarConstraints();
for(int constraintIdx = 0; constraintIdx < constraintArray.size(); constraintIdx++) {
Set<TypeClass> constraints = constraintArray.get(constraintIdx);
if(constraints.contains(targetClass)) {
return true;
}
}
}
}
}
}
}
}
if(searchType == SourceMetricFinder.SearchType.INSTANCES || searchType == SourceMetricFinder.SearchType.ALL) {
for(int i = 0; i < moduleTypeInfo.getNClassInstances(); i++) {
ClassInstance classInstance = moduleTypeInfo.getNthClassInstance(i);
for (final QualifiedName qualifiedName : targetNames) {
// Short-circuit return as soon as we know that there is an instance of
// one of the target classes in the module
if(classInstance.getTypeClass().getName().equals(qualifiedName)) {
return true;
}
}
}
}
if(searchType == SourceMetricFinder.SearchType.CLASSES || searchType == SourceMetricFinder.SearchType.ALL) {
for(int i = 0; i < moduleTypeInfo.getNClassInstances(); i++) {
ClassInstance classInstance = moduleTypeInfo.getNthClassInstance(i);
for (final QualifiedName qualifiedName : targetNames) {
TypeExpr typeExpr = classInstance.getType();
if(typeExpr.getArity() > 0 && qualifiedName.equals(CAL_Prelude.TypeConstructors.Function)) {
return true;
}
TypeConsApp rootTypeConsApp = typeExpr.rootTypeConsApp();
if(rootTypeConsApp == null) {
continue;
}
if(rootTypeConsApp.getName().equals(qualifiedName)) {
return true;
}
}
}
}
if(searchType == SourceMetricFinder.SearchType.DEFINITION || searchType == SourceMetricFinder.SearchType.ALL) {
for (final QualifiedName qualifiedName : targetNames) {
if(qualifiedName.getModuleName().equals(moduleTypeInfo.getModuleName())) {
return true;
}
}
}
if(searchType == SourceMetricFinder.SearchType.ALL) {
for (final QualifiedName qualifiedName : targetNames) {
if(moduleTypeInfo.doesImportedNameOccur(qualifiedName)) {
return true;
}
}
}
return false;
}
/**
* Check whether an entity can be seen in the specified module
* @param scopedEntity ScopedEntityImpl entity to check visibility of
* @param moduleTypeInfo ModuleTypeInfo of module to check for visibility from
* @return true if the module specified by metaModule can see the gem specified by envEntity,
* or false otherwise.
*/
private static boolean isEntityVisibleToModule(ScopedEntity scopedEntity, ModuleTypeInfo moduleTypeInfo) {
return moduleTypeInfo.isEntityVisible(scopedEntity);
}
/**
* Find all the instances of the given class.
* The monitor is called with incremental updates to the search results.
* @param targetName name (as a String) of the gem to look for references to
* @param moduleTypeInfos A list of the ModuleTypeInfo's of the modules to search.
* @param monitor An object that is notified of progress of the search.
* @param messageLogger Used to log messages describing conditions encountered
* during searching (eg, unparseable module file encountered).
* This argument must not be null.
*/
public void findInstanceOfClass(String targetName, Collection<ModuleTypeInfo> moduleTypeInfos, IProgressMonitor monitor, CompilerMessageLogger messageLogger){
if (messageLogger == null){
throw new NullPointerException("messageLogger must not be null");
}
List<ScopedEntity> targetEntities = findMatchingEntities(targetName);
performSearch(targetEntities, moduleTypeInfos, SourceMetricFinder.SearchType.INSTANCES, monitor, messageLogger);
}
/**
* Finds all of the entities in the workspace that have the specified unqualified name
* @param targetName String possibly-wildcarded name to search for
* @return List of ScopedEntity of entities with the specified unqualified name
*/
private List<ScopedEntity> findMatchingEntities(String targetName) {
String unqualifiedName = targetName;
Matcher moduleMatcher = null;
int indexOfPeriod = targetName.lastIndexOf('.');
if(indexOfPeriod != -1) {
unqualifiedName = targetName.substring(indexOfPeriod + 1);
String moduleName = targetName.substring(0, indexOfPeriod);
// we prepend the pattern with (.+\.)? so that if the user specified C.f, then B.C.f and A.B.C.f would also match
String modulePattern = "(.+\\.)?" + WildcardPatternMatcher.wildcardPatternToRegExp(moduleName);
moduleMatcher = Pattern.compile(modulePattern, Pattern.CASE_INSENSITIVE).matcher("");
}
Matcher unqualifiedMatcher = Pattern.compile(WildcardPatternMatcher.wildcardPatternToRegExp(unqualifiedName), Pattern.CASE_INSENSITIVE).matcher("");
List<ScopedEntity> results = new ArrayList<ScopedEntity>();
// Process modules in sorted order to ensure that the results come
// back sorted by module name.
for (final ModuleTypeInfo moduleTypeInfo : getSortedModuleTypeInfos()) {
if(moduleMatcher != null) {
moduleMatcher.reset(moduleTypeInfo.getModuleName().toSourceText());
if(!moduleMatcher.matches()) {
continue;
}
}
for(int j = 0; j < moduleTypeInfo.getNTypeClasses(); j++) {
TypeClass typeClass = moduleTypeInfo.getNthTypeClass(j);
unqualifiedMatcher.reset(typeClass.getName().getUnqualifiedName());
if(unqualifiedMatcher.matches()) {
results.add(typeClass);
}
}
for(int j = 0; j < moduleTypeInfo.getNTypeConstructors(); j++) {
TypeConstructor typeConstructor = moduleTypeInfo.getNthTypeConstructor(j);
unqualifiedMatcher.reset(typeConstructor.getName().getUnqualifiedName());
if(unqualifiedMatcher.matches()) {
results.add(typeConstructor);
}
}
FunctionalAgent[] envEntities = moduleTypeInfo.getFunctionalAgents();
for (final FunctionalAgent envEntity : envEntities) {
unqualifiedMatcher.reset(envEntity.getName().getUnqualifiedName());
if(unqualifiedMatcher.matches()) {
results.add(envEntity);
}
}
}
return results;
}
private List<ScopedEntity> findMatchingEntities(QualifiedName qualifiedName) {
// Ensure that the target gem is visible before doing any serious work
ModuleTypeInfo homeModuleTypeInfo = moduleContainer.getModuleTypeInfo(qualifiedName.getModuleName());
if(homeModuleTypeInfo == null){
return Collections.emptyList();
}
List<ScopedEntity> targetList = new ArrayList<ScopedEntity>();
FunctionalAgent envEntity = homeModuleTypeInfo.getFunctionalAgent(qualifiedName.getUnqualifiedName());
if(envEntity != null) {
targetList.add(envEntity);
}
TypeClass typeClass = homeModuleTypeInfo.getTypeClass(qualifiedName.getUnqualifiedName());
if(typeClass != null) {
targetList.add(typeClass);
}
TypeConstructor typeCons = homeModuleTypeInfo.getTypeConstructor(qualifiedName.getUnqualifiedName());
if(typeCons != null) {
targetList.add(typeCons);
}
return targetList;
}
/**
* {@inheritDoc}
* TODOJ The QualifiedName-based API is no longer used and may want to be deleted
*/
public List<SearchResult> findAllOccurrences(QualifiedName targetGem, CompilerMessageLogger messageLogger) {
if(messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
List<ScopedEntity> targetEntities = findMatchingEntities(targetGem);
return performSearch(targetEntities, SourceMetricFinder.SearchType.ALL, messageLogger);
}
/**
* {@inheritDoc}
*/
public List<SearchResult> findAllOccurrences(String targetName, CompilerMessageLogger messageLogger) {
if(messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
List<ScopedEntity> targetEntities = findMatchingEntities(targetName);
return performSearch(targetEntities, SourceMetricFinder.SearchType.ALL, messageLogger);
}
/**
* Find all the occurrences of the specified ScopedEntityImpl.
* The monitor is called with incremental updates to the search results.
* @param targetName name (as a String) of the entity to find occurrences of
* @param moduleTypeInfos A list of the ModuleTypeInfo's of the modules to search.
* @param monitor An object that is notified of progress of the search.
* @param messageLogger Used to log messages describing conditions encountered
* during searching (eg, unparseable module file encountered).
* This argument must not be null.
*/
public void findAllOccurrences(String targetName,
Collection<ModuleTypeInfo> moduleTypeInfos,
IProgressMonitor monitor,
CompilerMessageLogger messageLogger) {
if (messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
List<ScopedEntity> targetEntities = findMatchingEntities(targetName);
performSearch(targetEntities, moduleTypeInfos, SourceMetricFinder.SearchType.ALL, monitor, messageLogger);
}
/**
* {@inheritDoc}
* TODOJ The QualifiedName-based API is no longer used and may want to be deleted
*/
public List<SearchResult> findReferences(QualifiedName targetGem, CompilerMessageLogger messageLogger) {
if(messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
List<ScopedEntity> targetEntities = findMatchingEntities(targetGem);
return performSearch(targetEntities, SourceMetricFinder.SearchType.REFERENCES, messageLogger);
}
/**
* {@inheritDoc}
*/
public List<SearchResult> findReferences(String targetName, CompilerMessageLogger messageLogger) {
if(messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
List<ScopedEntity> targetEntities = findMatchingEntities(targetName);
return performSearch(targetEntities, SourceMetricFinder.SearchType.REFERENCES, messageLogger);
}
/**
* Find all the references to the specified gem in the workspace and return a list
* of SearchResults of reference to the gem.
* The monitor is called with incremental updates to the search results.
* @param targetName name (as a String) of the gem to look for references to
* @param moduleTypeInfos A list of the ModuleTypeInfo's of the modules to search.
* @param monitor An object that is notified of progress of the search.
* @param messageLogger Used to log messages describing conditions encountered
* during searching (eg, unparseable module file encountered).
* This argument must not be null.
*/
public void findReferences(String targetName,
Collection<ModuleTypeInfo> moduleTypeInfos,
IProgressMonitor monitor,
CompilerMessageLogger messageLogger) {
if (messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
List<ScopedEntity> targetEntities = findMatchingEntities(targetName);
performSearch(targetEntities, moduleTypeInfos, SourceMetricFinder.SearchType.REFERENCES, monitor, messageLogger);
}
/**
* {@inheritDoc}
* TODOJ The QualifiedName-based API is no longer used and may want to be deleted
*/
public List<SearchResult> findInstancesOfClass(QualifiedName targetClass, CompilerMessageLogger messageLogger) {
if(messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
// Ensure that the target class is visible before doing any serious work
ModuleTypeInfo homeModuleTypeInfo = moduleContainer.getModuleTypeInfo(targetClass.getModuleName());
if(homeModuleTypeInfo == null) {
return Collections.emptyList();
}
TypeClass typeClass = homeModuleTypeInfo.getTypeClass(targetClass.getUnqualifiedName());
if(typeClass == null) {
return Collections.emptyList();
}
return performSearch(Collections.<ScopedEntity>singletonList(typeClass), SourceMetricFinder.SearchType.INSTANCES, messageLogger);
}
/**
* {@inheritDoc}
*/
public List<SearchResult> findInstancesOfClass(String targetName, CompilerMessageLogger messageLogger) {
if(messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
List<ScopedEntity> targetEntities = findMatchingEntities(targetName);
return performSearch(targetEntities, SourceMetricFinder.SearchType.INSTANCES, messageLogger);
}
/**
* {@inheritDoc}
*/
public List<SearchResult> findTypeInstances(String targetName, CompilerMessageLogger messageLogger) {
if(messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
List<ScopedEntity> targetEntities = findMatchingEntities(targetName);
return performSearch(targetEntities, SourceMetricFinder.SearchType.CLASSES, messageLogger);
}
/**
* Find all the instances associated with the specified type (i.e., find all of the type classes of
* which the specified type is an instance) and return a list of SearchResults of the relevant
* instance definitions.
* The monitor is called with incremental updates to the search results.
* @param targetName name (as a String) of the gem to look for references to
* @param moduleTypeInfos A list of the ModuleTypeInfo's of the modules to search.
* @param monitor An object that is notified of progress of the search.
* @param messageLogger Used to log messages describing conditions encountered
* during searching (eg, unparseable module file encountered).
* This argument must not be null.
*/
public void findTypeInstances(String targetName,
Collection<ModuleTypeInfo> moduleTypeInfos,
IProgressMonitor monitor,
CompilerMessageLogger messageLogger) {
if (messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
List<ScopedEntity> targetEntities = findMatchingEntities(targetName);
performSearch(targetEntities, moduleTypeInfos,
SourceMetricFinder.SearchType.CLASSES, monitor, messageLogger);
}
/**
* Find all constructions of the specified data constructor and return a list
* of SearchResults of the expressions.
* The monitor is called with incremental updates to the search results.
* @param targetName name (as a String) of the gem to look for constructions of
* @param moduleTypeInfos A list of the ModuleTypeInfo's of the modules to search.
* @param monitor An object that is notified of progress of the search.
* @param messageLogger Used to log messages describing conditions encountered
* during searching (eg, unparseable module file encountered).
* This argument must not be null.
*/
public void findConstructions(String targetName,
Collection<ModuleTypeInfo> moduleTypeInfos,
IProgressMonitor monitor,
CompilerMessageLogger messageLogger) {
if (messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
final List<ScopedEntity> allTargetEntities = findMatchingEntities(targetName);
final List<DataConstructor> targetEntities = convertToOnlyConstructors(allTargetEntities);
performSearch(targetEntities, moduleTypeInfos,
SourceMetricFinder.SearchType.REFERENCES_CONSTRUCTIONS, monitor, messageLogger);
}
/**
* If the specified target is a constructor then find all constructions of the specified constructor.
* If the specified target is a type then final all constructions of any constructor that has the
* given type.
* @param targetName name (as a String) of the constructor to look for constructions of
* or the type to look for constructions of its constructors.
* @param messageLogger Used to log messages describing conditions encountered
* during searching (eg, unparseable module file encountered).
* This argument must not be null.
*/
public List<SearchResult> findConstructions(String targetName, CompilerMessageLogger messageLogger){
final List<ScopedEntity> allTargetEntities = findMatchingEntities(targetName);
final List<DataConstructor> targetEntities = convertToOnlyConstructors(allTargetEntities);
return performSearch(targetEntities, SourceMetricFinder.SearchType.REFERENCES_CONSTRUCTIONS, messageLogger);
}
/**
* @param entities list of entities to convert
* @return a list of contructors from the given list as well as contructors of any types in the given list.
*/
private List<DataConstructor> convertToOnlyConstructors(final List<ScopedEntity> entities) {
final List<DataConstructor> targetEntities = new ArrayList<DataConstructor>();
for (final ScopedEntity entity : entities) {
if (entity instanceof DataConstructor){
targetEntities.add((DataConstructor)entity);
}
else if (entity instanceof TypeConstructor){
TypeConstructor typeConstructor = (TypeConstructor) entity;
final int nDataConstructors = typeConstructor.getNDataConstructors();
for(int n = 0; n < nDataConstructors; ++n){
targetEntities.add(typeConstructor.getNthDataConstructor(n));
}
}
}
return targetEntities;
}
/**
* {@inheritDoc}
* TODOJ The QualifiedName-based API is no longer used and may want to be deleted
*/
public List<SearchResult> findTypeInstances(QualifiedName targetType, CompilerMessageLogger messageLogger) {
if(messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
// Ensure that the target type is visible before doing any serious work
ModuleTypeInfo homeModuleTypeInfo = moduleContainer.getModuleTypeInfo(targetType.getModuleName());
if(homeModuleTypeInfo == null){
return Collections.emptyList();
}
TypeConstructor typeCons = homeModuleTypeInfo.getTypeConstructor(targetType.getUnqualifiedName());
if(typeCons == null) {
return Collections.emptyList();
}
return performSearch(Collections.<ScopedEntity>singletonList(typeCons), SourceMetricFinder.SearchType.CLASSES, messageLogger);
}
class ResultsCollector implements IProgressMonitor {
private final ArrayList<SearchResult> results = new ArrayList<SearchResult>();
public List<SearchResult> getResults(){
return results;
}
public void numberOfModules(int numberOfModules) {
}
public void startModule(ModuleName module) {
}
public void done() {
}
public boolean isCancelled() {
return false;
}
public void moreResults(Collection<? extends SearchResult> moreResults) {
results.addAll(moreResults);
}
}
/**
* {@inheritDoc}
* TODOJ The QualifiedName-based API is no longer used and may want to be deleted
*/
public List<SearchResult> findDefinition(Name targetName, CompilerMessageLogger messageLogger) {
if(messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
ResultsCollector results = new ResultsCollector();
findDefinitionHelper(Collections.singletonList(targetName), results, SourceMetricFinder.SearchType.DEFINITION, messageLogger);
return results.getResults();
}
public List<SearchResult> findDefinition(SearchResult.Precise targetIdentifier, CompilerMessageLogger messageLogger) {
return findDefinition(targetIdentifier, false, messageLogger);
}
/**
* Find the definition of the given targetIdentifier.
*/
public List<SearchResult> findDefinition(SearchResult.Precise targetIdentifier, boolean getSourceText, CompilerMessageLogger messageLogger) {
if(messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
ResultsCollector results = new ResultsCollector();
SearchType searchType = SearchType.DEFINITION;
if (getSourceText){
searchType = SearchType.SOURCE_TEXT;
}
findDefinitionHelper(Collections.singletonList(targetIdentifier.getName()), results, searchType, messageLogger);
final List<SearchResult> resultsList = results.getResults();
SourceIdentifier.Category category = targetIdentifier.getCategory();
// If there is not category information then return the unfiltered list.
if (category == null){
return resultsList;
}
// If category information is present use that to reduce the number of options.
final List<SearchResult> filteredList = new ArrayList<SearchResult>();
// LOCAL_DEFINITION and LOCAL_VARIABLE are the same for this purpose
if (category == SourceIdentifier.Category.LOCAL_VARIABLE_DEFINITION){
category = SourceIdentifier.Category.LOCAL_VARIABLE;
}
for (final SearchResult result : resultsList) {
if (result instanceof SearchResult.Precise) {
SearchResult.Precise preciseResult = (SearchResult.Precise)result;
SourceIdentifier.Category resultCategory = preciseResult.getCategory();
// LOCAL_DEFINITION and LOCAL_VARIABLE are the same for this purpose
if (resultCategory == SourceIdentifier.Category.LOCAL_VARIABLE_DEFINITION){
resultCategory = SourceIdentifier.Category.LOCAL_VARIABLE;
}
if (resultCategory == category || resultCategory == null){
filteredList.add(preciseResult);
}
}
}
return filteredList;
}
/**
* Find the type declaration for the given object if any.
* @param functionName the object to get the type declaration of
* @param messageLogger
* @return the type declaration position of the given object or null if not found.
*/
public SearchResult.Precise findTypeDeclaration(QualifiedName functionName, CompilerMessageLogger messageLogger) {
// See if we can find the start of the type declaration and include that.
MessageLogger logger = new MessageLogger();
final SourceModel.ModuleDefn sourceModel = moduleContainer.getSourceModel(functionName.getModuleName(), true, logger);
// if there are errors then skip getting the type.
if (logger.getNErrors() == 0){
final int nDefs = sourceModel.getNTopLevelDefns();
for(int iType = 0; iType < nDefs; ++iType){
final TopLevelSourceElement element = sourceModel.getNthTopLevelDefn(iType);
if (element instanceof FunctionTypeDeclaration){
FunctionTypeDeclaration typeDeclaration = (FunctionTypeDeclaration) element;
if (typeDeclaration.getFunctionName().equals(functionName.getUnqualifiedName())){
SourceRange sourceRangeOfType = typeDeclaration.getSourceRangeOfDefn();
return new Precise(sourceRangeOfType, functionName, null, false);
}
}
}
}
return null;
}
/**
* {@inheritDoc}
*/
public List<SearchResult> findDefinition(String targetName, CompilerMessageLogger messageLogger) {
if(messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
List<ScopedEntity> targetEntities = findMatchingEntities(targetName);
Set<Name> qualifiedTargets = new LinkedHashSet<Name>();
for (final ScopedEntity scopedEntity : targetEntities) {
qualifiedTargets.add(scopedEntity.getName());
}
ResultsCollector results = new ResultsCollector();
findDefinitionHelper(new ArrayList<Name>(qualifiedTargets), results, SourceMetricFinder.SearchType.DEFINITION, messageLogger);
return results.getResults();
}
/**
* Find the position at which the specified typeclass, type, data constructor, function, or method was defined.
* The monitor is called with incremental updates to the search results.
* @param targetName name (as a String) of the gem to look for references to
* @param moduleNames A list of the names of the modules to search.
* @param monitor An object that is notified of progress of the search.
* @param messageLogger Used to log messages describing conditions encountered
* during searching (eg, unparseable module file encountered).
* This argument must not be null.
*/
public void findDefinition(String targetName,
Collection<ModuleName> moduleNames,
IProgressMonitor monitor,
CompilerMessageLogger messageLogger) {
if (messageLogger == null) {
throw new NullPointerException("messageLogger must not be null");
}
List<ScopedEntity> targetEntities = findMatchingEntities(targetName);
Set<QualifiedName> qualifiedTargets = new LinkedHashSet<QualifiedName>();
for (final ScopedEntity scopedEntity : targetEntities) {
if (moduleNames.contains(scopedEntity.getName().getModuleName())){
qualifiedTargets.add(scopedEntity.getName());
}
}
findDefinitionHelper(new ArrayList<Name>(qualifiedTargets), monitor, SourceMetricFinder.SearchType.DEFINITION, messageLogger);
}
/**
* Find the definitions of each of qualifiedTargets. This is a helper
* function that the two versions of findDefinition delegate to.
*
* @param qualifiedTargets
* List of Names to search for. These should be grouped
* by module name (ie, [M2.a, M2.d, M2.c, Prelude.b] is okay, but
* [M2.a, Prelude.b, M2.c, M2.d] is not)
*/
private void findDefinitionHelper(Collection<Name> qualifiedTargets,
IProgressMonitor monitor,
SearchType searchType,
CompilerMessageLogger messageLogger) {
ModuleName prevModuleName = null;
if (monitor != null){
monitor.numberOfModules(qualifiedTargets.size());
}
List<Name> moduleTargets = new ArrayList<Name>(); // Targets in the current module
for (final Name targetName : qualifiedTargets) {
ModuleName homeModuleName = targetName.getModuleName();
if (monitor != null){
monitor.startModule(homeModuleName);
if (monitor.isCancelled()){
return;
}
}
if (homeModuleName.equals(prevModuleName)) {
moduleTargets.add(targetName);
} else {
if (prevModuleName != null) {
monitor.moreResults(findDefinitionsInSingleModule(prevModuleName, moduleTargets, searchType, messageLogger));
}
moduleTargets.clear();
prevModuleName = homeModuleName;
moduleTargets.add(targetName);
}
}
if (moduleTargets.size() > 0) {
monitor.moreResults(findDefinitionsInSingleModule(prevModuleName, moduleTargets, searchType, messageLogger));
}
}
/**
* Search for the definitions of qualifiedTargets in the module named moduleName.
* @param moduleName Name of the module to search
* @param qualifiedTargets List of Names of entities to find in the specified module.
* Each of these Names should have moduleName as their module name.
* @return A List of SearchResults (these will include contextLines)
*/
private List<? extends SearchResult> findDefinitionsInSingleModule(ModuleName moduleName, List<Name> qualifiedTargets, SearchType searchType, CompilerMessageLogger messageLogger) {
MessageLogger moduleLogger = new MessageLogger();
// Parse a SourceModel to process
ModuleTypeInfo homeModuleTypeInfo = moduleContainer.getModuleTypeInfo(moduleName);
if (homeModuleTypeInfo == null){
return Collections.emptyList();
}
SourceModel.ModuleDefn moduleDefn = moduleContainer.getSourceModel(moduleName, true, moduleLogger);
// if the module is a sourceless module, then we need to process it differently.
if (moduleDefn == null && moduleLogger.getNErrors() == 0) {
final List<QualifiedName> qualifiedNamesOnly = new ArrayList<QualifiedName>();
for (final Name name : qualifiedTargets) {
if (name instanceof QualifiedName) {
qualifiedNamesOnly.add((QualifiedName)name);
}
}
return performSourcelessSearch(homeModuleTypeInfo, qualifiedNamesOnly, SourceMetricFinder.SearchType.DEFINITION);
}
if(moduleDefn == null) {
// Copy messages, adding source names to each SourcePosition
for (final CompilerMessage compilerMessage : moduleLogger.getCompilerMessages()) {
SourceRange sourceRange = compilerMessage.getSourceRange();
if(sourceRange != null) {
SourceRange augmentedRange = getAugmentedRange(sourceRange, moduleName);
messageLogger.logMessage(new CompilerMessage(augmentedRange, compilerMessage.getMessageKind()));
} else {
messageLogger.logMessage(compilerMessage);
}
}
return Collections.emptyList();
}
// Find the hits
return augmentSearchResultsWithContextLines(SourceMetricFinder.performSearch(moduleDefn, homeModuleTypeInfo, searchType, qualifiedTargets), moduleContainer.getModuleSource(moduleName));
}
/**
* Search for the definitions of the selected symbol if one is selected.
* @param moduleName Name of the module to search
* @param line The line number of the symbol to search for. The first line in the file is line number one.
* @param column The column number of the symbol to search for. The first column in the line is column number one.
* @return A SearchResult.Precise object for the definition of the selected symbol or null if not found.
*/
public SearchResult findDefinition(ModuleName moduleName, int line, int column, CompilerMessageLogger messageLogger) {
SearchResult.Precise[] result = findSymbolAt(moduleName, line, column, messageLogger);
if (result == null) {
return null;
}
// Search for the definition of the given symbol. The result[0] is a hack.
// The hack used to be implicit in findSymbolAt. TODO GJM: fix this
List<SearchResult> definitions = findDefinition(result[0].getName(), messageLogger);
// todo-jowong this is a workaround since we do not have kind info on the name found
// (i.e. for a cons name whether it's a type cons, data cons or type class)
// for the time being, we just return the first one - the name is likely
// both a valid type cons and a valid data cons (and potentially also a valid type class).
if (definitions.size() >= 1){
return definitions.get(0);
}
else{
return null;
}
}
/**
* Find the position of a given entity.
* @param name The object to find the position of.
* @return The position of the entity. Maybe be null if the entity does not correspond to source.
*/
public SearchResult.Precise getPosition(QualifiedName name, Category categoryExpected){
MessageLogger messageLogger = new MessageLogger();
// Search for the definition of the given symbol
List<SearchResult> definitions = findDefinition(name, messageLogger);
// todo-jowong this is a workaround since we do not have kind info on the name found
// (i.e. for a cons name whether it's a type cons, data cons or type class)
// for the time being, we just return the first one - the name is likely
// both a valid type cons and a valid data cons (and potentially also a valid type class).
if (definitions.size() >= 1){
for (int i = 0; i < definitions.size(); i++) {
Object definition = definitions.get(i);
// Pick out the first SearchResult object
if (definition instanceof SearchResult.Precise){
final SourceIdentifier.Category categoryFound = ((SearchResult.Precise) definition).getCategory();
if (categoryFound != null){
if (categoryFound != categoryExpected){
continue;
}
}
return (SearchResult.Precise) definition;
}
}
}
return null;
}
/**
* Find the position of a given class instance.
* @return The position of the ClassInstance. Maybe be null if the entity does not correspond to source.
*/
public SourceRange getPosition(ClassInstance classInstance, CompilerMessageLogger logger){
SourceModel.ModuleDefn moduleSourceModel = moduleContainer.getSourceModel(classInstance.getModuleName(), true, logger);
ModuleTypeInfo moduleTypeInfo = moduleContainer.getModuleTypeInfo(classInstance.getModuleName());
if (moduleTypeInfo == null){
return null;
}
for( int i = 0; i < moduleSourceModel.getNTopLevelDefns(); ++i){
TopLevelSourceElement topLevelDefn = moduleSourceModel.getNthTopLevelDefn(i);
if (topLevelDefn instanceof SourceModel.InstanceDefn){
SourceModel.InstanceDefn instanceDefn = (InstanceDefn) topLevelDefn;
if (same(classInstance, instanceDefn, moduleTypeInfo)){
return instanceDefn.getSourceRangeOfName();
}
}
}
return null;
}
/**
* Checks if the given class instance (from the compiler) and the instance definition (from the source model)
* refer the the same instance.
* @param classInstance
* @param instanceDefn
* @return true if the classInstance and instanceDefn refer to the same object.
*
* TODO put this function in a better place
*/
public static boolean same(ClassInstance classInstance, InstanceDefn instanceDefn, ModuleTypeInfo moduleTypeInfo){
// check the type class names
final QualifiedName name = classInstance.getTypeClass().getName();
SourceModel.Name.TypeClass typeClassName = instanceDefn.getTypeClassName();
ModuleName typeClass_moduleName = resolveModuleName(moduleTypeInfo, typeClassName);
if (!name.getModuleName().equals(typeClass_moduleName)){
return false;
}
if (!name.getUnqualifiedName().equals(typeClassName.getUnqualifiedName())){
return false;
}
// check instance type cons
{
InstanceTypeCons instanceTypeCons = instanceDefn.getInstanceTypeCons();
if (instanceTypeCons instanceof InstanceTypeCons.TypeCons){
InstanceTypeCons.TypeCons typeCons = (TypeCons) instanceTypeCons;
SourceModel.Name.TypeCons typeConsName = typeCons.getTypeConsName();
ModuleName typeCons_moduleName = resolveModuleName(moduleTypeInfo, typeConsName);
QualifiedName typeConsQualifiedName = QualifiedName.make(typeCons_moduleName, typeConsName.getUnqualifiedName());
return classInstance.getIdentifier().equals(new ClassInstanceIdentifier.TypeConstructorInstance(QualifiedName.make(typeClass_moduleName, typeClassName.getUnqualifiedName()), typeConsQualifiedName));
}
if (instanceTypeCons instanceof InstanceTypeCons.Function){
return classInstance.getIdentifier().equals(new ClassInstanceIdentifier.TypeConstructorInstance(QualifiedName.make(typeClass_moduleName, typeClassName.getUnqualifiedName()), CAL_Prelude.TypeConstructors.Function));
}
if (instanceTypeCons instanceof InstanceTypeCons.Unit){
return classInstance.getIdentifier().equals(new ClassInstanceIdentifier.TypeConstructorInstance(QualifiedName.make(typeClass_moduleName, typeClassName.getUnqualifiedName()), CAL_Prelude.TypeConstructors.Unit));
}
if (instanceTypeCons instanceof InstanceTypeCons.List){
return classInstance.getIdentifier().equals(new ClassInstanceIdentifier.TypeConstructorInstance(QualifiedName.make(typeClass_moduleName, typeClassName.getUnqualifiedName()), CAL_Prelude.TypeConstructors.List));
}
if (instanceTypeCons instanceof InstanceTypeCons.Record){
return classInstance.getIdentifier() instanceof UniversalRecordInstance;
}
}
return true;
}
// TODO make this into utility functions that are more accessible
private static ModuleName resolveModuleName(ModuleTypeInfo moduleTypeInfo, SourceModel.Name.TypeClass typeClassName) {
ModuleName moduleName = SourceModel.Name.Module.maybeToModuleName(typeClassName.getModuleName());
if (moduleName == null) {
if (moduleTypeInfo.getTypeClass(typeClassName.getUnqualifiedName()) != null) {
return moduleTypeInfo.getModuleName();
} else {
return moduleTypeInfo.getModuleOfUsingTypeClass(typeClassName.getUnqualifiedName());
}
} else {
return moduleTypeInfo.getModuleNameResolver().resolve(moduleName).getResolvedModuleName();
}
}
// TODO make this into utility functions that are more accessible
private static ModuleName resolveModuleName(ModuleTypeInfo moduleTypeInfo, SourceModel.Name.TypeCons typeConsName) {
ModuleName moduleName = SourceModel.Name.Module.maybeToModuleName(typeConsName.getModuleName());
if (moduleName == null) {
if (moduleTypeInfo.getTypeConstructor(typeConsName.getUnqualifiedName()) != null) {
return moduleTypeInfo.getModuleName();
} else {
return moduleTypeInfo.getModuleOfUsingTypeConstructor(typeConsName.getUnqualifiedName());
}
} else {
return moduleTypeInfo.getModuleNameResolver().resolve(moduleName).getResolvedModuleName();
}
}
/**
* the search result from the list that covers the smallest area. This is used to winnow
* the list to the match that is closest to the cursor. If more than once item has the same smallest
* range then a random one is selected. This code assumes that the search results all nest properly
* within each other. That is the kind of list that would be returned by findSymbolAt.
*
* @param results the list of results to check.
* @return the search result from the list that covers the smallest area.
*/
public static SearchResult.Precise selectMostPrecise(SearchResult.Precise[] results){
if (results.length == 0){
throw new IllegalArgumentException();
}
// Start with this one and then scan through the list looking for
// the item that is most inside the other ones.
SourceRange longestRange = results[0].getSourceRange();
SearchResult.Precise result = results[0];
for(int i = 1; i < results.length; ++i){
final SearchResult.Precise sr = results[i];
final int compareStart = SourcePosition.compareByPosition.compare(sr.getSourceRange().getStartSourcePosition(), longestRange.getStartSourcePosition());
switch(compareStart){
case 0:
final int compareEnd = SourcePosition.compareByPosition.compare(sr.getSourceRange().getEndSourcePosition(), longestRange.getEndSourcePosition());
switch(compareEnd){
case 0:
// current one is okay.
continue;
case 1:
continue;
}
case -1:
continue;
}
longestRange = sr.getSourceRange();
result = sr;
}
return result;
}
/**
* Finds the symbol at the specified line and column.
* @param moduleName Name of the module to search
* @param line The line number of the symbol to search for. The first line in the file is line number one.
* @param column The column number of the symbol to search for. The first column in the line is column number one.
* @param messageLogger
* @return An array of SearchResult.Precise objects for the symbol at the specified line and column.
*/
public SearchResult.Precise[] findSymbolAt(ModuleName moduleName, int line, int column, CompilerMessageLogger messageLogger) {
MessageLogger moduleLogger = new MessageLogger();
// Parse a SourceModel to process
ModuleTypeInfo homeModuleTypeInfo = moduleContainer.getModuleTypeInfo(moduleName);
if (homeModuleTypeInfo == null){
return null;
}
SourceModel.ModuleDefn moduleDefn = moduleContainer.getSourceModel(moduleName, false, moduleLogger);
if(moduleDefn == null) {
// Copy messages, adding source names to each SourcePosition
for (final CompilerMessage compilerMessage : moduleLogger.getCompilerMessages()) {
SourceRange sourceRange = compilerMessage.getSourceRange();
if(sourceRange != null) {
SourceRange augmentedRange = getAugmentedRange(sourceRange, moduleName);
messageLogger.logMessage(new CompilerMessage(augmentedRange, compilerMessage.getMessageKind()));
} else {
messageLogger.logMessage(compilerMessage);
}
}
return null;
}
// Find the symbol at the given position
List<SearchResult.Precise> results = SourceMetricFinder.findSymbolAt(moduleDefn, homeModuleTypeInfo, new SourcePosition(line, column, moduleName));
if (results.size() == 0){
// no symbol found
return null;
}
return results.toArray(new SearchResult.Precise[results.size()]);
}
/**
* Finds the position of the next top level element. This may return null.
* @param moduleName Name of the module to search
* @param line The line number of the symbol to search after. The first line in the file is line number one.
* @param column The column number of the symbol to search after. The first column in the line is column number one.
* @param messageLogger
* @return The source range of the name of the next top level element. This may be null.
*/
public SourceRange findNextTopLevelElement(ModuleName moduleName, int line, int column, CompilerMessageLogger messageLogger) {
// Parse a SourceModel to process
ModuleTypeInfo homeModuleTypeInfo = moduleContainer.getModuleTypeInfo(moduleName);
if (homeModuleTypeInfo == null){
return null;
}
SourceModel.ModuleDefn moduleDefn = moduleContainer.getSourceModel(moduleName, false, messageLogger);
if(moduleDefn == null) {
return null;
}
// Find the position of the next top level function
return SourceMetricFinder.findNextTopLevelElement(moduleDefn, new SourcePosition(line, column, moduleName));
}
/**
* Finds the position of the previous top level element. This may return null.
* @param moduleName Name of the module to search
* @param line The line number of the symbol to search before. The first line in the file is line number one.
* @param column The column number of the symbol to search before. The first column in the line is column number one.
* @param messageLogger
* @return The source range of the name of the previous top level element. This may be null.
*/
public SourceRange findPreviousTopLevelElement(ModuleName moduleName, int line, int column, CompilerMessageLogger messageLogger) {
// Parse a SourceModel to process
ModuleTypeInfo homeModuleTypeInfo = moduleContainer.getModuleTypeInfo(moduleName);
if (homeModuleTypeInfo == null){
return null;
}
SourceModel.ModuleDefn moduleDefn = moduleContainer.getSourceModel(moduleName, false, messageLogger);
if(moduleDefn == null) {
return null;
}
// Find the position of the next top level function
return SourceMetricFinder.findPreviousTopLevelElement(moduleDefn, new SourcePosition(line, column, moduleName));
}
/**
* Finds the source element that contains the given position. This may return null.
* @param moduleName Name of the module to search
* @param line The line number of the symbol to search before. The first line in the file is line number one.
* @param column The column number of the symbol to search before. The first column in the line is column number one.
* @param messageLogger
* @return A pair where the first element is the source element that
* contains the given position and the second element is the
* source range of the element. This may be null.
*/
public Pair<SourceElement, SourceRange> findContainingSourceElement(ModuleName moduleName, int line, int column, CompilerMessageLogger messageLogger) {
// Parse a SourceModel to process
ModuleTypeInfo homeModuleTypeInfo = moduleContainer.getModuleTypeInfo(moduleName);
if (homeModuleTypeInfo == null){
return null;
}
SourceModel.ModuleDefn moduleDefn = moduleContainer.getSourceModel(moduleName, false, messageLogger);
if(moduleDefn == null) {
return null;
}
return SourceMetricFinder.findContainingSourceElement(moduleDefn, new SourcePosition(line, column, moduleName));
}
/**
* Search for the definitions of qualifiedTargets in the module named moduleName.
* This needs some more work after the source model has better position information.
* @param moduleName Name of the module to search
* @return A path to the appropriate element. This maybe be null.
*/
// todo-jowong look at how Greg implemented this method when refactoring to use SearchManager...
public SourceElement[] findContainingSourceElement(ModuleName moduleName, int line, int column) {
MessageLogger moduleLogger = new MessageLogger();
// Parse a SourceModel to process
ModuleTypeInfo moduleTypeInfo = moduleContainer.getModuleTypeInfo(moduleName);
if (moduleTypeInfo == null){
return null;
}
SourceModel.ModuleDefn moduleDefn = moduleContainer.getSourceModel(moduleName, true, moduleLogger);
// if the module is a sourceless module, then we need to process it differently
// or this is a compile error
if (moduleDefn == null) {
return null;
}
// Find the top level element
final int nTopLevelDefns = moduleDefn.getNTopLevelDefns();
for(int i = 0; i < nTopLevelDefns; ++i){
TopLevelSourceElement tlse = moduleDefn.getNthTopLevelDefn(i);
SourceRange sourceRange = tlse.getSourceRangeOfDefn();
SourcePosition pos = new SourcePosition(line, column);
if (sourceRange != null && sourceRange.containsPosition(pos)){
// The scope of the Algebraic type includes the data constructors so search for the possible
// data constructor match first before going for the algebraic type
if (tlse instanceof SourceModel.TypeConstructorDefn.AlgebraicType){
SourceModel.TypeConstructorDefn.AlgebraicType at = (SourceModel.TypeConstructorDefn.AlgebraicType) tlse;
// This is a hacky way of finding out where the cursor is until the source model is richer.
final int nDataConstructors = at.getNDataConstructors();
for(int j = 0; j < nDataConstructors; ++j){
DataConsDefn dcd = at.getNthDataConstructor(j);
if (SourcePosition.compareByPosition.compare(dcd.getSourceRangeOfName().getStartSourcePosition(), pos) <= 0){
SourcePosition end = null;
if (dcd.getNTypeArgs() != 0){
// look for the position of the last arg
end = dcd.getNthTypeArg(dcd.getNTypeArgs()-1).getSourcePosition();
}
if (end == null){
// no args so just just the position of the end of the data cons.
end = dcd.getSourceRangeOfDefn().getEndSourcePosition();
}
if (SourcePosition.compareByPosition.compare(pos, end) <= 0){
return new SourceElement[] {tlse, dcd};
}
}
}
}
else if (tlse instanceof SourceModel.TypeClassDefn){
SourceModel.TypeClassDefn at = (SourceModel.TypeClassDefn) tlse;
// This is a hacky way of finding out where the cursor is until the source model is richer.
final int nClassMethods = at.getNClassMethodDefns();
for(int j = 0; j < nClassMethods; ++j){
ClassMethodDefn classMethod = at.getNthClassMethodDefn(j);
if (SourcePosition.compareByPosition.compare(classMethod.getSourceRangeOfName().getStartSourcePosition(), pos) <= 0){
if (classMethod.getSourceRangeOfClassDefn().containsPosition(pos)){
return new SourceElement[] {tlse, classMethod};
}
}
}
}
// no else this is the default for when the if fails
return new SourceElement[] {tlse};
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return dumpReferenceFrequencies(new AcceptAllModulesFilter(), new AcceptAllQualifiedNamesFilter(), false);
}
/**
* TODO-AE
* INTERNAL METHOD � Not part of public API
* <p>
* The {@link org.openquark.cal.compiler.SourceModel.SourceElement#getSourceRange()} method is package protected for
* various reasons. However, it is sometimes necessary to get the source range
* of an element. This method serves as a back door to get it.
* <p>
* If and when {@link org.openquark.cal.compiler.SourceModel.SourceElement#getSourceRange()} becomes public, this
* method will go away.
*
* @param elt the {@link org.openquark.cal.compiler.SourceModel.SourceElement} whose source range to get
* @return the element's source range
*/
public static SourceRange getSourceRange(SourceElement elt) {
return elt.getSourceRange();
}
}