/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 20102011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program 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 distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.transform;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.type.Type;
A ResultTransformer that is used to transform tuples to a value(s)
that can be cached.
Author: Gail Badner
/**
* A ResultTransformer that is used to transform tuples to a value(s)
* that can be cached.
*
* @author Gail Badner
*/
public class CacheableResultTransformer implements ResultTransformer {
// would be nice to be able to have this class extend
// PassThroughResultTransformer, but the default constructor
// is private (as it should be for a singleton)
private final static PassThroughResultTransformer ACTUAL_TRANSFORMER =
PassThroughResultTransformer.INSTANCE;
private final int tupleLength;
private final int tupleSubsetLength;
// array with the i-th element indicating whether the i-th
// expression returned by a query is included in the tuple;
// IMPLLEMENTATION NOTE:
// "joined" and "fetched" associations may use the same SQL,
// but result in different tuple and cached values. This is
// because "fetched" associations are excluded from the tuple.
// includeInTuple provides a way to distinguish these 2 cases.
private final boolean[] includeInTuple;
// indexes for tuple that are included in the transformation;
// set to null if all elements in the tuple are included
private final int[] includeInTransformIndex;
Returns a CacheableResultTransformer that is used to transform
tuples to a value(s) that can be cached.
Params: - transformer – - result transformer that will ultimately be
be used (after caching results)
- aliases – - the aliases that correspond to the tuple;
if it is non-null, its length must equal the number
of true elements in includeInTuple[]
- includeInTuple – - array with the i-th element indicating
whether the i-th expression returned by a query is
included in the tuple; the number of true values equals
the length of the tuple that will be transformed;
must be non-null
Returns: a CacheableResultTransformer that is used to transform
tuples to a value(s) that can be cached.
/**
* Returns a CacheableResultTransformer that is used to transform
* tuples to a value(s) that can be cached.
*
* @param transformer - result transformer that will ultimately be
* be used (after caching results)
* @param aliases - the aliases that correspond to the tuple;
* if it is non-null, its length must equal the number
* of true elements in includeInTuple[]
* @param includeInTuple - array with the i-th element indicating
* whether the i-th expression returned by a query is
* included in the tuple; the number of true values equals
* the length of the tuple that will be transformed;
* must be non-null
*
* @return a CacheableResultTransformer that is used to transform
* tuples to a value(s) that can be cached.
*/
public static CacheableResultTransformer create(
ResultTransformer transformer,
String[] aliases,
boolean[] includeInTuple) {
return transformer instanceof TupleSubsetResultTransformer
? create( ( TupleSubsetResultTransformer ) transformer, aliases, includeInTuple )
: create( includeInTuple );
}
Returns a CacheableResultTransformer that is used to transform
tuples to a value(s) that can be cached.
Params: - transformer – - a tuple subset result transformer;
must be non-null;
- aliases – - the aliases that correspond to the tuple;
if it is non-null, its length must equal the number
of true elements in includeInTuple[]
- includeInTuple – - array with the i-th element indicating
whether the i-th expression returned by a query is
included in the tuple; the number of true values equals
the length of the tuple that will be transformed;
must be non-null
Returns: a CacheableResultTransformer that is used to transform
tuples to a value(s) that can be cached.
/**
* Returns a CacheableResultTransformer that is used to transform
* tuples to a value(s) that can be cached.
*
* @param transformer - a tuple subset result transformer;
* must be non-null;
* @param aliases - the aliases that correspond to the tuple;
* if it is non-null, its length must equal the number
* of true elements in includeInTuple[]
* @param includeInTuple - array with the i-th element indicating
* whether the i-th expression returned by a query is
* included in the tuple; the number of true values equals
* the length of the tuple that will be transformed;
* must be non-null
*
* @return a CacheableResultTransformer that is used to transform
* tuples to a value(s) that can be cached.
*/
private static CacheableResultTransformer create(
TupleSubsetResultTransformer transformer,
String[] aliases,
boolean[] includeInTuple) {
if ( transformer == null ) {
throw new IllegalArgumentException( "transformer cannot be null" );
}
int tupleLength = ArrayHelper.countTrue( includeInTuple );
if ( aliases != null && aliases.length != tupleLength ) {
throw new IllegalArgumentException(
"if aliases is not null, then the length of aliases[] must equal the number of true elements in includeInTuple; " +
"aliases.length=" + aliases.length + "tupleLength=" + tupleLength
);
}
return new CacheableResultTransformer(
includeInTuple,
transformer.includeInTransform( aliases, tupleLength )
);
}
Returns a CacheableResultTransformer that is used to transform
tuples to a value(s) that can be cached.
Params: - includeInTuple – - array with the i-th element indicating
whether the i-th expression returned by a query is
included in the tuple; the number of true values equals
the length of the tuple that will be transformed;
must be non-null
Returns: a CacheableResultTransformer that is used to transform
tuples to a value(s) that can be cached.
/**
* Returns a CacheableResultTransformer that is used to transform
* tuples to a value(s) that can be cached.
*
* @param includeInTuple - array with the i-th element indicating
* whether the i-th expression returned by a query is
* included in the tuple; the number of true values equals
* the length of the tuple that will be transformed;
* must be non-null
*
* @return a CacheableResultTransformer that is used to transform
* tuples to a value(s) that can be cached.
*/
private static CacheableResultTransformer create(boolean[] includeInTuple) {
return new CacheableResultTransformer( includeInTuple, null );
}
private CacheableResultTransformer(boolean[] includeInTuple, boolean[] includeInTransform) {
if ( includeInTuple == null ) {
throw new IllegalArgumentException( "includeInTuple cannot be null" );
}
this.includeInTuple = includeInTuple;
tupleLength = ArrayHelper.countTrue( includeInTuple );
tupleSubsetLength = (
includeInTransform == null ?
tupleLength :
ArrayHelper.countTrue( includeInTransform )
);
if ( tupleSubsetLength == tupleLength ) {
includeInTransformIndex = null;
}
else {
includeInTransformIndex = new int[tupleSubsetLength];
for ( int i = 0, j = 0 ; i < includeInTransform.length ; i++ ) {
if ( includeInTransform[ i ] ) {
includeInTransformIndex[ j ] = i;
j++;
}
}
}
}
@Override
public Object transformTuple(Object[] tuple, String[] aliases) {
if ( aliases != null && aliases.length != tupleLength ) {
throw new IllegalStateException(
"aliases expected length is " + tupleLength +
"; actual length is " + aliases.length );
}
// really more correct to pass index( aliases.getClass(), aliases )
// as the 2nd arg to the following statement;
// passing null instead because it ends up being ignored.
return ACTUAL_TRANSFORMER.transformTuple( index( tuple.getClass(), tuple ), null );
}
Re-transforms, if necessary, a List of values previously
transformed by this (or an equivalent) CacheableResultTransformer.
Each element of the list is re-transformed in place (i.e, List
elements are replaced with re-transformed values) and the original
List is returned.
If re-transformation is unnecessary, the original List is returned
unchanged.
Params: - transformedResults – - results that were previously transformed
- aliases – - the aliases that correspond to the untransformed tuple;
- transformer – - the transformer for the re-transformation
- includeInTuple – indicates the indexes of
Returns: transformedResults, with each element re-transformed (if nececessary)
/**
* Re-transforms, if necessary, a List of values previously
* transformed by this (or an equivalent) CacheableResultTransformer.
* Each element of the list is re-transformed in place (i.e, List
* elements are replaced with re-transformed values) and the original
* List is returned.
* <p/>
* If re-transformation is unnecessary, the original List is returned
* unchanged.
*
* @param transformedResults - results that were previously transformed
* @param aliases - the aliases that correspond to the untransformed tuple;
* @param transformer - the transformer for the re-transformation
* @param includeInTuple indicates the indexes of
*
* @return transformedResults, with each element re-transformed (if nececessary)
*/
@SuppressWarnings( {"unchecked"})
public List retransformResults(
List transformedResults,
String[] aliases,
ResultTransformer transformer,
boolean[] includeInTuple) {
if ( transformer == null ) {
throw new IllegalArgumentException( "transformer cannot be null" );
}
if ( ! this.equals( create( transformer, aliases, includeInTuple ) ) ) {
throw new IllegalStateException(
"this CacheableResultTransformer is inconsistent with specified arguments; cannot re-transform"
);
}
boolean requiresRetransform = true;
String[] aliasesToUse = aliases == null ? null : index( ( aliases.getClass() ), aliases );
if ( transformer == ACTUAL_TRANSFORMER ) {
requiresRetransform = false;
}
else if ( transformer instanceof TupleSubsetResultTransformer ) {
requiresRetransform = ! ( ( TupleSubsetResultTransformer ) transformer ).isTransformedValueATupleElement(
aliasesToUse,
tupleLength
);
}
if ( requiresRetransform ) {
for ( int i = 0 ; i < transformedResults.size() ; i++ ) {
Object[] tuple = ACTUAL_TRANSFORMER.untransformToTuple(
transformedResults.get( i ),
tupleSubsetLength == 1
);
transformedResults.set( i, transformer.transformTuple( tuple, aliasesToUse ) );
}
}
return transformedResults;
}
Untransforms, if necessary, a List of values previously
transformed by this (or an equivalent) CacheableResultTransformer.
Each element of the list is untransformed in place (i.e, List
elements are replaced with untransformed values) and the original
List is returned.
If not unnecessary, the original List is returned
unchanged.
NOTE: If transformed values are a subset of the original
tuple, then, on return, elements corresponding to
excluded tuple elements will be null.
Params: - results – - results that were previously transformed
Returns: results, with each element untransformed (if nececessary)
/**
* Untransforms, if necessary, a List of values previously
* transformed by this (or an equivalent) CacheableResultTransformer.
* Each element of the list is untransformed in place (i.e, List
* elements are replaced with untransformed values) and the original
* List is returned.
* <p/>
* If not unnecessary, the original List is returned
* unchanged.
* <p/>
* NOTE: If transformed values are a subset of the original
* tuple, then, on return, elements corresponding to
* excluded tuple elements will be null.
* @param results - results that were previously transformed
* @return results, with each element untransformed (if nececessary)
*/
@SuppressWarnings( {"unchecked"})
public List untransformToTuples(List results) {
if ( includeInTransformIndex == null ) {
results = ACTUAL_TRANSFORMER.untransformToTuples(
results,
tupleSubsetLength == 1
);
}
else {
for ( int i = 0 ; i < results.size() ; i++ ) {
Object[] tuple = ACTUAL_TRANSFORMER.untransformToTuple(
results.get( i ),
tupleSubsetLength == 1
);
results.set( i, unindex( tuple.getClass(), tuple ) );
}
}
return results;
}
public Type[] getCachedResultTypes(Type[] tupleResultTypes) {
return tupleLength != tupleSubsetLength
? index( tupleResultTypes.getClass(), tupleResultTypes )
: tupleResultTypes;
}
@Override
public List transformList(List list) {
return list;
}
private <T> T[] index(Class<? extends T[]> clazz, T[] objects) {
T[] objectsIndexed = objects;
if ( objects != null &&
includeInTransformIndex != null &&
objects.length != tupleSubsetLength ) {
objectsIndexed = clazz.cast( Array.newInstance( clazz.getComponentType(), tupleSubsetLength ) );
for ( int i = 0 ; i < tupleSubsetLength; i++ ) {
objectsIndexed[ i ] = objects[ includeInTransformIndex[ i ] ];
}
}
return objectsIndexed;
}
private <T> T[] unindex(Class<? extends T[]> clazz, T[] objects) {
T[] objectsUnindexed = objects;
if ( objects != null &&
includeInTransformIndex != null &&
objects.length != tupleLength ) {
objectsUnindexed = clazz.cast( Array.newInstance( clazz.getComponentType(), tupleLength ) );
for ( int i = 0 ; i < tupleSubsetLength; i++ ) {
objectsUnindexed[ includeInTransformIndex[ i ] ] = objects[ i ];
}
}
return objectsUnindexed;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
CacheableResultTransformer that = ( CacheableResultTransformer ) o;
return tupleLength == that.tupleLength
&& tupleSubsetLength == that.tupleSubsetLength
&& Arrays.equals( includeInTuple, that.includeInTuple )
&& Arrays.equals( includeInTransformIndex, that.includeInTransformIndex );
}
@Override
public int hashCode() {
int result = tupleLength;
result = 31 * result + tupleSubsetLength;
result = 31 * result + ( includeInTuple != null ? Arrays.hashCode( includeInTuple ) : 0 );
result = 31 * result + ( includeInTransformIndex != null ? Arrays.hashCode( includeInTransformIndex ) : 0 );
return result;
}
}