/* * 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. */ /* * DataConstructorValueNode.java * Created: June 4, 2000 * By: Michael Cheng */ package org.openquark.cal.valuenode; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.openquark.cal.compiler.DataConstructor; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.compiler.SourceModel; import org.openquark.cal.compiler.TypeConsApp; import org.openquark.cal.compiler.TypeExpr; import org.openquark.cal.compiler.io.InputPolicy; import org.openquark.cal.compiler.io.OutputPolicy; import org.openquark.cal.foreignsupport.module.Prelude.AlgebraicValue; import org.openquark.cal.foreignsupport.module.Prelude.EitherValue; import org.openquark.cal.foreignsupport.module.Prelude.MaybeValue; import org.openquark.cal.foreignsupport.module.Prelude.OrderingValue; import org.openquark.cal.foreignsupport.module.Prelude.UnitValue; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.cal.services.MetaModule; import org.openquark.cal.services.Perspective; import org.openquark.util.UnsafeCast; /** * DataConstructorValueNode is a specialized ValueNode used to represent DataConstructor values. * It holds a DataConstructor object, and a list containing ValueNodes which represent the * component values of the DataConstructor object. * * DataConstructorValueNode is special among the derived classes of AlgebraicValueNode. This is * because it can be used to represent any algebraic CAL value. However, the representation of the * value may not be as efficient or easy-to-work-with for Java clients for the cases in which a * special purpose AlgebraicValueNode, optimized for values of a particular type, is available. * * @author Michael Cheng */ public class DataConstructorValueNode extends AlgebraicValueNode { /** * A custom ValueNodeProvider for the DataConstructorValueNode. * @author Frank Worsley */ public static class DataConstructorValueNodeProvider extends ValueNodeProvider<DataConstructorValueNode> { /** The working module for which the typeExprToNodeMap has been calculated. */ private MetaModule workingModule = null; /** * Map from typeExpr.toString() -> value node provided for that type. * Contains type expr for which value nodes have already been determined, or null if * 1) we are currently trying to build a value node for that type. * 2) a value node for that type cannot be built. **/ private final Map<String, DataConstructorValueNode> typeExprToNodeMap = new HashMap<String, DataConstructorValueNode>(); public DataConstructorValueNodeProvider(ValueNodeBuilderHelper builderHelper) { super(builderHelper); } /** * @see org.openquark.cal.valuenode.ValueNodeProvider#getValueNodeClass() */ @Override public Class<DataConstructorValueNode> getValueNodeClass() { return DataConstructorValueNode.class; } /** * @see org.openquark.cal.valuenode.ValueNodeProvider#isSpecialLiteralizedValueNode() */ @Override public boolean isSpecialLiteralizedValueNode() { return false; } /** * {@inheritDoc} * * This method builds a new DataConstructorValueNode for a type expression. * If no data constructor is provided, it smartly picks a default data constructor to use, * so that there is no endless chain of recursive data constructors. * If no non-recursive data constructor can be found this method returns null. * * This method also returns null if typeExpr is present in the typeExprToNodeMap but has a null * value node mapped to it. This case would occur if we are trying to find a data constructor and encounter * one that requires an argument for which we have not yet found a constructor itself. This would indicate * a cyclic constructor and therefore this method returns null. * * @param value the value for the value node * @param dataConstructor the data constructor for the value node * @param typeExpr the type expression for the value node * @return a new ValueNode or null if the type constructor has no non-recursive data constructors. */ @Override public DataConstructorValueNode getNodeInstance(Object value, DataConstructor dataConstructor, TypeExpr typeExpr) { // Check for handleability. TypeConsApp typeConsApp = typeExpr.rootTypeConsApp(); if (typeConsApp == null || typeConsApp.isFunctionType()) { return null; } // Check visibility of the type.. (should we do this?) Perspective perspective = getValueNodeBuilderHelper().getPerspective(); if (perspective.getTypeConstructor(typeConsApp.getName()) == null) { return null; } if (dataConstructor == null) { // No data constructor provided - we have to pick one, and create a default value. // If the perspective's working module has changed, the typeExprToNodeMap is no longer valid. if (perspective.getWorkingModule() != workingModule) { workingModule = perspective.getWorkingModule(); typeExprToNodeMap.clear(); } // TODOEL: ~HACK - we use the string-ified value as a key because we want it equals() in the map. String typeExprString = typeExpr.toString(); // First, check if we've previously created a value node for this expression. if (typeExprToNodeMap.containsKey(typeExprString)) { // We've already found a value node for this type, so just return it. // If we're currently in the process of finding a value node for it, then this // will end up returning null. That's ok since it indicates to the caller that // there is a cycle. DataConstructorValueNode valueNode = typeExprToNodeMap.get(typeExprString); return valueNode != null ? valueNode.copyValueNode(typeExpr) : null; } else { // Add a signal that we are building a value node for this type. typeExprToNodeMap.put(typeExprString, null); } // Iterate over the visible data constructors. DataConstructor[] dataConsArray = perspective.getDataConstructorsForType(typeConsApp.getName()); if (dataConsArray != null) { for (final DataConstructor element : dataConsArray) { // See if a value node can be built for this data constructor (call back with non-null data constructor). DataConstructorValueNode dataConsValueNode = getNodeInstance(null, element, typeExpr); if (dataConsValueNode != null) { typeExprToNodeMap.put(typeExprString, dataConsValueNode); return dataConsValueNode; } } } return null; } else { if (value == null) { // Try and get default child nodes for the value. // If there are no valid visible data constructors value will still be null. This should only happen // when a data constructor value is output and the child nodes are then initialized during unwinding. int arity = dataConstructor.getArity(); if (arity == 0) { // No arguments, so we don't need any child value nodes. value = Collections.EMPTY_LIST; } else { // Construct child value nodes for the arguments. ValueNode[] childValueNodes = new ValueNode[arity]; for (int i = 0 ; i < arity; i++) { TypeExpr argType = TypeExpr.getComponentTypeExpr(typeExpr, i, dataConstructor); childValueNodes[i] = getValueNodeBuilderHelper().getValueNodeForTypeExpr(argType); if (childValueNodes[i] == null) { // Looks like this argument type has already been encountered and we have // not been able to find a value node for it. That means this data constructor is // cyclic and cannot be used. return null; } } value = Arrays.asList(childValueNodes); } } List<ValueNode> listValue = UnsafeCast.asTypeOf(value, Collections.<ValueNode>emptyList()); return new DataConstructorValueNode(listValue, dataConstructor, typeExpr); } } } /** The data constructor this value node is for. */ private final DataConstructor dataConstructor; /** * The List containing the ValueNodes representing the value components of this DataConstructorValueNode. * There is a value component for each argument of the data constructor. */ private final List<ValueNode> childrenList; private Object outputValue; /** * Constructs a new DataConstructorValueNode representing a DataConstructor. * @param childrenList the list of child value nodes for the given data constructor * @param dataConstructorValue the data constructor this value node represents * @param typeExprParam the TypeExpr of the data value represented by this DataConstructorValueNode. */ public DataConstructorValueNode(List<ValueNode> childrenList, DataConstructor dataConstructorValue, TypeExpr typeExprParam) { super(typeExprParam); if (dataConstructorValue == null) { throw new NullPointerException(); } this.dataConstructor = dataConstructorValue; this.childrenList = childrenList != null ? new ArrayList<ValueNode>(childrenList) : new ArrayList<ValueNode>(); } /** * @see org.openquark.cal.valuenode.ValueNode#getValue() */ @Override public Object getValue() { return this; } /** * Makes a copy of this ValueNode, but with another TypeExpr instance (of the same type). * This is a deep copy, with respect to value nodes and the associated type expression. * Note: if the new TypeExpr is a different type from the present TypeExpr, an error is thrown. * @param newTypeExpr the new type of the copied node. * @return ValueNode */ @Override public DataConstructorValueNode copyValueNode(TypeExpr newTypeExpr) { checkCopyType(newTypeExpr); // eg. data Foo a = Bar (Baz a) a; // newTypeExpr will be of type "Foo a", but data constructor will be Bar, and children will be [Baz a, a]. // dataConstructor type will be a -> Foo a. int childrenCount = childrenList.size(); List<ValueNode> newChildrenList = new ArrayList<ValueNode>(childrenCount); for (int i = 0; i < childrenCount; i++) { ValueNode childVN = childrenList.get(i); TypeExpr elementTypeExpr = TypeExpr.getComponentTypeExpr(newTypeExpr, i, dataConstructor); newChildrenList.add(childVN.copyValueNode(elementTypeExpr)); } DataConstructorValueNode dvn = new DataConstructorValueNode(newChildrenList, getDataConstructor(), newTypeExpr); dvn.setOutputJavaValue(outputValue); return dvn; } /** * Returns the source model representation of the expression represented by * this ValueNode. * * @return SourceModel.Expr */ @Override public SourceModel.Expr getCALSourceModel() { int childrenCount = childrenList.size(); SourceModel.Expr[] params = new SourceModel.Expr[childrenCount]; for (int i = 0; i < childrenCount; i++) { params[i] = childrenList.get(i).getCALSourceModel(); } return SourceModel.Expr.makeGemCall(dataConstructor.getName(), params); } /** * Returns whether valueNode contains a parametric value * @see ValueNode#containsParametricValue() */ @Override public boolean containsParametricValue() { if (outputValue != null) { return false; } for (int i = 0, childrenCount = childrenList.size(); i < childrenCount; i++) { if (childrenList.get(i).containsParametricValue()) { return true; } } return false; } /** * Returns a list of the children ValueNodes representing the arguments values of the * data constructor this DataConstructorValueNode represents. Note that the returned * list is not modifiable. * @return List */ public List<ValueNode> getChildrenList() { return Collections.unmodifiableList(childrenList); } /** * Sets the children of this data constructor node to the nodes in the given list. * @param newChildrenList the new list of children. Must be non-null and same size as current list. */ public void setChildrenList(List<ValueNode> newChildrenList) { if (newChildrenList == null) { throw new IllegalArgumentException("new children list cannot be null"); } if (newChildrenList.size() != childrenList.size()) { throw new IllegalArgumentException("new children list cannot be different size than current list"); } childrenList.clear(); childrenList.addAll(newChildrenList); } /** * Returns the DataConstructor. * @return DataConstructor */ public final DataConstructor getDataConstructor() { return dataConstructor; } /** * Returns the display text representation of the expression represented by this ValueNode. * @return String */ @Override public String getTextValue() { if (outputValue != null) { return outputValue.toString(); } String dcName = dataConstructor.getName().getUnqualifiedName(); // If there is no children, then simply return the DataConstructor name (and no need for brackets either). if (childrenList.size() == 0) { return dcName; } StringBuilder sb = new StringBuilder("("); sb.append(dcName); for (int i = 0, childrenCount = childrenList.size(); i < childrenCount; i++) { sb.append(" "); sb.append(childrenList.get(i).getTextValue()); } sb.append(')'); return sb.toString(); } /** * @see ValueNode#transmuteValueNode(ValueNodeBuilderHelper, ValueNodeTransformer, TypeExpr) */ @Override public ValueNode transmuteValueNode(ValueNodeBuilderHelper valueNodeBuilderHelper, ValueNodeTransformer valueNodeTransformer, TypeExpr newTypeExpr) { QualifiedName typeConsName = getTypeExpr().rootTypeConsApp().getName(); TypeConsApp newTypeConsApp = newTypeExpr.rootTypeConsApp(); // If we don't need to change type constructors, then we just basically copy the value. if (newTypeConsApp != null && typeConsName.equals(newTypeConsApp.getName())) { List<ValueNode> componentList = getChildrenList(); List<ValueNode> newComponentList = new ArrayList<ValueNode>(componentList.size()); // Go thru the component ValueNodes and transmute them. for (int i = 0, componentCount = componentList.size(); i < componentCount; i++) { ValueNode componentVN = componentList.get(i); TypeExpr componentTypeExpr = TypeExpr.getComponentTypeExpr(newTypeExpr, i, dataConstructor); newComponentList.add(componentVN.transmuteValueNode(valueNodeBuilderHelper, valueNodeTransformer, componentTypeExpr)); } ValueNode newValueNode = valueNodeBuilderHelper.buildValueNode(newComponentList, dataConstructor, newTypeExpr); return newValueNode; } else { return valueNodeTransformer.transform(valueNodeBuilderHelper, this, newTypeExpr); } } /** * Return an array of objects which are the values needed by the marshaller * described by 'getInputPolicy()'. * @return - an array of Java objects corresponding to the value represented by a value node instance. */ @Override public Object[] getInputJavaValues() { // We simply gather up all the arguments required for the children in order. // These are the arguments expected by the custom input policy build by getInputPolicy() List<Object> argumentValues = new ArrayList<Object>(); for (final ValueNode child : childrenList) { Object[] vals = child.getInputJavaValues(); if (vals != null) { argumentValues.addAll(Arrays.asList(vals)); } } return argumentValues.toArray(); } /** * Return an input policy which describes how to marshall a value represented * by a value node from Java to CAL. * @return - the input policy associated with ValueNode instance. */ @Override public InputPolicy getInputPolicy() { // Instead of using Prelude.input as the input policy, we create a custom one that // simply applies the data constructor on the children, of the form: // (\arg_0 ... arg_N -> M.DC (<input policy for child 0> <args>) ... (<input policy for child k> <args>)) // // For example, if this value node's type is Maybe Int, and the DC is Just, the input policy will be: // (\arg_0 -> Prelude.Just (Prelude.input arg_0)) // // If this value node's type is (Foo.Tuple2 Int Int) (with one DC Tuple2, with 2 fields #1 and #2), the input policy will be: // (\arg_0 arg_1 -> Foo.Tuple2 (Prelude.input arg_0) (Prelude.input arg_1)) // // If this value node's type is Maybe (Maybe Unit), and the DC in both layers is just, then input policy will be: // (Prelude.Just (Prelude.Just Prelude.Unit)) // // Note that in the last example there are no arguments. int childrenCount = childrenList.size(); SourceModel.Expr[] childrenValues = new SourceModel.Expr[childrenCount]; int argCount = 0; List<SourceModel.Parameter> paramsForMarshaler = new ArrayList<SourceModel.Parameter>(); for (int i = 0; i < childrenCount; i++) { InputPolicy childInputPolicy = childrenList.get(i).getInputPolicy(); int nChildInputPolicyArgs = childInputPolicy.getNArguments(); SourceModel.Expr[] childInputPolicyParts = new SourceModel.Expr[nChildInputPolicyArgs + 1]; childInputPolicyParts[0] = childInputPolicy.getMarshaler(); //this loop is from 1 as the first element is always the input policy itself for (int j = 1; j <= nChildInputPolicyArgs; j++) { String childArg = "arg_" + (argCount++); paramsForMarshaler.add(SourceModel.Parameter.make(childArg, false)); childInputPolicyParts[j] = SourceModel.Expr.Var.makeUnqualified(childArg); } if (childInputPolicyParts.length >= 2) { childrenValues[i] = SourceModel.Expr.Application.make(childInputPolicyParts); } else { childrenValues[i] = childInputPolicyParts[0]; } } SourceModel.Expr marshaler; if (paramsForMarshaler.isEmpty()) { marshaler = SourceModel.Expr.makeGemCall(dataConstructor.getName(), childrenValues); } else { marshaler = SourceModel.Expr.Lambda.make( paramsForMarshaler.toArray(new SourceModel.Parameter[paramsForMarshaler.size()]), SourceModel.Expr.makeGemCall(dataConstructor.getName(), childrenValues)); } return InputPolicy.makeWithTypeAndMarshaler(getTypeExpr().toSourceModel().getTypeExprDefn(), marshaler, paramsForMarshaler.size()); } /** * Return an output policy which describes how to marshall a value represented * by a value node from CAL to Java. * @return - the output policy associated with the ValueNode instance. */ @Override public OutputPolicy getOutputPolicy() { return OutputPolicy.DEFAULT_OUTPUT_POLICY; } /** * Set a value which is the result of the marshaller described by * 'getOutputPolicy()'. * @param value - the java value */ @Override public void setOutputJavaValue(Object value) { TypeExpr typeExpr = getTypeExpr(); // We only restrict the value that can be set if it is one of the small number of types we know about. if (typeExpr.hasRootTypeConstructor(CAL_Prelude.TypeConstructors.Maybe) || typeExpr.hasRootTypeConstructor(CAL_Prelude.TypeConstructors.Either) || typeExpr.hasRootTypeConstructor(CAL_Prelude.TypeConstructors.Ordering) || typeExpr.hasRootTypeConstructor(CAL_Prelude.TypeConstructors.Unit)) { if (value instanceof AlgebraicValue || value instanceof MaybeValue || value instanceof EitherValue || value instanceof UnitValue || value instanceof OrderingValue) { outputValue = value; } else { outputValue = null; } } else { outputValue = value; } } }