blob: 981c32a1104ee087b86e19e0d68885218edafbe9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2019 SAP AG and IBM Corporation.
* 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:
* SAP AG - initial API and implementation
* IBM Corporation - bug fixes for instanceof, big changes for tables
*******************************************************************************/
package org.eclipse.mat.parser.internal.oql;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.collect.ArrayInt;
import org.eclipse.mat.collect.ArrayLong;
import org.eclipse.mat.collect.IteratorInt;
import org.eclipse.mat.collect.IteratorLong;
import org.eclipse.mat.collect.SetInt;
import org.eclipse.mat.parser.internal.Messages;
import org.eclipse.mat.parser.internal.oql.compiler.CompilerImpl;
import org.eclipse.mat.parser.internal.oql.compiler.EvaluationContext;
import org.eclipse.mat.parser.internal.oql.compiler.Expression;
import org.eclipse.mat.parser.internal.oql.compiler.Query;
import org.eclipse.mat.parser.internal.oql.compiler.Query.FromClause;
import org.eclipse.mat.parser.internal.oql.compiler.Query.SelectClause;
import org.eclipse.mat.parser.internal.oql.compiler.Query.SelectItem;
import org.eclipse.mat.parser.internal.oql.parser.OQLParser;
import org.eclipse.mat.parser.internal.oql.parser.ParseException;
import org.eclipse.mat.parser.internal.oql.parser.TokenMgrError;
import org.eclipse.mat.query.Column;
import org.eclipse.mat.query.ContextProvider;
import org.eclipse.mat.query.IContextObject;
import org.eclipse.mat.query.IContextObjectSet;
import org.eclipse.mat.query.IResultTable;
import org.eclipse.mat.query.IResultTree;
import org.eclipse.mat.query.IStructuredResult;
import org.eclipse.mat.query.ResultMetaData;
import org.eclipse.mat.snapshot.IOQLQuery;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.OQL;
import org.eclipse.mat.snapshot.OQLParseException;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.mat.util.PatternUtil;
import org.eclipse.mat.util.SilentProgressListener;
import org.eclipse.mat.util.SimpleMonitor;
import org.eclipse.mat.util.VoidProgressListener;
public class OQLQueryImpl implements IOQLQuery
{
Query query;
EvaluationContext ctx;
// //////////////////////////////////////////////////////////////
// result set implementations
// //////////////////////////////////////////////////////////////
private interface CustomTableResultSet extends IOQLQuery.Result, IResultTable, List<AbstractCustomTableResultSet.RowMap>
{}
/**
* Find the distinct rows in the table.
* @param table
* @param listener
* @return an array of indexes of unique rows
*/
private static ArrayInt distinctList(CustomTableResultSet table, IProgressListener listener)
{
LinkedHashSet<Object> hs = new LinkedHashSet<Object>();
ArrayInt newrows = new ArrayInt();
int i = 0;
for (Object row : table)
{
if (hs.add(row))
{
newrows.add(i);
}
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
++i;
}
hs.clear();
return newrows;
}
/**
* A table holding the result of a query.
*
*/
private static abstract class AbstractCustomTableResultSet extends AbstractList<AbstractCustomTableResultSet.RowMap> implements CustomTableResultSet
{
/**
* Holds a row from a sub-select with columns ready for a select.
*/
private static class RowMap extends AbstractMap<String,Object>
{
IStructuredResult isr;
IResultTable irtb;
IResultTree irtr;
int index;
int subindex;
public RowMap(IStructuredResult irt, int index)
{
this(irt, index, -1);
}
public RowMap(IStructuredResult irt, int index, int subindex)
{
this.isr = irt;
if (isr instanceof IResultTable)
this.irtb = (IResultTable)irt;
if (isr instanceof IResultTree)
this.irtr = (IResultTree)irt;
this.index = index;
this.subindex = subindex;
}
@Override
public int size()
{
return isr.getColumns().length;
}
@Override
public Object get(Object colname)
{
for (int col = 0; col < isr.getColumns().length; ++col)
{
if (isr.getColumns()[col].getLabel().equals(colname))
{
Object row;
if (irtb != null)
row = irtb.getRow(index);
else if (irtr != null)
row = irtr.getElements().get(index);
else
return null;
return isr.getColumnValue(row, col);
}
}
return null;
}
public int getObjectId()
{
IContextObject cx = isr.getContext(index);
if (cx != null)
return cx.getObjectId();
return -1;
}
@Override
public Set<Entry<String, Object>> entrySet()
{
Set<Entry<String, Object>> set = new LinkedHashSet<Entry<String, Object>>();
for (int col = 0; col < isr.getColumns().length; ++col)
{
String key = isr.getColumns()[col].getLabel();
set.add(new Entry<String, Object>() {
final Object NULL_VALUE = new Object();
Object value;
public Object getValue()
{
Object o = value;
if (o == NULL_VALUE)
return null;
else if (o != null)
return o;
o = get(getKey());
value = o;
return o;
}
public Object setValue(Object o)
{
throw new UnsupportedOperationException();
}
@Override
public String getKey()
{
return key;
}
public int hashCode()
{
return Objects.hash(getKey(), getValue());
}
public boolean equals(Object o)
{
if ((o instanceof Entry<?,?>))
{
Entry<?,?>ox = (Entry<?,?>)o;
return Objects.equals(getKey(), ox.getKey()) &&
Objects.equals(getValue(), ox.getValue());
}
{
return false;
}
}
});
}
return Collections.unmodifiableSet(set);
}
}
public AbstractCustomTableResultSet(OQLQueryImpl source)
{
this.source = source;
}
/**
* Find the object ID of an IObject or a row backed by an IObject
* @param o
* @return
*/
static int getObjectId(Object o)
{
if (o instanceof IObject)
return ((IObject)o).getObjectId();
//else if (o instanceof RowMap)
// return ((RowMap)o).getObjectId();
return -1;
}
protected OQLQueryImpl source;
protected Column[] columns;
@Override
public RowMap get(int index)
{
// Always return a map not a single item for consistency.
// Except if the alias is "" as this used for CompareTablesQuery.
if (getColumns().length == 1 && getColumns()[0].getLabel().length() == 0)
{
Object ret = getColumnValue(getRow(index), 0);
if (ret instanceof RowMap)
return (RowMap)ret;
}
// Delay resolving columns until needed
return new RowMap(this, index);
}
@Override
public int size()
{
return getRowCount();
}
/**
* Get more details for a table.
* Allows extra menu options for columns holding objects.
* @param rt
* @param columns
* @param source
* @return
*/
protected ResultMetaData getResultMetaData(OQLQueryImpl source)
{
Query query = source.query;
Column columns[] = getColumns();
int objectCount = source.ctx.getSnapshot().getSnapshotInfo().getNumberOfObjects();
ResultMetaData.Builder builder = new ResultMetaData.Builder();
int prov = 0;
for (int ii = 0; ii < columns.length; ++ii)
{
// Find an example object for each column
Object sample = getRowCount() > 0 ? getColumnValue(getRow(0), ii) : null;
// See if it is one or more objects from the dump
if (getObjectId(sample) != -1 ||
sample instanceof Iterable<?> && ((Iterable<?>)sample).iterator().hasNext() && getObjectId(((Iterable<?>)sample).iterator().next()) != -1 ||
sample instanceof int[])
{
// Also add the underlying row
if (prov == 0 && getContext(getRow(0)) != null)
{
String label;
FromClause fc = query.getFromClause();
String alias = fc.getAlias();
if (alias != null)
label = alias;
else
label = fc.toString();
// Distinguish the select for all the columns
label = "SELECT ... " + label; //$NON-NLS-1$
// Use a null label to provide a default context without a sub-menu
builder.addContext(new ContextProvider(label) {
@Override
public IContextObject getContext(Object row)
{
return AbstractCustomTableResultSet.this.getContext(row);
}
});
++prov;
};
int columnIndex = ii;
builder.addContext(new ContextProvider(columns[ii].getLabel()) {
@Override
public IContextObject getContext(Object row)
{
Object o = getColumnValue(row, columnIndex);
int objectId = getObjectId(o);
boolean goodContext = false;
if (objectId == -1 && !(o instanceof Iterable<?> || o instanceof int[]))
{
IContextObject cx = AbstractCustomTableResultSet.this.getContext(row);
if (cx != null)
{
int selectId = cx.getObjectId();
if (selectId != -1)
goodContext = true;
else if (cx instanceof IContextObjectSet)
{
if (((IContextObjectSet)cx).getOQL() != null)
goodContext = true;
}
}
}
if (objectId != -1 || goodContext)
{
return new IContextObjectSet() {
@Override
public int getObjectId()
{
return objectId;
}
@Override
public int[] getObjectIds()
{
if (objectId != -1)
return new int[] {objectId};
else
return new int[0];
}
@Override
public String getOQL()
{
String alias = query.getFromClause().getAlias();
String alias2;
if (alias == null)
alias2 = ""; //$NON-NLS-1$
else
alias2 = " "+alias; //$NON-NLS-1$
Query.SelectItem column = query.getSelectClause().getSelectList().get(columnIndex);
IContextObject cx = AbstractCustomTableResultSet.this.getContext(row);
if (cx != null)
{
int selectId = cx.getObjectId();
if (selectId != -1)
return "SELECT " + column.toString() + " FROM OBJECTS " + selectId + alias2; //$NON-NLS-1$ //$NON-NLS-2$
else if (cx instanceof IContextObjectSet)
{
IContextObjectSet cs = (IContextObjectSet)cx;
String oqlsource = cs.getOQL();
if (oqlsource != null)
{
try
{
OQLQueryImpl q2 = new OQLQueryImpl(oqlsource);
q2.query.getSelectClause().setSelectList(Collections.emptyList());
String oqlsource2 = q2.query.toString();
return "SELECT "+column.toString()+" FROM OBJECTS (" + oqlsource2+") "+alias2; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
catch (OQLParseException e)
{
}
}
}
}
if (objectId != -1)
return OQL.forObjectId(objectId);
else
return OQLforSubject("*", o, ""); //$NON-NLS-1$ //$NON-NLS-2$
}
};
}
else if (o instanceof Iterable<?> || o instanceof int[])
{
return new IContextObjectSet() {
@Override
public int getObjectId()
{
IContextObject cx = AbstractCustomTableResultSet.this.getContext(row);
if (cx != null)
return cx.getObjectId();
else
return -1;
}
@Override
public int[] getObjectIds()
{
if (o instanceof int[])
{
for (int ix : (int[])o)
{
if (ix < 0 || ix >= objectCount)
return new int[0];
}
return (int[])o;
}
ArrayInt ai = new ArrayInt();
if (o instanceof Iterable<?>)
{
Iterable<?>l = (Iterable<?>)o;
for (Object o : l)
{
int objectId = AbstractCustomTableResultSet.getObjectId(o);
if (objectId != -1)
{
ai.add(objectId);
}
}
}
return ai.toArray();
}
@Override
public String getOQL()
{
String alias = query.getFromClause().getAlias();
String alias2;
if (alias == null)
alias2 = ""; //$NON-NLS-1$
else
alias2 = " "+alias; //$NON-NLS-1$
Query.SelectItem column = query.getSelectClause().getSelectList().get(columnIndex);
int selectId = getObjectId();
if (selectId != -1)
return "SELECT "+column.toString()+" FROM OBJECTS " + selectId + alias2; //$NON-NLS-1$ //$NON-NLS-2$
else
{
IContextObject cx = AbstractCustomTableResultSet.this.getContext(row);
if (cx instanceof IContextObjectSet)
{
IContextObjectSet cs = (IContextObjectSet)cx;
String oqlsource = cs.getOQL();
if (oqlsource != null)
{
try
{
OQLQueryImpl q2 = new OQLQueryImpl(oqlsource);
q2.query.getSelectClause().setSelectList(Collections.emptyList());
String oqlsource2 = q2.query.toString();
return "SELECT " + column.toString() + " FROM OBJECTS (" + oqlsource2 + ") " + alias2; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
catch (OQLParseException e)
{
}
}
}
}
return OQL.forObjectIds(getObjectIds());
}
};
}
return null;
}
});
++prov;
}
}
return builder.build();
}
/**
* Simple toString() which does not evaluate all the rows and columns,
* which otherwise a subclass of AbstractList would do.
*/
public String toString()
{
int ncols = getColumns() != null ? getColumns().length : 0;
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getSimpleName()).append('@').append(Integer.toHexString(System.identityHashCode(this)));
sb.append('[').append(getRowCount()).append(']');
sb.append('[').append(ncols).append(']');
sb.append('[');
final int maxRows = 2;
for (int i = 0; i < Math.min(maxRows, getRowCount()); ++i)
{
sb.append(get(i).toString());
if (i + 1 < getRowCount())
sb.append(',');
}
if (getRowCount() > maxRows)
sb.append("..."); //$NON-NLS-1$
sb.append(']');
return sb.toString();
}
}
/**
* Result from a select with select list where the from clause returned a list or array of non-heap objects.
* Each row is backed by a non-heap object.
*/
private static class ObjectResultSet extends AbstractCustomTableResultSet implements CustomTableResultSet
{
private static final Object NULL_VALUE = new Object();
private static class ValueHolder
{
Object subject;
Object[] values;
public ValueHolder(Object subject, Object[] values)
{
this.subject = subject;
this.values = values;
}
}
Object[] objects;
ObjectResultSet(OQLQueryImpl source, List<Object> objects) throws SnapshotException
{
this(source, objects.toArray());
}
ObjectResultSet(OQLQueryImpl source, Object[] objects) throws SnapshotException
{
super(source);
this.objects = objects;
List<SelectItem> selectList = source.query.getSelectClause().getSelectList();
columns = new Column[selectList.size()];
try
{
for (int ii = 0; ii < columns.length; ii++)
columns[ii] = buildColumn(selectList.get(ii), getRowCount() > 0 ? getColumnValue(getRow(0), ii) : null);
}
catch (RuntimeException e)
{
throw SnapshotException.rethrow(e);
}
}
public ResultMetaData getResultMetaData()
{
return getResultMetaData(source);
}
public Column[] getColumns()
{
return columns;
}
public int getRowCount()
{
return objects.length;
}
public Object getColumnValue(Object row, int columnIndex)
{
int index = (Integer) row;
if (!(objects[index] instanceof ValueHolder))
resolve(index);
ValueHolder holder = ((ValueHolder) objects[index]);
if (holder.values[columnIndex] == null)
{
try
{
source.ctx.setSubject(holder.subject);
// Don't track progress here for reading the cell
IProgressListener old = source.ctx.getProgressListener();
source.ctx.setProgressListener(new SilentProgressListener(old));
Query.SelectItem column = source.query.getSelectClause().getSelectList().get(columnIndex);
Object v = column.getExpression().compute(source.ctx);
source.ctx.setProgressListener(old);
holder.values[columnIndex] = v == null ? NULL_VALUE : v;
}
catch (SnapshotException e)
{
throw new RuntimeException(e);
}
}
return holder.values[columnIndex] == NULL_VALUE ? null : holder.values[columnIndex];
}
public IContextObjectSet getContext(Object row)
{
final int index = (Integer) row;
if (!(objects[index] instanceof ValueHolder))
resolve(index);
Object subject = ((ValueHolder) objects[index]).subject;
int objectId = getObjectId(subject);
if (objectId != -1 || subject == null || subject instanceof Character || subject instanceof Integer
|| subject instanceof Long || subject instanceof Float || subject instanceof Double
|| subject instanceof Boolean || subject instanceof String || subject instanceof AbstractCustomTableResultSet.RowMap)
{
if (objectId == -1)
{
if (OQLforSubject("*", subject, "") == null) //$NON-NLS-1$ //$NON-NLS-2$
return null;
}
return new IContextObjectSet()
{
public int getObjectId()
{
return objectId;
}
public int[] getObjectIds()
{
if (getObjectId() != -1)
return new int[] {getObjectId()};
else
return new int[0];
}
public String getOQL()
{
String alias = source.query.getFromClause().getAlias();
String alias2;
if (alias == null)
alias2 = ""; //$NON-NLS-1$
else
alias2 = " "+alias; //$NON-NLS-1$
SelectClause sc = source.query.getSelectClause();
if (sc.isRetainedSet())
{
// Remove asRetainedSet() as we just have a single object here
SelectClause sc2 = new SelectClause();
sc2.setAsObjects(sc.isAsObjects());
sc2.setSelectList(sc.getSelectList());
sc = sc2;
}
String from;
if (getObjectId() != -1)
from = String.valueOf(getObjectId());
else
{
// OQL literals (not Byte or Short)
String oql = OQLforSubject("*", subject, alias2); //$NON-NLS-1$
from = "(" + oql + ")";//$NON-NLS-1$ //$NON-NLS-2$
}
return "SELECT "+sc.toString() +" FROM OBJECTS " + from + alias2; //$NON-NLS-1$ //$NON-NLS-2$
}
};
}
else
{
return null;
}
}
public Object getRow(int index)
{
return index;
}
public String getOQLQuery()
{
return source.toString();
}
private void resolve(int index)
{
ValueHolder answer = new ValueHolder(objects[index], new Object[columns.length]);
objects[index] = answer;
}
}
/**
* Result from a select with select list where the from clause returned an array of object IDs.
* Each row is backed by a heap object, so will appear in the inspector view.
*/
private static class ResultSet extends AbstractCustomTableResultSet implements CustomTableResultSet
{
private static final Object NULL_VALUE = new Object();
private static class ValueHolder
{
Object[] values;
public ValueHolder(Object[] values)
{
this.values = values;
}
}
int[] objectIds;
ValueHolder[] objects;
public ResultSet(OQLQueryImpl source, int[] objectIds) throws SnapshotException
{
super(source);
this.objectIds = objectIds;
this.objects = new ValueHolder[objectIds.length];
List<SelectItem> selectList = source.query.getSelectClause().getSelectList();
columns = new Column[selectList.size()];
try
{
for (int ii = 0; ii < columns.length; ii++)
columns[ii] = buildColumn(selectList.get(ii), getRowCount() > 0 ? getColumnValue(getRow(0), ii) : null);
}
catch (RuntimeException e)
{
throw SnapshotException.rethrow(e);
}
}
public ResultMetaData getResultMetaData()
{
return getResultMetaData(source);
}
public int getRowCount()
{
return objectIds.length;
}
public Object getColumnValue(Object row, int columnIndex)
{
int index = (Integer) row;
if (objects[index] == null)
objects[index] = new ValueHolder(new Object[columns.length]);
if (objects[index].values[columnIndex] == null)
{
// each column value is calculated separately, because I do not
// want sorting to resolve all values in all rows
// NULL_VALUE is used to keep track of column values which have
// been calculated but returned a null value
try
{
IObject object = source.ctx.getSnapshot().getObject(objectIds[index]);
source.ctx.setSubject(object);
// Don't track progress here for reading the cell
IProgressListener old = source.ctx.getProgressListener();
source.ctx.setProgressListener(new SilentProgressListener(old));
List<SelectItem> selectList = source.query.getSelectClause().getSelectList();
Object value = selectList.get(columnIndex).getExpression().compute(source.ctx);
source.ctx.setProgressListener(old);
objects[index].values[columnIndex] = value != null ? value : NULL_VALUE;
}
catch (SnapshotException e)
{
throw new RuntimeException(e);
}
}
Object value = objects[index].values[columnIndex];
return value == NULL_VALUE ? null : value;
}
public IContextObjectSet getContext(final Object row)
{
return new IContextObjectSet()
{
public int getObjectId()
{
return objectIds[(Integer) row];
}
public int[] getObjectIds()
{
return new int[] {getObjectId()};
}
public String getOQL()
{
String alias = source.query.getFromClause().getAlias();
String alias2;
if (alias == null)
alias2 = ""; //$NON-NLS-1$
else
alias2 = " "+alias; //$NON-NLS-1$
SelectClause sc = source.query.getSelectClause();
if (sc.isRetainedSet())
{
// Remove asRetainedSet() as we just have a single object here
SelectClause sc2 = new SelectClause();
sc2.setAsObjects(sc.isAsObjects());
sc2.setSelectList(sc.getSelectList());
sc = sc2;
}
return "SELECT " + sc.toString() + " FROM OBJECTS " + getObjectId() + alias2; //$NON-NLS-1$ //$NON-NLS-2$
}
};
}
public Object getRow(int index)
{
return index;
}
public Column[] getColumns()
{
return columns;
}
public String getOQLQuery()
{
return source.query.toString();
}
}
private static Column buildColumn(SelectItem column, Object columnValue)
{
String name = column.getName();
if (name == null)
name = column.getExpression().toString();
Class<?> type = columnValue != null ? columnValue.getClass() : Object.class;
return new Column(name, type).noTotals();
}
public static String OQLLiteral(Object subject)
{
// OQL literals (not Byte or Short)
if (subject instanceof Character)
return "'" + subject + "'"; //$NON-NLS-1$ //$NON-NLS-2$
else if (subject instanceof String)
return "\"" + subject + "\""; //$NON-NLS-1$ //$NON-NLS-2$
else if (subject instanceof Boolean || subject instanceof Integer
|| subject instanceof Float || subject instanceof Double || subject == null)
return String.valueOf(subject);
else if (subject instanceof Long)
return Long.toHexString((Long) subject) + "L)"; //$NON-NLS-1$
else if (subject instanceof IObject)
return "${snapshot}.getObject(" + Integer.toString(((IObject) subject).getObjectId()) + ")"; //$NON-NLS-1$//$NON-NLS-2$
else
return null;
}
public static String OQLforSubject(String select, Object subject, String alias)
{
String from;
// OQL literals (not Byte or Short)
String literal = OQLLiteral(subject);
if (literal != null)
from = "(" + literal + ")"; //$NON-NLS-1$ //$NON-NLS-2$
else if (subject instanceof AbstractCustomTableResultSet.RowMap)
{
AbstractCustomTableResultSet.RowMap rm = ( AbstractCustomTableResultSet.RowMap)subject;
StringBuilder buf = new StringBuilder();
for (Map.Entry<String,Object> e : rm.entrySet())
{
Object val = e.getValue();
String v1;
if (val == null)
{
// "null" doesn't work
v1 = "toString(\"\").toCharArray()[0:-1]"; //$NON-NLS-1$
}
else
{
v1 = OQLLiteral(val);
}
if (v1 == null)
{
v1 = OQLforSubject("*", val, ""); //$NON-NLS-1$ //$NON-NLS-2$
if (v1 == null)
return null;
v1 = "(" + v1 + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
if (buf.length() == 0)
buf.append("SELECT "); //$NON-NLS-1$
else
buf.append(", "); //$NON-NLS-1$
buf.append(v1).append(" AS "); //$NON-NLS-1$
String name = e.getKey();
boolean quote = !name.matches("[A-Za-z$_][A-Za-z0-9$_]*"); //$NON-NLS-1$
if (quote)
buf.append('"');
buf.append(name);
if (quote)
buf.append('"');
}
buf.append(" FROM OBJECTS (null)").append(alias); //$NON-NLS-1$
from = buf.toString();
if (select.equals("*")) //$NON-NLS-1$
return from;
}
else
return null;
return "SELECT " + select + " FROM OBJECTS " + from + alias; //$NON-NLS-1$ //$NON-NLS-2$
}
private static class UnionResultSet extends AbstractCustomTableResultSet implements Result, IResultTable
{
private static class ValueHolder
{
Query query;
IResultTable source;
Object row;
public ValueHolder(Query query, IResultTable source, Object row)
{
this.query = query;
this.source = source;
this.row = row;
}
}
int size = 0;
List<Query> queries = new ArrayList<Query>();
List<CustomTableResultSet> resultSets = new ArrayList<CustomTableResultSet>();
ArrayInt sizes = new ArrayInt(5);
public UnionResultSet(OQLQueryImpl source)
{
super(source);
}
public void addResultSet(Query query, CustomTableResultSet resultSet)
{
queries.add(query);
sizes.add(size);
size += resultSet.getRowCount();
resultSets.add(resultSet);
}
/**
* Metadata for object columns for UNION queries
*/
public ResultMetaData getResultMetaData()
{
Column columns[] = getColumns();
int objectCount = source.ctx.getSnapshot().getSnapshotInfo().getNumberOfObjects();
ResultMetaData.Builder builder = new ResultMetaData.Builder();
int prov = 0;
for (int ii = 0; ii < columns.length; ++ii)
{
// Find an example object for each column
Object sample = null;
IContextObject sampleContext = null;
// Look in each table in case single columns have blanks
for (IResultTable tab : resultSets)
{
Object o = tab.getRowCount() > 0 ? tab.getColumnValue(tab.getRow(0), ii) : null;
if (o != null)
{
sample = o;
if (prov == 0)
sampleContext = tab.getContext(tab.getRow(0));
break;
}
}
// See if it is one or more objects from the dump
if (getObjectId(sample) != -1 ||
sample instanceof Iterable<?> && ((Iterable<?>)sample).iterator().hasNext() && getObjectId(((Iterable<?>)sample).iterator().next()) != -1 ||
sample instanceof int[])
{
// Also add the underlying row
if (prov == 0 && sampleContext != null)
{
String label;
FromClause fc = queries.get(0).getFromClause();
String alias = fc.getAlias();
if (alias != null)
label = alias;
else
label = fc.toString();
// Distinguish the select for all the columns
label = "SELECT ... " + label; //$NON-NLS-1$
// Use a null label to provide a default context without a sub-menu
builder.addContext(new ContextProvider(label) {
@Override
public IContextObject getContext(Object row)
{
return UnionResultSet.this.getContext(row);
}
});
++prov;
};
int columnIndex = ii;
builder.addContext(new ContextProvider(columns[ii].getLabel()) {
@Override
public IContextObject getContext(Object row)
{
Object o = getColumnValue(row, columnIndex);
int objectId = getObjectId(o);
boolean goodContext = false;
if (objectId == -1 && !(o instanceof Iterable<?> || o instanceof int[]))
{
IContextObject cx = UnionResultSet.this.getContext(row);
if (cx != null)
{
int selectId = cx.getObjectId();
if (selectId != -1)
goodContext = true;
else if (cx instanceof IContextObjectSet)
{
if (((IContextObjectSet)cx).getOQL() != null)
goodContext = true;
}
}
}
if (objectId != -1 || goodContext)
{
return new IContextObjectSet() {
@Override
public int getObjectId()
{
return objectId;
}
@Override
public int[] getObjectIds()
{
if (objectId != -1)
return new int[] {objectId};
else
return new int[0];
}
@Override
public String getOQL()
{
String alias = ((ValueHolder)row).query.getFromClause().getAlias();
String alias2;
if (alias == null)
alias2 = ""; //$NON-NLS-1$
else
alias2 = " "+alias; //$NON-NLS-1$
Query.SelectItem column = ((ValueHolder)row).query.getSelectClause().getSelectList().get(columnIndex);
IContextObject cx = UnionResultSet.this.getContext(row);
if (cx != null)
{
int selectId = cx.getObjectId();
if (selectId != -1)
return "SELECT "+column.toString()+" FROM OBJECTS " + selectId+alias2; //$NON-NLS-1$ //$NON-NLS-2$
else if (cx instanceof IContextObjectSet)
{
IContextObjectSet cs = (IContextObjectSet)cx;
String oqlsource = cs.getOQL();
if (oqlsource != null)
{
try
{
OQLQueryImpl q2 = new OQLQueryImpl(oqlsource);
q2.query.getSelectClause().setSelectList(Collections.emptyList());
String oqlsource2 = q2.query.toString();
return "SELECT "+column.toString()+" FROM OBJECTS (" + oqlsource2+") "+alias2; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
catch (OQLParseException e)
{
}
}
}
}
if (objectId != -1)
return OQL.forObjectId(objectId);
else
return OQLforSubject("*", o, ""); //$NON-NLS-1$ //$NON-NLS-2$
}
};
}
else if (o instanceof Iterable<?> || o instanceof int[])
{
return new IContextObjectSet() {
@Override
public int getObjectId()
{
IContextObject cx = UnionResultSet.this.getContext(row);
if (cx != null)
return cx.getObjectId();
else
return -1;
}
@Override
public int[] getObjectIds()
{
if (o instanceof int[])
{
for (int ix : (int[])o)
{
if (ix < 0 || ix >= objectCount)
return new int[0];
}
return (int[])o;
}
ArrayInt ai = new ArrayInt();
if (o instanceof Iterable<?>)
{
Iterable<?>l = (Iterable<?>)o;
for (Object o : l)
{
int objectId = UnionResultSet.getObjectId(o);
if (objectId != -1)
{
ai.add(objectId);
}
}
}
return ai.toArray();
}
@Override
public String getOQL()
{
String alias = ((ValueHolder)row).query.getFromClause().getAlias();
String alias2;
if (alias == null)
alias2 = ""; //$NON-NLS-1$
else
alias2 = " "+alias; //$NON-NLS-1$
Query.SelectItem column = ((ValueHolder)row).query.getSelectClause().getSelectList().get(columnIndex);
int selectId = getObjectId();
if (selectId != -1)
return "SELECT "+column.toString()+" FROM OBJECTS " + selectId+alias2; //$NON-NLS-1$ //$NON-NLS-2$
else
{
IContextObject cx = UnionResultSet.this.getContext(row);
if (cx instanceof IContextObjectSet)
{
IContextObjectSet cs = (IContextObjectSet)cx;
String oqlsource = cs.getOQL();
if (oqlsource != null)
{
try
{
OQLQueryImpl q2 = new OQLQueryImpl(oqlsource);
q2.query.getSelectClause().setSelectList(Collections.emptyList());
String oqlsource2 = q2.query.toString();
return "SELECT " + column.toString() + " FROM OBJECTS (" + oqlsource2 + ") " + alias2; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
catch (OQLParseException e)
{
}
}
}
}
return OQL.forObjectIds(getObjectIds());
}
};
}
return null;
}
});
++prov;
}
}
return builder.build();
}
public Column[] getColumns()
{
return resultSets.get(0).getColumns();
}
public int getRowCount()
{
return size;
}
public Object getRow(int index)
{
int ii = findPageFor(index);
Query query = queries.get(ii);
IResultTable rs = resultSets.get(ii);
Object value = rs.getRow(index - sizes.get(ii));
return new ValueHolder(query, rs, value);
}
public Object getColumnValue(Object row, int columnIndex)
{
ValueHolder holder = (ValueHolder) row;
return holder.source.getColumnValue(holder.row, columnIndex);
}
public IContextObject getContext(Object row)
{
ValueHolder holder = (ValueHolder) row;
return holder.source.getContext(holder.row);
}
private int findPageFor(int rowNo)
{
int pageIndex = 0;
while (pageIndex + 1 < sizes.size() && rowNo >= sizes.get(pageIndex + 1))
pageIndex++;
return pageIndex;
}
public String getOQLQuery()
{
StringBuilder buf = new StringBuilder();
for (Result resultSet : resultSets)
OQL.union(buf, resultSet.getOQLQuery());
return buf.toString();
}
}
private interface IntIterator
{
int nextInt();
boolean hasNext();
}
private interface IntResult
{
void add(int id);
void addAll(int[] ids);
void addAll(IntResult intResult);
int size();
int[] toArray();
boolean isEmpty();
IntIterator iterator();
}
private static class IntArrayResult implements IntResult
{
ArrayInt arrayInt;
public IntArrayResult(int capacity)
{
this.arrayInt = new ArrayInt(capacity);
}
public IntArrayResult(int[] initialValues)
{
this.arrayInt = new ArrayInt(initialValues);
}
public IntArrayResult(ArrayInt values)
{
this.arrayInt = values;
}
public void add(int id)
{
this.arrayInt.add(id);
}
public void addAll(int[] ids)
{
this.arrayInt.addAll(ids);
}
public void addAll(IntResult intResult)
{
if (intResult instanceof IntArrayResult)
{
this.arrayInt.addAll(((IntArrayResult) intResult).arrayInt);
}
else
{
for (IntIterator iter = intResult.iterator(); iter.hasNext();)
this.arrayInt.add(iter.nextInt());
}
}
public int size()
{
return this.arrayInt.size();
}
public int[] toArray()
{
return this.arrayInt.toArray();
}
public boolean isEmpty()
{
return this.arrayInt.isEmpty();
}
public IntIterator iterator()
{
return new IntIterator()
{
int nextIndex = 0;
public boolean hasNext()
{
return nextIndex < arrayInt.size();
}
public int nextInt()
{
return arrayInt.get(nextIndex++);
}
};
}
}
private static class IntSetResult implements IntResult
{
SetInt setInt;
public IntSetResult(int capacity)
{
this.setInt = new SetInt(capacity);
}
public void add(int id)
{
this.setInt.add(id);
}
public void addAll(int[] ids)
{
for (int id : ids)
this.setInt.add(id);
}
public void addAll(IntResult intResult)
{
for (IntIterator iter = intResult.iterator(); iter.hasNext();)
this.setInt.add(iter.nextInt());
}
public int size()
{
return this.setInt.size();
}
public int[] toArray()
{
return this.setInt.toArray();
}
public boolean isEmpty()
{
return this.setInt.isEmpty();
}
public IntIterator iterator()
{
return new IntIterator()
{
IteratorInt intEnum = setInt.iterator();
public boolean hasNext()
{
return intEnum.hasNext();
}
public int nextInt()
{
return intEnum.next();
}
};
}
}
// //////////////////////////////////////////////////////////////
// oql execution
// //////////////////////////////////////////////////////////////
public OQLQueryImpl(EvaluationContext parent, Query query)
{
init(parent, query);
}
public OQLQueryImpl(String queryString) throws OQLParseException
{
try
{
OQLParser p = new OQLParser(new StringReader(queryString));
p.setCompiler(new CompilerImpl());
Query query = p.ParseQuery();
init(null, query);
}
catch (ParseException e)
{
int line = e.currentToken.next.beginLine;
int column = e.currentToken.next.beginColumn;
// stack of no additional use but clutters UI
throw new OQLParseException(e.getMessage(), null, line, column);
}
catch (TokenMgrError e)
{
String msg = e.getMessage();
int line = 1, column = 1;
Pattern pattern = Pattern.compile("Lexical error at line ([0-9]*), column ([0-9]*)\\..*");//$NON-NLS-1$
Matcher matcher = pattern.matcher(msg);
if (matcher.matches())
{
line = Integer.parseInt(matcher.group(1));
column = Integer.parseInt(matcher.group(2));
}
// stack of no additional use but clutters UI
throw new OQLParseException(msg, null, line, column);
}
}
private void init(EvaluationContext parent, Query query)
{
this.query = query;
this.ctx = new EvaluationContext(parent);
if (query.getFromClause() != null)
this.ctx.setAlias(query.getFromClause().getAlias());
}
private void initSnapshot(ISnapshot snapshot)
{
this.ctx.setSnapshot(snapshot);
}
public Object execute(ISnapshot snapshot, IProgressListener monitor) throws SnapshotException
{
if (snapshot == null)
throw new NullPointerException(Messages.OQLQueryImpl_Error_MissingSnapshot);
initSnapshot(snapshot);
if (monitor == null)
{
if (this.ctx.getProgressListener() != null)
monitor = new SilentProgressListener(this.ctx.getProgressListener());
else
monitor = new VoidProgressListener();
}
IProgressListener old = this.ctx.getProgressListener();
this.ctx.setProgressListener(monitor);
Object result = internalExecute(monitor);
this.ctx.setProgressListener(old);
return result instanceof IntResult ? ((IntResult) result).toArray() : result;
}
protected Object internalExecute(IProgressListener monitor) throws SnapshotException
{
int percentages[] = new int[(1 + (query.getUnionQueries() != null ? query.getUnionQueries().size() : 0))];
Arrays.fill(percentages, 100);
SimpleMonitor listener = new SimpleMonitor(query.toString(), monitor, percentages);
// process query
Object result = null;
if (query.getFromClause().getSubSelect() != null)
{
result = doSubQuery(listener.nextMonitor());
}
else if (query.getFromClause().getCall() != null)
{
result = doMethodCall(listener.nextMonitor());
}
else
{
result = doFromItem(listener.nextMonitor());
}
if (query.getUnionQueries() != null)
{
result = union(listener, result);
}
monitor.done();
return result;
}
private Object union(SimpleMonitor monitor, Object result) throws SnapshotException, OQLParseException
{
// one of those will hold the result
UnionResultSet unionResultSet = null;
IntResult unionIntResult = null;
if (result instanceof CustomTableResultSet)
{
unionResultSet = new UnionResultSet(this);
unionResultSet.addResultSet(query, (CustomTableResultSet) result);
}
else if (result instanceof IntResult)
{
IntResult intResult = (IntResult) result;
unionIntResult = new IntArrayResult(intResult.size());
unionIntResult.addAll(intResult);
}
else
{
// Create a dummy result to hold the query for redisplay later
if (!(this.query.getSelectClause().getSelectList().isEmpty() || this.query.getSelectClause().isAsObjects()))
{
unionResultSet = new UnionResultSet(this);
unionResultSet.addResultSet(query, new ResultSet(getSelectQuery(), new int[0]));
}
}
for (Query q : query.getUnionQueries())
{
// check the compatibility of UNION queries
if (this.query.getSelectClause().getSelectList().isEmpty() || this.query.getSelectClause().isAsObjects())
{
if (!q.getSelectClause().getSelectList().isEmpty() && !q.getSelectClause().isAsObjects()) { throw new SnapshotException(
MessageUtil.format(Messages.OQLQueryImpl_Error_QueryMustReturnObjects,
new Object[] { q })); }
}
else
{
if (q.getSelectClause().getSelectList().size() != this.query.getSelectClause().getSelectList().size()) { throw new SnapshotException(
MessageUtil.format(Messages.OQLQueryImpl_Error_QueryMustHaveIdenticalSelectItems,
new Object[] { q })); }
}
OQLQueryImpl unionQuery = new OQLQueryImpl(this.ctx, q);
Object unionResult = unionQuery.internalExecute(monitor.nextMonitor());
if (unionResult != null)
{
if (unionResultSet != null)
{
unionResultSet.addResultSet(q, (CustomTableResultSet) unionResult);
}
else if (unionIntResult != null)
{
unionIntResult.addAll((IntResult) unionResult);
}
// If no combined result has been created then get one now.
else if (this.query.getSelectClause().getSelectList().isEmpty() || this.query.getSelectClause().isAsObjects())
{
unionIntResult = new IntArrayResult(0);
unionIntResult.addAll((IntResult) unionResult);
}
else
{
unionResultSet = new UnionResultSet(this);
unionResultSet.addResultSet(q, (CustomTableResultSet) unionResult);
}
}
else
{
// Create a dummy result to hold the query for redisplay later
if (unionResultSet != null)
{
unionResultSet.addResultSet(q, new ResultSet(unionQuery, new int[0]));
}
}
}
return unionResultSet != null ? (unionResultSet.getRowCount() > 0 ? unionResultSet : null) : unionIntResult;
}
private Object doSubQuery(IProgressListener monitor) throws SnapshotException
{
int percentages[] = new int[] {300,100};
SimpleMonitor listener = new SimpleMonitor(query.toString(), monitor, percentages);
OQLQueryImpl subQuery = new OQLQueryImpl(this.ctx, query.getFromClause().getSubSelect());
Object result = subQuery.internalExecute(listener.nextMonitor());
monitor = listener.nextMonitor();
if (query.getFromClause().includeObjects() && !query.getFromClause().includeSubClasses())
{
if (result == null)
{
return null;
}
else if (result instanceof AbstractCustomTableResultSet)
{
/*
* Experiment - flatten sub-select containing selects in select items
*/
return filterAndSelect((AbstractCustomTableResultSet)result, monitor);
}
else if (result instanceof Iterable)
{
List<Object> r = new ArrayList<Object>();
for (Object obj : (Iterable<?>) result)
{
if (accept(obj, monitor))
r.add(obj);
if (monitor.isCanceled())
throw new IProgressListener.OperationCanceledException();
}
return r.isEmpty() ? null : select(r, monitor);
}
else if (result.getClass().isArray())
{
List<Object> r = new ArrayList<Object>();
int length = Array.getLength(result);
for (int ii = 0; ii < length; ii++)
{
Object obj = Array.get(result, ii);
if (accept(obj, monitor))
r.add(obj);
if (monitor.isCanceled())
throw new IProgressListener.OperationCanceledException();
}
return r.isEmpty() ? null : select(r, monitor);
}
else if (result instanceof IResultTable || result instanceof IResultTree)
{
return filterAndSelect((IStructuredResult)result, monitor);
}
else if (!(result instanceof IntResult))
{
return accept(result, monitor) ? select(result, monitor) : null;
}
}
if (!(result instanceof IntResult))
throw new SnapshotException(MessageUtil.format(Messages.OQLQueryImpl_Error_MustReturnObjectList,
new Object[] { query.getFromClause().getSubSelect() }));
IntResult baseSet = (IntResult) result;
if (query.getFromClause().includeObjects() && !query.getFromClause().includeSubClasses())
{
return filterAndSelect(baseSet, monitor);
}
else
{
// result must contain only classes
// convert and process as usual
try
{
List<IClass> classes = new ArrayList<IClass>(baseSet.size());
for (IntIterator iter = baseSet.iterator(); iter.hasNext();)
{
int id = iter.nextInt();
IObject o = ctx.getSnapshot().getObject(id);
if (!(o instanceof IClass))
throw new SnapshotException(Messages.OQLQueryImpl_Error_ClassCastExceptionOccured);
IClass subjectClass = (IClass)o;
classes.add(subjectClass);
if (query.getFromClause().includeSubClasses())
{
classes.addAll(subjectClass.getAllSubclasses());
}
}
return filterClasses(monitor, classes);
}
catch (ClassCastException e)
{
throw new SnapshotException(Messages.OQLQueryImpl_Error_ClassCastExceptionOccured, e);
}
}
}
/**
* Does the FROM clause of a select with classes/objects.
* Input from:
* <pre>
* classname
* classnamepattern
* object ids
* object addresses
* </pre>
* @param listener
* @return
* @throws SnapshotException
*/
private Object doFromItem(IProgressListener listener) throws SnapshotException
{
Collection<IClass> classes = null;
if (query.getFromClause().getClassName() != null)
{
// [a] class name given
classes = ctx.getSnapshot().getClassesByName(query.getFromClause().getClassName(),
query.getFromClause().includeSubClasses());
}
else if (query.getFromClause().getClassNamePattern() != null)
{
// [b] class name pattern given
try
{
Pattern pattern = Pattern.compile(PatternUtil.smartFix(query.getFromClause().getClassNamePattern(),
false));
classes = ctx.getSnapshot().getClassesByName(pattern, query.getFromClause().includeSubClasses());
}
catch (PatternSyntaxException e)
{
throw new SnapshotException(MessageUtil.format(Messages.OQLQueryImpl_Error_InvalidClassNamePattern,
new Object[] { query.getFromClause().getClassNamePattern() }), e);
}
}
else if (query.getFromClause().getObjectIds() != null)
{
if (query.getFromClause().includeObjects() && !query.getFromClause().includeSubClasses())
{
return filterAndSelect(new IntArrayResult(query.getFromClause().getObjectIds().toArray()), listener);
}
else
{
classes = new ArrayList<IClass>();
for (IteratorInt ee = query.getFromClause().getObjectIds().iterator(); ee.hasNext();)
{
IObject subject = ctx.getSnapshot().getObject(ee.next());
if (subject instanceof IClass)
{
IClass subjectClass = (IClass) subject;
classes.add(subjectClass);
if (query.getFromClause().includeSubClasses())
{
classes.addAll(subjectClass.getAllSubclasses());
}
}
else
{
throw new SnapshotException(MessageUtil.format(Messages.OQLQueryImpl_Errot_IsNotClass,
new Object[] { Long.toHexString(subject.getObjectAddress()) }));
}
}
}
}
else if (query.getFromClause().getObjectAddresses() != null)
{
ArrayLong objectAddresses = query.getFromClause().getObjectAddresses();
IntArrayResult result = new IntArrayResult(objectAddresses.size());
for (IteratorLong ee = objectAddresses.iterator(); ee.hasNext();)
result.add(ctx.getSnapshot().mapAddressToId(ee.next()));
if (query.getFromClause().includeObjects() && !query.getFromClause().includeSubClasses())
{
return filterAndSelect(result, listener);
}
else
{
classes = new ArrayList<IClass>();
for (IntIterator iter = result.iterator(); iter.hasNext();)
{
IObject subject = ctx.getSnapshot().getObject(iter.nextInt());
if (subject instanceof IClass)
{
IClass subjectClass = (IClass) subject;
classes.add(subjectClass);
if (query.getFromClause().includeSubClasses())
{
classes.addAll(subjectClass.getAllSubclasses());
}
}
else
{
throw new SnapshotException(MessageUtil.format(Messages.OQLQueryImpl_Errot_IsNotClass,
new Object[] { Long.toHexString(subject.getObjectAddress()) }));
}
}
}
}
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
if (classes == null || classes.isEmpty())
return null;
return filterClasses(listener, classes);
}
/**
* Does a method call in a FROM clause of a select.
* <pre>
* OBJECTS
* null
* Iterable
* array
* NONE/INSTANCEOF
* null
* Iterable/array
* null
* Integer
* int[]
* IClass
* </pre>
* @param listener
* @return
* @throws SnapshotException
*/
private Object doMethodCall(IProgressListener listener) throws SnapshotException
{
int percentages[] = new int[] {300,100};
SimpleMonitor smlistener = new SimpleMonitor(query.toString(), listener, percentages);
Expression method = query.getFromClause().getCall();
this.ctx.setSubject(this.ctx.getSnapshot());
IProgressListener old = ctx.getProgressListener();
this.ctx.setProgressListener(smlistener.nextMonitor());
Object result = method.compute(this.ctx);
this.ctx.setProgressListener(old);
listener = smlistener.nextMonitor();
if (query.getFromClause().includeObjects() && !query.getFromClause().includeSubClasses())
{
if (result == null)
{
return null;
}
else if (result instanceof Iterable)
{
List<Object> r = new ArrayList<Object>();
for (Object obj : (Iterable<?>) result)
{
if (accept(obj, listener))
r.add(obj);
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
}
return r.isEmpty() ? null : select(r, listener);
}
else if (result.getClass().isArray())
{
List<Object> r = new ArrayList<Object>();
int length = Array.getLength(result);
for (int ii = 0; ii < length; ii++)
{
Object obj = Array.get(result, ii);
if (accept(obj, listener))
r.add(obj);
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
}
return r.isEmpty() ? null : select(r, listener);
}
else if (result instanceof IResultTable || result instanceof IResultTree)
{
return filterAndSelect((IStructuredResult)result, listener);
}
else
{
return accept(result, listener) ? select(result, listener) : null;
}
}
else
{
// result must contain only classes
// convert and process as usual
List<IClass> classes = new ArrayList<IClass>();
try
{
if (result == null)
{
return null;
}
else if (result instanceof Iterable)
{
for (Object obj : (Iterable<?>) result)
{
if (obj == null)
{
// allowed value
}
else if (obj instanceof Integer)
{
IObject o = this.ctx.getSnapshot().getObject(((Integer) obj).intValue());
if (!(o instanceof IClass))
throw new SnapshotException(MessageUtil.format(Messages.OQLQueryImpl_Error_ElementIsNotClass,
query.getFromClause().toString(), o.getClass().getName()));
IClass subjectClass = (IClass)o;
classes.add(subjectClass);
if (query.getFromClause().includeSubClasses())
{
classes.addAll(subjectClass.getAllSubclasses());
}
}
else if (obj instanceof int[])
{
for (int id : (int[]) obj)
{
IObject o = this.ctx.getSnapshot().getObject(id);
if (!(o instanceof IClass))
throw new SnapshotException(MessageUtil.format(Messages.OQLQueryImpl_Error_ElementIsNotClass,
query.getFromClause().toString(), o.getClass().getName()));
IClass subjectClass = (IClass)o;
classes.add(subjectClass);
if (query.getFromClause().includeSubClasses())
{
classes.addAll(subjectClass.getAllSubclasses());
}
}
}
else if (obj instanceof IClass)
{
IClass subjectClass = (IClass) obj;
classes.add(subjectClass);
if (query.getFromClause().includeSubClasses())
{
classes.addAll(subjectClass.getAllSubclasses());
}
}
else
{
throw new SnapshotException(MessageUtil.format(Messages.OQLQueryImpl_Error_ElementIsNotClass, query.getFromClause().toString(), obj.getClass().getName()));
}
}
}
else if (result.getClass().isArray())
{
int length = Array.getLength(result);
for (int ii = 0; ii < length; ii++)
{
Object obj = Array.get(result, ii);
if (obj == null)
{
// allowed value
}
else if (obj instanceof Integer)
{
IObject o = this.ctx.getSnapshot().getObject(((Integer) obj).intValue());
if (!(o instanceof IClass))
throw new SnapshotException(MessageUtil.format(Messages.OQLQueryImpl_Error_ElementIsNotClass,
query.getFromClause().toString(), o.getClass().getName()));
IClass subjectClass = (IClass)o;
classes.add(subjectClass);
if (query.getFromClause().includeSubClasses())
{
classes.addAll(subjectClass.getAllSubclasses());
}
}
else if (obj instanceof int[])
{
for (int id : (int[]) obj)
{
IClass subjectClass = (IClass) this.ctx.getSnapshot().getObject(id);
classes.add(subjectClass);
if (query.getFromClause().includeSubClasses())
{
classes.addAll(subjectClass.getAllSubclasses());
}
}
}
else if (obj instanceof IClass)
{
IClass subjectClass = (IClass) obj;
classes.add(subjectClass);
if (query.getFromClause().includeSubClasses())
{
classes.addAll(subjectClass.getAllSubclasses());
}
}
else
{
throw new SnapshotException(MessageUtil.format(Messages.OQLQueryImpl_Error_ElementIsNotClass, query.getFromClause().toString(), obj.getClass().getName()));
}
}
}
else if (result instanceof IClass)
{
IClass subjectClass = (IClass) result;
classes.add(subjectClass);
if (query.getFromClause().includeSubClasses())
{
classes.addAll(subjectClass.getAllSubclasses());
}
}
else
{
throw new SnapshotException(MessageUtil.format(Messages.OQLQueryImpl_Error_ElementIsNotClass, query.getFromClause().toString(), result.getClass().getName()));
}
}
catch (ClassCastException e)
{
throw new SnapshotException(MessageUtil.format(Messages.OQLQueryImpl_Error_ElementIsNotClass,
query.getFromClause().toString(), e.getMessage()), e);
}
return filterClasses(listener, classes);
}
}
private Object filterClasses(IProgressListener listener, Collection<IClass> classes) throws SnapshotException
{
if (query.getFromClause().includeObjects())
{
listener.beginTask(Messages.OQLQueryImpl_SelectingObjects, classes.size());
IntResult filteredSet = createIntResult(classes.size());
for (IClass clasz : classes)
{
if (accept(clasz.getObjectId(), listener))
filteredSet.add(clasz.getObjectId());
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
listener.worked(1);
}
return filteredSet.isEmpty() ? null : select(filteredSet, listener);
}
else
{
// Keep track of progress via classes or objects
int work = classes.size();
boolean countObjs = work < 1000;
if (countObjs)
{
for (IClass clasz : classes)
{
work += clasz.getNumberOfObjects();
}
if (work < classes.size())
{
// Original way
countObjs = false;
work = classes.size();
}
}
listener.beginTask(Messages.OQLQueryImpl_CollectingObjects, work);
IntResult filteredSet = createIntResult(classes.size() * 100);
for (IClass clasz : classes)
{
listener.subTask(MessageUtil.format(Messages.OQLQueryImpl_CheckingClass,
new Object[] { clasz.getName() }));
int[] ids = clasz.getObjectIds();
for (int id : ids)
{
if (accept(id, listener))
filteredSet.add(id);
if (countObjs)
listener.worked(1);
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
}
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
if (!countObjs)
listener.worked(1);
}
return filteredSet.isEmpty() ? null : select(filteredSet, listener);
}
}
private boolean accept(int objectId, IProgressListener mon) throws SnapshotException
{
if (query.getWhereClause() == null)
return true;
return accept(ctx.getSnapshot().getObject(objectId), mon);
}
private boolean accept(Object object, IProgressListener mon) throws SnapshotException
{
if (query.getWhereClause() == null)
return true;
ctx.setSubject(object);
// We don't track work for the WHERE clause
IProgressListener old = ctx.getProgressListener();
ctx.setProgressListener(new SilentProgressListener(mon));
Boolean result = (Boolean) query.getWhereClause().compute(ctx);
ctx.setProgressListener(old);
return result == null ? false : result.booleanValue();
}
private Object filterAndSelect(IntResult objectIds, IProgressListener listener) throws SnapshotException
{
IntResult filteredSet = createIntResult(objectIds.size());
for (IntIterator iter = objectIds.iterator(); iter.hasNext();)
{
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
int id = iter.nextInt();
if (accept(id, listener))
filteredSet.add(id);
}
return filteredSet.isEmpty() ? null : select(filteredSet, listener);
}
private Object select(IntResult objectIds, IProgressListener listener) throws SnapshotException
{
Query.SelectClause select = query.getSelectClause();
// calculate retained set
if (select.isRetainedSet())
{
objectIds = new IntArrayResult(ctx.getSnapshot().getRetainedSet(objectIds.toArray(), new SilentProgressListener(listener)));
}
if (select.getSelectList().isEmpty())
{
return objectIds;
}
else if (select.isAsObjects())
{
ResultSet temp = new ResultSet(getSelectQuery(), objectIds.toArray());
IntResult r = createIntResult(objectIds.size());
convertToObjects(temp, r, listener);
return r;
}
else
{
if (select.isDistinct())
{
int ids[] = objectIds.toArray();
ResultSet r1 = new ResultSet(getSelectQuery(), ids);
ArrayInt aa = distinctList(r1, listener);
// Reuse the array from remapping list to the list of object ids
for (int i = 0; i < aa.size(); ++i)
{
aa.set(i, ids[aa.get(i)]);
}
return new ResultSet(getSelectQuery(), aa.toArray());
}
else
{
return new ResultSet(getSelectQuery(), objectIds.toArray());
}
}
}
private Object select(List<Object> objects, IProgressListener listener) throws SnapshotException
{
Query.SelectClause select = query.getSelectClause();
// calculate retained set
if (select.isRetainedSet()) { return select(convertToObjectIds(objects), listener); }
if (select.getSelectList().isEmpty())
{
return objects;
}
else if (select.isAsObjects())
{
AbstractCustomTableResultSet temp = new ObjectResultSet(getSelectQuery(), objects);
IntResult r = createIntResult(temp.getRowCount());
convertToObjects(temp, r, listener);
return r;
}
else
{
if (select.isDistinct())
{
// Prefilter the list, we also make distinct on column values
Set<Object>so = new LinkedHashSet<Object>(objects);
Object objs[] = so.toArray(new Object[so.size()]);
so.clear();
AbstractCustomTableResultSet s1 = new ObjectResultSet(getSelectQuery(), objs);
ArrayInt aa = distinctList(s1, listener);
Object objs2[] = new Object[aa.size()];
for (int i = 0; i < aa.size(); ++i)
{
objs2[i] = objs[aa.get(i)];
}
return new ObjectResultSet(getSelectQuery(), objs2);
}
else
{
return new ObjectResultSet(getSelectQuery(), objects);
}
}
}
private Object filterAndSelect(IStructuredResult result, IProgressListener listener) throws SnapshotException
{
List<Object> r = new ArrayList<Object>();
IStructuredResult irt = (IStructuredResult)result;
List<?>elements = irt instanceof IResultTree ? ((IResultTree)irt).getElements() : null;
int count = irt instanceof IResultTable ? ((IResultTable)irt).getRowCount() : elements.size();
listener.beginTask(Messages.OQLQueryImpl_Selecting, count);
for (int ii = 0; ii < count; ii++)
{
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
Object rowobj = new AbstractCustomTableResultSet.RowMap(irt, ii);
// Don't use any context object for the select
//IContextObject ic = irt.getContext(row);
//IObject o = ctx.getSnapshot().getObject(ic.getObjectId());
if (accept(rowobj, listener))
r.add(rowobj);
listener.worked(1);
}
listener.done();
return r.isEmpty() ? null : select(r, listener);
}
private Object filterAndSelect(AbstractCustomTableResultSet result, IProgressListener listener) throws SnapshotException
{
List<Object> r = new ArrayList<Object>();
listener.beginTask(Messages.OQLQueryImpl_Selecting, result.getRowCount() * 2);
IProgressListener old = this.ctx.getProgressListener();
this.ctx.setProgressListener(new SilentProgressListener(listener));
for (AbstractCustomTableResultSet.RowMap rowobj : result)
{
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
/*
* Possible flatten
*/
int maxlen = -1;
for (Object v : rowobj.values())
{
if (v instanceof List)
{
int ll = ((List<?>)v).size();
if (ll > maxlen)
maxlen = ll;
}
else if (v != null && v.getClass().isArray())
{
int ll = Array.getLength(v);
if (ll > maxlen)
maxlen = ll;
};
}
listener.worked(1);
if (maxlen >= 0)
{
// Create a row even if the list/array is empty, will be replaced with null
if (maxlen == 0)
maxlen = 1;
for (int i = 0; i < maxlen; ++i)
{
int ix = i;
AbstractCustomTableResultSet.RowMap rm2 = new AbstractCustomTableResultSet.RowMap(result, rowobj.index, i) {
public Object get(Object key)
{
Object v = rowobj.get(key);
if (v instanceof List)
{
if (ix >= ((List<?>)v).size())
return null;
return ((List<?>)v).get(ix);
}
else if (v != null && v.getClass().isArray())
{
if (ix >= Array.getLength(v))
return null;
return Array.get(v, ix);
}
else
{
return v;
}
}
};
if (accept(rm2, listener))
r.add(rm2);
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
}
}
else
{
if (accept(rowobj, listener))
r.add(rowobj);
}
listener.worked(1);
}
this.ctx.setProgressListener(old);
listener.done();
return r.isEmpty() ? null : select(r, listener);
}
private Object select(Object object, IProgressListener listener) throws SnapshotException
{
Query.SelectClause select = query.getSelectClause();
// calculate retained set
if (select.isRetainedSet()) { return select(convertToObjectIds(Arrays.asList(new Object[] { object })),
listener); }
if (select.getSelectList().isEmpty())
{
return object;
}
else if (select.isAsObjects())
{
AbstractCustomTableResultSet temp = new ObjectResultSet(getSelectQuery(), Arrays.asList(new Object[] { object }));
IntResult r = createIntResult(temp.getRowCount());
convertToObjects(temp, r, listener);
return r;
}
else
{
return new ObjectResultSet(getSelectQuery(), Arrays.asList(new Object[] { object }));
}
}
/**
* Get an query without the union clause for results before applying the union clause.
* @return A new query without the union clause.
*/
private OQLQueryImpl getSelectQuery() {
Query q2 = new Query();
q2.setSelectClause(query.getSelectClause());
q2.setFromClause(query.getFromClause());
q2.setWhereClause(query.getWhereClause());
OQLQueryImpl qi = new OQLQueryImpl(ctx, q2);
return qi;
}
private IntArrayResult convertToObjectIds(List<?> objects) throws SnapshotException
{
ArrayInt a = new ArrayInt();
for (Object object : objects)
{
if (object == null)
{
// valid value
}
else if (object instanceof Integer)
{
a.add(((Integer) object).intValue());
}
else if (object instanceof IObject)
{
a.add(((IObject) object).getObjectId());
}
else if (object instanceof int[])
{
a.addAll((int[]) object);
}
else
{
throw new SnapshotException(MessageUtil.format(Messages.OQLQueryImpl_Error_CannotCalculateRetainedSet,
new Object[] { object }));
}
}
return new IntArrayResult(a);
}
private void convertToObjects(CustomTableResultSet set, IntResult resultSet, IProgressListener listener)
throws SnapshotException
{
if (set.getColumns().length != 1) { throw new SnapshotException(MessageUtil.format(
Messages.OQLQueryImpl_Error_QueryCannotBeConverted, new Object[] { set.getOQLQuery() })); }
// We don't track work for converting objects
IProgressListener old = ctx.getProgressListener();
ctx.setProgressListener(new SilentProgressListener(listener));
int count = set.getRowCount();
for (int ii = 0; ii < count; ii++)
{
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
Object rowObject = set.getColumnValue(set.getRow(ii), 0);
/**
* Convert arrays or collections of IObjects
* or ints or int arrays or IObjects
* or longs or arrays of longs into object ids.
*/
Iterable<?> it;
if (rowObject instanceof Iterable)
{
it = (Iterable<?>)rowObject;
}
else if (rowObject instanceof Object[])
{
it = Arrays.asList((Object[])rowObject);
}
else
{
it = Collections.singleton(rowObject);
}
for (Object object : it)
{
if (object == null)
{
// acceptable value -> do nothing
}
else if (object instanceof Integer)
{
resultSet.add(((Integer) object).intValue());
}
else if (object instanceof int[])
{
resultSet.addAll((int[]) object);
}
else if (object instanceof IObject)
{
resultSet.add(((IObject) object).getObjectId());
}
else if (object instanceof Long)
{
long addr = ((Long) object).longValue();
if (addr != 0)
{
int id = ctx.getSnapshot().mapAddressToId(addr);
resultSet.add(id);
}
}
else if (object instanceof long[])
{
for (long addr : (long[])object)
{
if (addr != 0)
{
int id = ctx.getSnapshot().mapAddressToId(addr);
resultSet.add(id);
}
}
}
else
{
throw new SnapshotException(MessageUtil.format(Messages.OQLQueryImpl_Error_ResultMustReturnObjectList,
new Object[] { set.getOQLQuery(), String.valueOf(rowObject) }));
}
}
}
ctx.setProgressListener(old);
}
private IntResult createIntResult(int capacity)
{
return query.getSelectClause().isDistinct() || query.getSelectClause().isRetainedSet() ? new IntSetResult(
capacity) : new IntArrayResult(capacity);
}
@Override
public String toString()
{
return query.toString();
}
}