/*******************************************************************************
* Copyright (c) 2009 the CHISEL group and contributors.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* the CHISEL group - initial API and implementation
*******************************************************************************/
package ca.uvic.chisel.javasketch.ui.internal.search;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.search.ui.ISearchQuery;
import org.eclipse.search.ui.ISearchResult;
import org.eclipse.search.ui.text.Match;
import ca.uvic.chisel.javasketch.IProgramSketch;
import ca.uvic.chisel.javasketch.SketchPlugin;
import ca.uvic.chisel.javasketch.data.model.IActivation;
import ca.uvic.chisel.javasketch.data.model.IMessage;
import ca.uvic.chisel.javasketch.data.model.IOriginMessage;
import ca.uvic.chisel.javasketch.data.model.ITargetMessage;
import ca.uvic.chisel.javasketch.data.model.ITraceClass;
import ca.uvic.chisel.javasketch.data.model.ITraceClassMethod;
import ca.uvic.chisel.javasketch.data.model.ITraceModel;
import ca.uvic.chisel.javasketch.data.model.ITraceModelProxy;
import ca.uvic.chisel.javasketch.data.model.imple.internal.ActivationImpl;
import ca.uvic.chisel.javasketch.data.model.imple.internal.MethodKey;
import ca.uvic.chisel.javasketch.ui.internal.presentation.metadata.PresentationData;
/**
* @author Del Myers
*
*/
public class TraceSearchQuery implements ISearchQuery {
public static final int CASE_SENSITIVE = 1;
public static final int CLASSES = 1 << 1;
public static final int METHODS = 1 << 2;
public static final int ACTIVATIONS = 1 << 3;
public static final int PROPERTIES = 1 << 4;
private TraceSearchQueryResults searchResults;
private IProgramSketch[] scope;
private String searchString;
private int searchMask;
TraceSearchQuery(IProgramSketch[] scope, String searchString, int searchMask) {
this.scope = scope;
this.searchString = searchString;
if ((searchMask & CASE_SENSITIVE) == 0) {
this.searchString = searchString.toUpperCase();
}
this.searchMask = searchMask;
this.searchResults = new TraceSearchQueryResults(this);
}
/* (non-Javadoc)
* @see org.eclipse.search.ui.ISearchQuery#canRerun()
*/
@Override
public boolean canRerun() {
return true;
}
/* (non-Javadoc)
* @see org.eclipse.search.ui.ISearchQuery#canRunInBackground()
*/
@Override
public boolean canRunInBackground() {
return true;
}
/* (non-Javadoc)
* @see org.eclipse.search.ui.ISearchQuery#getLabel()
*/
@Override
public String getLabel() {
return "Searching Traces for " + searchString;
}
/* (non-Javadoc)
* @see org.eclipse.search.ui.ISearchQuery#getSearchResult()
*/
@Override
public ISearchResult getSearchResult() {
return searchResults;
}
/* (non-Javadoc)
* @see org.eclipse.search.ui.ISearchQuery#run(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public IStatus run(IProgressMonitor monitor)
throws OperationCanceledException {
int work = scope.length * 100;
monitor.beginTask("Searching Traces...", work);
MultiStatus s = new MultiStatus(SketchPlugin.PLUGIN_ID, 0, "Trace Search", null);
for (int i = 0; i < scope.length; i++) {
try {
SortedSet<String> classes = null;
SortedSet<MethodKey> methods = null;
SortedSet<Long> activations = null;
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
monitor.subTask(scope[i].getLabel());
if ((searchMask & CLASSES) != 0) {
classes = new TreeSet<String>();
searchForClasses(classes, scope[i]);
}
if ((searchMask & METHODS) != 0) {
methods = new TreeSet<MethodKey>();
searchForMethods(methods, scope[i]);
}
if ((searchMask & ACTIVATIONS) != 0) {
SortedSet<String> activationClasses = new TreeSet<String>();
SortedSet<MethodKey> activationMethods = new TreeSet<MethodKey>();
if (classes != null) {
activationClasses.addAll(classes);
}
if (methods != null) {
activationMethods.addAll(methods);
}
activations = findActivations(activationClasses, activationMethods, scope[i]);
//make sure that the methods and classes are included
if (classes == null) {
classes = activationClasses;
}
if (methods == null) {
methods = activationMethods;
}
}
if (methods != null) {
//make sure that all of the classes are included
//in the search results, if they have methods
//associated with them.
if (classes == null) {
classes = new TreeSet<String>();
}
for (MethodKey mk : methods) {
classes.add(mk.type);
}
}
List<Match> matches = null;
if ((searchMask & PROPERTIES) != 0) {
matches = searchForAnnotations(scope[i]);
for (Match match : matches) {
ITraceModelProxy proxy = (ITraceModelProxy) match.getElement();
IActivation activation = null;
ITraceClassMethod method = null;
ITraceClass clazz = null;
if (proxy.isMessageElement()) {
IMessage message = (IMessage) proxy.getElement();
if (message instanceof ITargetMessage) {
activation = message.getActivation();
} else if (message instanceof IOriginMessage) {
activation = ((IOriginMessage)message).getTarget().getActivation();
}
} else if (proxy.getKind() == ITraceModel.TRACE_CLASS_METHOD) {
method = (ITraceClassMethod) proxy.getElement();
} else if (proxy.getKind() == ITraceModel.TRACE_CLASS) {
clazz = (ITraceClass) proxy.getElement();
} else if (proxy.getKind() == ITraceModel.ACTIVATION) {
activation = (IActivation) proxy.getElement();
}
if (activation != null) {
if (activations == null) {
activations = new TreeSet<Long>();
}
activations.add(((ActivationImpl)activation).getModelID());
method = activation.getMethod();
}
if (method != null) {
if (methods == null) {
methods = new TreeSet<MethodKey>();
}
methods.add(new MethodKey(method.getTraceClass().getName(), method.getName(), method.getSignature()));
clazz = method.getTraceClass();
}
if (clazz != null) {
if (classes == null) {
classes = new TreeSet<String>();
}
classes.add(clazz.getName());
}
}
}
if ((classes != null && !classes.isEmpty()) || (matches != null && !matches.isEmpty())) {
//add a new search result
searchResults.updateSearch(scope[i], classes, methods, activations, matches);
}
} catch (SQLException e) {
s.merge(SketchPlugin.getDefault().createStatus(e));
} catch (CoreException e) {
s.merge(e.getStatus());
}
monitor.worked(100);
}
monitor.done();
return s;
}
/**
* @param classes the class names to search for. This will be pruned of classes that
* don't contain any activations.
* @param methods the methods to search for. This will be pruned of methods
* that don't contain any activations.
* @param iProgramSketch
* @return
* @throws CoreException
* @throws SQLException
*/
private TreeSet<Long> findActivations(SortedSet<String> classes,
SortedSet<MethodKey> methods, IProgramSketch sketch) throws SQLException, CoreException {
//first, remove all of the methods to search for if
//there are classes that already match.
TreeSet<MethodKey> workingMethods = new TreeSet<MethodKey>();
for (Iterator<MethodKey> it = methods.iterator(); it.hasNext();) {
MethodKey key = it.next();
if (!classes.contains(key.type)) {
workingMethods.add(key);
}
}
//now, find all of the activations that exist for matching classes.
PreparedStatement ps = sketch.getPortal().prepareStatement("SELECT model_id FROM Activation WHERE type_name=?");
TreeSet<Long> activations = new TreeSet<Long>();
for (Iterator<String> it = classes.iterator(); it.hasNext();) {
String c = it.next();
ps.setString(1, c);
ResultSet results = ps.executeQuery();
if (results.next()) {
do {
activations.add(results.getLong(1));
} while (results.next());
} else {
it.remove();
}
}
ps = sketch.getPortal().prepareStatement("SELECT model_id FROM Activation WHERE type_name=? AND method_name=? AND method_signature=?");
for (Iterator<MethodKey> it = methods.iterator(); it.hasNext();) {
MethodKey mk = it.next();
ps.setString(1, mk.type);
ps.setString(2, mk.name);
ps.setString(3, mk.signature);
ResultSet results = ps.executeQuery();
if (results.next()) {
do {
activations.add(results.getLong(1));
} while (results.next());
} else {
it.remove();
}
}
return activations;
}
/**
* @param methods2
* @param iProgramSketch
* @throws CoreException
* @throws SQLException
*/
private void searchForMethods(Collection<MethodKey> methods,
IProgramSketch sketch) throws SQLException, CoreException {
Statement s = sketch.getPortal().getDefaultConnection().createStatement();
String likeString = getLikeString();
ResultSet results = s.executeQuery("Select * from Method where " +
(((searchMask & CASE_SENSITIVE) == 0) ? "UCASE" : "") +
"(method_name) LIKE '" + likeString + "' ESCAPE '/'");
while (results.next()) {
String name = results.getString("method_name");
String type = results.getString("type_name");
String sig = results.getString("method_signature");
methods.add(new MethodKey(type, name, sig));
}
}
private List<Match> searchForAnnotations(IProgramSketch sketch) {
PresentationData data = PresentationData.connect(sketch);
Pattern pattern = getRegex();
LinkedList<Match> matches = new LinkedList<Match>();
try {
ITraceModelProxy[] proxies = data.getAnnotatedElements();
for (ITraceModelProxy proxy : proxies) {
String text = data.getAnnotation(proxy.getElementId());
if (text != null && !text.isEmpty()) {
text = text.replaceAll("\\s+", " ");
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
Match match = new Match(proxy, matcher.start(), matcher.end() - matcher.start());
if (match.getLength() > 0) {
matches.add(match);
}
}
}
}
} finally {
data.disconnect();
}
return matches;
}
/**
*
*/
private String getLikeString() {
String likeString = searchString.replace("/", "//");
likeString = searchString.replace("'", "/'");
likeString = searchString.replace("_", "/_");
likeString = searchString.replace('?', '_');
likeString = searchString.replace('*', '%');
return likeString;
}
private Pattern getRegex() {
StringBuffer buff = new StringBuffer();
for (char c : searchString.toCharArray()) {
switch (c) {
case '?':
buff.append('.');
break;
case '*':
buff.append(".*");
break;
// characters that need to be escaped in the regex.
case '(':
case ')':
case '{':
case '}':
case '.':
case '[':
case ']':
case '$':
case '^':
case '+':
case '|':
buff.append("\\\\");
default:
buff.append(c);
}
}
int flags =
(((CASE_SENSITIVE & searchMask) == 0) ? Pattern.CASE_INSENSITIVE : 0) |
//Pattern.MULTILINE |
Pattern.UNICODE_CASE;
return Pattern.compile(buff.toString(), flags);
}
/**
* @return
* @throws CoreException
* @throws SQLException
*/
private void searchForClasses(Collection<String> classes, IProgramSketch sketch) throws SQLException, CoreException {
Statement s = sketch.getPortal().getDefaultConnection().createStatement();
String likeString = getLikeString();
ResultSet results = s.executeQuery("Select * from TraceClass where " +
(((searchMask & CASE_SENSITIVE) == 0) ? "UCASE" : "") +
"(type_name) LIKE '" + likeString + "' ESCAPE '/'");
while (results.next()) {
String type = results.getString("type_name");
classes.add(type);
}
}
}