blob: d37150e5a48339caa8d2b00733c4e4cfc1565992 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2020 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
* Andrew Johnson - conversion to proper query, set operations via contexts
******************************************************************************/
package org.eclipse.mat.internal.snapshot.inspections;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Serializable;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.collect.ArrayInt;
import org.eclipse.mat.internal.Messages;
import org.eclipse.mat.query.Bytes;
import org.eclipse.mat.query.BytesFormat;
import org.eclipse.mat.query.Column;
import org.eclipse.mat.query.ContextDerivedData;
import org.eclipse.mat.query.ContextDerivedData.DerivedOperation;
import org.eclipse.mat.query.ContextProvider;
import org.eclipse.mat.query.IContextObject;
import org.eclipse.mat.query.IContextObjectSet;
import org.eclipse.mat.query.IDecorator;
import org.eclipse.mat.query.IIconProvider;
import org.eclipse.mat.query.IQuery;
import org.eclipse.mat.query.IQueryContext;
import org.eclipse.mat.query.IResult;
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.query.annotations.Argument;
import org.eclipse.mat.query.annotations.HelpUrl;
import org.eclipse.mat.query.annotations.Icon;
import org.eclipse.mat.query.annotations.Menu;
import org.eclipse.mat.query.annotations.Menu.Entry;
import org.eclipse.mat.query.refined.Filter;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.OQL;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.snapshot.model.IObjectArray;
import org.eclipse.mat.snapshot.model.NamedReference;
import org.eclipse.mat.snapshot.query.Icons;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.mat.util.SimpleMonitor;
import org.eclipse.mat.util.VoidProgressListener;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.NumberFormat;
@Icon("/META-INF/icons/compare.gif")
@HelpUrl("/org.eclipse.mat.ui.help/tasks/comparingdata.html")
@Menu({ @Entry(options = "-setop ALL")
,@Entry(options = "-mode DIFF_TO_PREVIOUS -prefix -mask \"\\s@ 0x[0-9a-f]+|^\\[[0-9]+\\]$\" -x java.util.HashMap$Node:key java.util.Hashtable$Entry:key java.util.WeakHashMap$Entry:referent java.util.concurrent.ConcurrentHashMap$Node:key")
,@Entry(options = "-mode DIFF_TO_PREVIOUS -prefix -mask \"\\s@ 0x[0-9a-f]+|^\\[[0-9]+\\]$|(?<=\\p{javaJavaIdentifierPart}\\[)\\d+(?=\\])\" -x java.util.HashMap$Node:key java.util.Hashtable$Entry:key java.util.WeakHashMap$Entry:referent java.util.concurrent.ConcurrentHashMap$Node:key")
,@Entry(options = "-mode DIFF_TO_PREVIOUS -prefix -mask \"\\s@ 0x[0-9a-f]+|^\\[[0-9]+\\]$|(?<=\\p{javaJavaIdentifierPart}\\[)\\d+(?=\\])\" -x java.util.HashMap$Node:key java.util.Hashtable$Entry:key java.util.WeakHashMap$Entry:referent java.util.concurrent.ConcurrentHashMap$Node:key -setop ALL")
})
public class CompareTablesQuery implements IQuery
{
@Argument
public IStructuredResult[] tables;
@Argument
public IQueryContext queryContext;
@Argument(isMandatory = false)
public ISnapshot[] snapshots;
@Argument(isMandatory = false)
public Mode mode = Mode.ABSOLUTE;
@Argument(isMandatory = false)
public Operation setOp = Operation.NONE;
@Argument(isMandatory = false)
public int keyColumn = 1;
@Argument(isMandatory = false)
public Pattern mask;
@Argument(isMandatory = false)
public String replace;
@Argument(isMandatory = false)
public boolean prefix;
@Argument(isMandatory = false)
public boolean suffix;
// @Argument(isMandatory = false)
public boolean addrefs = true;
@Argument(isMandatory = false, flag = "x")
public String[] extraReferences;
@Argument(isMandatory = false, flag = "xfile")
public File extraReferencesListFile;
private boolean[] sameSnapshot;
public enum Mode
{
ABSOLUTE("ABSOLUTE"), // //$NON-NLS-1$
DIFF_TO_FIRST("DIFF_TO_FIRST"), // //$NON-NLS-1$
DIFF_TO_PREVIOUS("DIFF_TO_PREVIOUS"), //$NON-NLS-1$
DIFF_RATIO_TO_FIRST("DIFF_RATIO_TO_FIRST"), // //$NON-NLS-1$
DIFF_RATIO_TO_PREVIOUS("DIFF_RATIO_TO_PREVIOUS"); //$NON-NLS-1$
String label;
private Mode(String label)
{
this.label = label;
}
public String toString()
{
return label;
}
}
public enum Operation
{
NONE,
ALL,
INTERSECTION,
UNION,
SYMMETRIC_DIFFERENCE,
DIFFERENCE,
REVERSE_DIFFERENCE
}
public IResult execute(IProgressListener listener) throws Exception
{
if (tables == null) return null;
// Length 1 table is valid, and we need to process it in case it is from a different snapshot
// Check key column is present
for (int i = 0; i < tables.length; ++i)
{
if (keyColumn > tables[i].getColumns().length || keyColumn < 1)
throw new IllegalArgumentException(MessageUtil.format(Messages.CompareTablesQuery_MissingKeyColumn, keyColumn, i + 1));
}
IStructuredResult base = tables[0];
Column[] baseColumns = base.getColumns();
Column key = baseColumns[keyColumn-1];
sameSnapshot = new boolean[tables.length];
ISnapshot sn = (ISnapshot)queryContext.get(ISnapshot.class, null);
boolean foundTree = false;
for (int i = 0; i < tables.length; ++i)
{
sameSnapshot[i] = (snapshots[i] == null || sn.equals(snapshots[i]));
if (tables[i] instanceof IResultTree)
foundTree = true;
}
List<ComparedColumn> attributes = new ArrayList<ComparedColumn>();
for (int i = 0; i < baseColumns.length; i++)
{
if (i == keyColumn-1)
continue;
// Check for duplicate column names
int prevDuplicateCol = -1;
for (int k = 0; k < i - 1; ++k)
{
if (baseColumns[i].getLabel().equals(baseColumns[k].getLabel()))
prevDuplicateCol = k > keyColumn - 1 ? k - 1 : k; // Adjust for key column
}
int[] indexes = new int[tables.length];
for (int j = 0; j < indexes.length; j++)
{
if (prevDuplicateCol >= 0)
{
// Start search after previous found column
int pc = attributes.get(prevDuplicateCol).getColumnIndexes()[j];
int ci = getColumnIndex(baseColumns[i].getLabel(), tables[j], pc + 1);
// Not found, so duplicate the last column
if (ci == -1)
ci = pc;
indexes[j] = ci;
}
else
{
indexes[j] = getColumnIndex(baseColumns[i].getLabel(), tables[j], 0);
}
}
attributes.add(new ComparedColumn(baseColumns[i], indexes, true));
}
collectExtraRefs();
return foundTree ? new ComparisonResultTree(mergeKeys(null, listener), key, attributes, mode, setOp)
: new ComparisonResultTable(mergeKeys(null, listener), key, attributes, mode, setOp);
}
private int getColumnIndex(String name, IStructuredResult table, int colstart)
{
Column[] columns = table.getColumns();
for (int i = colstart; i < columns.length; i++)
{
if (columns[i].getLabel().equals(name)) return i;
}
return -1;
}
/**
* Calculate extra parts for the key.
* @param table index
* @param row
* @return a String or null
*/
String extraKey(int table, Object row)
{
final String noextra = null;
if (extraReferences == null || extraReferences.length == 0)
return noextra;
if (snapshots[table] == null)
return noextra;
IContextObject ctx = tables[table].getContext(row);
if (ctx == null)
return noextra;
int objId = ctx.getObjectId();
if (objId == -1)
return noextra;
IObject obj;
try
{
obj = snapshots[table].getObject(objId);
}
catch (SnapshotException e)
{
return noextra;
}
StringBuilder sb = new StringBuilder();
for (String s : extraReferences)
{
String p1[] = s.split(":", 2); //$NON-NLS-1$
if (p1.length < 1)
continue;
else if (p1.length < 2)
{
// Just the class name, so resolve just the object
String val = obj.getClassSpecificName();
if (val != null)
{
if (sb.length() > 0)
sb.append(' ');
sb.append(val);
}
continue;
}
try
{
if (obj.getClazz().doesExtend(p1[0]))
{
for (String field : p1[1].split(",")) //$NON-NLS-1$
{
Object o;
try
{
o = obj.resolveValue(field);
}
catch (SnapshotException e)
{
continue;
}
String val;
if (o instanceof IObject)
{
val = ((IObject)o).getClassSpecificName();
}
else if (o != null)
{
val = o.toString();
}
else
{
val = null;
}
if (val != null)
{
if (sb.length() > 0)
sb.append(' ');
sb.append(val);
}
}
}
}
catch (SnapshotException e)
{
}
}
if (sb.length() > 0)
return sb.toString();
return noextra;
}
/**
* Calculate extra prefix for the key.
* Look for the immediate dominator and see if any fields point to this.
* Also do this for small object arrays.
* @param table index
* @param row
* @return a String or null
*/
String extraPrefix(int table, Object row)
{
// Only generate prefixes for small dominator arrays
final int SMALL_ARRAY_SIZE = 512;
final String noextra = null;
if (!addrefs)
return noextra;
if (snapshots[table] == null)
return noextra;
IContextObject ctx = tables[table].getContext(row);
if (ctx == null)
return noextra;
int objId = ctx.getObjectId();
if (objId == -1)
return noextra;
IObject obj;
try
{
obj = snapshots[table].getObject(objId);
}
catch (SnapshotException e)
{
return noextra;
}
StringBuilder sb = new StringBuilder();
try
{
int immdom = snapshots[table].getImmediateDominatorId(obj.getObjectId());
if (immdom >= 0)
{
if (!snapshots[table].isArray(immdom))
{
IObject immobj = snapshots[table].getObject(immdom);
for (NamedReference ref : immobj.getOutboundReferences())
{
if (ref.getObjectId() == objId)
{
if (sb.length() > 0)
sb.append(',');
sb.append(ref.getName());
}
}
}
else
{
if (snapshots[table].getClassOf(immdom).getObjectId() == obj.getObjectId())
sb.append("<class>"); //$NON-NLS-1$
// Big arrays could be expensive to read
if (snapshots[table].getHeapSize(immdom) < SMALL_ARRAY_SIZE)
{
IObject immobj = snapshots[table].getObject(immdom);
// Don't get named references for object array - expensive
if (immobj instanceof IObjectArray)
{
IObjectArray immarr = (IObjectArray)immobj;
long arr[] = immarr.getReferenceArray();
for (int j = 0; j < arr.length; ++j)
{
if (arr[j] == obj.getObjectAddress())
{
if (sb.length() > 0)
sb.append(',');
sb.append('[').append(j).append(']');
}
}
}
}
}
}
}
catch (SnapshotException e1)
{
}
if (sb.length() > 0)
return sb.toString();
return noextra;
}
/**
* Collect the extra type+field references.
*/
void collectExtraRefs() throws IOException
{
// extra key refs
// read the file (if any)
String[] fromFile = getLinesFromFile();
if (fromFile != null && fromFile.length > 0)
{
if (extraReferences != null)
{
// merge from file and manually entered entries
String[] tmp = new String[fromFile.length + extraReferences.length];
System.arraycopy(fromFile, 0, tmp, 0, fromFile.length);
System.arraycopy(extraReferences, 0, tmp, fromFile.length, extraReferences.length);
extraReferences = tmp;
}
else
{
extraReferences = fromFile;
}
}
}
/**
* Read from a file.
* @return
* @throws IOException
*/
private String[] getLinesFromFile() throws IOException
{
if (extraReferencesListFile == null)
return null;
BufferedReader in = null;
List<String> result = new ArrayList<String>();
try
{
in = new BufferedReader(new FileReader(extraReferencesListFile));
String line = null;
while ((line = in.readLine()) != null)
{
result.add(line);
}
}
finally
{
if (in != null)
{
in.close();
}
}
return result.toArray(new String[0]);
}
/**
* Hold a place for a row when the key is a duplicate.
*/
static class PlaceHolder
{
Object key;
int pos;
PlaceHolder(Object key, int pos)
{
this.key = key;
this.pos = pos;
}
}
private List<ComparedRow> mergeKeys(ComparedRow parent, IProgressListener listener)
{
SimpleMonitor sm = new SimpleMonitor(Messages.CompareTablesQuery_Comparing, listener, new int[] {60,30,10});
Map<Object, Object[]> map = new LinkedHashMap<Object, Object[]>();
int sizes[] = new int[tables.length];
// Only get elements once
List<?>elements[] = new List<?>[tables.length];
// Calculate the total number of rows
int totalsize = 0;
for (int i = 0; i < tables.length; i++)
{
IStructuredResult table = tables[i];
int size;
if (table instanceof IResultTable)
{
if (parent == null)
size = ((IResultTable)table).getRowCount();
else
size = 0;
}
else if (table instanceof IResultTree)
{
if (parent == null)
{
elements[i] = ((IResultTree)table).getElements();
size = elements[i].size();
}
else
{
Object treerow = parent.getRows()[i];
if (treerow != null && ((IResultTree)table).hasChildren(treerow))
{
elements[i] = ((IResultTree)table).getChildren(treerow);
size = elements[i].size();
}
else
{
size = 0;
}
}
}
else
{
size = 0;
}
sizes[i] = size;
totalsize += size;
// For overflow
if (totalsize < 0)
totalsize = Integer.MAX_VALUE;
}
listener = sm.nextMonitor();
listener.beginTask(Messages.CompareTablesQuery_Initial, totalsize);
long sortwork = 0;
for (int i = 0; i < tables.length; i++)
{
listener.subTask(MessageUtil.format(Messages.CompareTablesQuery_InitialComparisonForTable, i + 1));
IStructuredResult table = tables[i];
int size = sizes[i];
List<?>treeRows;
if (table instanceof IResultTree)
{
treeRows = elements[i];
}
else
{
treeRows = null;
}
Map<Object, Integer>lastcache = new HashMap<Object,Integer>();
for (int j = 0; j < size; j++)
{
Object row;
if (table instanceof IResultTable)
{
row = ((IResultTable)table).getRow(j);
}
else if (table instanceof IResultTree)
{
row = treeRows.get(j);
}
else
{
row = null;
}
Object key = table.getColumnValue(row, keyColumn-1);
key = modifyKey(i, row, key);
Object[] rows = map.get(key);
if (rows == null)
{
rows = new Object[tables.length];
map.put(key, rows);
}
int ii = 0;
if (lastcache.containsKey(key))
{
ii = lastcache.get(key);
}
while (rows[ii + i] != null)
{
/*
* Normally:
* rows[rowTable1, rowTableb]
* With duplicate keys from one Table we extend the array
* rows[rowTable1, rowTable2, rowTable1v2, rowTable2v2, rowTable1v3, null]
* etc.
* and later convert to multiple rows
*/
ii += tables.length;
if (ii >= rows.length)
{
sortwork -= rows.length * rows.length;
// Grow the row array
int spare = rows.length / tables.length / 2;
rows = Arrays.copyOf(rows, rows.length + (spare + 1) * tables.length);
map.put(key, rows);
sortwork += rows.length * rows.length;
}
}
rows[ii + i] = row;
if (ii > 0)
{
// With many duplicates it can take a long time to find a free slot, so cache the last used
lastcache.put(key, ii);
// Add a placeholder so that a row goes here
map.put(new PlaceHolder(key, ii), new Object[0]);
}
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
listener.worked(1);
}
}
listener.done();
listener = sm.nextMonitor();
listener.beginTask(Messages.CompareTablesQuery_Initial, totalsize);
// Match up duplicate keys
for (Map.Entry<Object, Object[]> entry : map.entrySet())
{
Object rows[] = entry.getValue();
if (rows.length > tables.length)
{
listener.subTask(MessageUtil.format(Messages.CompareTablesQuery_ResolvingDuplicateKey, entry.getKey()));
// Duplicated key, so expand to separate rows
sortRows(rows);
// Guess n-squared work
listener.worked((int)(totalsize * (long)rows.length * rows.length / sortwork));
}
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
}
listener.done();
listener = sm.nextMonitor();
listener.beginTask(Messages.CompareTablesQuery_BuildingResult, totalsize);
List<ComparedRow> result = new ArrayList<ComparedRow>(map.size());
for (Map.Entry<Object, Object[]> entry : map.entrySet())
{
Object key = entry.getKey();
Object rows[] = entry.getValue();
if (key instanceof PlaceHolder)
{
// A subsequent duplicated key
PlaceHolder ph = (PlaceHolder)key;
// Find the real, sorted row list
rows = map.get(ph.key);
// Extract the appropriate part
Object rows1[] = Arrays.copyOfRange(rows, ph.pos, ph.pos + tables.length);
result.add(new ComparedRow(ph.key, rows1));
}
else if (rows.length <= tables.length)
result.add(new ComparedRow(entry.getKey(), entry.getValue()));
else
{
// Duplicated key, but this is the first record
Object rows1[] = Arrays.copyOfRange(rows, 0, tables.length);
result.add(new ComparedRow(entry.getKey(), rows1));
}
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
listener.worked(1);
}
listener.done();
return result;
}
private Object modifyKey(int i, Object row, Object key)
{
String extrakey = extraKey(i, row);
if (extrakey != null)
{
key = key + " " + extrakey; //$NON-NLS-1$
}
if (mask != null && key != null)
{
String keystr = key.toString();
String keystr2 = mask.matcher(keystr).replaceAll(replace == null ? "" : replace); //$NON-NLS-1$
if (!keystr.equals(keystr2))
{
key = keystr2;
}
}
// Fix up decoration
if (prefix || suffix)
{
Column c = tables[i].getColumns()[keyColumn - 1];
IDecorator id = c.getDecorator();
String pfx;
if (prefix)
{
if (id != null)
{
pfx = id.prefix(row);
if (pfx == null)
pfx = extraPrefix(i, row);
}
else
{
pfx = extraPrefix(i, row);
}
}
else
{
pfx = null;
}
String sfx = suffix && id != null ? id.suffix(row) : null;
if (mask != null && pfx != null)
{
pfx = mask.matcher(pfx).replaceAll(replace == null ? "" : replace); //$NON-NLS-1$
if (pfx.length() == 0)
pfx = null;
}
if (mask != null && sfx != null)
{
sfx = mask.matcher(sfx).replaceAll(replace == null ? "" : replace); //$NON-NLS-1$
if (sfx.length() == 0)
sfx = null;
}
if (pfx != null || sfx != null)
key = new ComparedRow(pfx, key, sfx, null);
}
return key;
}
/**
* See if two rows from two tables match.
* Do a bit more matching via IContextObject.
* Match via context object ID or address.
* @param table1 index
* @param row1
* @param table2 index
* @param row2
* @param matchType - the higher the number the more likely a match
* @return true if they seem the same
*/
boolean rowMatches(int table1, Object row1, int table2, Object row2, int matchType)
{
IContextObject ctx1 = tables[table1].getContext(row1);
IContextObject ctx2 = tables[table2].getContext(row2);
if (ctx1 == null && ctx2 == null)
return true;
if (ctx1 == null || ctx2 == null)
return false;
int objectId1 = ctx1.getObjectId();
int objectId2 = ctx2.getObjectId();
if (sameSnapshot[table1] && sameSnapshot[table2] ||
snapshots[table1].equals(snapshots[table2]))
{
//System.out.println("compare "+ctx1.getObjectId() +" "+ ctx2.getObjectId());
return objectId1 == objectId2;
}
if (objectId1 == -1 && objectId2 == -1)
return true;
if (objectId1 == -1 || objectId2 == -1)
return false;
try
{
long addr1 = snapshots[table1].mapIdToAddress(objectId1);
long addr2 = snapshots[table2].mapIdToAddress(objectId2);
// Address match, guess the same?
if (addr1 == addr2)
return true;
// Classes don't match, so different
if (snapshots[table1].getClassOf(objectId1).equals(snapshots[table2].getClassOf(objectId2)))
return false;
if (matchType >= 1)
{
// Guess match on retained size, presuming it retains more than itself!
if (snapshots[table1].getRetainedHeapSize(objectId1) > snapshots[table1].getHeapSize(1)
&& snapshots[table1].getRetainedHeapSize(objectId1) == snapshots[table2]
.getRetainedHeapSize(objectId2))
return true;
}
// Don't compare using getClassSpecificName() as this could be done
// on the key
if (matchType >= 2)
{
String val1 = snapshots[table1].getObject(objectId1).getClassSpecificName();
String val2 = snapshots[table1].getObject(objectId1).getClassSpecificName();
if (val1 != null && val1.equals(val2))
return true;
}
return false;
}
catch (SnapshotException e)
{
return false;
}
}
/**
* Try to match rows from different tables. Have definite matches in same
* row. Unmatched can be anywhere.
*
* <pre>
* A2=B3,A2=C1,A3=B5,A4=C2
* [A1,B1,C1,A2,B2,C2,A3,B3,null,A4,B4,null,null,B5,null]
* A1 B1 C1
* A2 B2 C2
* A3 B3
* A4 B4
* B5
* </pre>
*
* Goes to:
*
* <pre>
* A1 B1
* A2 B3 C1
* A3 B5
* A4 B2 C2
* B4
* </pre>
* <ol>
* <li>find first match of B[1]..B[5] with A[1] and put in first slot (&
* shift others up)</li>
* <li>find next match of B[2]..B[5] in with A[2] and put in second slot (&
* shift others up)</li>
* <li>..</li>
* <li>find first match of C[1]..C[2] with A[1] and put in first slot (&
* shift others up)</li>
* <li>if no match find first match of C[1]..C[2] with B[1] and put in first
* slot (& shift others up)</li>
* <li>if no match find first match of C[2]..C[2] with A[2] and put in first
* slot (& shift others up)</li>
* </ol>
* Not perfect though:
*
* <pre>
* 1 1 2
* 2 3 5
* 3 5
* 4
* </pre>
*
* gives
*
* <pre>
* 1 1 5
* 2 5 2
* 3 3
* 4
* </pre>
*
* not
*
* <pre>
* 1 1
* 2 2
* 3 3
* 4 5 5
* </pre>
* Try a second pass to fix up those.
* @param rows
*/
void sortRows(Object rows[])
{
int rn = rows.length;
int tn = tables.length;
int n = rn / tn;
Object queue[] = new Object[n];
// This entry is matched to another table
boolean matched[] = new boolean[rows.length];
//System.out.println("Sorting " + n);
// Consider each table other than the first
for (int i = 1; i < tn; ++i)
{
// Distribute this table to matching slots
// Fill the queue
int q1 = 0, q2 = 0;
for (int l = 0; l < n; ++l)
{
Object o = rows[l * tn + i];
if (o != null)
{
queue[q2++] = o;
rows[l * tn + i] = null;
}
}
// We don't need to clear the remainder of the queue
// Matching slot in preceding table
slot: for (int j = 0; j < n; ++j)
{
// System.out.println("Slot "+i+":"+j);
for (int pass = 0; pass < 2; ++pass)
{
// Choose preceding table
for (int k = 0; k < i; ++k)
{
// Preceding value
Object rowPrev = rows[j * tn + k];
if (rowPrev == null)
continue;
// Matching preceding tables
// Choose entry from the queue
for (int l = q1; l < q2; ++l)
{
Object rowThis = queue[l];
if (rowThis == null)
{
// Optimization - adjust the queue limit
if (l == q1)
++q1;
continue;
}
//System.out.println("Comparing " + i + ":" + l + " to " + k + ":" + j);
if (rowMatches(i, rowThis, k, rowPrev, pass))
{
//System.out.println("Matched " + i + ":" + l + " to " + k + ":" + j);
matched[j * tn + i] = true;
matched[j * tn + k] = true;
rows[j * tn + i] = rowThis;
// stop reuse
queue[l] = null;
// optimization: last queue entry, so avoid looking here again
if (l == q2 - 1)
--q2;
continue slot;
}
}
}
}
// Say this slot might need to be filled later
rows[j * tn + i] = null;
}
// Distribute the unused
for (int l = 0, lq = q1; l < n && lq < q2; ++l)
{
// Read slot
Object o = rows[l * tn + i];
if (o == null)
{
// Read unused element from queue
while (lq < q2)
{
Object q = queue[lq++];
if (q != null)
{
// and fill the slot
rows[l * tn + i] = q;
break;
}
}
}
}
}
// Try some polishing of the result
// Consider each table other than the first
for (int i = 1; i < tn; ++i)
{
// Matching slot in preceding table
for (int j = 0; j < n; ++j)
{
for (int pass = 0; pass < 2; ++pass)
{
// Choose preceding table
for (int k = 0; k < i; ++k)
{
if (matched[j * tn + k])
continue;
// Preceding value
Object rowPrev = rows[j * tn + k];
if (rowPrev == null)
continue;
for (int l = 0; l < n; ++l)
{
if (matched[l * tn + i])
continue;
Object rowThis = rows[l * tn + i];
if (rowThis == null)
continue;
if (rowMatches(i, rowThis, k, rowPrev, 0))
{
// System.out.println("extra match " + i + ":" + l + " " + k + ":" + j);
// Find a corresponding free slot in both tables
for (int m = 0; m < n; ++m)
{
if (!matched[m * tn + i] && !matched[m * tn + k])
{
// System.out.println("Swapped " + i + ":" + l + " " + i + ":" + m + " " + k + ":" + j + " " + k + ":" + m);
// Swap and mark as matched
Object oldThis = rows[m * tn + i];
rows[m * tn + i] = rowThis;
rows[l * tn + i] = oldThis;
matched[m * tn + i] = true;
Object oldPrev = rows[m * tn + k];
rows[m * tn + k] = rowPrev;
rows[j * tn + i] = oldPrev;
matched[m * tn + k] = true;
}
}
}
}
}
}
}
}
}
public static class ComparedColumn
{
Column description;
int[] columnIndexes;
boolean displayed;
public ComparedColumn(Column description, int[] columnIndexes, boolean displayed)
{
super();
this.displayed = displayed;
this.description = description;
this.columnIndexes = columnIndexes;
}
public Column getDescription()
{
return description;
}
public void setDescription(Column description)
{
this.description = description;
}
public int[] getColumnIndexes()
{
return columnIndexes;
}
public void setColumnIndexes(int[] columnIndexes)
{
this.columnIndexes = columnIndexes;
}
public boolean isDisplayed()
{
return displayed;
}
public void setDisplayed(boolean displayed)
{
this.displayed = displayed;
}
}
static class ComparedRow
{
Object key;
private String prefix;
private String suffix;
Object[] rows;
public ComparedRow(Object key, Object[] rows)
{
super();
if (key instanceof ComparedRow)
{
ComparedRow cr = (ComparedRow)key;
this.key = cr.key;
this.prefix = cr.prefix;
this.suffix = cr.suffix;
}
else
{
this.key = key;
}
this.rows = rows;
}
public ComparedRow(String prefix, Object key, String suffix, Object[] rows)
{
super();
this.prefix = prefix;
this.key = key;
this.suffix = suffix;
this.rows = rows;
}
public Object getKey()
{
return key;
}
public void setKey(Object key)
{
this.key = key;
}
public Object[] getRows()
{
return rows;
}
public void setRows(Object[] rows)
{
this.rows = rows;
}
String getPrefix()
{
return prefix;
}
void setPrefix(String prefix)
{
this.prefix = prefix;
}
String getSuffix()
{
return suffix;
}
void setSuffix(String suffix)
{
this.suffix = suffix;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
result = prime * result + ((prefix == null) ? 0 : prefix.hashCode());
result = prime * result + ((suffix == null) ? 0 : suffix.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ComparedRow other = (ComparedRow) obj;
if (key == null)
{
if (other.key != null)
return false;
}
else if (!key.equals(other.key))
return false;
if (prefix == null)
{
if (other.prefix != null)
return false;
}
else if (!prefix.equals(other.prefix))
return false;
if (suffix == null)
{
if (other.suffix != null)
return false;
}
else if (!suffix.equals(other.suffix))
return false;
return true;
}
public String toString()
{
String p1 = prefix != null ? "(" + prefix + ") " : ""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
String s1 = suffix != null ? " (" + suffix + ")" : ""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
return p1 + key + s1 + ":" + Arrays.toString(rows); //$NON-NLS-1$
}
}
/**
* Types of delta retained size.
*/
enum DeltaEncoding {
/**
* The size delta is exact.
*/
EXACT,
/**
* The size delta is at least this much.
*/
GE,
/**
* The size delta is not more than this.
*/
LE,
/**
* The size delta is uncertain, but this is an
* estimate. For example the difference between
* two sizes given as greater than or equal to.
*/
APPROX
}
/**
* A class to format the difference between two retained sizes.
* Similar to {@link org.eclipse.mat.snapshot.query.RetainedSizeDerivedData.RetainedSizeFormat}.
* Sorting should use {@link Filter.ValueConverter} so that a dedicated comparator is
* not required. See {@link org.eclipse.mat.query.refined.RefinedStructuredResult.NaturalComparator}.
*/
private static class DeltaRetainedBytesFormat extends BytesFormat
{
/**
* Converts a encoded stored value to a simple value, losing any
* approximation details.
*/
private class DeltaRetainedConverter implements Filter.ValueConverter, Serializable
{
private static final long serialVersionUID = 1L;
public double convert(double source)
{
if (source >= SPECIAL + SPECIAL2)
return source - SPECIAL - SPECIAL2; // +ve approx
else if (source >= SPECIAL)
return source - SPECIAL2; // >=
else if (source < -SPECIAL - SPECIAL2)
return source + SPECIAL + SPECIAL2; // -ve approx
else if (source < -SPECIAL)
return source + SPECIAL2; // <=
return source;
}
}
/**
*
*/
private static final long serialVersionUID = 1L;
/*
* encode >=, <= for +ve -ve byte values
* convert long to double for Filter.ValueConverter
* > 1,000,000,000,000,000 means >=
* <-1,000,000,000,000,000 means <=
* <-3,000,000,000,000,000 means ~
*
* e.g.
* ~= : 39
* ~=3 : 33
* ~=0 : 30
* >=9 : 29
* >=3 : 23
* >=-3 : 17
* >=-10: 10
*
* <=9 : -11
* <=3 : -17
* <=-3 : -23
* <=-10: -30
*
* ~-3 : -33
* ~-9 : -39
*/
/**
* Break point for special encoding.
* Chosen to be big, but to fit precisely in a double
* as well as a long.
*/
private long SPECIAL = 1000000000000000L;
/**
* How much to adjust a value to move it into a different range.
*/
private long SPECIAL2 = SPECIAL * 2;
/**
* Convert the encoded value to a normal value.
*/
final Filter.ValueConverter converter = new DeltaRetainedConverter();
/**
* Encode a value as greater than or equal.
* @param l the raw value
* @return the encoded value
*/
private long encodege(long l)
{
if (l >= SPECIAL)
return SPECIAL - 1 + SPECIAL2; // saturate
else if (l < -SPECIAL)
return encodeun(l); // can't be encoded as GE
else
return l + SPECIAL2;
}
/**
* Encode a value as less than or equal.
* @param l the raw value
* @return the encoded value
*/
private long encodele(long l)
{
if (l >= SPECIAL)
return encodeun(l); // can't be coded as LE
else if (l < -SPECIAL)
return -SPECIAL - SPECIAL2; // saturate
else
return l - SPECIAL2;
}
/**
* Encode a value as inexact.
* @param l the raw value
* @return the encoded value
*/
private long encodeun(long l)
{
if (l >= 0)
if (l >= Long.MAX_VALUE - SPECIAL - SPECIAL2)
return Long.MAX_VALUE; // saturate
else
return l + SPECIAL2 + SPECIAL;
else if (l <= Long.MIN_VALUE + SPECIAL + SPECIAL2)
return Long.MIN_VALUE; // saturate
else
return l - SPECIAL2 - SPECIAL;
}
/**
* Create a formatter for the difference between two retained sizes.
* @param encapsulatedNumberFormat
* @param encapsulatedDecimalFormat
*/
public DeltaRetainedBytesFormat(Format encapsulatedNumberFormat, Format encapsulatedDecimalFormat)
{
super(encapsulatedNumberFormat, encapsulatedDecimalFormat);
}
@Override
public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos)
{
Number v;
if (obj instanceof Bytes)
v = ((Bytes)obj).getValue();
else
v = (Number) obj;
if (v.longValue() >= SPECIAL)
{
if (v.longValue() >= SPECIAL + SPECIAL2)
{
String approx = Messages.CompareTablesQuery_APPROX;
toAppendTo.append(approx);
return super.format(new Bytes(v.longValue() - SPECIAL - SPECIAL2), toAppendTo, pos);
}
else
{
String approx = Messages.CompareTablesQuery_GE;
toAppendTo.append(approx);
return super.format(new Bytes(v.longValue() - SPECIAL2), toAppendTo, pos);
}
}
else if (v.longValue() < -SPECIAL)
{
if (v.longValue() < -SPECIAL - SPECIAL2)
{
String approx = Messages.CompareTablesQuery_APPROX;
toAppendTo.append(approx);
return super.format(new Bytes(v.longValue() + SPECIAL + SPECIAL2), toAppendTo, pos);
}
else
{
String approx = Messages.CompareTablesQuery_LE;
toAppendTo.append(approx);
return super.format(new Bytes(v.longValue() + SPECIAL2), toAppendTo, pos);
}
}
else
{
return super.format(new Bytes(v.longValue()), toAppendTo, pos);
}
}
@Override
public Object parseObject(String source, ParsePosition pos)
{
Object ret;
for (String match : new String[] {Messages.CompareTablesQuery_GE, Messages.CompareTablesQuery_LE, Messages.CompareTablesQuery_APPROX})
{
if (source.regionMatches(pos.getIndex(), match, 0, match.length()))
{
int pi = pos.getIndex();
pos.setIndex(pi + match.length());
ret = super.parseObject(source, pos);
if (ret != null)
{
long v;
if (ret instanceof Bytes)
{
v = ((Bytes)ret).getValue();
if (match.equals(Messages.CompareTablesQuery_GE))
v = encodege(v);
else if (match.equals(Messages.CompareTablesQuery_LE))
v = encodele(v);
else if (match.equals(Messages.CompareTablesQuery_APPROX))
v = encodeun(v);
return new Bytes(v);
}
else if (ret instanceof Number)
{
v = ((Number)ret).longValue();
if (match.equals(Messages.CompareTablesQuery_GE))
v = encodege(v);
else if (match.equals(Messages.CompareTablesQuery_LE))
v = encodele(v);
else if (match.equals(Messages.CompareTablesQuery_APPROX))
v = encodeun(v);
return new Bytes(v);
}
}
// >= in front of something else
pos.setErrorIndex(pi + match.length());
pos.setIndex(pi);
ret = null;
return ret;
}
}
ret = super.parseObject(source, pos);
return ret;
}
}
public class TableComparisonResult implements IStructuredResult, IIconProvider
{
private Column key;
protected List<ComparedRow> rows;
/** each compared column is a column from the original table, and is displayed as several actual columns, one or more for each table */
private List<ComparedColumn> comparedColumns;
/** each displayed column is a column from the original table, and is displayed as several actual columns, one or more for each table */
private List<ComparedColumn> displayedColumns;
/** Actual columns displayed */
private Column[] columns;
private Mode mode;
private Operation setOp;
public TableComparisonResult(List<ComparedRow> rows, Column key, List<ComparedColumn> comparedColumns, Mode mode, Operation setOp)
{
this.key = key;
this.mode = mode;
this.rows = rows;
this.comparedColumns = comparedColumns;
updateColumns();
setMode(mode);
this.setOp = setOp;
}
public List<ComparedColumn> getComparedColumns()
{
return comparedColumns;
}
public void setComparedColumns(List<ComparedColumn> comparedColumns)
{
this.comparedColumns = comparedColumns;
}
public Object getColumnValue(Object row, int columnIndex)
{
ComparedRow cr = (ComparedRow) row;
if (columnIndex == 0) return cr.getKey();
/*
* Each compared column has several subcolumns for data from each
* table. The first column is the key and is common for all tables,
* so there is only one actual column. For absolute or difference
* modes there is one column per table. For percentage modes there
* is one column for the first table and two for the rest.
*/
int subCols = mode == Mode.DIFF_RATIO_TO_FIRST || mode == Mode.DIFF_RATIO_TO_PREVIOUS ? tables.length
+ tables.length - 1 : tables.length;
int comparedColumnIdx = (columnIndex - 1) / subCols;
int tableIdx = (columnIndex - 1) % subCols;
if (tableIdx == 0) return getAbsoluteValue(cr, comparedColumnIdx, tableIdx);
switch (mode)
{
case ABSOLUTE:
return getAbsoluteValue(cr, comparedColumnIdx, tableIdx);
case DIFF_TO_FIRST:
return getDiffToPrevious(cr, columnIndex, comparedColumnIdx, tableIdx, 0, false);
case DIFF_TO_PREVIOUS:
return getDiffToPrevious(cr, columnIndex, comparedColumnIdx, tableIdx, tableIdx - 1, false);
case DIFF_RATIO_TO_FIRST:
return getDiffToPrevious(cr, columnIndex, comparedColumnIdx, (tableIdx + 1) / 2, 0, tableIdx % 2 == 0);
case DIFF_RATIO_TO_PREVIOUS:
return getDiffToPrevious(cr, columnIndex, comparedColumnIdx, (tableIdx + 1) / 2, (tableIdx + 1) / 2 - 1, tableIdx % 2 == 0);
default:
break;
}
return null;
}
public Column[] getColumns()
{
return columns;
}
public IContextObject getContext(Object row)
{
// Find a context from one of the tables
IContextObject ret = null;
for (int i = 0; i < tables.length && ret == null; ++i)
{
ret = getContextFromTable(i, row);
}
return ret;
}
public ResultMetaData getResultMetaData()
{
ResultMetaData.Builder bb = new ResultMetaData.Builder();
int previous = -1;
for (int i = 0; i < tables.length; ++i)
{
if (!sameSnapshot[i])
continue;
if (setOp != Operation.NONE && previous >= 0)
{
for (int op = 0; op < 5; ++op)
{
if (setOp == Operation.INTERSECTION && op != 0) continue;
if (setOp == Operation.UNION && op != 1) continue;
if (setOp == Operation.SYMMETRIC_DIFFERENCE && op != 2) continue;
if (setOp == Operation.DIFFERENCE && op != 3) continue;
if (setOp == Operation.REVERSE_DIFFERENCE && op != 4) continue;
final int op1 = op;
// intersection, union, symmetric difference, difference, difference
String title1;
final LinkedList<Integer> toDo = new LinkedList<Integer>();
if (mode == Mode.ABSOLUTE)
{
toDo.add(previous);
if (op == 4)
{
for (int k = previous + 1; k <= i; ++k)
{
if (!sameSnapshot[k])
continue;
toDo.addFirst(k);
}
}
else
{
for (int k = previous + 1; k <= i; ++k)
{
if (!sameSnapshot[k])
continue;
toDo.add(k);
}
}
}
else
{
if (op == 4)
{
toDo.add(i);
toDo.add(previous);
}
else
{
toDo.add(previous);
toDo.add(i);
}
}
// Convert the list of tables to a readable menu item
if (toDo.size() == 2)
{
switch (op)
{
case 0:
title1 = MessageUtil.format(Messages.CompareTablesQuery_IntersectionOf2, toDo.get(0)+1, toDo.get(1)+1);
break;
case 1:
title1 = MessageUtil.format(Messages.CompareTablesQuery_UnionOf2, toDo.get(0)+1, toDo.get(1)+1);
break;
case 2:
title1 = MessageUtil.format(Messages.CompareTablesQuery_SymmetricDifferenceOf2, toDo.get(0)+1, toDo.get(1)+1);
break;
case 3:
case 4:
default:
title1 = MessageUtil.format(Messages.CompareTablesQuery_DifferenceOf2, toDo.get(0)+1, toDo.get(1)+1);
break;
}
} else {
String soFar;
switch (op)
{
case 0:
soFar = MessageUtil.format(Messages.CompareTablesQuery_IntersectionFirst, toDo.get(0)+1, toDo.get(1)+1);
break;
case 1:
soFar = MessageUtil.format(Messages.CompareTablesQuery_UnionFirst, toDo.get(0)+1, toDo.get(1)+1);
break;
case 2:
soFar = MessageUtil.format(Messages.CompareTablesQuery_SymmetricDifferenceFirst, toDo.get(0)+1, toDo.get(1)+1);
break;
case 3:
case 4:
default:
soFar = MessageUtil.format(Messages.CompareTablesQuery_DifferenceFirst, toDo.get(0)+1, toDo.get(1)+1);
break;
}
for (int t = 2; t < toDo.size() - 1; ++t)
{
switch (op)
{
case 0:
soFar = MessageUtil.format(Messages.CompareTablesQuery_IntersectionMiddle, soFar, toDo.get(t)+1);
break;
case 1:
soFar = MessageUtil.format(Messages.CompareTablesQuery_UnionMiddle, soFar, toDo.get(t)+1);
break;
case 2:
soFar = MessageUtil.format(Messages.CompareTablesQuery_SymmetricDifferenceMiddle, soFar, toDo.get(t)+1);
break;
case 3:
case 4:
default:
soFar = MessageUtil.format(Messages.CompareTablesQuery_DifferenceMiddle, soFar, toDo.get(t)+1);
break;
}
}
int t = toDo.size() - 1;
switch (op)
{
case 0:
title1 = MessageUtil.format(Messages.CompareTablesQuery_IntersectionLast, soFar, toDo.get(t)+1);
break;
case 1:
title1 = MessageUtil.format(Messages.CompareTablesQuery_UnionLast, soFar, toDo.get(t)+1);
break;
case 2:
title1 = MessageUtil.format(Messages.CompareTablesQuery_SymmetricDifferenceLast, soFar, toDo.get(t)+1);
break;
case 3:
case 4:
default:
title1 = MessageUtil.format(Messages.CompareTablesQuery_DifferenceLast, soFar, toDo.get(t)+1);
break;
}
}
ContextProvider prov2 = new ContextProvider(title1)
{
private static final String EMPTY_QUERY = "SELECT * FROM OBJECTS 0 WHERE false"; //$NON-NLS-1$
public URL getIcon()
{
switch (op1)
{
case 0:
return Icons.getURL("set_intersection.gif"); //$NON-NLS-1$
case 1:
return Icons.getURL("set_union.gif"); //$NON-NLS-1$
case 2:
return Icons.getURL("set_symmetric_difference.gif"); //$NON-NLS-1$
case 3:
return Icons.getURL("set_differenceA.gif"); //$NON-NLS-1$
case 4:
return Icons.getURL("set_differenceB.gif"); //$NON-NLS-1$
}
return null;
}
public IContextObject getContext(final Object row)
{
int nullContexts = 0;
for (int i = 0; i < toDo.size(); ++i)
{
if (getContextFromTable(i, row) == null)
{
++nullContexts;
}
else
{
break;
}
}
// If all the contexts were null then skip this generated context too
if (nullContexts == toDo.size())
return null;
// First non-null context
final IContextObject cb = getContextFromTable(nullContexts, row);
return new IContextObjectSet()
{
public int getObjectId()
{
return cb.getObjectId();
}
public String getOQL()
{
LinkedList<Integer> toDo2 = new LinkedList<Integer>(toDo);
switch (op1)
{
// Intersection
case 0:
String resultOQL0 = null;
for (int j : toDo2)
{
IContextObject cb = getContextFromTable(j, row);
if (cb == null)
{
// No objects so intersection empty
return EMPTY_QUERY;
}
String oql = getOQLfromContext(cb);
if (oql != null)
{
if (resultOQL0 == null)
resultOQL0 = oql;
else
{
resultOQL0 = OQLintersection(resultOQL0, oql);
}
}
else
{
return null;
}
}
return resultOQL0;
// Union
case 1:
StringBuilder resultOQL = new StringBuilder();
for (int j : toDo2)
{
IContextObject cb = getContextFromTable(j, row);
if (cb == null)
continue;
String oql = getOQLfromContext(cb);
if (oql != null)
{
OQL.union(resultOQL, oql);
}
else
{
return null;
}
}
// Remove duplicates
return OQLdistinct(resultOQL.toString());
// Symmetric Difference
case 2:
String resultOQL2 = null;
for (int j : toDo2)
{
IContextObject cb = getContextFromTable(j, row);
if (cb == null)
continue;
String oql = getOQLfromContext(cb);
if (oql != null)
{
if (resultOQL2 == null)
resultOQL2 = oql;
else
{
// A^B = A&~B | ~A&B
StringBuilder sb = new StringBuilder();
sb.append(OQLexcept(resultOQL2, oql));
OQL.union(sb, OQLexcept(oql, resultOQL2));
resultOQL2 = sb.toString();
}
}
else
{
return null;
}
}
return resultOQL2;
// Difference
case 3:
case 4:
String resultOQL3 = null;
for (int j : toDo2)
{
IContextObject cb = getContextFromTable(j, row);
if (cb == null)
{
if (resultOQL3 == null)
{
// First table has no objects so subtraction empty
return EMPTY_QUERY;
}
continue;
}
String oql = getOQLfromContext(cb);
if (oql != null)
{
if (resultOQL3 == null)
resultOQL3 = oql;
else
{
resultOQL3 = OQLexcept(resultOQL3, oql);
}
}
else
{
return null;
}
}
return resultOQL3;
}
return null;
}
/**
* Simulate EXCEPT
* @param oql1
* @param oql2
* @return
*/
private String OQLexcept(String oql1, String oql2)
{
//SELECT * FROM OBJECTS (a) where @objectid in (b)
if ((OQLobjectQuery(oql1)))
oql1 = "SELECT * FROM OBJECTS ("+oql1+")" + //$NON-NLS-1$//$NON-NLS-2$
" WHERE @objectId in ("+oql2+") = false"; //$NON-NLS-1$//$NON-NLS-2$
else
oql1 = "SELECT s as \"\" FROM OBJECTS ("+oql1+") s" + //$NON-NLS-1$//$NON-NLS-2$
" WHERE s in ("+oql2+") = false"; //$NON-NLS-1$//$NON-NLS-2$
return oql1;
}
/**
* Simulate INTERSECT
* @param oql1
* @param oql2
* @return
*/
private String OQLintersection(String oql1, String oql2)
{
//SELECT * FROM OBJECTS (a) where @objectid in (b)
if ((OQLobjectQuery(oql1)))
oql1 = "SELECT * FROM OBJECTS (" + oql1 + ")" + //$NON-NLS-1$//$NON-NLS-2$
" WHERE @objectId in (" + oql2 + ")"; //$NON-NLS-1$//$NON-NLS-2$
else
oql1 = "SELECT s as \"\" FROM OBJECTS (" + oql1 + ") s" + //$NON-NLS-1$//$NON-NLS-2$
" WHERE s in (" + oql2 + ")"; //$NON-NLS-1$//$NON-NLS-2$
return oql1;
}
/**
* Handle DISTINCT
* @param oql
* @return
*/
private String OQLdistinct(String oql)
{
//SELECT DISTINCT OBJECTS @objectId FROM OBJECTS (a)
if (OQLobjectQuery(oql))
return "SELECT DISTINCT OBJECTS @objectId FROM OBJECTS (" + oql +")"; //$NON-NLS-1$ //$NON-NLS-2$
else
return "SELECT DISTINCT s as \"\" FROM OBJECTS (" + oql +") s"; //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Is this a query returning a list of objects
* or one with columns?
* @param oql
* @return
*/
public boolean OQLobjectQuery(String oql)
{
oql = oql.toLowerCase(Locale.ENGLISH).replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$
return oql.startsWith("select * ") || oql.startsWith("select distinct objects ") //$NON-NLS-1$ //$NON-NLS-2$
|| oql.startsWith("select as retained set "); //$NON-NLS-1$
}
/**
* Calculate the object ids
*/
public int[] getObjectIds()
{
LinkedList<Integer> toDo2 = new LinkedList<Integer>(toDo);
int j = toDo2.remove();
final IContextObject cb = getContextFromTable(j, row);
int b[] = getObjectIdsFromContext(cb);
ArrayInt bb;
if (b == null)
{
bb = new ArrayInt();
}
else
{
bb = new ArrayInt(b);
bb.sort();
}
while (!toDo2.isEmpty())
{
j = toDo2.remove();
IContextObject ca = getContextFromTable(j, row);
int a[] = getObjectIdsFromContext(ca);
ArrayInt aa;
if (a == null)
{
aa = new ArrayInt();
}
else
{
aa = new ArrayInt(a);
aa.sort();
}
switch (op1)
{
case 0:
bb = intersectionArray(aa, bb);
break;
case 1:
bb = unionArray(aa, bb);
break;
case 2:
bb = symdiffArray(aa, bb);
break;
case 3:
case 4:
bb = diffArray(aa, bb);
break;
}
}
final int res[] = bb.toArray();
return res;
}
/**
* Union of aa and bb
*
* @param aa sorted
* @param bb sorted
* @return
*/
private ArrayInt unionArray(ArrayInt aa, ArrayInt bb)
{
if (aa.size() == 0)
return bb;
if (bb.size() == 0)
return aa;
ArrayInt cc = new ArrayInt();
int j = 0;
for (int i = 0; i < bb.size(); ++i)
{
while (j < aa.size() && aa.get(j) < bb.get(i))
{
cc.add(aa.get(j));
++j;
}
cc.add(bb.get(i));
if (j < aa.size() && aa.get(j) == bb.get(i))
{
++j;
}
}
while (j < aa.size())
{
cc.add(aa.get(j));
++j;
}
return cc;
}
/**
* Remove aa from bb
*
* @param aa sorted
* @param bb sorted
* @return
*/
private ArrayInt diffArray(ArrayInt aa, ArrayInt bb)
{
if (bb.size() == 0)
return bb;
if (aa.size() == 0)
return bb;
ArrayInt cc = new ArrayInt();
int j = 0;
for (int i = 0; i < bb.size(); ++i)
{
while (j < aa.size() && aa.get(j) < bb.get(i))
++j;
if (j < aa.size() && aa.get(j) == bb.get(i))
{
++j;
}
else
{
cc.add(bb.get(i));
}
}
return cc;
}
/**
* Intersection of aa and bb
*
* @param aa sorted
* @param bb sorted
* @return
*/
private ArrayInt intersectionArray(ArrayInt aa, ArrayInt bb)
{
if (aa.size() == 0)
return aa;
if (bb.size() == 0)
return bb;
ArrayInt cc = new ArrayInt();
int j = 0;
for (int i = 0; i < bb.size(); ++i)
{
while (j < aa.size() && aa.get(j) < bb.get(i))
++j;
if (j < aa.size() && aa.get(j) == bb.get(i))
{
cc.add(bb.get(i));
++j;
}
}
return cc;
}
/**
* Symmetric difference of aa and bb
*
* @param aa sorted
* @param bb sorted
* @return
*/
private ArrayInt symdiffArray(ArrayInt aa, ArrayInt bb)
{
if (aa.size() == 0)
return bb;
if (bb.size() == 0)
return aa;
ArrayInt cc = new ArrayInt();
int j = 0;
for (int i = 0; i < bb.size(); ++i)
{
while (j < aa.size() && aa.get(j) < bb.get(i))
{
cc.add(aa.get(j));
++j;
}
if (j < aa.size() && aa.get(j) == bb.get(i))
{
++j;
}
else
{
cc.add(bb.get(i));
}
}
while (j < aa.size())
{
cc.add(aa.get(j));
++j;
}
return cc;
}
private int[] getObjectIdsFromContext(IContextObject b)
{
int bobjs[];
if (b instanceof IContextObjectSet)
{
bobjs = ((IContextObjectSet) b).getObjectIds();
}
else if (b != null)
{
int id = b.getObjectId();
if (id >= 0)
bobjs = new int[] { id };
else
bobjs = null;
}
else
{
bobjs = null;
}
return bobjs;
}
private String getOQLfromContext(IContextObject b)
{
String oql;
if (b instanceof IContextObjectSet)
{
oql = ((IContextObjectSet) b).getOQL();
}
else if (b != null)
{
int id = b.getObjectId();
if (id >= 0)
oql = OQL.forObjectId(id);
else
oql = null;
}
else
{
oql = null;
}
return oql;
}
};
}
};
bb.addContext(prov2);
}
}
if (setOp == Operation.NONE || setOp == Operation.ALL)
{
final int i2 = i;
String title = MessageUtil.format(Messages.CompareTablesQuery_Table, i + 1);
ContextProvider prov = new ContextProvider(title)
{
public IContextObject getContext(Object row)
{
return getContextFromTable(i2, row);
}
};
bb.addContext(prov);
}
if (mode == Mode.DIFF_TO_PREVIOUS || mode == Mode.DIFF_RATIO_TO_PREVIOUS ||previous == -1)
previous = i;
}
/*
* No need to add derived operations as a refined table
* will have the extra columns already.
*/
//derivedops(bb);
return bb.build();
}
private Object getAbsoluteValue(ComparedRow cr, int comparedColumnIdx, int tableIdx)
{
Object tableRow = cr.getRows()[tableIdx];
if (tableRow == null) return null;
int tableColumnIdx = displayedColumns.get(comparedColumnIdx).getColumnIndexes()[tableIdx];
if (tableColumnIdx == -1) return null;
return tables[tableIdx].getColumnValue(tableRow, tableColumnIdx);
}
private Object percentDivide(Number d1, Number d2)
{
if (d1.doubleValue() == 0.0
&& d2.doubleValue() == 0.0
&& (d2 instanceof Integer || d2 instanceof Long || d2 instanceof Byte || d2 instanceof Short))
{
// Helps sorting if 0 -> 0 maps to +0% rather than NaN%
return Double.valueOf(0.0);
}
else
{
return Double.valueOf(d1.doubleValue() / d2.doubleValue());
}
}
/**
* Convert a encoded value from a cell.
* The value can be encoded, and needs to be
* decoded before arithmetic or filtering.
* The formatter can handle the encoded
* value.
* @param value
* @param tableIdx
* @param comparedColumnIdx
* @return the converted value, of the same type
* as the original.
*/
private Object valueConvert(Object value, int tableIdx, int comparedColumnIdx)
{
// Optimization, presume no converter will change null
if (value == null)
return null;
int tableColumnIdx = displayedColumns.get(comparedColumnIdx).getColumnIndexes()[tableIdx];
if (tableColumnIdx == -1)
return value;
Filter.ValueConverter vc = (Filter.ValueConverter) tables[tableIdx].getColumns()[tableColumnIdx]
.getData(Filter.ValueConverter.class);
if (vc != null)
{
if (value instanceof Bytes)
{
double v0 = ((Bytes) value).getValue();
double v = vc.convert(v0);
if (v != v0)
value = new Bytes(Double.valueOf(v).longValue());
}
else if (value instanceof Number)
{
double v0 = ((Number) value).doubleValue();
double v = vc.convert(v0);
if (v != v0)
{
// Try to convert back to original type
if (value instanceof Long)
{
long v1 = (long)v;
if (v1 == v)
value = v1;
else
value = v;
}
else if (value instanceof Integer)
{
int v1 = (int)v;
if (v1 == v)
value = v1;
else
value = v;
}
else if (value instanceof Short)
{
short v1 = (short)v;
if (v1 == v)
value = v1;
else
value = v;
}
else if (value instanceof Byte)
{
byte v1 = (byte)v;
if (v1 == v)
value = v1;
else
value = v;
}
else if (value instanceof Double)
{
double v1 = (double)v;
if (v1 == v)
value = v1;
else
value = v;
}
else if (value instanceof Float)
{
float v1 = (float)v;
if (v1 == v)
value = v1;
else
value = v;
}
else
value = v;
}
}
}
return value;
}
/**
* Did the converted value look like an approximate value?
* @param value original value
* @param value2 converted value
* @param tableIdx the table
* @param comparedColumnIdx the column index
* @return the approximation type
*/
private DeltaEncoding approxValue(Object value, Object value2, int tableIdx, int comparedColumnIdx)
{
if (value != null && !value.equals(value2))
{
int tableColumnIdx = displayedColumns.get(comparedColumnIdx).getColumnIndexes()[tableIdx];
if (tableColumnIdx == -1)
return DeltaEncoding.EXACT;
try
{
String fv = tables[tableIdx].getColumns()[tableColumnIdx].getFormatter().format(value);
if (fv != null)
{
if (fv.startsWith(Messages.RetainedSizeDerivedData_Approximate))
return DeltaEncoding.GE;
else if (fv.startsWith(Messages.CompareTablesQuery_GE))
return DeltaEncoding.GE;
else if (fv.startsWith("<= ")) //$NON-NLS-1$
// E.g. Quantize_LessEq_Prefix
return DeltaEncoding.LE;
else if (fv.startsWith(Messages.CompareTablesQuery_LE))
return DeltaEncoding.LE;
else if (fv.startsWith(Messages.CompareTablesQuery_APPROX))
return DeltaEncoding.APPROX;
}
}
catch (IllegalArgumentException e)
{}
}
return DeltaEncoding.EXACT;
}
/**
* Get the value for a row and column which is a difference to a previous table
* (either the first or the immediately previous table).
* @param cr the row of created compared table/tree
* @param columnIdx the column index of the created compared table/tree
* @param comparedColumnIdx the index for the set of columns of the same name which are compared
* @param tableIdx the table to read
* @param prevTableIdx the previous table to compare to
* @param ratio calculate a ration, not a difference
* @return
*/
private Object getDiffToPrevious(ComparedRow cr, int columnIdx, int comparedColumnIdx, int tableIdx, int prevTableIdx, boolean ratio)
{
Object tableRow = cr.getRows()[tableIdx];
if (tableRow == null) return null;
int tableColumnIdx = displayedColumns.get(comparedColumnIdx).getColumnIndexes()[tableIdx];
if (tableColumnIdx == -1) return null;
Object value = tables[tableIdx].getColumnValue(tableRow, tableColumnIdx);
Object previousTableValue = getAbsoluteValue(cr, comparedColumnIdx, prevTableIdx);
Object value2 = valueConvert(value, tableIdx, comparedColumnIdx);
DeltaEncoding approxValue = approxValue(value, value2, tableIdx, comparedColumnIdx);
value = value2;
value2 = valueConvert(previousTableValue, prevTableIdx, comparedColumnIdx);
DeltaEncoding approxPreviousValue = approxValue(previousTableValue, value2, prevTableIdx, comparedColumnIdx);
previousTableValue = value2;
if (value == null && previousTableValue == null) return null;
if (value == null && (previousTableValue instanceof Number || previousTableValue instanceof Bytes)) return null;
if ((value instanceof Number || value instanceof Bytes) && previousTableValue == null)
{
if (ratio)
return null;
/*
* Fix up encoding of single value
* The source value has a difference formatter to the output so could need conversion.
*/
if (approxValue != DeltaEncoding.EXACT)
{
if (value instanceof Bytes)
return encodeResult(((Bytes)value).getValue(), true, approxValue, approxPreviousValue, columnIdx);
else
return encodeResult(value, false, approxValue, approxPreviousValue, columnIdx);
}
return value;
}
boolean returnBytes = value instanceof Bytes && previousTableValue instanceof Bytes;
if (value instanceof Bytes)
value = Long.valueOf(((Bytes)value).getValue());
if (previousTableValue instanceof Bytes)
previousTableValue = Long.valueOf(((Bytes)previousTableValue).getValue());
if (value instanceof Number && previousTableValue instanceof Number)
{
Object ret = computeDiff((Number) previousTableValue, (Number) value);
if (ratio && ret instanceof Number)
{
return percentDivide((Number)ret, (Number)previousTableValue);
}
else
{
return encodeResult(ret, returnBytes, approxValue, approxPreviousValue, columnIdx);
}
}
else
{
if (ratio)
return null;
if (previousTableValue == null || !previousTableValue.equals(value))
return value;
}
return null;
}
/**
* Encode the result for a delta retained size formatter.
* @param ret
* @param returnBytes
* @param approxValue
* @param approxPreviousValue
* @param columnIdx
* @return
*/
private Object encodeResult(Object ret, boolean returnBytes, DeltaEncoding approxValue, DeltaEncoding approxPreviousValue,
int columnIdx)
{
if (returnBytes || ret instanceof Long)
{
long val = ((Number)ret).longValue();
Format fmt = columns[columnIdx].getFormatter();
if (fmt instanceof DeltaRetainedBytesFormat)
{
DeltaRetainedBytesFormat dfmt = (DeltaRetainedBytesFormat)fmt;
if (approxValue == DeltaEncoding.EXACT && approxPreviousValue == DeltaEncoding.EXACT)
;
else if ((approxValue == DeltaEncoding.GE || approxValue == DeltaEncoding.EXACT) && (approxPreviousValue == DeltaEncoding.EXACT || approxPreviousValue == DeltaEncoding.LE))
val = dfmt.encodege(val);
else if ((approxValue == DeltaEncoding.LE || approxValue == DeltaEncoding.EXACT) && (approxPreviousValue == DeltaEncoding.EXACT || approxPreviousValue == DeltaEncoding.GE))
val = dfmt.encodele(val);
else if (approxValue != DeltaEncoding.EXACT || approxPreviousValue != DeltaEncoding.EXACT)
val = dfmt.encodeun(val);
}
if (returnBytes)
return new Bytes(val);
else
return val;
}
return ret;
}
private Object computeDiff(Number o1, Number o2)
{
if (o1 instanceof Long && o2 instanceof Long) return (o2.longValue() - o1.longValue());
if (o1 instanceof Integer && o2 instanceof Integer) return (o2.intValue() - o1.intValue());
if (o1 instanceof Short && o2 instanceof Short) return (o2.shortValue() - o1.shortValue());
if (o1 instanceof Byte && o2 instanceof Byte) return (o2.byteValue() - o1.byteValue());
if (o1 instanceof Float && o2 instanceof Float) return (o2.floatValue() - o1.floatValue());
if (o1 instanceof Double && o2 instanceof Double) return (o2.doubleValue() - o1.doubleValue());
return null;
}
/**
* Get the icon for the row.
* Chose the icon from the underlying tables if they all agree,
* others choose a special compare icon.
*/
public URL getIcon(Object row)
{
URL ret = null;
final ComparedRow cr = (ComparedRow) row;
// Find the rows from the tables
boolean foundIcon = false;
for (int i = 0; i < tables.length; ++i)
{
Object r = cr.getRows()[i];
if (r != null)
{
URL tableIcon = (tables[i] instanceof IIconProvider) ? ((IIconProvider) tables[i]).getIcon(r)
: null;
if (foundIcon)
{
try
{
if (ret == null ? tableIcon != null : tableIcon == null
|| !ret.toURI().equals(tableIcon.toURI()))
{
// Mismatch, so use compare icon instead
ret = Icons.getURL("compare.gif"); //$NON-NLS-1$
break;
}
}
catch (URISyntaxException e)
{
// URI problem, so use compare icon instead
ret = Icons.getURL("compare.gif"); //$NON-NLS-1$
break;
}
}
else
{
ret = tableIcon;
foundIcon = true;
}
}
}
return ret;
}
public Mode getMode()
{
return mode;
}
public void setMode(Mode mode)
{
this.mode = mode;
updateColumns();
}
public Operation getOperation()
{
return setOp;
}
public void setOperation(Operation op)
{
if (op != Operation.NONE)
{
// Don't allow an operation if there are not enough local tables
int samec = 0;
for (boolean same : CompareTablesQuery.this.sameSnapshot)
{
if (same)
++samec;
}
if (samec < 2)
op = Operation.NONE;
}
this.setOp = op;
updateColumns();
}
private void addPositiveIndicator(Format formatter)
{
if (formatter instanceof DecimalFormat)
{
DecimalFormat pctFmt = (DecimalFormat) formatter;
if ((pctFmt.getPositivePrefix().length() == 0
|| pctFmt.getPositivePrefix().equals(pctFmt.getNegativePrefix()))
&& (pctFmt.getPositiveSuffix().length() == 0
|| pctFmt.getPositiveSuffix().equals(pctFmt.getNegativeSuffix())))
{
// No positive prefix, or positive suffix
DecimalFormatSymbols sym = DecimalFormatSymbols.getInstance();
// find the symbol
String plus = Character.toString(sym.getPlusSign());
// Make it a prefix, unless there is a prefix (same as negative) but no suffix
if (pctFmt.getPositivePrefix().length() > 0 && pctFmt.getPositiveSuffix().length() == 0)
pctFmt.setPositiveSuffix(plus);
else
pctFmt.setPositivePrefix(plus);
}
}
}
private void setFormatter()
{
int i = 1;
// Rather than giving a format, modify the default
Format formatter = NumberFormat.getIntegerInstance();
addPositiveIndicator(formatter);
NumberFormat formatterPercent = NumberFormat.getPercentInstance();
addPositiveIndicator(formatterPercent);
// Force the sign for Bytes formatting
// Compare with org.eclipse.mat.snapshot.Histogram.getColumns()
String detailed1 = "+"+BytesFormat.DETAILED_DECIMAL_FORMAT+";-"+BytesFormat.DETAILED_DECIMAL_FORMAT; //$NON-NLS-1$ //$NON-NLS-2$
DecimalFormat bcf = new DecimalFormat(detailed1);
NumberFormat nf = NumberFormat.getNumberInstance();
if (nf instanceof DecimalFormat)
{
DecimalFormat bcf2 = (DecimalFormat)nf;
bcf2.setMinimumFractionDigits(bcf.getMinimumFractionDigits());
bcf2.setMaximumFractionDigits(bcf.getMaximumFractionDigits());
addPositiveIndicator(bcf2);
bcf = bcf2;
}
BytesFormat bfm = new BytesFormat(formatter, bcf);
DeltaRetainedBytesFormat drbfm = new DeltaRetainedBytesFormat(formatter, bcf);
for (ComparedColumn comparedColumn : displayedColumns)
{
Column c = comparedColumn.description;
for (int j = 0; j < comparedColumn.getColumnIndexes().length; j++)
{
if (mode != Mode.ABSOLUTE && j > 0)
{
if (!columns[i].getCalculateTotals() && c.getCalculateTotals())
{
// Set the totals mode
IDecorator decorator = columns[i].getDecorator();
columns[i] = new Column(columns[i].getLabel(), columns[i].getType(), columns[i].getAlign(),
columns[i].getSortDirection(), columns[i].getFormatter(),
columns[i].getComparator());
columns[i].decorator(decorator);
}
// Set the converter
Object converter = c.getData(Filter.ValueConverter.class);
if (converter != null || columns[i].getData(Filter.ValueConverter.class) != null)
columns[i].setData(Filter.ValueConverter.class, converter);
if (c.getFormatter() instanceof DecimalFormat)
{
DecimalFormat fm = ((DecimalFormat) c.getFormatter().clone());
fm.setPositivePrefix("+"); //$NON-NLS-1$
columns[i].formatting(fm);
}
else if (c.getFormatter() instanceof BytesFormat)
{
//BytesFormat fm = ((BytesFormat) c.getFormatter().clone());
// Force the sign - can't retrieve information from existing formatter
if (c.getFormatter().getClass() == BytesFormat.class)
{
columns[i].formatting(bfm);
}
else
{
columns[i].formatting(drbfm);
columns[i].setData(Filter.ValueConverter.class, drbfm.converter);
}
}
else
{
columns[i].formatting(formatter);
}
}
else
{
if (!columns[i].getCalculateTotals() && c.getCalculateTotals())
{
// Set the totals mode
IDecorator decorator = columns[i].getDecorator();
columns[i] = new Column(columns[i].getLabel(), columns[i].getType(), columns[i].getAlign(),
columns[i].getSortDirection(), columns[i].getFormatter(),
columns[i].getComparator());
columns[i].decorator(decorator);
}
columns[i].formatting(c.getFormatter());
// Set the converter
Object converter = c.getData(Filter.ValueConverter.class);
if (converter != null || columns[i].getData(Filter.ValueConverter.class) != null)
columns[i].setData(Filter.ValueConverter.class, converter);
}
i++;
if ((mode == Mode.DIFF_RATIO_TO_FIRST || mode == Mode.DIFF_RATIO_TO_PREVIOUS) && j > 0)
{
columns[i].formatting(formatterPercent);
columns[i].noTotals();
i++;
}
}
}
}
class KeyDecorator implements IDecorator
{
public String prefix(Object row)
{
final ComparedRow cr = (ComparedRow) row;
return cr.prefix;
}
public String suffix(Object row)
{
final ComparedRow cr = (ComparedRow) row;
return cr.suffix;
}
}
class Decorator implements IDecorator
{
int table;
IDecorator dec;
Decorator(IDecorator dec, int table)
{
this.table = table;
this.dec = dec;
}
public String prefix(Object row)
{
final ComparedRow cr = (ComparedRow) row;
Object r = cr.getRows()[table];
return r != null ? dec.prefix(r) : null;
}
public String suffix(Object row)
{
final ComparedRow cr = (ComparedRow) row;
Object r = cr.getRows()[table];
return r != null ? dec.suffix(r) : null;
}
}
public void updateColumns()
{
List<Column> result = new ArrayList<Column>();
result.add(new Column(key.getLabel(), key.getType(), key.getAlign(), null, key.getFormatter(), null));
result.get(result.size() - 1).decorator(new KeyDecorator());
displayedColumns = new ArrayList<ComparedColumn>();
for (ComparedColumn comparedColumn : comparedColumns)
{
Column c = comparedColumn.description;
if (comparedColumn.isDisplayed())
{
displayedColumns.add(comparedColumn);
for (int j = 0; j < comparedColumn.getColumnIndexes().length; j++)
{
String label;
final int prev = mode == Mode.DIFF_TO_PREVIOUS || mode == Mode.DIFF_RATIO_TO_PREVIOUS ? j - 1 : 0;
Comparator<?>comparator;
if (j == 0 || mode == Mode.ABSOLUTE)
{
label = MessageUtil.format(Messages.CompareTablesQuery_ColumnAbsolute, c.getLabel(), j + 1);
final Comparator<Object> cmp = (Comparator<Object>) c.getComparator();
if (cmp != null)
{
final int tab = j;
comparator = new Comparator<ComparedRow>(){
public int compare(ComparedRow o1, ComparedRow o2)
{
Object row1 = o1.rows[tab];
Object row2 = o2.rows[tab];
// Compare nulls - sort first
if (row1 == null)
return row2 == null ? 0 : -1;
else if (row2 == null)
return 1;
else
return cmp.compare(row1, row2);
}
};
}
else
{
comparator = null;
}
}
else
{
label = MessageUtil.format(Messages.CompareTablesQuery_ColumnDifference,
c.getLabel(), j + 1,
prev + 1);
comparator = null;
}
result.add(new Column(label, c.getType(), c.getAlign(), c.getSortDirection(), c.getFormatter(),
comparator));
// Pass through the decorator
if (c.getDecorator() != null)
result.get(result.size() - 1).decorator(new Decorator(c.getDecorator(), j));
// For percentage modes also add a percent change column for subsequent tables
if (j > 0 && (mode == Mode.DIFF_RATIO_TO_FIRST || mode == Mode.DIFF_RATIO_TO_PREVIOUS))
{
label = MessageUtil.format(Messages.CompareTablesQuery_ColumnPercentDifference,
c.getLabel(), j + 1,
prev + 1);
result.add(new Column(label, c.getType(), c.getAlign(), c.getSortDirection(), c
.getFormatter(), null));
}
}
}
}
columns = result.toArray(new Column[result.size()]);
setFormatter();
}
IContextObject getContextFromTable(int i, Object row)
{
if (!sameSnapshot[i])
return null;
final ComparedRow cr = (ComparedRow) row;
IContextObject ret = null;
Object r = cr.getRows()[i];
if (r != null)
{
ret = tables[i].getContext(r);
}
return ret;
}
/**
* Add the derived operations from the source tables.
* No needed as using the RefinedResult versions of the tables
* already has them refined.
* @param answer
*/
void derivedops(ResultMetaData.Builder answer)
{
int found = 0;
Set<DerivedOperation>allops = new HashSet<DerivedOperation>();
for (int i = 0; i < tables.length; ++i)
{
IStructuredResult rr = tables[i];
ResultMetaData data = rr.getResultMetaData();
if (data != null)
{
++found;
Collection<DerivedOperation>ops = data.getDerivedOperations();
if (ops != null)
{
for (DerivedOperation op : data.getDerivedOperations())
{
allops.add(op);
}
}
}
}
if (found == 0)
return;
if (allops.size() == 0)
return;
for (ContextDerivedData.DerivedOperation operation : allops)
answer.addDerivedData(operation);
return;
}
}
public class ComparisonResultTable extends TableComparisonResult implements IResultTable
{
public ComparisonResultTable(List<ComparedRow> rows, Column key, List<ComparedColumn> comparedColumns,
Mode mode, Operation setOp)
{
super(rows, key, comparedColumns, mode, setOp);
}
public Object getRow(int rowId)
{
return rows.get(rowId);
}
public int getRowCount()
{
return rows.size();
}
}
public class ComparisonResultTree extends TableComparisonResult implements IResultTree
{
public ComparisonResultTree(List<ComparedRow> rows, Column key, List<ComparedColumn> comparedColumns,
Mode mode, Operation setOp)
{
super(rows, key, comparedColumns, mode, setOp);
}
public List<?> getElements()
{
return rows;
}
public boolean hasChildren(Object element)
{
for (int i = 0; i < tables.length; i++)
{
IStructuredResult table = tables[i];
if (table instanceof IResultTree)
{
Object treerow = ((ComparedRow)element).getRows()[i];
if (treerow != null && ((IResultTree)table).hasChildren(treerow))
return true;
}
}
return false;
}
public List<?> getChildren(Object parent)
{
return mergeKeys((ComparedRow)parent, new VoidProgressListener());
}
}
}