/* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2010, Red Hat Middleware LLC 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 Middleware LLC. * * 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 transfor 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. * * @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. * * @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. * * @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++; } } } } /** * {@inheritDoc} */ 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. * <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 * @return transformedResults, with each element re-transformed (if nececessary) */ 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. * <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) */ 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; } /** * Returns the result types for the transformed value. * @param tupleResultTypes * @return */ public Type[] getCachedResultTypes(Type[] tupleResultTypes) { return tupleLength != tupleSubsetLength ? index( tupleResultTypes.getClass(), tupleResultTypes ) : tupleResultTypes ; } /** * {@inheritDoc} */ 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; if ( tupleLength != that.tupleLength ) { return false; } if ( tupleSubsetLength != that.tupleSubsetLength ) { return false; } if ( !Arrays.equals( includeInTuple, that.includeInTuple ) ) { return false; } if ( !Arrays.equals( includeInTransformIndex, that.includeInTransformIndex ) ) { return false; } return true; } @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; } }