/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * UnorderedTypeMatchFilter.java * Creation date: Jan 28, 2003. * By: Edward Lam */ package org.openquark.cal.services; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import org.apache.commons.collections.Bag; import org.apache.commons.collections.bag.HashBag; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.TypeExpr; /** * A filter that matches against a subset of the gem's input types, and optionally against a range of output types. * <p> * * Where a gem's type is t1 -> t2 -> ... -> tn * <ul> * <li>the input types are t1, t2, ..., t(n-1) * <li>the output type is tn * </ul> * * A gem passes through the filter if * <ol> * <li>the gem has input types that match the filter's input types, in any order. * The gem may have other input types which are not matched. * <li>the gem's output type matches one of the filter's output types. * </ol> * * For example, * <dl> * <dd> * If this is instantiated with inputs types: {a, a} * <br> * and output types: {Num b => b, [c]} * </dl> * Gems that would pass through this filter would be any gem with a pair of identical inputs, * and an output that was either a Num or a list. * <p> * Caveat: * One must be careful when constructing this filter that one maintains the desired referential semantics. * <p> * For example: * <ul> * <li>If one desires to filter on gem type "Num a => a -> a", this is accomplished by passing in the same reference to the * type "Num a => a" as both the input and the output type. * <li>If separate instances of the type "Num a => a" were passed in as the arguments, the resulting filter would filter * on the type "(Num a, Num b) => a -> b". * <li>The type "Int -> Double" would not pass through the filter in the first case, but it would in the second. * </ul> * * @author Edward Lam */ public class UnorderedTypeMatchFilter extends GemFilter { private static final TypeExpr[] EMPTY_TYPE_ARRAY = new TypeExpr[0]; private final TypeExpr[] filterInputTypes; private final TypeExpr[] filterOutputTypes; private final ModuleTypeInfo currentModuleTypeInfo; private final boolean selectMoreGeneralized; /** * A trivial helper class two hold two arrays of TypeExpr. * Warning: these arrays are not immutable. * @author Edward Lam */ private static class TypeArraysPair { private final TypeExpr[] array1; private final TypeExpr[] array2; private TypeArraysPair(TypeExpr[] array1, TypeExpr[] array2) { this.array1 = array1; this.array2 = array2; } TypeExpr[] getArray1() { return array1; } TypeExpr[] getArray2() { return array2; } } /** * Constructor for a UnorderedTypeMatchFilter. * * @param inputTypes the input types to match, or null if any (non-functional) types match. * @param outputTypes the output types to match, or null if any (non-functional) types match. * @param currentModuleTypeInfo the current module. * @param selectMoreGeneralized in the case where type variables are involved, whether a gem should be accepted if * its types are -at most- or -at least- as specialized as the filter types (true == at most). * * For example, for the gem Prelude.id (type: a -> a), if we pass in the arguments (Num a => a, null): * if true, the filter would select any gem whose input type could be specialized to Num a => a * this means that Prelude.id would be returned. * if false, the filter would select any gem whose input type was at least as specialized as Num a => a * this means that Prelude.id would not be returned */ public UnorderedTypeMatchFilter(TypeExpr[] inputTypes, TypeExpr[] outputTypes, ModuleTypeInfo currentModuleTypeInfo, boolean selectMoreGeneralized) { if (inputTypes == null) { inputTypes = new TypeExpr[0]; } if (outputTypes == null) { outputTypes = new TypeExpr[] {TypeExpr.makeParametricType()}; } for (final TypeExpr element : outputTypes) { if (element.getArity() != 0) { throw new IllegalArgumentException("Output type must not be functional."); } } // Copy the type expressions, since they are not immutable. TypeArraysPair copiedTypeArrays = copyTypeArrays(inputTypes, outputTypes); this.filterInputTypes = copiedTypeArrays.getArray1(); this.filterOutputTypes = copiedTypeArrays.getArray2(); this.currentModuleTypeInfo = currentModuleTypeInfo; this.selectMoreGeneralized = selectMoreGeneralized; } /** * {@inheritDoc} */ @Override public boolean select(GemEntity gemEntity) { TypeExpr gemType = gemEntity.getTypeExpr(); // no need to check if the gem doesn't have enough inputs. if (gemType.getArity() < filterInputTypes.length) { return false; } // Instantiate members for the other select() TypeExpr[] typePieces = gemType.getTypePieces(); TypeExpr outputType = typePieces[typePieces.length - 1]; Bag remainingInputTypes = new HashBag(Arrays.asList(typePieces)); remainingInputTypes.remove(outputType, 1); return select(new ArrayList<TypeExpr>(), remainingInputTypes, outputType); } /** * Returns true if any of the filter output types matches with the outputTypeToTry. This is a necessary condition * for the filter to match a type. * @param outputTypeToTry * @return boolean */ private boolean anyFilterOutputTypesMatch(TypeExpr outputTypeToTry) { for (final TypeExpr filterOutputType : filterOutputTypes) { if (selectMoreGeneralized) { if (TypeExpr.canUnifyType (outputTypeToTry, filterOutputType, currentModuleTypeInfo)) { return true; } } else { if (TypeExpr.canPatternMatch (outputTypeToTry, filterOutputType, currentModuleTypeInfo)) { return true; } } } return false; } /** * Helper method for the other select(). * * What happens: * We initially call this method with an empty list, with all the input types in the remaining input types set. * We build up the input types to try by iterating over the remaining input types, and for each iteration * calling this method recursively with the remaining inputs. * When we get to the point where the number of input types to try is the same as the number of input types in the * filter, we carry out our test. * * @param inputTypesToTry the list of type expr to attempt to match against the input types in this filter. * The nth type in this list will be matched against the nth type in the filter. * @param remainingInputTypes the type expr which are unmatched. * @param outputTypeToTry the output type to match against the types in this filter * @return boolean */ private boolean select(List<TypeExpr> inputTypesToTry, Bag remainingInputTypes, TypeExpr outputTypeToTry) { int numInputsToTry = inputTypesToTry.size(); // If this is the first call to this method, check that any output types match. // If not, we don't need to waste our time with the recursive calls. // Note: it will be more efficient to thread through the set of filter output types which match on to successive calls, // and have the test for filter output types only test for types which pass through here, since we perform all the // necessary calculation here. if (numInputsToTry == 0) { if (!anyFilterOutputTypesMatch(outputTypeToTry)) { return false; } } // Now check if we should call ourselves recursively.. if (numInputsToTry < filterInputTypes.length) { // Create a set that represents the remaining input types that will be passed down to the next level. Bag remainingInputTypesArg = new HashBag(remainingInputTypes); for (Iterator it = remainingInputTypes.iterator(); it.hasNext(); ) { TypeExpr nextType = (TypeExpr)it.next(); inputTypesToTry.add(nextType); remainingInputTypesArg.remove(nextType, 1); // recursive call. if (select(inputTypesToTry, remainingInputTypesArg, outputTypeToTry)) { return true; } inputTypesToTry.remove(numInputsToTry); remainingInputTypesArg.add(nextType); } // none of the matching succeeded. return false; } else { // We have enough inputs to try. // Create a list/array of types to try: [inputType1, inputType2, ..., inputTypeN, outputType] List<TypeExpr> typesToTryList = new ArrayList<TypeExpr>(inputTypesToTry); typesToTryList.add(outputTypeToTry); TypeExpr[] typesToTryArray = typesToTryList.toArray(EMPTY_TYPE_ARRAY); // Note: here we are performing the matching/unifying all the inputs with the output for each filter output type. // Iterate over the filter's output types. for (final TypeExpr element : filterOutputTypes) { List<TypeExpr> filterTypesList = new ArrayList<TypeExpr> (Arrays.asList(filterInputTypes)); filterTypesList.add(element); TypeExpr[] filterTypesArray = filterTypesList.toArray(EMPTY_TYPE_ARRAY); if (selectMoreGeneralized) { if (TypeExpr.canUnifyTypePieces(typesToTryArray, filterTypesArray, currentModuleTypeInfo)) { return true; } } else { if (TypeExpr.canPatternMatchPieces(typesToTryArray, filterTypesArray, currentModuleTypeInfo)) { return true; } } } // Doesn't pass matching/unifying for any of the filter output types. return false; } } /** * A helper method to copy two arrays of TypeExpr. * @param typesArray1 the first array to copy. * @param typesArray2 the second array to copy. * @return TypeArraysPair the copied arrays. TypeExpr among these arrays maintain referential equality exhibited in the arguments. */ private static TypeArraysPair copyTypeArrays(TypeExpr[] typesArray1, TypeExpr[] typesArray2) { // Create one unified array. TypeExpr[] allTypesArray = new TypeExpr[typesArray1.length + typesArray2.length]; System.arraycopy(typesArray1, 0, allTypesArray, 0, typesArray1.length); System.arraycopy(typesArray2, 0, allTypesArray, typesArray1.length, typesArray2.length); // Copy the array TypeExpr[] allTypesArrayCopy = TypeExpr.copyTypeExprs(allTypesArray); // Break up the copied array into two smaller arrays TypeExpr[] returnArray1 = new TypeExpr[typesArray1.length]; System.arraycopy(allTypesArrayCopy, 0, returnArray1, 0, returnArray1.length); TypeExpr[] returnArray2 = new TypeExpr[typesArray2.length]; System.arraycopy(allTypesArrayCopy, returnArray1.length, returnArray2, 0, returnArray2.length); // Return the two arrays. return new TypeArraysPair(returnArray1, returnArray2); } }