Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
dk.eobjects.metamodel.MetaModelHelper Maven / Gradle / Ivy
/**
* This file is part of MetaModel.
*
* MetaModel is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MetaModel is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MetaModel. If not, see .
*/
package dk.eobjects.metamodel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import dk.eobjects.metamodel.data.DataSet;
import dk.eobjects.metamodel.data.Row;
import dk.eobjects.metamodel.query.FilterItem;
import dk.eobjects.metamodel.query.FromItem;
import dk.eobjects.metamodel.query.GroupByItem;
import dk.eobjects.metamodel.query.OrderByItem;
import dk.eobjects.metamodel.query.Query;
import dk.eobjects.metamodel.query.SelectItem;
import dk.eobjects.metamodel.schema.Column;
import dk.eobjects.metamodel.schema.ColumnType;
import dk.eobjects.metamodel.schema.Table;
import dk.eobjects.metamodel.util.ObjectComparator;
/**
* This class contains various helper functionality to common tasks involving
* MetaModel, eg.:
*
* Easy-access for traversing common schema items
* Manipulate data in memory. These methods are primarily used to enable
* queries for non-queryable data sources like CSV files and spreadsheets.
* Query rewriting, traversing and manipulation.
*
*/
public class MetaModelHelper {
private static final Log _log = LogFactory.getLog(MetaModelHelper.class);
// Preven instantiation
private MetaModelHelper() {
}
/**
* Creates an array of tables where all occurences of tables in the provided
* list of tables and columns are included
*/
public static Table[] getTables(List tableList,
List columnList) {
HashSet set = new HashSet();
set.addAll(tableList);
for (Column column : columnList) {
set.add(column.getTable());
}
return set.toArray(new Table[set.size()]);
}
/**
* Converts a list of columns to a corresponding array of tables
*
* @param columns
* the columns that the tables will be extracted from
* @return an array containing the tables of the provided columns.
*/
public static Table[] getTables(List columns) {
ArrayList result = new ArrayList();
for (Column column : columns) {
Table table = column.getTable();
if (!result.contains(table)) {
result.add(table);
}
}
return result.toArray(new Table[result.size()]);
}
/**
* Creates a subset array of columns, where only columns that are contained
* within the specified table are included.
*
* @param table
* @param columns
* @return an array containing the columns that exist in the table
*/
public static Column[] getTableColumns(Table table, List columns) {
return getTableColumns(table, columns
.toArray(new Column[columns.size()]));
}
/**
* Creates a subset array of columns, where only columns that are contained
* within the specified table are included.
*
* @param table
* @param columns
* @return an array containing the columns that exist in the table
*/
public static Column[] getTableColumns(Table table, Column[] columns) {
ArrayList result = new ArrayList();
for (int i = 0; i < columns.length; i++) {
Column column = columns[i];
if (table == column.getTable()) {
result.add(column);
}
}
return result.toArray(new Column[result.size()]);
}
public static DataSet getCarthesianProduct(DataSet... fromDataSets) {
return getCarthesianProduct(fromDataSets, new FilterItem[0]);
}
public static DataSet getCarthesianProduct(DataSet[] fromDataSets,
List whereItems) {
return getCarthesianProduct(fromDataSets, whereItems
.toArray(new FilterItem[whereItems.size()]));
}
public static DataSet getCarthesianProduct(DataSet[] fromDataSets,
FilterItem... filterItems) {
List selectItems = new ArrayList();
for (DataSet dataSet : fromDataSets) {
for (int i = 0; i < dataSet.getSelectItems().length; i++) {
SelectItem item = dataSet.getSelectItems()[i];
selectItems.add(item);
}
}
int selectItemOffset = 0;
List data = new ArrayList();
for (int fromDataSetIndex = 0; fromDataSetIndex < fromDataSets.length; fromDataSetIndex++) {
DataSet fromDataSet = fromDataSets[fromDataSetIndex];
SelectItem[] fromSelectItems = fromDataSet.getSelectItems();
if (fromDataSetIndex == 0) {
while (fromDataSet.next()) {
Object[] values = fromDataSet.getRow().getValues();
Object[] row = new Object[selectItems.size()];
System.arraycopy(values, 0, row, selectItemOffset,
values.length);
data.add(row);
}
fromDataSet.close();
} else {
List fromDataRows = new ArrayList();
while (fromDataSet.next()) {
fromDataRows.add(fromDataSet.getRow().getValues());
}
fromDataSet.close();
for (int i = 0; i < data.size(); i = i + fromDataRows.size()) {
Object[] originalRow = data.get(i);
data.remove(i);
for (int j = 0; j < fromDataRows.size(); j++) {
Object[] newRow = fromDataRows.get(j);
System.arraycopy(newRow, 0, originalRow,
selectItemOffset, newRow.length);
data.add(i + j, originalRow.clone());
}
}
}
selectItemOffset += fromSelectItems.length;
}
SelectItem[] finalSelectItems = selectItems
.toArray(new SelectItem[selectItems.size()]);
for (Iterator it = data.iterator(); it.hasNext();) {
Object[] objects = it.next();
Row row = new Row(finalSelectItems, objects);
for (FilterItem whereItem : filterItems) {
if (!whereItem.evaluate(row)) {
it.remove();
break;
}
}
}
return new DataSet(finalSelectItems, data);
}
public static DataSet getFiltered(DataSet dataSet,
List filterItems) {
return getFiltered(dataSet, filterItems
.toArray(new FilterItem[filterItems.size()]));
}
public static DataSet getFiltered(DataSet dataSet,
FilterItem... filterItems) {
List data = new ArrayList();
while (dataSet.next()) {
Row row = dataSet.getRow();
boolean valid = true;
for (FilterItem havingItem : filterItems) {
if (!havingItem.evaluate(row)) {
valid = false;
break;
}
}
if (valid) {
data.add(row.getValues());
}
}
dataSet.close();
return new DataSet(dataSet.getSelectItems(), data);
}
public static DataSet getSelection(List selectItems,
DataSet dataSet) {
return getSelection(selectItems.toArray(new SelectItem[selectItems
.size()]), dataSet);
}
public static DataSet getSelection(SelectItem[] selectItems, DataSet dataSet) {
SelectItem[] dataSetSelectItems = dataSet.getSelectItems();
if (!ArrayUtils.isEquals(dataSetSelectItems, selectItems)) {
List resultData = new ArrayList();
while (dataSet.next()) {
Row row = dataSet.getRow();
Object[] resultRow = new Object[selectItems.length];
for (int i = 0; i < selectItems.length; i++) {
SelectItem selectItem = selectItems[i];
if (selectItem.getSubQuerySelectItem() != null) {
resultRow[i] = row.getValue(selectItem
.getSubQuerySelectItem());
} else {
resultRow[i] = row.getValue(selectItem);
}
}
resultData.add(resultRow);
}
dataSet.close();
dataSet = new DataSet(selectItems, resultData);
}
return dataSet;
}
public static DataSet getGrouped(List selectItems,
DataSet dataSet, List groupByItems) {
return getGrouped(selectItems
.toArray(new SelectItem[selectItems.size()]), dataSet,
groupByItems.toArray(new GroupByItem[groupByItems.size()]));
}
public static DataSet getGrouped(SelectItem[] selectItems, DataSet dataSet,
GroupByItem[] groupByItems) {
DataSet result = dataSet;
if (groupByItems != null && groupByItems.length > 0) {
Map>> uniqueRows = new HashMap>>();
SelectItem[] groupBySelects = new SelectItem[groupByItems.length];
for (int i = 0; i < groupBySelects.length; i++) {
groupBySelects[i] = groupByItems[i].getSelectItem();
}
// Creates a list of SelectItems that have functions
List functionItems = getFunctionSelectItems(Arrays
.asList(selectItems));
// Loop through the dataset and identify groups
while (dataSet.next()) {
Row row = dataSet.getRow();
// Subselect a row prototype with only the unique values that
// define the group
Row uniqueRow = row.getSubSelection(groupBySelects);
// function input is the values used for calculating aggregate
// functions in the group
Map> functionInput;
if (!uniqueRows.containsKey(uniqueRow)) {
// If this group already exist, use an existing function
// input
functionInput = new HashMap>();
for (SelectItem item : functionItems) {
functionInput.put(item, new ArrayList());
}
uniqueRows.put(uniqueRow, functionInput);
} else {
// If this is a new group, create a new function input
functionInput = uniqueRows.get(uniqueRow);
}
// Loop through aggregate functions to check for validity
for (SelectItem item : functionItems) {
List objects = functionInput.get(item);
Column column = item.getColumn();
if (column != null) {
Object value = row.getValue(new SelectItem(column));
objects.add(value);
} else if (SelectItem.isCountAllItem(item)) {
// Just use the empty string, since COUNT(*) don't
// evaluate values (but null values should be prevented)
objects.add("");
} else {
throw new IllegalArgumentException(
"Expression function not supported: " + item);
}
}
}
dataSet.close();
List resultData = new ArrayList();
// Loop through the groups to generate aggregates
for (Entry>> entry : uniqueRows
.entrySet()) {
Row row = entry.getKey();
Map> functionInput = entry.getValue();
Object[] resultRow = new Object[selectItems.length];
// Loop through select items to generate a row
for (int i = 0; i < selectItems.length; i++) {
SelectItem item = selectItems[i];
int uniqueRowIndex = row.indexOf(item);
if (uniqueRowIndex != -1) {
// If there's already a value for the select item in the
// row, keep it (it's one of the grouped by columns)
resultRow[i] = row.getValue(uniqueRowIndex);
} else {
// Use the function input to calculate the aggregate
// value
List objects = functionInput.get(item);
if (objects != null) {
Object functionResult = item.getFunction()
.evaluate(objects.toArray());
resultRow[i] = functionResult;
} else {
if (item.getFunction() != null) {
_log
.error("No function input found for SelectItem: "
+ item);
}
}
}
}
resultData.add(resultRow);
}
result = new DataSet(selectItems, resultData);
}
result = getSelection(selectItems, result);
return result;
}
public static DataSet getAggregated(List resultSelectItems,
DataSet dataSet) {
List functionItems = getFunctionSelectItems(resultSelectItems);
if (functionItems.isEmpty()) {
return dataSet;
} else {
SelectItem[] resultSelectItemsArray = resultSelectItems
.toArray(new SelectItem[resultSelectItems.size()]);
Map> functionInput = new HashMap>();
for (SelectItem item : functionItems) {
functionInput.put(item, new ArrayList());
}
boolean onlyAggregates = true;
if (functionItems.size() != resultSelectItems.size()) {
onlyAggregates = false;
}
List resultRows = new ArrayList();
while (dataSet.next()) {
Row inputRow = dataSet.getRow();
for (SelectItem item : functionItems) {
List objects = functionInput.get(item);
Column column = item.getColumn();
if (column != null) {
Object value = inputRow
.getValue(new SelectItem(column));
objects.add(value);
} else if (SelectItem.isCountAllItem(item)) {
// Just use the empty string, since COUNT(*) don't
// evaluate values (but null values should be prevented)
objects.add("");
} else {
throw new IllegalArgumentException(
"Expression function not supported: " + item);
}
}
// If the result should also contain non-aggregated values, we
// will keep those in the rows list
if (!onlyAggregates) {
Object[] values = new Object[resultSelectItemsArray.length];
for (int i = 0; i < resultSelectItemsArray.length; i++) {
Object value = inputRow
.getValue(resultSelectItemsArray[i]);
if (value != null) {
values[i] = value;
}
}
resultRows.add(new Row(resultSelectItemsArray, values));
}
}
dataSet.close();
// Create the aggregates
Map functionResult = new HashMap();
for (SelectItem item : functionItems) {
List input = functionInput.get(item);
Object result = item.getFunction().evaluate(input.toArray());
functionResult.put(item, result);
}
functionInput = null;
if (onlyAggregates) {
// We will only create a single row with all the aggregates
Object[] values = new Object[resultSelectItemsArray.length];
for (int i = 0; i < resultSelectItemsArray.length; i++) {
values[i] = functionResult.get(resultSelectItemsArray[i]);
}
Row row = new Row(resultSelectItemsArray, values);
resultRows.add(row);
} else {
// We will create the aggregates as well as regular values
for (int i = 0; i < resultRows.size(); i++) {
Row row = resultRows.get(i);
Object[] values = row.getValues();
for (Entry entry : functionResult
.entrySet()) {
SelectItem item = entry.getKey();
int itemIndex = row.indexOf(item);
if (itemIndex != -1) {
Object value = entry.getValue();
values[itemIndex] = value;
}
}
resultRows.set(i, new Row(resultSelectItemsArray, values));
}
}
return new DataSet(resultRows);
}
}
public static List getFunctionSelectItems(
List selectItems) {
List result = new ArrayList();
for (SelectItem selectItem : selectItems) {
if (selectItem.getFunction() != null) {
result.add(selectItem);
}
}
if (_log.isDebugEnabled()) {
_log.debug("SelectItems with functions: " + result);
}
return result;
}
public static DataSet getOrdered(DataSet dataSet,
List orderByItems) {
return getOrdered(dataSet, orderByItems
.toArray(new OrderByItem[orderByItems.size()]));
}
public static DataSet getOrdered(DataSet dataSet,
final OrderByItem... orderByItems) {
if (orderByItems != null && orderByItems.length != 0) {
final int[] sortIndexes = new int[orderByItems.length];
for (int i = 0; i < orderByItems.length; i++) {
OrderByItem item = orderByItems[i];
int indexOf = dataSet.indexOf(item.getSelectItem());
sortIndexes[i] = indexOf;
}
List data = dataSet.toObjectArrays();
final Comparator valueComparator = ObjectComparator
.getComparator();
Collections.sort(data, new Comparator() {
public int compare(Object[] o1, Object[] o2) {
for (int i = 0; i < sortIndexes.length; i++) {
Object sortObj1 = o1[sortIndexes[i]];
Object sortObj2 = o2[sortIndexes[i]];
int compare = valueComparator.compare(sortObj1,
sortObj2);
if (compare != 0) {
OrderByItem orderByItem = orderByItems[i];
boolean ascending = orderByItem.isAscending();
if (ascending) {
return compare;
} else {
return compare * -1;
}
}
}
return 0;
}
});
dataSet = new DataSet(dataSet.getSelectItems(), data);
}
return dataSet;
}
/**
* Examines a query and extracts an array of FromItem's that refer
* (directly) to tables (hence Joined FromItems and SubQuery FromItems are
* traversed but not included).
*
* @param q
* the query to examine
* @return an array of FromItem's that refer directly to tables
*/
public static FromItem[] getTableFromItems(Query q) {
List result = new ArrayList();
List items = q.getFromClause().getItems();
for (FromItem item : items) {
result.addAll(getTableFromItems(item));
}
return result.toArray(new FromItem[result.size()]);
}
public static List getTableFromItems(FromItem item) {
List result = new ArrayList();
if (item.getTable() != null) {
result.add(item);
} else if (item.getSubQuery() != null) {
FromItem[] sqItems = getTableFromItems(item.getSubQuery());
for (int i = 0; i < sqItems.length; i++) {
result.add(sqItems[i]);
}
} else if (item.getJoin() != null) {
FromItem leftSide = item.getLeftSide();
result.addAll(getTableFromItems(leftSide));
FromItem rightSide = item.getRightSide();
result.addAll(getTableFromItems(rightSide));
} else {
throw new IllegalStateException(
"FromItem was neither of Table type, SubQuery type or Join type: "
+ item);
}
return result;
}
/**
* Executes a single row query, like "SELECT COUNT(*), MAX(SOME_COLUMN) FROM
* MY_TABLE" or similar.
*
* @param dataContext
* the DataContext object to use for executing the query
* @param query
* the query to execute
* @return a row object representing the single row returned from the query
* @throws MetaModelException
* if less or more than one Row is returned from the query
*/
public static Row executeSingleRowQuery(DataContext dataContext, Query query)
throws MetaModelException {
DataSet dataSet = dataContext.executeQuery(query);
boolean next = dataSet.next();
if (!next) {
throw new MetaModelException("No rows returned from query: "
+ query);
}
Row row = dataSet.getRow();
next = dataSet.next();
if (next) {
throw new MetaModelException(
"More than one row returned from query: " + query);
}
dataSet.close();
return row;
}
/**
* Performs a left join (aka left outer join) operation on two datasets.
*
* @param ds1
* the left dataset
* @param ds2
* the right dataset
* @param onConditions
* the conditions to join by
* @return the left joined result dataset
*/
public static DataSet getLeftJoin(DataSet ds1, DataSet ds2,
FilterItem[] onConditions) {
if (ds1 == null) {
throw new IllegalArgumentException("Left DataSet cannot be null");
}
if (ds2 == null) {
throw new IllegalArgumentException("Right DataSet cannot be null");
}
SelectItem[] si1 = ds1.getSelectItems();
SelectItem[] si2 = ds2.getSelectItems();
SelectItem[] selectItems = new SelectItem[si1.length + si2.length];
System.arraycopy(si1, 0, selectItems, 0, si1.length);
System.arraycopy(si2, 0, selectItems, si1.length, si2.length);
List resultRows = new ArrayList();
List ds2data = ds2.toObjectArrays();
while (ds1.next()) {
// Construct a single-row dataset for making a carthesian product
// against ds2
Row ds1row = ds1.getRow();
List ds1rows = new ArrayList();
ds1rows.add(ds1row);
List carthesianRows = getCarthesianProduct(
new DataSet[] { new DataSet(ds1rows),
new DataSet(si2, ds2data) }, onConditions)
.toObjectArrays();
if (carthesianRows.size() > 0) {
resultRows.addAll(carthesianRows);
} else {
Object[] values = ds1row.getValues();
Object[] row = new Object[selectItems.length];
System.arraycopy(values, 0, row, 0, values.length);
resultRows.add(row);
}
}
ds1.close();
return new DataSet(selectItems, resultRows);
}
/**
* Performs a right join (aka right outer join) operation on two datasets.
*
* @param ds1
* the left dataset
* @param ds2
* the right dataset
* @param onConditions
* the conditions to join by
* @return the right joined result dataset
*/
public static DataSet getRightJoin(DataSet ds1, DataSet ds2,
FilterItem[] onConditions) {
SelectItem[] ds1selects = ds1.getSelectItems();
SelectItem[] ds2selects = ds2.getSelectItems();
SelectItem[] leftOrderedSelects = new SelectItem[ds1selects.length
+ ds2selects.length];
System.arraycopy(ds1selects, 0, leftOrderedSelects, 0,
ds1selects.length);
System.arraycopy(ds2selects, 0, leftOrderedSelects, ds1selects.length,
ds2selects.length);
// We will reuse the left join algorithm (but switch the datasets
// around)
DataSet dataSet = getLeftJoin(ds2, ds1, onConditions);
dataSet = getSelection(leftOrderedSelects, dataSet);
return dataSet;
}
public static SelectItem[] createSelectItems(Column[] columns) {
SelectItem[] items = new SelectItem[columns.length];
for (int i = 0; i < items.length; i++) {
items[i] = new SelectItem(columns[i]);
}
return items;
}
public static DataSet getDistinct(DataSet dataSet) {
SelectItem[] selectItems = dataSet.getSelectItems();
GroupByItem[] groupByItems = new GroupByItem[selectItems.length];
for (int i = 0; i < groupByItems.length; i++) {
groupByItems[i] = new GroupByItem(selectItems[i]);
}
return getGrouped(selectItems, dataSet, groupByItems);
}
public static Table[] getTables(Column[] columns) {
return getTables(Arrays.asList(columns));
}
public static Column[] getColumnsByType(Column[] columns,
ColumnType columnType) {
ArrayList result = new ArrayList();
for (Column column : columns) {
if (columnType == column.getType()) {
result.add(column);
}
}
return result.toArray(new Column[result.size()]);
}
}