/* * 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. */ /* * NRecordValue.java * Created: Apr 12, 2004 * By: RCypher */ package org.openquark.cal.internal.machine.g; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.openquark.cal.compiler.FieldName; import org.openquark.cal.internal.machine.g.functions.NDeepSeq; import org.openquark.cal.internal.runtime.RecordType; import org.openquark.cal.runtime.CALExecutorException; import org.openquark.cal.runtime.CalValue; /** * NRecordValue * This class is the graph node representing a record. * * Note that NRecordValue is used in 2 ways: * a. as a concrete class to represent a record value (corresponding to RTRecordValue in lecc) * b. as a base for NRecordExtension * The lecc implementation separates out these 2 cases, and thus differs slightly from the g machine * implementation. * * @author RCypher * Created: Apr 12, 2004 */ public class NRecordValue extends NInd implements RecordType { /** String -> Node map. */ final Map<String, Node> fieldToValueMap; /** * used to order field names in CAL source form (as Strings) so that ordinal field names occur before textual field names and * ordinals are ordered numerically. */ private static final Comparator<String> calSourceFormComparator = new FieldName.CalSourceFormComparator(); /** * Create an empty record. * */ NRecordValue () { fieldToValueMap = new HashMap<String, Node>(); } /** * Create a new record with a pre-set size. * @param nFields number of fields in the record */ public NRecordValue (int nFields) { //Note: the HashMap documentation has some advice for setting the initialCapacity. //Since we know the final size of the map, we can completely avoid rehashing. //the default load factor of HashMap is 0.75. If the initial capacity is greater than the maximum number //of entries divided by the load factor, no rehash operations will ever occur. int initialCapacity = (int)(nFields / 0.75 + 1); fieldToValueMap = new HashMap<String, Node>(initialCapacity); } /** * Create a new record that is a copy of an existing record. * @param baseRecord */ NRecordValue (NRecordValue baseRecord) { fieldToValueMap = new HashMap<String, Node> (baseRecord.fieldToValueMap); } /* (non-Javadoc) * @see org.openquark.cal.internal.runtime.g.Node#toString(int) */ @Override public String toString(int n) { StringBuilder is = new StringBuilder(); for (int i = 0; i < n; ++i) { is.append(" "); } return is + idString(0); } /** * {@inheritDoc} */ @Override public int debug_getNChildren() { if (hasIndirection()) { return super.debug_getNChildren(); } return fieldToValueMap.size(); } /** * {@inheritDoc} */ @Override public CalValue debug_getChild(int childN) { if (hasIndirection()) { return super.debug_getChild(childN); } return getNthFieldValue(childN); } /** * {@inheritDoc} */ @Override public String debug_getNodeStartText() { if (hasIndirection()) { return super.debug_getNodeStartText(); } if (isTuple2OrMoreRecord()) { return "("; } return "{"; } /** * {@inheritDoc} */ @Override public String debug_getNodeEndText() { if (hasIndirection()) { return super.debug_getNodeEndText(); } if (isTuple2OrMoreRecord()) { return ")"; } return "}"; } /** * {@inheritDoc} */ @Override public String debug_getChildPrefixText(int childN) { if (hasIndirection()) { return super.debug_getChildPrefixText(childN); } if (isTuple2OrMoreRecord()) { if (childN < 0 || childN >= fieldToValueMap.size()) { throw new IndexOutOfBoundsException(); } if (childN == 0) { return ""; } return ", "; } String fieldName = getNthFieldName(childN).getCalSourceForm(); StringBuilder sb = new StringBuilder(); if (childN > 0) { sb.append(", "); } sb.append(fieldName); sb.append(" = "); return sb.toString(); } /* (non-Javadoc) * @see org.openquark.cal.internal.runtime.g.Node#addChildren(java.util.Collection) */ @Override protected void addChildren(Collection<Node> c) { Iterator<String> it = fieldToValueMap.keySet().iterator(); while (it.hasNext ()) { Node n = fieldToValueMap.get(it.next()); if (!c.contains(n)) { c.add (n); n.addChildren(c); } } } /* (non-Javadoc) * @see org.openquark.cal.internal.runtime.g.Node#i_eval(org.openquark.cal.internal.runtime.g.Executor) */ @Override protected void i_eval(Executor e) throws CALExecutorException { // Do nothing, a record value is already in WHNF. } /** * Do Unwind state transition. */ @Override protected void i_unwind (Executor e) throws CALExecutorException { e.popDumpItem(); } /** * Add a field value to this record. * @param fieldName * @param n */ public void putValue (String fieldName, Node n) { fieldToValueMap.put (fieldName, n.getLeafNode()); } /** * Remove a field value from this record. * @param fieldName */ void removeValue (String fieldName) { fieldToValueMap.remove(fieldName); } /** * Retrieve a field value from this record. * @param fieldName * @return - the graph node representing a field value. */ public Node getValue (String fieldName) { Node n = fieldToValueMap.get(fieldName); if (n instanceof NInd) { n = n.getLeafNode(); fieldToValueMap.put(fieldName, n); } return n; } /** * Returns the value of the value of the field with the specified index * @param fieldIndex int Index of field to retrieve the value for * @return Node the value of the specified field */ public Node getNthValue (int fieldIndex) { String fieldName = getNthFieldName(fieldIndex).getCalSourceForm(); Node n = fieldToValueMap.get(fieldName); if (n instanceof NInd) { n = n.getLeafNode(); fieldToValueMap.put(fieldName, n); } return n; } /** * {@inheritDoc} */ public CalValue getNthFieldValue(int fieldIndex) { return getNthValue(fieldIndex); } /** * {@inheritDoc} */ public int getNFields() { return fieldToValueMap.size(); } /** * {@inheritDoc} */ public FieldName getNthFieldName(int fieldIndex) { return FieldName.make(fieldNames().get(fieldIndex)); } /** * {@inheritDoc} */ public boolean isTuple2OrMoreRecord() { if(getNFields() < 2) { return false; } List<String> fieldNames = fieldNames(); int prevField = 0; for (final String fieldName : fieldNames) { if(!isOrdinalFieldName(fieldName)) { return false; } FieldName.Ordinal fieldOrdinal = (FieldName.Ordinal)FieldName.make(fieldName); if(fieldOrdinal.getOrdinal() != (prevField + 1)) { return false; } prevField = fieldOrdinal.getOrdinal(); } return true; } /** * {@inheritDoc} */ public boolean sameFields(RecordType otherRecordType) { if(!(otherRecordType instanceof NRecordValue)) { throw new IllegalArgumentException("otherRecordType must be an NRecordValue"); } NRecordValue otherRecord = (NRecordValue)otherRecordType; if(otherRecordType.getNFields() != getNFields()) { return false; } List<String> ownFieldNames = fieldNames(); List<String> otherFieldNames = otherRecord.fieldNames(); Iterator<String> ownIt = ownFieldNames.iterator(); Iterator<String> otherIt = otherFieldNames.iterator(); while(ownIt.hasNext() && otherIt.hasNext()) { String ownName = ownIt.next(); String otherName = otherIt.next(); if(!ownName.equals(otherName)) { return false; } } return true; } /** * {@inheritDoc} */ public RecordType appendRecordType(RecordType otherRecordType) { if(!(otherRecordType instanceof NRecordValue)) { throw new IllegalArgumentException("otherRecordType must be an NRecordValue"); } NRecordValue otherRecord = (NRecordValue)otherRecordType; return appendRecord(otherRecord); } /** * {@inheritDoc} */ public RecordType insertRecordTypeField(String fieldName, Object fieldValue) { return insertRecordField(fieldName, (Node)fieldValue); } /** * @param fieldName Name of field to insert * @param fieldValue Value of field to insert * @return A new NRecordValue containing all the fields of this value, plus the * the field whose name and value are provided. If this record already * contains a field of the specified name, the new record will contain the * specified field value, not the current field value. */ public NRecordValue insertRecordField(String fieldName, Node fieldValue) { NRecordValue newRecordValue = new NRecordValue(this); newRecordValue.putValue(fieldName, fieldValue); return newRecordValue; } /** * @param fieldName The name of a field * @return True if the field is an ordinal field name, false otherwise */ private static boolean isOrdinalFieldName(String fieldName) { return FieldName.Ordinal.isValidCalSourceForm(fieldName); } /** * Used to implement the primitive Prelude.hasField function. * @param fieldName * @return boolean whether this record has a mapping for the given fieldName. */ public boolean hasField(String fieldName) { return fieldToValueMap.containsKey(fieldName); } /** * Used to implement the primitive Prelude.fieldNamesPrimitive function. * Note this function makes a copy of the underlying keyset for safety reasons. * * @return List (of Strings) the sorted list of fieldnames. The sort order is as specified by * the FieldName.CalSourceFormComparator comparator i.e. ordinal field names before textual field names. */ public List<String> fieldNames() { List<String> fieldNames = new ArrayList<String>(fieldToValueMap.keySet()); Collections.sort(fieldNames, NRecordValue.calSourceFormComparator); return fieldNames; } /** * Used to implement the primitive Prelude.recordFieldIndex function. * @param fieldName Name of the field to check * @return -1 if the record does not have the specified field, or the 0-based index * of the field otherwise. */ public int indexOfField(String fieldName) { return fieldNames().indexOf(fieldName); } /** * Used to implement the primitive Prelude.fieldValuesPrimitive function. * * @return List (of Node) the list of field values, in the order determined by the field-names. * The sort order on field names is as specified by * the FieldName.CalSourceFormComparator comparator i.e. ordinal field names before textual field names. */ public List<Node> fieldValues() { List<String> fieldNames = fieldNames(); List<Node> fieldValues = new ArrayList<Node>(fieldNames.size()); for (final String key : fieldNames) { Node n = fieldToValueMap.get(key); if (n instanceof NInd) { n = n.getLeafNode(); fieldToValueMap.put(key, n); } fieldValues.add(n); } return fieldValues; } /** * @param baseRecord Node representing the base record for the extension * @return Node a new Node representing the extension of the specified base record. */ public static NRecordValue makeExtension(Node baseRecord) { return new NRecordExtension(baseRecord); } /** * Returns a new record containing the fields of both this record and of otherRecord. * If the records have any overlapping field names, the fields will have the value of * the field in this record, not otherRecord. * @param otherRecord The record to append to this record * @return NRecordValue a new record containing the fields of both this record and of otherRecord */ public NRecordValue appendRecord(NRecordValue otherRecord) { NRecordValue appended = new NRecordValue(fieldToValueMap.size() + otherRecord.fieldToValueMap.size()); for (final String fieldName : fieldToValueMap.keySet()) { appended.putValue(fieldName, getValue(fieldName)); } for (final String fieldName : otherRecord.fieldToValueMap.keySet()) { if(hasField(fieldName)) { continue; } appended.putValue(fieldName, otherRecord.getValue(fieldName)); } return appended; } /** * Application of a record value is a special case which represents pointwise application of * the argument to each of the fields in the record. * @param n * @return the application of this node to the argument. */ @Override public Node apply (Node n) { NRecordValue appliedRecord = new NRecordValue (this.fieldToValueMap.size()); for (final String fieldName : fieldToValueMap.keySet()) { Node fieldValue = getValue(fieldName); appliedRecord.putValue(fieldName, fieldValue.apply(n)); } return appliedRecord; } /** * Build an application of deepSeq */ @Override protected Node buildDeepSeqInternal(Node rhs) { List<String> fieldNames = fieldNames(); for (int i = fieldNames.size()-1; i >= 0; --i) { Node fieldVal = getValue(fieldNames.get(i)); rhs = NDeepSeq.instance.apply(fieldVal).apply(rhs); } return rhs; } @Override public Object getValue() { return this; } }