/*
* 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.
*/
/*
* RTRecordExtension.java
* Created: Apr 8, 2004
* By: Bo Ilic
*/
package org.openquark.cal.internal.runtime.lecc;
import org.openquark.cal.runtime.CALExecutorException;
import org.openquark.cal.runtime.CalValue;
/**
* Models record extension in a lazy context.
* For example:
* {r | field1 = "abc", field2 = 20.0}
* where we don't want to force the evaluation of r (to a record).
*
* For example, if
* projection x y = y;
* then we don't want to call error on:
* testProjection4 = projection {Prelude.error "this should not be evaluated" | field1 = 10.0} "very good result";
*
* Conceptually one can think of RTRecordExtension as implementing the fully saturated reduction:
* recordExtension recordExpr extensionFields = {recordExpr | extensionFields}
*
* @author Bo Ilic
*/
public abstract class RTRecordExtension extends RTResultFunction {
/** a value that is guaranteed by the compiler to evaluate to a RTRecordValue */
private RTValue baseRecordExpr;
/**
* Represents extension by a tuple-record field set i.e.
* -all the fields in the record are ordinal fields
* -the ordinal fields are consecutive i.e. #1, #2, ..., #n.
* -not the empty record
* For example, {r | #1 = "abc", #2 = 100.0, #3 = True}
*
* @author Bo Ilic
*/
private static final class TupleExtension extends RTRecordExtension {
/**
* the ith element of ordinalValues is the field value corresponding
* to the ordinal field name #i. Will have positive length.
*/
private RTValue[] ordinalValues;
private TupleExtension(RTValue baseRecord, RTValue[] ordinalValues) {
super(baseRecord);
assert RTRecordValue.verifyTupleData(ordinalValues) :
"Invalid tuple data in TupleExtension constructor.";
this.ordinalValues = ordinalValues;
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.runtime.lecc.RTValue#reduce(org.openquark.cal.internal.runtime.lecc.RTExecutionContext)
*/
@Override
protected final RTValue reduce(RTExecutionContext ec) throws CALExecutorException {
// Update and return result
if (super.baseRecordExpr != null) {
setResult(((RTRecordValue)super.baseRecordExpr.evaluate(ec)).makeTupleRecordExtension(ordinalValues));
clearMembers();
if (result == null) {
throw new NullPointerException ("Invalid reduction state in record extension. This is probably caused by a circular record definition.");
}
}
return result;
}
/*
* (non-Javadoc)
* @see org.openquark.cal.internal.runtime.lecc.RTResultFunction#clearMembers()
*/
@Override
public void clearMembers () {
super.baseRecordExpr = null;
ordinalValues = null;
}
/** {@inheritDoc} */
@Override
public int getNFields() {
return ordinalValues.length;
}
/** {@inheritDoc} */
@Override
public String getNthFieldName(int n) {
return "#" + (n + 1);
}
/** {@inheritDoc} */
@Override
public RTValue getNthValue(int n) {
return ordinalValues[n];
}
}
/**
* Represents extension by an ordinal record field set i.e.
* -all the fields in the record are ordinal fields
* -not a TupleRecord or the EmptyRecord.
*
* For example,
* {r | #1 = "abc", #3 = True} is an OrdinalExtension
* {r | #2 = 100.0, #3 = True} is an OrdinalExtension
* {r| #1 = "abc", #2 = 100.0, #3 = True} is a TupleExtension and not an OrdinalExtension.
*
* @author Bo Ilic
*/
private static final class OrdinalExtension extends RTRecordExtension {
/**
* the ordinal field names (as int values) in ascending ordinal order. Will have positive length
* and not be [1, 2, 3, ..., ordinalValues.length]
*/
private int[] ordinalNames;
/**
* the ith element of ordinalValues is the field value corresponding to the ordinal field name
* held at the ith element of ordinalNames.
*/
private RTValue[] ordinalValues;
private OrdinalExtension(RTValue baseRecord, int[] ordinalNames, RTValue[] ordinalValues) {
super(baseRecord);
assert RTRecordValue.verifyOrdinalData(ordinalNames, ordinalValues) : "Invalid ordinal data in OrdinalExtension().";
this.ordinalNames = ordinalNames;
this.ordinalValues = ordinalValues;
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.runtime.lecc.RTValue#reduce(org.openquark.cal.internal.runtime.lecc.RTExecutionContext)
*/
@Override
protected final RTValue reduce(RTExecutionContext ec) throws CALExecutorException {
// Update and return result
if (super.baseRecordExpr != null) {
setResult(((RTRecordValue)super.baseRecordExpr.evaluate(ec)).makeOrdinalRecordExtension(ordinalNames, ordinalValues));
clearMembers();
}
return result;
}
/*
* (non-Javadoc)
* @see org.openquark.cal.internal.runtime.lecc.RTResultFunction#clearMembers()
*/
@Override
public void clearMembers () {
super.baseRecordExpr = null;
ordinalNames = null;
ordinalValues = null;
}
/** {@inheritDoc} */
@Override
public int getNFields() {
return ordinalNames.length;
}
/** {@inheritDoc} */
@Override
public String getNthFieldName(int n) {
return "#" + ordinalNames[n];
}
/** {@inheritDoc} */
@Override
public RTValue getNthValue(int n) {
return ordinalValues[n];
}
}
/**
* Represents extension by a textual records field set i.e.
* -all the fields in the record are textual fields
* -not the EmptyExtension (i.e. there is at least 1 textual field).
*
* For example,
* {r | name = "abc", flag = True} is a TextualExtension
*
* @author Bo Ilic
*/
private static final class TextualExtension extends RTRecordExtension {
/** the textual field names of the record, in ascending alphabetical order. Will have positive length. */
private String[] textualNames;
/**
* the ith element of textualValues is the field value corresponding to the textual field name
* held at the ith element of ordinalNames.
*/
private RTValue[] textualValues;
private TextualExtension(RTValue baseRecord, String[] textualNames, RTValue[] textualValues) {
super(baseRecord);
assert RTRecordValue.verifyTextualData(textualNames, textualValues) :
"Invalid textual data in TextualExtension constructor.";
this.textualNames = textualNames;
this.textualValues = textualValues;
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.runtime.lecc.RTValue#reduce(org.openquark.cal.internal.runtime.lecc.RTExecutionContext)
*/
@Override
protected final RTValue reduce(RTExecutionContext ec) throws CALExecutorException {
// Update and return result
if (super.baseRecordExpr != null) {
setResult(((RTRecordValue)super.baseRecordExpr.evaluate(ec)).makeTextualRecordExtension(textualNames, textualValues));
clearMembers();
if (result == null) {
throw new NullPointerException ("Invalid reduction state in record extension. This is probably caused by a circular record definition.");
}
}
return result;
}
/*
* (non-Javadoc)
* @see org.openquark.cal.internal.runtime.lecc.RTResultFunction#clearMembers()
*/
@Override
public void clearMembers () {
super.baseRecordExpr = null;
textualNames = null;
textualValues = null;
}
/** {@inheritDoc} */
@Override
public int getNFields() {
return textualNames.length;
}
/** {@inheritDoc} */
@Override
public String getNthFieldName(int n) {
return textualNames[n];
}
/** {@inheritDoc} */
@Override
public RTValue getNthValue(int n) {
return textualValues[n];
}
}
/**
* Represents extension by a record field set having both ordinal and textual fields, and such that the ordinal part is a tuple i.e.
* -there is at least 1 ordinal field, and at least 1 textual field
* -the ordinal fields are consecutive i.e. #1, #2, ..., #n.
* For example,
* {r| #1 = 2.0, #2 = "FooBar", name = "abc", flag = True} is a TupleMixedExtension
*
* @author Bo Ilic
*/
private static final class TupleMixedExtension extends RTRecordExtension {
/**
* the ith element of ordinalValues is the field value corresponding to
* the ordinal field name #i. Will have positive length.
*/
private RTValue[] ordinalValues;
/**
* the textual field names of the record, in ascending alphabetical
* order. Will have positive length.
*/
private String[] textualNames;
/**
* the ith element of textualValues is the field value corresponding to
* the textual field name held at the ith element of ordinalNames.
*/
private RTValue[] textualValues;
private TupleMixedExtension(RTValue baseRecord, RTValue[] ordinalValues, String[] textualNames, RTValue[] textualValues) {
super(baseRecord);
assert RTRecordValue.verifyTupleData(ordinalValues) :
"Invalid tuple data in TupleMixedExtension constructor.";
assert RTRecordValue.verifyTextualData(textualNames, textualValues) :
"Invalid textual data in TupleMixedExtension constructor.";
this.ordinalValues = ordinalValues;
this.textualNames = textualNames;
this.textualValues = textualValues;
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.runtime.lecc.RTValue#reduce(org.openquark.cal.internal.runtime.lecc.RTExecutionContext)
*/
@Override
protected final RTValue reduce(RTExecutionContext ec) throws CALExecutorException {
// Update and return result
if (super.baseRecordExpr != null) {
setResult(((RTRecordValue)super.baseRecordExpr.evaluate(ec)).makeTupleMixedRecordExtension(ordinalValues, textualNames, textualValues));
clearMembers();
if (result == null) {
throw new NullPointerException ("Invalid reduction state in record extension. This is probably caused by a circular record definition.");
}
}
return result;
}
/*
* (non-Javadoc)
* @see org.openquark.cal.internal.runtime.lecc.RTResultFunction#clearMembers()
*/
@Override
public void clearMembers () {
super.baseRecordExpr = null;
ordinalValues = null;
textualNames = null;
textualValues = null;
}
/** {@inheritDoc} */
@Override
public int getNFields() {
return ordinalValues.length + textualNames.length;
}
/** {@inheritDoc} */
@Override
public String getNthFieldName(int n) {
int nOrdinalFields = ordinalValues.length;
if (n < nOrdinalFields) {
return "#" + (n + 1);
}
return textualNames[n - nOrdinalFields];
}
/** {@inheritDoc} */
@Override
public RTValue getNthValue(int n) {
if (n < ordinalValues.length) {
return ordinalValues[n];
}
return textualValues[n - ordinalValues.length];
}
}
/**
* Represents extension by a record field set having both ordinal and textual fields, and such that the ordinal part is not a tuple i.e.
* -there is at least 1 ordinal field, and at least 1 textual field
* -the ordinal fields are not consecutive i.e. #1, #2, ..., #n.
* For example,
* {r | #1 = 2.0, #3 = "FooBar", name = "abc", flag = True} is a MixedExtension
*
* @author Bo Ilic
*/
private static final class MixedExtension extends RTRecordExtension {
/**
* the ordinal field names (as int values) in ascending ordinal order.
* Will have positive length and not be [1, 2, 3, ...,
* ordinalValues.length]
*/
private int[] ordinalNames;
/**
* the ith element of ordinalValues is the field value corresponding to
* the ordinal field name held at the ith element of ordinalNames.
*/
private RTValue[] ordinalValues;
/**
* the textual field names of the record, in ascending alphabetical
* order. Will have positive length.
*/
private String[] textualNames;
/**
* the ith element of textualValues is the field value corresponding to
* the textual field name held at the ith element of ordinalNames.
*/
private RTValue[] textualValues;
private MixedExtension(RTValue baseRecord, int[] ordinalNames, RTValue[] ordinalValues, String[] textualNames, RTValue[] textualValues) {
super(baseRecord);
assert RTRecordValue.verifyOrdinalData(ordinalNames, ordinalValues) : "Invalid ordinal data in MixedExtension constructor.";
assert RTRecordValue.verifyTextualData(textualNames, textualValues) : "Invalid textual data in MixedExtension constructor.";
this.ordinalNames = ordinalNames;
this.ordinalValues = ordinalValues;
this.textualNames = textualNames;
this.textualValues = textualValues;
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.runtime.lecc.RTValue#reduce(org.openquark.cal.internal.runtime.lecc.RTExecutionContext)
*/
@Override
protected final RTValue reduce(RTExecutionContext ec) throws CALExecutorException {
// Update and return result
if (super.baseRecordExpr != null) {
setResult(((RTRecordValue)super.baseRecordExpr.evaluate(ec)).makeMixedRecordExtension(ordinalNames, ordinalValues, textualNames, textualValues));
clearMembers();
}
return result;
}
/*
* (non-Javadoc)
* @see org.openquark.cal.internal.runtime.lecc.RTResultFunction#clearMembers()
*/
@Override
public void clearMembers () {
super.baseRecordExpr = null;
ordinalNames = null;
ordinalValues = null;
textualNames = null;
textualValues = null;
}
/** {@inheritDoc} */
@Override
public int getNFields() {
return ordinalNames.length + textualNames.length;
}
/** {@inheritDoc} */
@Override
public String getNthFieldName(int n) {
int nOrdinalFields = ordinalNames.length;
if (n < nOrdinalFields) {
return "#" + ordinalNames[n];
}
return textualNames[n - nOrdinalFields];
}
/** {@inheritDoc} */
@Override
public RTValue getNthValue(int n) {
if (n < ordinalValues.length) {
return ordinalValues[n];
}
return textualValues[n - ordinalValues.length];
}
}
private RTRecordExtension(RTValue baseRecordExpr) {
assert (baseRecordExpr != null);
this.baseRecordExpr = baseRecordExpr;
}
public static RTRecordExtension makeTupleRecordExtension(RTValue baseRecordExpr, RTValue[] ordinalValues) {
return new TupleExtension(baseRecordExpr, ordinalValues);
}
public static RTRecordExtension makeOrdinalRecordExtension(RTValue baseRecordExpr, int[] ordinalNames, RTValue[] ordinalValues) {
return new OrdinalExtension(baseRecordExpr, ordinalNames, ordinalValues);
}
public static RTRecordExtension makeTextualRecordExtension(RTValue baseRecordExpr, String[] textualNames, RTValue[] textualValues) {
return new TextualExtension(baseRecordExpr, textualNames, textualValues);
}
public static RTRecordExtension makeTupleMixedRecordExtension(RTValue baseRecordExpr, RTValue[] ordinalValues, String[] textualNames, RTValue[] textualValues) {
return new TupleMixedExtension(baseRecordExpr, ordinalValues, textualNames, textualValues);
}
public static RTRecordExtension makeMixedRecordExtension(RTValue baseRecordExpr, int[] ordinalNames, RTValue[] ordinalValues, String[] textualNames, RTValue[] textualValues) {
return new MixedExtension(baseRecordExpr, ordinalNames, ordinalValues, textualNames, textualValues);
}
/**
* @return the number of fields that we are extending by. This includes textual as well as ordinal fields.
*/
abstract public int getNFields();
/**
* Note that the fields of the record extension are ordered in field-name order i.e.
* ordinal fields first, in numeric order, followed by textual fields in alphabetical order.
* @param n
* @return the field name of the nth field in the record extension as a String e.g. "#1" or "orderDate".
*/
abstract public String getNthFieldName(int n);
/**
* The value of the nth record extension field, with the fields of the extension ordered in field-name order.
* @param n
* @return RTValue
*/
abstract public RTValue getNthValue(int n);
/**
* {@inheritDoc}
*/
@Override
public final int debug_getNChildren() {
if (result != null) {
return super.debug_getNChildren();
}
return getNFields() + 1;
}
/**
* {@inheritDoc}
*/
@Override
public final CalValue debug_getChild(int childN) {
if (result != null) {
return super.debug_getChild(childN);
}
if (childN == 0) {
return baseRecordExpr;
}
return getNthValue(childN - 1);
}
/**
* {@inheritDoc}
*/
@Override
public final String debug_getNodeStartText() {
if (result != null) {
return super.debug_getNodeStartText();
}
return "{";
}
/**
* {@inheritDoc}
*/
@Override
public final String debug_getNodeEndText() {
if (result != null) {
return super.debug_getNodeEndText();
}
return "}";
}
/**
* {@inheritDoc}
*/
@Override
public final String debug_getChildPrefixText(int childN) {
if (result != null) {
return super.debug_getChildPrefixText(childN);
}
if (childN == 0) {
//prefix to the record being extended
return "";
}
String fieldName = getNthFieldName(childN - 1);
StringBuilder sb = new StringBuilder();
if (childN == 1) {
sb.append(" | ");
} else {
sb.append(", ");
}
sb.append(fieldName);
sb.append(" = ");
return sb.toString();
}
}