/*
* 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.
*/
/*
* RangeValueNode.java
* Creation date: (05/10/04 10:14:21 AM)
* By: Iulian Radu
*/
package org.openquark.cal.valuenode;
import java.util.ArrayList;
import java.util.List;
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.TypeVar;
import org.openquark.cal.compiler.io.InputPolicy;
import org.openquark.cal.compiler.io.OutputPolicy;
import org.openquark.cal.foreignsupport.module.Range.RangeValue;
import org.openquark.cal.module.Cal.Utilities.CAL_Range;
/**
* A specialized AlgebraicValueNode used to handle values of the Range.Range type.
*
* Creation date: May 10, 2004
* @author Iulian Radu
*/
public class RangeValueNode extends AlgebraicValueNode {
/**
* Class describing the form of a range.
* This indicates whether the range has a left and/or right bound,
* whether the range includes its endpoints, and the name
* of the supercombinator constructing such a range.
*
* @author Iulian Radu
*/
public static class Form {
// Names of the supercombinators constructing individual range forms (contained in module: Range)
static final private String RANGE_CONSTRUCTOR_WHOLE = CAL_Range.Functions.makeEntireRange.getUnqualifiedName();
static final private String RANGE_CONSTRUCTOR_LESS = CAL_Range.Functions.makeIsLessThanRange.getUnqualifiedName();
static final private String RANGE_CONSTRUCTOR_LESS_EQUALS = CAL_Range.Functions.makeIsLessThanEqualsRange.getUnqualifiedName();
static final private String RANGE_CONSTRUCTOR_GREATER = CAL_Range.Functions.makeIsGreaterThanRange.getUnqualifiedName();
static final private String RANGE_CONSTRUCTOR_GREATER_EQUALS = CAL_Range.Functions.makeIsGreaterThanEqualsRange.getUnqualifiedName();
static final private String RANGE_CONSTRUCTOR_INCLUDE_BOTH = CAL_Range.Functions.makeBetweenIncludingEndpointsRange.getUnqualifiedName();
static final private String RANGE_CONSTRUCTOR_INCLUDE_RIGHT = CAL_Range.Functions.makeBetweenIncludingRightEndpointRange.getUnqualifiedName();
static final private String RANGE_CONSTRUCTOR_INCLUDE_LEFT = CAL_Range.Functions.makeBetweenIncludingLeftEndpointRange.getUnqualifiedName();
static final private String RANGE_CONSTRUCTOR_INCLUDE_NONE = CAL_Range.Functions.makeBetweenExcludingEndpointsRange.getUnqualifiedName();
/** Whether the range has a left endpoint */
private final boolean hasLeft;
/** Whether the range has a right endpoint */
private final boolean hasRight;
/** Whether the range includes its left endpoint */
private final boolean includesLeft;
/** Whether the range includes its right endpoint */
private final boolean includesRight;
/** Constructor **/
public Form(boolean leftBounded, boolean rightBounded, boolean includesLeft, boolean includesRight) {
this.hasLeft = leftBounded;
this.hasRight = rightBounded;
this.includesLeft = includesLeft;
this.includesRight = includesRight;
}
/**
* @return the name of the supercombinator constructing a range with this form
*/
public String getConstructorSCName() {
if (!hasLeft && !hasRight) {
// Does not have any bounds
return RANGE_CONSTRUCTOR_WHOLE;
} else if (!hasLeft) {
// Does not have left bound, so has right
if (includesRight) {
return RANGE_CONSTRUCTOR_LESS_EQUALS;
} else {
return RANGE_CONSTRUCTOR_LESS;
}
} else if (!hasRight) {
// Does not have right bound, so has left
if (includesLeft) {
return RANGE_CONSTRUCTOR_GREATER_EQUALS;
} else {
return RANGE_CONSTRUCTOR_GREATER;
}
} else {
// Both endpoints exist
if (includesLeft) {
if (includesRight) {
return RANGE_CONSTRUCTOR_INCLUDE_BOTH;
} else {
return RANGE_CONSTRUCTOR_INCLUDE_LEFT;
}
} else {
if (includesRight) {
return RANGE_CONSTRUCTOR_INCLUDE_RIGHT;
} else {
return RANGE_CONSTRUCTOR_INCLUDE_NONE;
}
}
}
}
/**
* @return whether the range has a left bound
*/
public boolean hasLeftBound() {
return hasLeft;
}
/**
* @return whether the range has a right bound
*/
public boolean hasRightBound() {
return hasRight;
}
/**
* @return whether the range includes its left bound
*/
public boolean includesLeftBound() {
return includesLeft;
}
/**
* @return whether the range includes its right bound
*/
public boolean includesRightBound() {
return includesRight;
}
/**
* @return a copy of this range form
*/
public Form makeCopy() {
return new Form(hasLeft, hasRight, includesLeft, includesRight);
}
}
/**
* Placeholder class for a range node form and left/right endpoint nodes.
* This is used to pass information about an existing range node, when
* transmuting such a node to a different range type expression via
* transmuteValueNode()
*
* @author Iulian Radu
*/
private static class SimpleRangeData {
public Form form;
public ValueNode leftNode;
public ValueNode rightNode;
public SimpleRangeData(Form form, ValueNode leftNode, ValueNode rightNode) {
this.leftNode = leftNode;
this.rightNode = rightNode;
this.form = form;
}
public Form getForm() {
return form;
}
public ValueNode getLeftNode() {
return leftNode;
}
public ValueNode getRightNode() {
return rightNode;
}
}
/**
* A custom ValueNodeProvider for the RangeValueNode.
*
* @author Iulian Radu
*/
public static class RangeValueNodeProvider extends ValueNodeProvider<RangeValueNode> {
/** The default form of a range includes both endpoints */
private static final Form RANGE_DEFAULT_FORM = new Form(true, true, true, true);
/** Qualified name of the supercombinator used for default construction of ranges */
public static final QualifiedName RANGE_CONSTRUCTION_SC_NAME = QualifiedName.make(CAL_Range.MODULE_NAME, RANGE_DEFAULT_FORM.getConstructorSCName());
/**
* Constructs a new RangeValueNodeProvider.
* @param builderHelper the value node builder helper to use for building value nodes.
*/
public RangeValueNodeProvider(ValueNodeBuilderHelper builderHelper) {
super(builderHelper);
}
/**
* {@inheritDoc}
*/
@Override
public Class<RangeValueNode> getValueNodeClass() {
return RangeValueNode.class;
}
/**
* {@inheritDoc}
*/
@Override
public RangeValueNode getNodeInstance(Object value, DataConstructor dataConstructor, TypeExpr typeExpr) {
// Check for handleability.
if (typeExpr.hasRootTypeConstructor(CAL_Range.TypeConstructors.Range)) {
// Ensure Orderability.
// The value node is restricted to handle Ordinal instance types for
// the endpoints. The following code enforces this, by ensuring the
// type constructor argument (ie: "Double" in "Range Double") is valid
// for use as argument type for the range construction supercombinators.
//
// Since these require ordinal type arguments, "Range Boolean" will be
// available as a value node, whereas "Range BinaryOp" will not.
// Get the more specialized type required by the construction sc (this will be "Ord a => a").
TypeExpr scArgType = getValueNodeBuilderHelper().getPerspective().getWorkingModuleTypeInfo().getVisibleFunction(RANGE_CONSTRUCTION_SC_NAME).getTypeExpr().getTypePieces()[0];
// Get the argument type of the value node type expression (this will be "Boolean" for "Range Boolean")
TypeExpr endpointType = ((TypeConsApp)typeExpr).getArg(0);
if (!(endpointType instanceof TypeVar)) {
// If the type is specialized, check if the type is valid by unifying with
// the construction sc argument type.
if (!TypeExpr.canUnifyType(scArgType, endpointType, getValueNodeBuilderHelper().getPerspective().getWorkingModuleTypeInfo())) {
return null;
}
} else {
// The type was not specialized (ie: the range type is "Range a"), so the type
// of the endpoints will be simply what is required by the construction sc.
endpointType = scArgType;
}
// Construct range from a specified value
if (value != null) {
// A value was specified; it contains a form and left/right value nodes.
SimpleRangeData data = (SimpleRangeData)value;
return new RangeValueNode(typeExpr, data.getForm(), data.getLeftNode(), data.getRightNode());
}
// Construct the default value nodes for the endpoints
ValueNode leftVN = getValueNodeBuilderHelper().getValueNodeForTypeExpr(endpointType);
ValueNode rightVN = getValueNodeBuilderHelper().getValueNodeForTypeExpr(endpointType);
// Construct the default range form
Form form;
if (dataConstructor != null) {
// This should not occur
throw new IllegalArgumentException("Data constructor supplied, not expected.");
} else {
// Otherwise, the default form includes both endpoints
form = RANGE_DEFAULT_FORM;
}
// Construct the range value node
return new RangeValueNode(typeExpr, form, leftVN, rightVN);
} else {
// Not a range data type; the provider does not handle this
return null;
}
}
}
/** The range form */
private Form form;
/** The node representing the left endpoint */
private final ValueNode leftNode;
/** The node representing the right endpoint */
private final ValueNode rightNode;
/**
* Range ValueNode constructor.
* @param typeExprParam the type expression of the value node. Must be of Range type.
* @param rangeForm describes the form of this range
* @param start value node corresponding to the start endpoint (null if none)
* @param end value node corresponding to the end endpoint (null if none)
*/
public RangeValueNode(TypeExpr typeExprParam, Form rangeForm, ValueNode start, ValueNode end) {
super(typeExprParam);
checkTypeConstructorName(typeExprParam, CAL_Range.TypeConstructors.Range);
if (rangeForm == null) {
throw new IllegalArgumentException();
}
this.form = rangeForm;
this.leftNode = start;
this.rightNode = end;
}
/**
* @return whether the range type is parametric (eg: "Range a" is parametric)
*/
@Override
public boolean containsParametricValue() {
return getTypeExpr().isPolymorphic();
}
/**
* 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 TypeExpr the new type of the copied node.
* @return ValueNode
*/
@Override
public RangeValueNode copyValueNode(TypeExpr newTypeExpr) {
checkCopyType(newTypeExpr);
RangeValueNode other = new RangeValueNode(newTypeExpr, form, leftNode.copyValueNode(), rightNode.copyValueNode());
return other;
}
/**
* @return the source model representation of the expression represented by this ValueNode.
*/
@Override
public SourceModel.Expr getCALSourceModel() {
if (form == null) {
throw new IllegalStateException("Range form does not exist");
}
List<SourceModel.Expr> arguments = new ArrayList<SourceModel.Expr>();
if (form.hasLeftBound()) {
arguments.add(leftNode.getCALSourceModel());
}
if (form.hasRightBound()) {
arguments.add(rightNode.getCALSourceModel());
}
return SourceModel.Expr.makeGemCall(
QualifiedName.make(CAL_Range.MODULE_NAME, form.getConstructorSCName()),
arguments.toArray(new SourceModel.Expr[0]));
}
/**
* @return the display text representation of the expression represented by this ValueNode.
*/
@Override
public String getTextValue() {
return getFormattedTextValue(leftNode.getTextValue(), rightNode.getTextValue());
}
/**
* Returns a text representation of the current range form and endpoints.
* Ex: If the range form represents the constructor "IsLessThan" and the right
* endpoint is 2.0, then the text representation will be "X < 2.0"
*
* @param startTextValue text representation of the left endpoint
* @param endTextValue text representation of the right endpoint
* @return text representation of the range
*/
private String getFormattedTextValue(String startTextValue, String endTextValue) {
StringBuilder sb = new StringBuilder();
if (!form.hasLeftBound() && !form.hasRightBound()) {
return "Entire range";
}
if (form.hasLeftBound()) {
sb.append(startTextValue + " <" + (form.includesLeftBound()? "=":"") + " ");
}
sb.append("X");
if (form.hasRightBound()) {
sb.append(" <" + (form.includesRightBound()? "=":"") + " " + endTextValue);
}
return sb.toString();
}
/**
* @see org.openquark.cal.valuenode.ValueNode#getValue()
*/
@Override
public Object getValue() {
return new SimpleRangeData(form, leftNode.copyValueNode(), rightNode.copyValueNode());
}
/**
* @return the value node representing the left endpoint
*/
public ValueNode getLeftEndpoint() {
return leftNode;
}
/**
* @return the value node representing the right endpoint
*/
public ValueNode getRightEndpoint() {
return rightNode;
}
/**
* @return whether the range includes the left endpoint
*/
public boolean getIncludesLeftEndpoint() {
return form.includesLeftBound();
}
/**
* @return whether the range includes the left endpoint
*/
public boolean getIncludesRightEndpoint() {
return form.includesRightBound();
}
/**
* @return the form of this range node
*/
public Form getForm() {
return form;
}
/**
* 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 () {
return InputPolicy.makeTypedDefaultInputPolicy(getTypeExpr().toSourceModel().getTypeExprDefn());
}
/**
* 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() {
// Give a RangeValue object representing this range
Object[] lvals = leftNode.getInputJavaValues();
Object[] rvals = rightNode.getInputJavaValues();
return new Object[]{ getRangeValue(
(lvals != null && lvals.length > 0)? lvals[0] : null,
(rvals != null && rvals.length > 0)? rvals[0] : null)};
}
/**
* 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) {
if (!(value instanceof RangeValue)) {
throw new IllegalArgumentException("Error in RangeValueNode.setOutputJavaValue: output must be an instance of RangeValue, not an instance of: " + value.getClass().getName());
}
// Take info from range value and put into ours
RangeValue rangeValue = (RangeValue)value;
form = new Form(rangeValue.hasLeftBound(), rangeValue.hasRightBound(), rangeValue.includesLeftBound(), rangeValue.includesRightBound());
if (form.hasLeftBound()) {
leftNode.setOutputJavaValue(rangeValue.getLeftEndpoint());
}
if (form.hasRightBound()) {
rightNode.setOutputJavaValue(rangeValue.getRightEndpoint());
}
}
/**
* {@inheritDoc}
*/
@Override
public ValueNode transmuteValueNode(ValueNodeBuilderHelper valueNodeBuilderHelper, ValueNodeTransformer valueNodeTransformer, TypeExpr newTypeExpr) {
Class<?> handlerClass = valueNodeBuilderHelper.getValueNodeClass(newTypeExpr);
if ((getClass().equals(handlerClass))) {
// Transmute the endpoint nodes to the new types
TypeExpr childTypeExpr = ((TypeConsApp)newTypeExpr).getArg(0);
ValueNode leftNode;
ValueNode rightNode;
if (!childTypeExpr.sameType(this.leftNode.getTypeExpr())) {
leftNode = this.leftNode.transmuteValueNode(valueNodeBuilderHelper, valueNodeTransformer, childTypeExpr);
rightNode = this.rightNode.transmuteValueNode(valueNodeBuilderHelper, valueNodeTransformer, childTypeExpr);
} else {
leftNode = this.leftNode.copyValueNode();
rightNode = this.rightNode.copyValueNode();
}
// Then recreate the range value node from the new type, maintaining the endpoints and form
ValueNode vn = valueNodeBuilderHelper.buildValueNode(new SimpleRangeData(form, leftNode, rightNode), null, newTypeExpr);
return vn;
} else {
return valueNodeTransformer.transform(valueNodeBuilderHelper, this, newTypeExpr);
}
}
/**
* Get the range value representation of this value node.
*
* @param left input representation of the left endpoint node
* @param right input representation of the right endpoint node
* @return RangeValue
*/
private RangeValue getRangeValue(Object left, Object right) {
if (!form.hasLeft && !form.hasRight) {
// Does not have any bounds
return RangeValue.constructEntireRange();
} else if (!form.hasLeft) {
// Does not have left bound, so has right
if (form.includesRight) {
return RangeValue.constructIsLessThanEquals(right);
} else {
return RangeValue.constructIsLessThan(right);
}
} else if (!form.hasRight) {
// Does not have right bound, so has left
if (form.includesLeft) {
return RangeValue.constructIsGreaterThanEquals(left);
} else {
return RangeValue.constructIsGreaterThan(left);
}
} else {
// Both endpoints exist
if (form.includesLeft) {
if (form.includesRight) {
return RangeValue.constructBetweenIncludingEndpoints(left, right);
} else {
return RangeValue.constructBetweenIncludingLeftEndpoint(left, right);
}
} else {
if (form.includesRight) {
return RangeValue.constructBetweenIncludingRightEndpoint(left, right);
} else {
return RangeValue.constructBetweenExcludingEndpoints(left, right);
}
}
}
}
}