/*
* 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.
*/
/*
* RecordValueNode.java
* Created: July 5, 2004
* By: Iulian Radu
*/
package org.openquark.cal.valuenode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.openquark.cal.compiler.DataConstructor;
import org.openquark.cal.compiler.FieldName;
import org.openquark.cal.compiler.RecordType;
import org.openquark.cal.compiler.SourceModel;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.io.InputPolicy;
import org.openquark.cal.compiler.io.OutputPolicy;
import org.openquark.util.UnsafeCast;
/**
* Specialized AlgebraicValueNode for handling record values.
*
* This object holds a list of value nodes whose elements correspond to values of the record fields,
* along with a list of field names.
*
* @author Iulian Radu
*/
public class RecordValueNode extends AbstractRecordValueNode {
/**
* A custom ValueNodeProvider for the RecordValueNode.
* @author Iulian Radu
*/
public static class RecordValueNodeProvider extends ValueNodeProvider<RecordValueNode> {
public RecordValueNodeProvider(ValueNodeBuilderHelper builderHelper) {
super(builderHelper);
}
/**
* @see org.openquark.cal.valuenode.ValueNodeProvider#getValueNodeClass()
*/
@Override
public Class<RecordValueNode> getValueNodeClass() {
return RecordValueNode.class;
}
/**
* @see org.openquark.cal.valuenode.ValueNodeProvider#isSpecialLiteralizedValueNode()
*/
@Override
public boolean isSpecialLiteralizedValueNode() {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public RecordValueNode getNodeInstance(Object value, DataConstructor dataConstructor, TypeExpr typeExpr) {
RecordType recordType = typeExpr.rootRecordType();
if (recordType == null) {
// This expression is not a record type, so this provides does not handle it
return null;
}
List<FieldName> fieldNames = recordType.getHasFieldNames();
// value is either a List of ValueNode or null
List<ValueNode> listValue = UnsafeCast.asTypeOf(value, Collections.<ValueNode>emptyList());
if (listValue == null) {
// Create default value nodes for the fields
int arity = fieldNames.size();
listValue = new ArrayList<ValueNode>(arity);
for (int i = 0 ; i < arity; i++) {
TypeExpr argType = recordType.getHasFieldType(fieldNames.get(i));
ValueNode valueNode = getValueNodeBuilderHelper().getValueNodeForTypeExpr(argType);
if (valueNode == null) {
// Cannot create a node represented, thus cannot create this node
return null;
}
listValue.add(valueNode);
}
}
return new RecordValueNode(recordType, listValue, fieldNames);
}
}
/** Names of the record fields, sorted via the FieldName ordering. */
private final List<FieldName> fieldNames;
/** Value nodes of the record fields. fieldNodes[i] is the node for field fieldNames[i] */
private final List<ValueNode> fieldNodes;
/**
* Constructor
*
* @param recordType type expression of the record
* @param fieldNodes list of record field values
* @param fieldNames list of record field names corresponding to values, sorted in the FieldName ordering.
*/
RecordValueNode(TypeExpr recordType, List<ValueNode> fieldNodes, List<FieldName> fieldNames) {
super(recordType);
if ((fieldNodes == null) || (fieldNames == null)) {
throw new NullPointerException();
}
if (recordType.rootRecordType() == null) {
throw new IllegalArgumentException();
}
this.fieldNodes = fieldNodes;
this.fieldNames = fieldNames;
}
/**
* @see org.openquark.cal.valuenode.ValueNode#getValue()
*/
@Override
public Object getValue() {
return new ArrayList<ValueNode>(fieldNodes);
}
/**
* Retrieve the value node representing the nth field in the record.
* Note: Fields are indexed alphabetically
*
* @param n number of field
* @return ValueNode
*/
@Override
public ValueNode getValueAt(int n) {
return fieldNodes.get(n);
}
/**
* @return the number of fields that the record is asserted to have.
*/
@Override
public int getNFieldNames() {
return fieldNames.size();
}
/**
* {@inheritDoc}
*/
@Override
public FieldName getFieldName(int i) {
return fieldNames.get(i);
}
/**
* {@inheritDoc}
*/
@Override
public List<FieldName> getFieldNames() {
return fieldNames;
}
/**
* {@inheritDoc}
*/
@Override
public TypeExpr getFieldTypeExpr(FieldName fieldName) {
return fieldNodes.get(fieldNames.indexOf(fieldName)).getTypeExpr();
}
/**
* {@inheritDoc}
*/
@Override
public void setValueNodeAt(int i, ValueNode valueNode) {
fieldNodes.set(i, valueNode);
}
/**
* 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 RecordValueNode copyValueNode(TypeExpr newTypeExpr) {
checkCopyType(newTypeExpr);
// Copy field nodes
int fieldCount = fieldNames.size();
List<ValueNode> newFieldNodes = new ArrayList<ValueNode>(fieldCount);
for (int i = 0; i < fieldCount; i++) {
ValueNode fieldVN = fieldNodes.get(i);
newFieldNodes.add(fieldVN.copyValueNode());
}
RecordValueNode newNode = new RecordValueNode(newTypeExpr, newFieldNodes, fieldNames);
return newNode;
}
/**
* Returns the source model representation of the expression represented by
* this ValueNode. This creates a record constructor from the contained
* fields.
*
* Ex: for a record with fields "age" and "height" with values 1.0 and 2.0,
* this produces "{age = 1.0, height = 2.0}"
*
* @return SourceModel.Expr
*/
@Override
public SourceModel.Expr getCALSourceModel() {
int numFields = fieldNodes.size();
SourceModel.Expr.Record.FieldModification[] fieldValuePairs = new SourceModel.Expr.Record.FieldModification[numFields];
for (int i = 0; i < numFields; i++) {
fieldValuePairs[i] =
SourceModel.Expr.Record.FieldModification.Extension.make(
SourceModel.Name.Field.make(fieldNames.get(i)),
fieldNodes.get(i).getCALSourceModel());
}
return SourceModel.Expr.Record.make(null, fieldValuePairs);
}
/**
* Returns the text representation of the expression represented by this ValueNode.
* This produces a similar output to getCALValue, except getting text values for each field node.
* @return String
*/
@Override
public String getTextValue() {
StringBuilder calValue = new StringBuilder("{");
for (int i = 0, n = fieldNodes.size(); i < n; i++) {
if (i > 0) {
calValue.append(", ");
}
calValue.append(fieldNames.get(i).getCalSourceForm()).append( " = ").append(fieldNodes.get(i).getTextValue());
}
calValue.append("}");
return calValue.toString();
}
/**
* {@inheritDoc}
*/
@Override
public boolean containsParametricValue() {
return getTypeExpr().isPolymorphic();
}
/**
* 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 record constructor on the fields, of the form:
// (\arg_0 ... arg_N -> { <field 0 name> :: (<input policy for field 0> <args>) , ... , <field k name> :: (<input policy for field k> <args>) })
int nFields = fieldNodes.size();
SourceModel.Expr.Record.FieldModification[] fieldValuePairs = new SourceModel.Expr.Record.FieldModification[nFields];
int argCount = 0;
List<SourceModel.Parameter> paramsForMarshaler = new ArrayList<SourceModel.Parameter>();
for (int i = 0; i < nFields; i++) {
InputPolicy fieldInputPolicy = fieldNodes.get(i).getInputPolicy();
int nFieldInputPolicyArgs = fieldInputPolicy.getNArguments();
SourceModel.Expr[] fieldInputPolicyArgs = new SourceModel.Expr[nFieldInputPolicyArgs + 1];
fieldInputPolicyArgs[0] = fieldInputPolicy.getMarshaler();
//this loop is from 1 as the first element is always the input policy itself
for (int j = 1; j <= nFieldInputPolicyArgs; j++) {
String fieldArg = "arg_" + (argCount++);
paramsForMarshaler.add(SourceModel.Parameter.make(fieldArg, false));
fieldInputPolicyArgs[j] = SourceModel.Expr.Var.makeUnqualified(fieldArg);
}
final SourceModel.Expr fieldValue;
if (fieldInputPolicyArgs.length >= 2) {
fieldValue = SourceModel.Expr.Application.make(fieldInputPolicyArgs);
} else {
fieldValue = fieldInputPolicyArgs[0];
}
fieldValuePairs[i] = SourceModel.Expr.Record.FieldModification.Extension.make(SourceModel.Name.Field.make(fieldNames.get(i)), fieldValue);
}
SourceModel.Expr marshaler;
if (paramsForMarshaler.isEmpty()) {
marshaler = SourceModel.Expr.Record.make(null, fieldValuePairs);
} else {
marshaler = SourceModel.Expr.Lambda.make(
paramsForMarshaler.toArray(new SourceModel.Parameter[paramsForMarshaler.size()]),
SourceModel.Expr.Record.make(null, fieldValuePairs));
}
return InputPolicy.makeWithTypeAndMarshaler(
getNonParametricType().toSourceModel().getTypeExprDefn(),
marshaler, paramsForMarshaler.size());
}
/**
* Return an array of objects which are the values needed by the marshaler
* 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 components in order.
// These are the arguments expected by the custom input policy build by getInputPolicy()
List<Object> argumentValues = new ArrayList<Object>();
for (final ValueNode vn : fieldNodes) {
Object[] vals = vn.getInputJavaValues();
if (vals != null) {
argumentValues.addAll(Arrays.asList(vals));
}
}
return argumentValues.toArray();
}
/**
* 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()'.
*
* The output value for a record is a java.util.List whose elements correspond to input
* values of the record fields (ordered alphabetically).
*
* @param value - the java value
*/
@Override
public void setOutputJavaValue(Object value) {
if (!(value instanceof List)) {
throw new IllegalArgumentException("Error in RangeValueNode.setOutputJavaValue: output must be an instance of List, not an instance of: " + value.getClass().getName());
}
List<?> fieldNodesList = (List<?>)value;
for (int i = 0, n = fieldNodesList.size(); i < n; i++) {
fieldNodes.get(i).setOutputJavaValue(fieldNodesList.get(i));
}
}
/**
* Transmutes the record value node. This creates a new value node which results from
* converting the current node's type expression to the specified type expression.
*
* If transmuting to a record type expression, this method creates value nodes for record fields
* which are not already contained in this node, and transmutes fields common to both records
*
* Ex: Transmuting the record (r\name,r\width)=>{r|name:String,width::Double} where width = 11.0, name="Ana"
* to the new type (r\height, r\name, r\width)=>{r|height::Double, name::String, width::Long} produces a
* record value node having the new type specified, a new value node height, a copied node name = "Ana",
* and transmuted value node width = 11.
*
* @see org.openquark.cal.valuenode.ValueNode#transmuteValueNode(org.openquark.cal.valuenode.ValueNodeBuilderHelper, org.openquark.cal.valuenode.ValueNodeTransformer, org.openquark.cal.compiler.TypeExpr)
*/
@Override
public ValueNode transmuteValueNode(ValueNodeBuilderHelper valueNodeBuilderHelper, ValueNodeTransformer valueNodeTransformer, TypeExpr newTypeExpr) {
Class<? extends ValueNode> handlerClass = valueNodeBuilderHelper.getValueNodeClass(newTypeExpr);
if ((getClass().equals(handlerClass))) {
RecordType recordType = newTypeExpr.rootRecordType();
List<FieldName> newFieldNames = recordType.getHasFieldNames();
List<ValueNode> newFieldNodes = new ArrayList<ValueNode>(newFieldNames.size());
// Transmute all fields which have equal names, and create those which do not
for (final FieldName newFieldName : newFieldNames) {
ValueNode newFieldNode;
TypeExpr newFieldType = recordType.getHasFieldType(newFieldName);
int nameIndex = fieldNames.indexOf(newFieldName);
if (nameIndex != -1) {
// The record field in the new type is contained in our record
ValueNode oldFieldNode = this.fieldNodes.get(nameIndex);
// Transmute field to copy/change type
newFieldNode = oldFieldNode.transmuteValueNode(
valueNodeBuilderHelper,
valueNodeTransformer,
newFieldType);
} else {
// Field not contained in our record; ask node builder to create a new node
newFieldNode = valueNodeBuilderHelper.getValueNodeForTypeExpr(newFieldType);
}
newFieldNodes.add(newFieldNode);
}
// Now build the record with the new type expression and value nodes
ValueNode vn = valueNodeBuilderHelper.buildValueNode(newFieldNodes, null, newTypeExpr);
return vn;
} else {
return valueNodeTransformer.transform(valueNodeBuilderHelper, this, newTypeExpr);
}
}
}