/*
* #%~
* org.overture.ide.ui
* %%
* Copyright (C) 2008 - 2014 Overture
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #~%
*/
package org.overture.ide.ui.utility.ast;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.overture.ast.analysis.AnalysisException;
import org.overture.ast.analysis.DepthFirstAnalysisAdaptor;
import org.overture.ast.definitions.ATypeDefinition;
import org.overture.ast.definitions.PDefinition;
import org.overture.ast.expressions.AVariableExp;
import org.overture.ast.expressions.PExp;
import org.overture.ast.intf.lex.ILexLocation;
import org.overture.ast.modules.AFromModuleImports;
import org.overture.ast.modules.AModuleImports;
import org.overture.ast.modules.PExport;
import org.overture.ast.modules.PImport;
import org.overture.ast.node.INode;
import org.overture.ast.patterns.PPattern;
import org.overture.ast.statements.PStm;
import org.overture.ast.types.AFieldField;
import org.overture.ast.types.AFunctionType;
import org.overture.ast.types.ARecordInvariantType;
import org.overture.ide.core.IVdmElement;
import org.overture.ide.core.resources.IVdmSourceUnit;
import org.overture.ide.ui.internal.viewsupport.ImportsContainer;
/**
* Class used by an editor to search the editor text for source code node locations. Used to find nodes in the source
* code to sync with outline
*
* @author kela
*/
public final class AstLocationSearcher extends DepthFirstAnalysisAdaptor
{
private static boolean DEBUG_PRINT = false;
/**
* Best match to the offset. This means that this node has a location where the offset is within
*/
private INode bestHit = null;
/**
* Best alternative hit is a location which is abs close to the offset
*/
private INode bestAlternativeHit = null;
/**
* Best alternative hit is a node which has a location which is abs close to the offset
*/
private ILexLocation bestAlternativeLocation;
/**
* The offset used when searching for nodes within this location of the source code
*/
private int offSet;
/**
* The source file to search
*/
private File sourceFile;
private static final AstLocationSearcher seacher = new AstLocationSearcher();
private static final Map<IVdmElement, Map<ILexLocation, INode>> elementNodeCache = new HashMap<IVdmElement, Map<ILexLocation, INode>>();
private IVdmElement currentElement = null;
private boolean indexing = false;
/**
* Private constructor, special care is needed to the state of the class this no instanciation allowed outside this
* class
*/
private AstLocationSearcher()
{
}
private void init()
{
seacher._visitedNodes.clear();// We cheat with undeclared exception, this breaks the state of the adaptor, and
// we use
// static so we need to clear the cache.
seacher.bestHit = null;
seacher.bestAlternativeHit = null;
seacher.bestAlternativeLocation = null;
seacher.currentElement = null;
seacher.indexing = false;
}
/**
* Search method to find the closest node to a location specified by a test offset
*
* @param nodes
* The nodes to search within
* @param offSet
* The offset to match a node to
* @return The node closest to the offset or null
*/
public static INode search(List<INode> nodes, int offSet,
IVdmSourceUnit source)
{
synchronized (seacher)
{
if (DEBUG_PRINT)
{
System.out.println("Search start");
}
seacher.init();
seacher.offSet = offSet;
seacher.sourceFile = source.getSystemFile();
try
{
for (INode node : nodes)
{
node.apply(seacher);
}
} catch (AnalysisException e)
{
// We found what we are looking for
}
return seacher.bestHit != null ? seacher.bestHit
: seacher.bestAlternativeHit;
}
}
/**
* Search method to find the closest node to a location specified by a test offset
*
* @param nodes
* The nodes to search within
* @param offSet
* The offset to match a node to
* @param element
* @return The node closest to the offset or null
*/
public static INode searchCache(List<INode> nodes, int offSet,
IVdmElement element)
{
synchronized (seacher)
{
if (DEBUG_PRINT)
{
System.out.println("Search start");
}
seacher.init();
seacher.offSet = offSet;
seacher.currentElement = element;
try
{
if (elementNodeCache.get(element) == null
|| elementNodeCache.get(element).isEmpty())
{
// elementNodeCache.put(element, new HashMap<ILexLocation, INode>());
// seacher.indexing = true;
// for (INode node : nodes)
// {
// node.apply(seacher);
// }
return null;
} else
{
for (Entry<ILexLocation, INode> entry : elementNodeCache.get(element).entrySet())
{
seacher.check(entry.getValue(), entry.getKey());
}
}
} catch (AnalysisException e)
{
// We found what we are looking for
}
return seacher.bestHit != null ? seacher.bestHit
: seacher.bestAlternativeHit;
}
}
public static void createIndex(List<INode> nodes, IVdmElement element)
throws Throwable
{
seacher.init();
seacher.currentElement = element;
elementNodeCache.put(element, new HashMap<ILexLocation, INode>());
seacher.indexing = true;
for (INode node : nodes)
{
node.apply(seacher);
}
}
@Override
public void defaultInPDefinition(PDefinition node) throws AnalysisException
{
check(node, node.getLocation());
}
@Override
public void defaultInPExp(PExp node) throws AnalysisException
{
check(node, node.getLocation());
}
@Override
public void defaultInPStm(PStm node) throws AnalysisException
{
check(node, node.getLocation());
}
@Override
public void caseAVariableExp(AVariableExp node) throws AnalysisException
{
check(node, node.getLocation());
}
@Override
public void caseAFieldField(AFieldField node) throws AnalysisException
{
check(node, node.getTagname().getLocation());
}
@Override
public void defaultInPPattern(PPattern node) throws AnalysisException
{
check(node, node.getLocation());
}
@Override
public void caseAFunctionType(AFunctionType node)
{
// Skip
}
@Override
public void caseARecordInvariantType(ARecordInvariantType node)
throws AnalysisException
{
if (node.parent() instanceof ATypeDefinition)
{
super.caseARecordInvariantType(node);
}
// Skip
}
private void check(INode node, ILexLocation location)
throws AnalysisException
{
if (DEBUG_PRINT)
{
System.out.println("Checking location span " + offSet + ": "
+ location.getStartOffset() + " to "
+ location.getEndOffset() + " line: "
+ location.getStartLine() + ":" + location.getStartPos());
}
if (currentElement != null)
{
elementNodeCache.get(currentElement).put(location, node);
}
if (location.getStartOffset() - 1 <= this.offSet
&& location.getEndOffset() - 1 >= this.offSet
&& location.getFile().equals(sourceFile))
{
bestHit = node;
if (!indexing)
{
throw new AnalysisException("Hit found stop search");
}
}
// Store the last best match where best is closest with abs
if (bestAlternativeLocation == null
|| Math.abs(offSet - location.getStartOffset()) <= Math.abs(offSet
- bestAlternativeLocation.getStartOffset())
&& location.getFile().equals(sourceFile))
{
bestAlternativeLocation = location;
bestAlternativeHit = node;
if (DEBUG_PRINT)
{
System.out.println("Now best is: " + offSet + ": "
+ location.getStartOffset() + " to "
+ location.getEndOffset() + " line: "
+ location.getStartLine() + ":"
+ location.getStartPos());
}
} else if (bestAlternativeLocation == null
|| offSet - bestAlternativeLocation.getStartOffset() > 0
&& Math.abs(offSet - location.getStartOffset()) > Math.abs(offSet
- bestAlternativeLocation.getStartOffset())
&& location.getFile().equals(sourceFile))
{
if (DEBUG_PRINT)
{
System.out.println("Going back...");
}
} else
{
if (DEBUG_PRINT)
{
System.out.println("Rejected is: " + offSet + ": "
+ location.getStartOffset() + " to "
+ location.getEndOffset() + " line: "
+ location.getStartLine() + ":"
+ location.getStartPos());
}
if (!indexing)
{
throw new AnalysisException("Hit found stop search");
}
}
}
public static int[] getNodeOffset(INode node)
{
if (node instanceof PDefinition)
{
return getNodeOffset(((PDefinition) node).getLocation());
} else if (node instanceof PExp)
{
return getNodeOffset(((PExp) node).getLocation());
} else if (node instanceof PStm)
{
return getNodeOffset(((PStm) node).getLocation());
} else if (node instanceof AFieldField)
{
return getNodeOffset(((AFieldField) node).getTagname().getLocation());
} else if (node instanceof PImport)
{
return getNodeOffset(((PImport) node).getLocation());
} else if (node instanceof PExport)
{
return getNodeOffset(((PExport) node).getLocation());
} else if (node instanceof AFromModuleImports)
{
return getNodeOffset(((AFromModuleImports) node).getName().getLocation());
} else if (node instanceof ImportsContainer)
{
return getNodeOffset(((ImportsContainer) node).getImports().getImports().getFirst());
} else if (node instanceof AModuleImports)
{
return getNodeOffset(((AModuleImports) node).getName().getLocation());
}
return new int[] { -1, -1 };
}
public static int[] getNodeOffset(ILexLocation location)
{
return new int[] { location.getStartOffset() - 1,
location.getEndOffset() - location.getStartOffset() };
}
}