org.modeshape.jcr.query.process.QueryResultColumns Maven / Gradle / Ivy
/*
* ModeShape (http://www.modeshape.org)
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
* See the AUTHORS.txt file in the distribution for a full listing of
* individual contributors.
*
* ModeShape is free software. Unless otherwise indicated, all code in ModeShape
* is licensed to you under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* ModeShape 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.modeshape.jcr.query.process;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.common.collection.ArrayListMultimap;
import org.modeshape.common.collection.Multimap;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.GraphI18n;
import org.modeshape.jcr.query.QueryResults.Columns;
import org.modeshape.jcr.query.QueryResults.Location;
import org.modeshape.jcr.query.QueryResults.TupleReformatter;
import org.modeshape.jcr.query.model.Column;
import org.modeshape.jcr.query.model.Constraint;
import org.modeshape.jcr.query.model.FullTextSearch;
import org.modeshape.jcr.query.model.Visitors;
/**
* Defines the columns associated with the results of a query. This definition allows the values to be accessed
*/
@Immutable
public class QueryResultColumns implements Columns {
private static final long serialVersionUID = 1L;
protected static final List NO_COLUMNS = Collections.emptyList();
protected static final List NO_TYPES = Collections.emptyList();
protected static final QueryResultColumns EMPTY = new QueryResultColumns(false, null, null);
protected static final String DEFAULT_SELECTOR_NAME = "Results";
/**
* Get an empty results column definition.
*
* @return the empty columns definition; never null
*/
public static QueryResultColumns empty() {
return EMPTY;
}
private final int tupleSize;
private final List columns;
private final List columnNames;
private final List columnTypes;
private final List selectorNames;
private List tupleValueNames;
private final Map selectorNameByColumnName;
private final Map columnIndexByColumnName;
private final Map columnTypeNameByColumnName;
private final Map locationIndexBySelectorName;
private final Map locationIndexByColumnName;
private final Map locationIndexByColumnIndex;
private final Map> columnIndexByPropertyNameBySelectorName;
private final Map fullTextSearchScoreIndexBySelectorName;
private final Map propertyNameByColumnName;
private final TupleReformatter reformatter;
protected final static class ColumnInfo {
protected final int columnIndex;
protected final String type;
protected ColumnInfo( int columnIndex,
String type ) {
this.columnIndex = columnIndex;
this.type = type;
}
@Override
public String toString() {
return "" + columnIndex + "(" + type + ")";
}
}
/**
* Create a new definition for the query results given the supplied columns.
*
* @param columns the columns that define the results; should never be modified directly
* @param columnTypes the names of the types for each column in columns
* @param includeFullTextSearchScores true if room should be made in the tuples for the full-text search scores for each
* {@link Location}, or false otherwise
*/
public QueryResultColumns( List extends Column> columns,
List columnTypes,
boolean includeFullTextSearchScores ) {
this(includeFullTextSearchScores, columns, columnTypes);
CheckArg.isNotEmpty(columns, "columns");
CheckArg.isNotEmpty(columnTypes, "columnTypes");
}
/**
* Create a new definition for the query results given the supplied columns.
*
* @param includeFullTextSearchScores true if room should be made in the tuples for the full-text search scores for each
* {@link Location}, or false otherwise
* @param columns the columns that define the results; should never be modified directly
* @param columnTypes the names of the types for each column in columns
*/
protected QueryResultColumns( boolean includeFullTextSearchScores,
List extends Column> columns,
List columnTypes ) {
this.columns = columns != null ? Collections.unmodifiableList(columns) : NO_COLUMNS;
this.columnTypes = columnTypes != null ? Collections.unmodifiableList(columnTypes) : NO_TYPES;
this.columnIndexByColumnName = new HashMap();
this.columnTypeNameByColumnName = new HashMap();
Set selectors = new HashSet();
final int columnCount = this.columns.size();
this.locationIndexBySelectorName = new HashMap();
this.locationIndexByColumnIndex = new HashMap();
this.locationIndexByColumnName = new HashMap();
this.selectorNameByColumnName = new HashMap();
this.columnIndexByPropertyNameBySelectorName = new HashMap>();
this.propertyNameByColumnName = new HashMap();
List names = new ArrayList(columnCount);
List selectorNames = new ArrayList(columnCount);
Set sameNameColumns = findColumnsWithSameNames(this.columns);
// Find all the selector names ...
Integer selectorIndex = new Integer(columnCount - 1);
for (int i = 0, max = this.columns.size(); i != max; ++i) {
Column column = this.columns.get(i);
assert column != null;
String selectorName = column.selectorName().name();
if (selectors.add(selectorName)) {
selectorNames.add(selectorName);
selectorIndex = new Integer(selectorIndex.intValue() + 1);
locationIndexBySelectorName.put(selectorName, selectorIndex);
}
}
// Now, find all of the column names ...
selectorIndex = new Integer(columnCount - 1);
Set tempSelectors = new HashSet();
for (int i = 0, max = this.columns.size(); i != max; ++i) {
Column column = this.columns.get(i);
assert column != null;
String selectorName = column.selectorName().name();
if (tempSelectors.add(selectorName)) {
selectorIndex = new Integer(selectorIndex.intValue() + 1);
}
String columnName = columnNameFor(column, names, sameNameColumns, selectors);
assert columnName != null;
propertyNameByColumnName.put(columnName, column.getPropertyName());
selectorNameByColumnName.put(columnName, selectorName);
columnIndexByColumnName.put(columnName, new Integer(i));
columnTypeNameByColumnName.put(columnName, this.columnTypes.get(i));
locationIndexByColumnIndex.put(new Integer(i), selectorIndex);
locationIndexByColumnName.put(columnName, selectorIndex);
// Insert the entry by selector name and property name ...
Map byPropertyName = columnIndexByPropertyNameBySelectorName.get(selectorName);
if (byPropertyName == null) {
byPropertyName = new HashMap();
columnIndexByPropertyNameBySelectorName.put(selectorName, byPropertyName);
}
String columnType = this.columnTypes.get(i);
byPropertyName.put(column.getPropertyName(), new ColumnInfo(i, columnType));
}
if (columns != null && selectorNames.isEmpty()) {
String selectorName = DEFAULT_SELECTOR_NAME;
selectorNames.add(selectorName);
locationIndexBySelectorName.put(selectorName, 0);
}
this.selectorNames = Collections.unmodifiableList(selectorNames);
this.columnNames = Collections.unmodifiableList(names);
if (includeFullTextSearchScores) {
this.fullTextSearchScoreIndexBySelectorName = new HashMap();
int numSelectors = selectorNames.size();
for (String selectorName : selectorNames) {
int index = locationIndexBySelectorName.get(selectorName).intValue() + numSelectors;
fullTextSearchScoreIndexBySelectorName.put(selectorName, new Integer(index));
}
this.tupleSize = columnNames.size() + selectorNames.size() + selectorNames.size();
} else {
this.fullTextSearchScoreIndexBySelectorName = null;
this.tupleSize = columnNames.size() + selectorNames.size();
}
this.reformatter = null;
}
private QueryResultColumns( List columns,
QueryResultColumns wrappedAround ) {
assert columns != null;
this.columns = Collections.unmodifiableList(columns);
this.columnIndexByColumnName = new HashMap();
this.columnTypeNameByColumnName = new HashMap();
this.locationIndexBySelectorName = new HashMap();
this.locationIndexByColumnIndex = new HashMap();
this.locationIndexByColumnName = new HashMap();
this.columnIndexByPropertyNameBySelectorName = new HashMap>();
this.propertyNameByColumnName = new HashMap();
this.selectorNameByColumnName = new HashMap();
this.selectorNames = new ArrayList(columns.size());
List types = new ArrayList(columns.size());
List names = new ArrayList(columns.size());
Set sameNameColumns = findColumnsWithSameNames(this.columns);
// Find all the selector names ...
for (int i = 0, max = this.columns.size(); i != max; ++i) {
Column column = this.columns.get(i);
assert column != null;
String selectorName = column.selectorName().name();
if (!selectorNames.contains(selectorName)) {
selectorNames.add(selectorName);
}
}
for (int i = 0, max = this.columns.size(); i != max; ++i) {
Column column = this.columns.get(i);
assert column != null;
String selectorName = column.selectorName().name();
String columnName = columnNameFor(column, names, sameNameColumns, selectorNames);
assert columnName != null;
propertyNameByColumnName.put(columnName, column.getPropertyName());
selectorNameByColumnName.put(columnName, selectorName);
Integer columnIndex = wrappedAround.columnIndexForName(columnName);
if (columnIndex == null) {
String columnNameWithoutSelector = column.getColumnName() != null ? column.getColumnName() : column.getPropertyName();
if (columnNameWithoutSelector.startsWith(selectorName + ".")
&& columnNameWithoutSelector.length() > (selectorName.length() + 1)) {
columnNameWithoutSelector = columnNameWithoutSelector.substring(selectorName.length() + 1);
}
columnIndex = wrappedAround.columnIndexForName(columnNameWithoutSelector);
if (columnIndex == null) {
String columnNameWithSelector = column.selectorName() + "." + columnNameWithoutSelector;
columnIndex = wrappedAround.columnIndexForName(columnNameWithSelector);
}
// if column index is still null, lookup the column index by property name.
if (columnIndex == null) {
columnNameWithoutSelector = column.getPropertyName();
if (columnNameWithoutSelector.startsWith(selectorName + ".")
&& columnNameWithoutSelector.length() > (selectorName.length() + 1)) {
columnNameWithoutSelector = columnNameWithoutSelector.substring(selectorName.length() + 1);
}
columnIndex = wrappedAround.columnIndexForName(columnNameWithoutSelector);
if (columnIndex == null) {
String columnNameWithSelector = column.selectorName() + "." + columnNameWithoutSelector;
columnIndex = wrappedAround.columnIndexForName(columnNameWithSelector);
}
}
}
assert columnIndex != null;
columnIndexByColumnName.put(columnName, columnIndex);
String columnType = wrappedAround.columnTypeNameByColumnName.get(columnName);
types.add(columnType);
Integer selectorIndex = new Integer(wrappedAround.getLocationIndex(selectorName));
locationIndexBySelectorName.put(selectorName, selectorIndex);
locationIndexByColumnIndex.put(columnIndex, selectorIndex);
locationIndexByColumnName.put(columnName, selectorIndex);
// Insert the entry by selector name and property name ...
Map byPropertyName = columnIndexByPropertyNameBySelectorName.get(selectorName);
if (byPropertyName == null) {
byPropertyName = new HashMap();
columnIndexByPropertyNameBySelectorName.put(selectorName, byPropertyName);
}
byPropertyName.put(column.getPropertyName(), new ColumnInfo(columnIndex, columnType));
}
if (selectorNames.isEmpty()) {
String selectorName = DEFAULT_SELECTOR_NAME;
selectorNames.add(selectorName);
locationIndexBySelectorName.put(selectorName, 0);
}
this.columnNames = Collections.unmodifiableList(names);
this.columnTypes = Collections.unmodifiableList(types);
if (wrappedAround.fullTextSearchScoreIndexBySelectorName != null) {
this.fullTextSearchScoreIndexBySelectorName = new HashMap();
for (String selectorName : selectorNames) {
Integer selectorIndex = new Integer(wrappedAround.getFullTextSearchScoreIndexFor(selectorName));
fullTextSearchScoreIndexBySelectorName.put(selectorName, selectorIndex);
}
this.tupleSize = columnNames.size() + selectorNames.size() + selectorNames.size();
} else {
this.fullTextSearchScoreIndexBySelectorName = null;
this.tupleSize = columnNames.size() + selectorNames.size();
}
// Create the indexes for the tuple reformatter ...
if (columnIndexByColumnName == null) {
this.reformatter = null;
} else {
int[] reformatIndexes = new int[this.tupleSize];
int i = 0;
// Add the columns ...
for (String columnName : columnNames) {
reformatIndexes[i++] = columnIndexByColumnName.get(columnName);
}
// Add the locations ...
for (String selectorName : selectorNames) {
reformatIndexes[i++] = locationIndexBySelectorName.get(selectorName);
}
// Add the full-text search scores ...
if (fullTextSearchScoreIndexBySelectorName != null) {
for (String selectorName : selectorNames) {
reformatIndexes[i++] = fullTextSearchScoreIndexBySelectorName.get(selectorName);
}
}
this.reformatter = new WrappedReformatter(reformatIndexes,
new QueryResultColumns(this.fullTextSearchScoreIndexBySelectorName != null,
this.columns, this.columnTypes));
}
}
public static boolean includeFullTextScores( Iterable constraints ) {
for (Constraint constraint : constraints) {
if (includeFullTextScores(constraint)) return true;
}
return false;
}
public static boolean includeFullTextScores( Constraint constraint ) {
final AtomicBoolean includeFullTextScores = new AtomicBoolean(false);
if (constraint != null) {
Visitors.visitAll(constraint, new Visitors.AbstractVisitor() {
@Override
public void visit( FullTextSearch obj ) {
includeFullTextScores.set(true);
}
});
}
return includeFullTextScores.get();
}
@Override
public Columns subSelect( List columns ) {
return new QueryResultColumns(columns, this);
}
@Override
public Columns subSelect( Column... columns ) {
return new QueryResultColumns(Arrays.asList(columns), this);
}
@Override
public TupleReformatter getTupleReformatter() {
return reformatter;
}
@Override
public Columns joinWith( Columns rightColumns ) {
if (this == rightColumns) return this;
List columns = new ArrayList(this.getColumnCount() + rightColumns.getColumnCount());
columns.addAll(this.getColumns());
columns.addAll(rightColumns.getColumns());
List types = new ArrayList(this.getColumnCount() + rightColumns.getColumnCount());
types.addAll(this.getColumnTypes());
types.addAll(rightColumns.getColumnTypes());
boolean includeFullTextScores = this.hasFullTextSearchScores() || rightColumns.hasFullTextSearchScores();
return new QueryResultColumns(columns, types, includeFullTextScores);
}
@Override
public List extends Column> getColumns() {
return columns;
}
@Override
@SuppressWarnings( "unchecked" )
public Iterator iterator() {
return (Iterator)getColumns().iterator();
}
@Override
public List getColumnNames() {
return columnNames;
}
@Override
public List getColumnTypes() {
return columnTypes;
}
@Override
public int getColumnCount() {
return columns.size();
}
@Override
public int getLocationCount() {
return selectorNames.size();
}
@Override
public List getSelectorNames() {
return selectorNames;
}
@Override
public int getTupleSize() {
return tupleSize;
}
@Override
public List getTupleValueNames() {
if (this.tupleValueNames == null) {
// This is idempotent, so no need to lock ...
List results = new ArrayList(getTupleSize());
// Add the column names ...
results.addAll(columnNames);
// Add the location names ...
for (String selectorName : selectorNames) {
String name = "Location(" + selectorName + ")";
results.add(name);
}
// Add the full-text search score names ...
if (fullTextSearchScoreIndexBySelectorName != null) {
for (String selectorName : selectorNames) {
String name = "Score(" + selectorName + ")";
results.add(name);
}
}
this.tupleValueNames = results;
}
return this.tupleValueNames;
}
@Override
public int getLocationIndexForColumn( int columnIndex ) {
if (locationIndexByColumnIndex.isEmpty()) return 0;
Integer result = locationIndexByColumnIndex.get(new Integer(columnIndex));
if (result == null) {
throw new IndexOutOfBoundsException(GraphI18n.columnDoesNotExistInQuery.text(columnIndex));
}
return result.intValue();
}
@Override
public int getLocationIndexForColumn( String columnName ) {
if (locationIndexByColumnName.isEmpty()) return 0;
Integer result = locationIndexByColumnName.get(columnName);
if (result == null) {
throw new NoSuchElementException(GraphI18n.columnDoesNotExistInQuery.text(columnName));
}
return result.intValue();
}
@Override
public int getLocationIndex( String selectorName ) {
Integer result = locationIndexBySelectorName.get(selectorName);
if (result == null) {
throw new NoSuchElementException(GraphI18n.selectorDoesNotExistInQuery.text(selectorName));
}
return result.intValue();
}
@Override
public boolean hasSelector( String selectorName ) {
return locationIndexBySelectorName.containsKey(selectorName);
}
@Override
public String getPropertyNameForColumn( int columnIndex ) {
return columns.get(columnIndex).getPropertyName();
}
@Override
public String getPropertyNameForColumnName( String columnName ) {
String result = propertyNameByColumnName.get(columnName);
return result != null ? result : columnName;
}
@Override
public int getColumnIndexForName( String columnName ) {
Integer result = columnIndexByColumnName.get(columnName);
if (result == null) {
throw new NoSuchElementException(GraphI18n.columnDoesNotExistInQuery.text(columnName));
}
return result.intValue();
}
protected Integer columnIndexForName( String columnName ) {
return columnIndexByColumnName.get(columnName);
}
@Override
public String getSelectorNameForColumnName( String columnName ) {
return selectorNameByColumnName.get(columnName);
}
@Override
public int getColumnIndexForProperty( String selectorName,
String propertyName ) {
Map byPropertyName = columnIndexByPropertyNameBySelectorName.get(selectorName);
if (byPropertyName == null) {
throw new NoSuchElementException(GraphI18n.selectorDoesNotExistInQuery.text(selectorName));
}
ColumnInfo result = byPropertyName.get(propertyName);
if (result == null) {
throw new NoSuchElementException(GraphI18n.propertyOnSelectorIsNotUsedInQuery.text(propertyName, selectorName));
}
return result.columnIndex;
}
@Override
public String getColumnTypeForProperty( String selectorName,
String propertyName ) {
Map byPropertyName = columnIndexByPropertyNameBySelectorName.get(selectorName);
if (byPropertyName == null) {
throw new NoSuchElementException(GraphI18n.selectorDoesNotExistInQuery.text(selectorName));
}
ColumnInfo result = byPropertyName.get(propertyName);
if (result == null) {
throw new NoSuchElementException(GraphI18n.propertyOnSelectorIsNotUsedInQuery.text(propertyName, selectorName));
}
return result.type;
}
@Override
public int getFullTextSearchScoreIndexFor( String selectorName ) {
if (fullTextSearchScoreIndexBySelectorName == null) return -1;
Integer result = fullTextSearchScoreIndexBySelectorName.get(selectorName);
if (result == null) {
throw new NoSuchElementException(GraphI18n.selectorDoesNotExistInQuery.text(selectorName));
}
return result.intValue();
}
@Override
public boolean hasFullTextSearchScores() {
return fullTextSearchScoreIndexBySelectorName != null;
}
@Override
public boolean includes( Columns other ) {
if (other == this) return true;
if (other == null) return false;
return this.getColumns().containsAll(other.getColumns());
}
@Override
public boolean isUnionCompatible( Columns other ) {
if (this == other) return true;
if (other == null) return false;
if (this.hasFullTextSearchScores() != other.hasFullTextSearchScores()) return false;
if (this.getColumnCount() != other.getColumnCount()) return false;
return this.getColumns().containsAll(other.getColumns()) && other.getColumns().containsAll(this.getColumns());
}
@Override
public int hashCode() {
return getColumns().hashCode();
}
@Override
public boolean equals( Object obj ) {
if (obj == this) return true;
if (obj instanceof QueryResultColumns) {
QueryResultColumns that = (QueryResultColumns)obj;
return this.getColumns().equals(that.getColumns());
}
return false;
}
protected static String columnNameFor( Column column,
List columnNames,
Set columnsWithDuplicateNames,
Collection selectorNames ) {
String columnName = column.getColumnName() != null ? column.getColumnName() : column.getPropertyName();
boolean qualified = columnName != null ? columnName.startsWith(column.getSelectorName() + ".") : false;
boolean aliased = columnName != null ? !columnName.equals(column.getPropertyName()) : false;
if (column.getPropertyName() == null || columnNames.contains(columnName) || columnsWithDuplicateNames.contains(column)) {
// Per section 6.7.39 of the JSR-283 specification, if the property name for a column is not given
// then the name for the column in the result set must be "selectorName.propertyName" ...
columnName = column.selectorName() + "." + columnName;
} else if (!qualified && !aliased && selectorNames.size() > 1) {
// When there is more than one selector, all columns need to be named "selectorName.propertyName" ...
columnName = column.selectorName() + "." + columnName;
}
columnNames.add(columnName);
return columnName;
}
protected static Set findColumnsWithSameNames( List columns ) {
Multimap columnNames = ArrayListMultimap.create();
for (Column column : columns) {
String columnName = column.getColumnName() != null ? column.getColumnName() : column.getPropertyName();
columnNames.put(columnName, column);
}
Set results = new HashSet();
for (Map.Entry> entry : columnNames.asMap().entrySet()) {
if (entry.getValue().size() > 1) {
results.addAll(entry.getValue());
}
}
return results;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (this.columnNames != null) {
sb.append("Columns[");
boolean first = true;
Iterator nameIter = this.columnNames.iterator();
for (Column column : getColumns()) {
if (first) first = false;
else sb.append(", ");
sb.append(column);
sb.append('(').append(getColumnIndexForName(nameIter.next())).append(')');
}
sb.append("]");
}
if (this.locationIndexByColumnIndex != null) {
sb.append("=> Locations[");
boolean first = true;
for (int i : locationIndexByColumnIndex.keySet()) {
if (first) first = false;
else sb.append(", ");
sb.append(getLocationIndexForColumn(i));
}
sb.append(']');
}
return sb.toString();
}
protected static final class WrappedReformatter implements TupleReformatter {
private final int[] indexes;
private final int tupleSize;
private final Columns columns;
protected WrappedReformatter( int[] indexes,
Columns columns ) {
this.indexes = indexes;
this.tupleSize = indexes.length;
this.columns = columns;
}
@Override
public Columns getColumns() {
return columns;
}
@Override
public Object[] reformat( Object[] input ) {
Object[] result = new Object[tupleSize];
for (int i = 0; i != tupleSize; ++i) {
result[i] = input[indexes[i]];
}
return result;
}
@Override
public int hashCode() {
return tupleSize;
}
@Override
public boolean equals( Object obj ) {
if (obj == this) return true;
if (obj instanceof WrappedReformatter) {
WrappedReformatter that = (WrappedReformatter)obj;
return this.tupleSize == that.tupleSize && Arrays.equals(this.indexes, that.indexes);
}
return false;
}
@Override
public String toString() {
return Arrays.toString(indexes);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy