/* ==================================================================== * Limited Evaluation License: * * This software is open source, but licensed. The license with this package * is an evaluation license, which may not be used for productive systems. If * you want a full license, please contact us. * * The exclusive owner of this work is the OpenRate project. * This work, including all associated documents and components * is Copyright of the OpenRate project 2006-2015. * * The following restrictions apply unless they are expressly relaxed in a * contractual agreement between the license holder or one of its officially * assigned agents and you or your organisation: * * 1) This work may not be disclosed, either in full or in part, in any form * electronic or physical, to any third party. This includes both in the * form of source code and compiled modules. * 2) This work contains trade secrets in the form of architecture, algorithms * methods and technologies. These trade secrets may not be disclosed to * third parties in any form, either directly or in summary or paraphrased * form, nor may these trade secrets be used to construct products of a * similar or competing nature either by you or third parties. * 3) This work may not be included in full or in part in any application. * 4) You may not remove or alter any proprietary legends or notices contained * in or on this work. * 5) This software may not be reverse-engineered or otherwise decompiled, if * you received this work in a compiled form. * 6) This work is licensed, not sold. Possession of this software does not * imply or grant any right to you. * 7) You agree to disclose any changes to this work to the copyright holder * and that the copyright holder may include any such changes at its own * discretion into the work * 8) You agree not to derive other works from the trade secrets in this work, * and that any such derivation may make you liable to pay damages to the * copyright holder * 9) You agree to use this software exclusively for evaluation purposes, and * that you shall not use this software to derive commercial profit or * support your business or personal activities. * * This software is provided "as is" and any expressed or impled warranties, * including, but not limited to, the impled warranties of merchantability * and fitness for a particular purpose are disclaimed. In no event shall * The OpenRate Project or its officially assigned agents be liable to 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 any business interruption) however caused * and on 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. * This software contains portions by The Apache Software Foundation, Robert * Half International. * ==================================================================== */ package OpenRate.record.flexRecord; import OpenRate.exception.InitializationException; import OpenRate.exception.ProcessingException; import OpenRate.record.AbstractRecord; import OpenRate.record.ErrorType; import OpenRate.record.IError; import OpenRate.record.RecordError; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; /** * A flex record is a type of record that is used in situations where we want * to decouple the record definition from the code. We provide a configuration * file for the record hierarchy and methods to create the record template * and fill it. * * The structure of a flex record is divided into two parts: * 1) The definition of the structure. This is a hierarchical list of the * structure of the records, divided into blocks. This is more or less static * once the flex record has been defined * 2) The instances of the blocks that have been created. This is dynamic and * changes as new block instances are created. However, for speed there is a * trick that we perform here. The actual fields of the record are not stored * in a hierarchical manner, only the *indexes* are. The actual fields of the * records are stored in flat vectors. */ public class FlexRecord extends AbstractRecord { private static final long serialVersionUID = -4654007386889558251L; /** * Data type string */ public final static int FIELD_TYPE_STRING = 0; /** * Data type integer */ public final static int FIELD_TYPE_INTEGER = 1; /** * Data type float */ public final static int FIELD_TYPE_FLOAT = 2; // The symbolic module name of the class stack private String symbolicName = "FlecRecord"; /** * @return the defRoot */ public RecordBlockDef getDefRoot() { return defRoot; } /** * @param defRoot the defRoot to set */ public void setDefRoot(RecordBlockDef defRoot) { this.defRoot = defRoot; } /** * @return the symbolicName */ public String getSymbolicName() { return symbolicName; } /** * This is the implementation of the record structure. The structure has been * defined in the Definition class, and this holds the data once the instances * of the blocks have been created. */ protected class RecordBlock { // Holds the fields Object[] Fields; // Holds the access data to the fields HashMap<String, Integer> FieldMap; } /** * Maps the field information */ public class FieldInfo { String FieldName; int FieldType; Object FieldValue; } // This is the index to allow us to find the mapping quickly. This takes the // name of the block and returns the map info, the current number of blocks // of that type that have been created and the index to the block object HashMap<String, RecordBlock> BlockIndex; // This is the index to allow us to find the mapping quickly. This takes the // name of the block and returns the map info, the current number of blocks // of that type that have been created and the index to the block object HashMap<String, Integer> BlockCount; /** * This is the root block */ private RecordBlockDef defRoot; /** * Creates a new instance of FlatRecord * * @param RootName The name of the root * @param FieldCount The number of fields */ public FlexRecord(String RootName, int FieldCount) { super(); // Prepare the block index BlockIndex = new HashMap<>(10); BlockCount = new HashMap<>(10); } private RecordBlockDef FindBlock(String BlockName) throws InitializationException { int i; RecordBlockDef tmpRecordBlock = null; String[] tmpBlockPath; String tmpRootBlock; String tmpPathSoFar; // force correct splitting tmpRootBlock = BlockName + "."; // Find the block that we are working on tmpBlockPath = tmpRootBlock.split("\\."); if (!tmpBlockPath[0].equalsIgnoreCase("ROOT")) { throw new InitializationException("Block definition must begin with ROOT.",getSymbolicName()); } else { tmpRecordBlock = getDefRoot(); tmpPathSoFar = "ROOT"; // find the correct record block to work on for (i = 1 ; i < tmpBlockPath.length ; i++) { tmpPathSoFar = tmpPathSoFar + ":" + tmpBlockPath[i]; if (tmpRecordBlock.ChildTemplates.containsKey(tmpPathSoFar)) { // Found definition - get it tmpRecordBlock = (RecordBlockDef) tmpRecordBlock.ChildTemplates.get(tmpPathSoFar); } else { throw new InitializationException("Cannot find path to <" + BlockName + ">",getSymbolicName()); } } } return tmpRecordBlock; } /** * Adds a field definition to a block. The number of fields have already been * set during block creation. * * @param RootBlockName The name of the root block * @param NewFieldName The new field name * @param FieldNumber The field number * @param FieldType The field type * @throws InitializationException */ public void AddFieldDef(String RootBlockName, String NewFieldName, int FieldNumber, String FieldType) throws InitializationException { RecordBlockDef tmpRecordBlock; Boolean FieldTypeFound = false; int tmpFieldNumber; tmpFieldNumber = FieldNumber - 1; tmpRecordBlock = FindBlock(RootBlockName); if (tmpRecordBlock != null) { if ((tmpFieldNumber < 0) | (FieldNumber>tmpRecordBlock.NumberOfFields)) { throw new InitializationException("Field Index <" + FieldNumber + "> invalid for block <" + RootBlockName + ">",getSymbolicName()); } if (tmpRecordBlock.FieldNames[tmpFieldNumber] != null) { throw new InitializationException("Field Index <" + FieldNumber + "> overwrites existing definition <" + tmpRecordBlock.FieldNames[tmpFieldNumber] + "> in block <" + RootBlockName + ">",getSymbolicName()); } // Add the field definition tmpRecordBlock.FieldNames[tmpFieldNumber] = NewFieldName; // parse the field type if (FieldType.equalsIgnoreCase("STRING")) { tmpRecordBlock.FieldTypes[tmpFieldNumber] = FIELD_TYPE_STRING; FieldTypeFound = true; } // parse the field type if (FieldType.equalsIgnoreCase("INTEGER")) { tmpRecordBlock.FieldTypes[tmpFieldNumber] = FIELD_TYPE_INTEGER; FieldTypeFound = true; } // parse the field type if (FieldType.equalsIgnoreCase("FLOAT")) { tmpRecordBlock.FieldTypes[tmpFieldNumber] = FIELD_TYPE_FLOAT; FieldTypeFound = true; } if (!FieldTypeFound) { throw new InitializationException("Cannot find field type <" + FieldType + ">",getSymbolicName()); } tmpRecordBlock.FieldNameIndex.put(RootBlockName + "." + NewFieldName,tmpFieldNumber); } else { throw new InitializationException("Cannot find block <" + RootBlockName + ">",getSymbolicName()); } } /** * Adds a block definition to a block. This function is mostly validation, and * calls AddRecordBlockDef to do the real work * * @param RootBlockName The name of the root block * @param NewBlockName The name of the new block * @param FieldCount The number of fields * @throws InitializationException */ public void AddBlockDef(String RootBlockName, String NewBlockName, int FieldCount) throws InitializationException { RecordBlockDef tmpRecordBlock; RecordBlockDef ParentBlock = null; if (!NewBlockName.equalsIgnoreCase("ROOT")) { ParentBlock = FindBlock(RootBlockName); // check the credentials of the parent block if (ParentBlock != null) { // now we should have the correct block to work on, so do it if (ParentBlock.ChildTemplates.containsKey(NewBlockName)) { throw new InitializationException("Block <" + NewBlockName + "> already exists in <" + RootBlockName + ">",getSymbolicName()); } } else { throw new InitializationException("Cannot find block <" + RootBlockName + ">",getSymbolicName()); } } // Now create the block tmpRecordBlock = new RecordBlockDef(); if (FieldCount > 0) { tmpRecordBlock.NumberOfFields = FieldCount; //tmpRecordBlock.Fields = new String[FieldCount]; tmpRecordBlock.FieldNames = new String[FieldCount]; tmpRecordBlock.FieldTypes = new int[FieldCount]; } // Initialise the child definition map tmpRecordBlock.ChildTemplates = new HashMap<>(5); // Initialise the child instance map //tmpRecordBlock.Children = new HashMap(5); // Initialise the child instance map tmpRecordBlock.FieldNameIndex = new HashMap<>(10); // Create the mapping definition ArrayList tmpRecordBlock.Mapping = new ArrayList<>(); if (NewBlockName.equalsIgnoreCase("ROOT")) { setDefRoot(tmpRecordBlock); // Set the name path tmpRecordBlock.BlockName = NewBlockName; } else { // Set the name path tmpRecordBlock.BlockName = RootBlockName + ":" + NewBlockName; ParentBlock.ChildTemplates.put(tmpRecordBlock.BlockName,tmpRecordBlock); } } /** * This adds the mapping to a block * * @param BlockName The block name * @param Offset The offset * @param FieldName The name of the field * @throws InitializationException */ public void AddMappingDef(String BlockName, int Offset, String FieldName) throws InitializationException { RecordBlockDef tmpRecordBlock; MapElement tmpMapElement; boolean FieldFound = false; int i; tmpRecordBlock = FindBlock(BlockName); if (tmpRecordBlock != null) { // Verify that the map is possible for (i = 0 ; i < tmpRecordBlock.FieldNames.length ; i++) { if (FieldName.equalsIgnoreCase(tmpRecordBlock.FieldNames[i])) { tmpMapElement = new MapElement(); tmpMapElement.OffsetFrom = Offset; tmpMapElement.OffsetTo = i; tmpMapElement.Name = FieldName; tmpMapElement.Type = tmpRecordBlock.FieldTypes[i]; // Add the mapping record tmpRecordBlock.Mapping.add(tmpMapElement); FieldFound = true; } } } else { throw new InitializationException("Cannot find block <" + BlockName + ">",getSymbolicName()); } if (!FieldFound) { throw new InitializationException("Field name <" + FieldName + "> not found in block <" + BlockName + ">",getSymbolicName()); } } /** * This adds a definition of the separator for a block * * @param BlockName The name of the block * @param Separator The separator * @throws InitializationException */ public void MapSeparatorDef(String BlockName, String Separator) throws InitializationException { RecordBlockDef tmpRecordBlock; String tmpSep; tmpRecordBlock = FindBlock(BlockName); if (Separator.equalsIgnoreCase("semicolon")) { tmpSep = ";"; } else { tmpSep = Separator; } if (tmpRecordBlock != null) { tmpRecordBlock.Separator = tmpSep; } else { throw new InitializationException("Cannot find block <" + BlockName + ">",getSymbolicName()); } } /** * This performs the actual mapping of an input record to a block. In the case * of the root block, no block instance is created. In the case of child blocks * the instance is created according to the definition. The Block Index is * maintained in both cases (the root block, although created is not yet in * the index) * * In each case the field indexes are updated, and the field containers are * created. * * @param BlockName The name of the block * @param tmpData The data * @throws ProcessingException */ public void MapRecord(String BlockName, String tmpData) throws ProcessingException { MapElement tmpMapElement; RecordBlockDef tmpRecordBlockDef; String[] tmpFields; int i; String tmpCurrentFieldStr; int tmpCurrentFieldInt; double tmpCurrentFieldFloat = 0; RecordBlock tmpRecordBlock; String tmpBlockName; Integer tmpBlockCounter; // Search for the definition try { tmpRecordBlockDef = FindBlock(BlockName); } catch (InitializationException ie) { throw new ProcessingException(ie,"FlexRecord"); } if (tmpRecordBlockDef != null) { tmpFields = tmpData.split(tmpRecordBlockDef.Separator); // check the length of the data we have if (tmpFields.length < tmpRecordBlockDef.NumberOfFields) { throw new ProcessingException("Input data too short for mapping block <" + BlockName + ">","FlexRecord"); } // Create the block tmpRecordBlock = new RecordBlock(); tmpRecordBlock.FieldMap = new HashMap<>(10); tmpRecordBlock.Fields = new Object[tmpRecordBlockDef.NumberOfFields]; // Now try the mapping for(i = 0 ; i < tmpRecordBlockDef.Mapping.size() ; i++) { tmpMapElement = (MapElement) tmpRecordBlockDef.Mapping.get(i); switch (tmpMapElement.Type) { case FIELD_TYPE_STRING: { // Get the value to map tmpCurrentFieldStr = tmpFields[tmpMapElement.OffsetFrom]; tmpRecordBlock.Fields[tmpMapElement.OffsetTo] = tmpCurrentFieldStr; tmpRecordBlock.FieldMap.put(tmpMapElement.Name,tmpMapElement.OffsetTo); break; } case FIELD_TYPE_INTEGER: { // Get the value to map tmpCurrentFieldStr = tmpFields[tmpMapElement.OffsetFrom]; try { tmpCurrentFieldInt = Integer.parseInt(tmpCurrentFieldStr); } catch (NumberFormatException nfe) { this.addError(new RecordError("Conversion Error",ErrorType.DATA_VALIDATION)); tmpCurrentFieldInt = 0; } tmpRecordBlock.Fields[tmpMapElement.OffsetTo] = tmpCurrentFieldInt; tmpRecordBlock.FieldMap.put(tmpMapElement.Name,tmpMapElement.OffsetTo); break; } case FIELD_TYPE_FLOAT: { // Get the value to map tmpCurrentFieldStr = tmpFields[tmpMapElement.OffsetFrom]; try { tmpCurrentFieldFloat = Double.parseDouble(tmpCurrentFieldStr); } catch (NumberFormatException nfe) { this.addError(new RecordError("Conversion Error",ErrorType.DATA_VALIDATION)); } tmpRecordBlock.Fields[tmpMapElement.OffsetTo] = tmpCurrentFieldFloat; tmpRecordBlock.FieldMap.put(tmpMapElement.Name,tmpMapElement.OffsetTo); break; } } } // we have the block, now perform the split and store the data if (BlockName.equalsIgnoreCase("ROOT")) { // we add the root without an index BlockIndex.put(BlockName,tmpRecordBlock); if (!BlockCount.containsKey(BlockName)) { // ToDo check this tmpBlockCounter = 0; } else { tmpBlockCounter = BlockCount.get(BlockName); } tmpBlockCounter++; BlockCount.put(BlockName,tmpBlockCounter); } else { // we have to know the index of this block if (!BlockCount.containsKey(BlockName)) { // ToDo check this tmpBlockCounter = 0; } else { tmpBlockCounter = BlockCount.get(BlockName); } tmpBlockName = BlockName + "_" + Integer.toString(tmpBlockCounter); BlockIndex.put(tmpBlockName,tmpRecordBlock); tmpBlockCounter++; BlockCount.put(BlockName,tmpBlockCounter); } } else { throw new ProcessingException("Cannot find block <" + BlockName + ">","FlexRecord"); } } /** * Dump the record information * We need to iterate through all of the blocks outputting the name and the * data associated with the fields */ @Override public ArrayList<String> getDumpInfo() { ArrayList<String> tmpDumpList; tmpDumpList = new ArrayList<>(); if (this != null) { tmpDumpList.addAll(DumpBlockIter("ROOT",1)); } return tmpDumpList; } /** * This is a recursive way of getting the information from the blocks */ private Collection<String> DumpBlockIter(String StartBlock, int Level) { RecordBlockDef tmpRecordBlockDef; RecordBlock tmpRecordBlock; int i; Collection<String> tmpChildren; Iterator<String> tmpChildIter; int blockCounter; String tmpChildBlockName; Integer tmpBlockCounter; String currentIndent = ""; String PaddedName; String FieldType = null; ArrayList<String> tmpDumpList; tmpDumpList = new ArrayList<>(); for (i=0 ; i < Level ; i++) { currentIndent = currentIndent + " "; } // Start the search from the root tmpRecordBlockDef = getDefRoot(); try { tmpRecordBlockDef = FindBlock(StartBlock); } catch (InitializationException ex) { ex.printStackTrace(); } // Output this block if (tmpRecordBlockDef != null) { tmpDumpList.add(currentIndent + "=====<BLOCK=" + StartBlock + ">====="); tmpBlockCounter = BlockCount.get(StartBlock); for (blockCounter = 0 ; blockCounter < tmpBlockCounter ; blockCounter++) { // get the data block if (StartBlock.equalsIgnoreCase("ROOT")) { tmpRecordBlock = BlockIndex.get(StartBlock); } else { tmpRecordBlock = BlockIndex.get(StartBlock + "_" + Integer.toString(blockCounter)); } for (i = 0 ; i < tmpRecordBlockDef.NumberOfFields ; i++) { PaddedName = currentIndent + tmpRecordBlockDef.FieldNames[i] + " "; if (tmpRecordBlock.Fields[i] instanceof Integer) { FieldType = "> <integer>"; } if (tmpRecordBlock.Fields[i] instanceof String) { FieldType = "> <string>"; } if (tmpRecordBlock.Fields[i] instanceof Double) { FieldType = "> <float>"; } tmpDumpList.add(PaddedName.substring(1,60) + " = <" + tmpRecordBlock.Fields[i] + FieldType); } } } // Now the child record types tmpChildren = tmpRecordBlockDef.ChildTemplates.keySet(); tmpChildIter = tmpChildren.iterator(); while (tmpChildIter.hasNext()) { tmpChildBlockName = (String) tmpChildIter.next(); tmpDumpList.addAll(DumpBlockIter(tmpChildBlockName,Level+1)); } return tmpDumpList; } /** * FieldName is of the form ROOT:PATH.NAME * * @param FieldName The name of the field * @return The field info */ public FieldInfo GetFieldInfo(String FieldName) { RecordBlock tmpRecordBlock; Object tmpField; FieldInfo tmpResult; String[] SplitName; SplitName = FieldName.split("/."); tmpRecordBlock = BlockIndex.get(SplitName[0]); tmpField = tmpRecordBlock.FieldMap.get(SplitName[1]); tmpResult = new FieldInfo(); if (tmpField instanceof String) { tmpResult.FieldType = FIELD_TYPE_STRING; } if (tmpField instanceof Integer) { tmpResult.FieldType = FIELD_TYPE_INTEGER; } if (tmpField instanceof Double) { tmpResult.FieldType = FIELD_TYPE_FLOAT; } tmpResult.FieldValue = tmpField; tmpResult.FieldName = SplitName[1]; return tmpResult; } /** * Add a single error to the error list for this record * * @param error The new error to add */ @Override public void addError(IError error) { } /** * FieldName is of the form ROOT:PATH.NAME * * @param FieldName The field name * @return The float value */ public double GetFieldFloat(String FieldName) { int tmpFieldIndex; RecordBlock tmpRecordBlock; Object tmpField; double tmpResult = 0; String[] SplitName; SplitName = FieldName.split("~"); tmpRecordBlock = BlockIndex.get(SplitName[0]); tmpFieldIndex = ((Number)tmpRecordBlock.FieldMap.get(SplitName[1])).intValue(); tmpField = tmpRecordBlock.Fields[tmpFieldIndex]; if (tmpField instanceof Double) { tmpResult = ((Number)tmpField).doubleValue(); } else if (tmpField instanceof Integer) { tmpResult = ((Number)tmpField).doubleValue(); } else { this.addError(new RecordError("Cannot retrieve float value",ErrorType.DATA_VALIDATION)); } return tmpResult; } /** * Put a float field * * @param FieldName The name of the field * @param NewValue The value */ public void PutFieldFloat(String FieldName, double NewValue) { int tmpFieldIndex; RecordBlock tmpRecordBlock; String[] SplitName; SplitName = FieldName.split("~"); tmpRecordBlock = BlockIndex.get(SplitName[0]); tmpFieldIndex = ((Number)tmpRecordBlock.FieldMap.get(SplitName[1])).intValue(); tmpRecordBlock.Fields[tmpFieldIndex] = NewValue; } }