blob: c2d1dc30a132e85c3d4ba143d0be135f48248c04 [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
package org.apache.ofbiz.datafile;
import java.io.Serializable;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import org.apache.ofbiz.base.crypto.HashCrypt;
import org.apache.ofbiz.base.util.UtilValidate;
import org.apache.ofbiz.common.login.LoginServices;
/**
* Record
*/
@SuppressWarnings("serial")
public class Record implements Serializable {
/** Contains a map with field data by name */
protected Map<String, Object> fields;
/** Contains the name of the record definition */
protected String recordName;
/** Contains the definition for the record */
protected transient ModelRecord modelRecord;
protected Record parentRecord = null;
protected List<Record> childRecords = new ArrayList<Record>();
/** Creates new Record */
protected Record(ModelRecord modelRecord) {
if (modelRecord == null)
throw new IllegalArgumentException("Cannont create a Record with a null modelRecord parameter");
this.recordName = modelRecord.name;
this.modelRecord = modelRecord;
this.fields = new HashMap<String, Object>();
}
/** Creates new Record from existing Map */
protected Record(ModelRecord modelRecord, Map<String, Object> fields) {
if (modelRecord == null)
throw new IllegalArgumentException("Cannont create a Record with a null modelRecord parameter");
this.recordName = modelRecord.name;
this.modelRecord = modelRecord;
this.fields = (fields == null ? new HashMap<String, Object>() : new HashMap<String, Object>(fields));
}
public String getRecordName() {
return recordName;
}
public ModelRecord getModelRecord() {
if (modelRecord == null) {
throw new IllegalStateException("[Record.getModelRecord] could not find modelRecord for recordName " + recordName);
}
return modelRecord;
}
public Object get(String name) {
if (getModelRecord().getModelField(name) == null) {
throw new IllegalArgumentException("[Record.get] \"" + name + "\" is not a field of " + recordName);
// Debug.logWarning("[GenericRecord.get] \"" + name + "\" is not a field of " + recordName + ", but getting anyway...", module);
}
return fields.get(name);
}
public String getString(String name) {
Object object = get(name);
if (object == null)
return null;
if (object instanceof java.lang.String)
return (String) object;
else
return object.toString();
}
public String getStringAndEmpty(String name) {
Object object = get(name);
if (object == null)
return "";
if (object instanceof java.lang.String)
return (String) object;
else
return object.toString();
}
public java.sql.Timestamp getTimestamp(String name) {
return (java.sql.Timestamp) get(name);
}
public java.sql.Time getTime(String name) {
return (java.sql.Time) get(name);
}
public java.sql.Date getDate(String name) {
return (java.sql.Date) get(name);
}
public Integer getInteger(String name) {
return (Integer) get(name);
}
public Long getLong(String name) {
return (Long) get(name);
}
public Float getFloat(String name) {
return (Float) get(name);
}
public Double getDouble(String name) {
return (Double) get(name);
}
/** Sets the named field to the passed value, even if the value is null
* @param name The field name to set
* @param value The value to set
*/
public void set(String name, Object value) {
set(name, value, true);
}
/** Sets the named field to the passed value. If value is null, it is only
* set if the setIfNull parameter is true.
* @param name The field name to set
* @param value The value to set
* @param setIfNull Specifies whether or not to set the value if it is null
*/
public synchronized void set(String name, Object value, boolean setIfNull) {
if (getModelRecord().getModelField(name) == null) {
throw new IllegalArgumentException("[Record.set] \"" + name + "\" is not a field of " + recordName);
// Debug.logWarning("[GenericRecord.set] \"" + name + "\" is not a field of " + recordName + ", but setting anyway...", module);
}
if (value != null || setIfNull) {
if (value instanceof Boolean) {
value = ((Boolean) value).booleanValue() ? "Y" : "N";
}
fields.put(name, value);
}
}
/**
* little endian reader for 2 byte short.
*/
public final short readLEShort(byte[] byteArray) {
return (short)(
(byteArray[1]&0xff) << 8 |
(byteArray[0]&0xff));
}
/**
* little endian reader for 4 byte int.
*/
public final int readLEInt(byte []byteArray) {
return
(byteArray[3]) << 24 |
(byteArray[2]&0xff) << 16 |
(byteArray[1]&0xff) << 8 |
(byteArray[0]&0xff);
}
/**
* little endian reader for 8 byte long.
*/
public final long readLELong(byte []byteArray) {
return
(long)(byteArray[7]) << 56 | /* long cast needed or shift done modulo 32 */
(long)(byteArray[6]&0xff) << 48 |
(long)(byteArray[5]&0xff) << 40 |
(long)(byteArray[4]&0xff) << 32 |
(long)(byteArray[3]&0xff) << 24 |
(long)(byteArray[2]&0xff) << 16 |
(long)(byteArray[1]&0xff) << 8 |
(byteArray[0]&0xff);
}
/** Sets the named field to the passed value, converting the value from a String to the current type using <code>Type.valueOf()</code>
* @param name The field name to set
* @param value The String value to convert and set
*/
public void setString(String name, String value) throws ParseException {
if (name == null || value == null || value.equals(""))
return;
ModelField field = getModelRecord().getModelField(name);
if (field == null)
set(name, value); // this will get an error in the set() method...
// if the string is all spaces ignore
boolean nonSpace = false;
for (int i = 0; i < value.length(); i++) {
if (value.charAt(i) != ' ') {
nonSpace = true;
break;
}
}
if (!nonSpace)
return;
// if (Debug.verboseOn()) Debug.logVerbose("Value: " + value, module);
String fieldType = field.type;
// first the custom types that need to be parsed
if (fieldType.equals("CustomTimestamp")) {
// this custom type will take a string a parse according to date formatting
// string then put the result in a java.sql.Timestamp
// a common timestamp format for flat files is with no separators: yyyyMMddHHmmss
SimpleDateFormat sdf = new SimpleDateFormat(field.format);
java.util.Date tempDate = sdf.parse(value);
java.sql.Timestamp timestamp = new java.sql.Timestamp(tempDate.getTime());
set(name, timestamp);
} else if (fieldType.equals("CustomDate")) {
// a common date only format for flat files is with no separators: yyyyMMdd or MMddyyyy
SimpleDateFormat sdf = new SimpleDateFormat(field.format);
java.util.Date tempDate = sdf.parse(value);
java.sql.Date date = new java.sql.Date(tempDate.getTime());
set(name, date);
} else if (fieldType.equals("CustomTime")) {
// a common time only format for flat files is with no separators: HHmmss
SimpleDateFormat sdf = new SimpleDateFormat(field.format);
java.util.Date tempDate = sdf.parse(value);
java.sql.Time time = new java.sql.Time(tempDate.getTime());
set(name, time);
} else if (fieldType.equals("FixedPointDouble")) {
// this custom type will parse a fixed point number according to the number
// of decimal places in the formatting string then place it in a Double
NumberFormat nf = NumberFormat.getNumberInstance();
Number tempNum = nf.parse(value);
double number = tempNum.doubleValue();
double decimalPlaces = Double.parseDouble(field.format);
double divisor = Math.pow(10.0, decimalPlaces);
number = number / divisor;
set(name, Double.valueOf(number));
} // standard types
else if (fieldType.equals("java.lang.String") || fieldType.equals("String"))
if (field.format.equals("EncryptedString")) {
String hashType = LoginServices.getHashType();
set(name, HashCrypt.digestHash(hashType, value.getBytes()));
} else {
set(name, value);
}
else if (fieldType.equals("NullTerminatedString")) {
int terminate = value.indexOf(0x0);
set(name, terminate>0?value.substring(0,terminate):value);
} else if (fieldType.equals("java.sql.Timestamp") || fieldType.equals("Timestamp"))
set(name, java.sql.Timestamp.valueOf(value));
else if (fieldType.equals("java.sql.Time") || fieldType.equals("Time"))
set(name, java.sql.Time.valueOf(value));
else if (fieldType.equals("java.sql.Date") || fieldType.equals("Date"))
set(name, java.sql.Date.valueOf(value));
else if (fieldType.equals("java.lang.Integer") || fieldType.equals("Integer"))
set(name, Integer.valueOf(value));
else if (fieldType.equals("java.lang.Long") || fieldType.equals("Long"))
set(name, Long.valueOf(value));
else if (fieldType.equals("java.lang.Float") || fieldType.equals("Float"))
set(name, Float.valueOf(value));
else if (fieldType.equals("java.lang.Double") || fieldType.equals("Double"))
set(name, Double.valueOf(value));
else if (fieldType.equals("LEShort"))
set(name, Short.valueOf(readLEShort(value.getBytes())));
else if (fieldType.equals("LEInteger"))
set(name, Integer.valueOf(readLEInt(value.getBytes())));
else if (fieldType.equals("LELong"))
set(name, Long.valueOf(readLELong(value.getBytes())));
else {
throw new IllegalArgumentException("Field type " + fieldType + " not currently supported. Sorry.");
}
}
public String getFixedString(String name) {
if (name == null)
return null;
if (getModelRecord() == null)
throw new IllegalArgumentException("Could not find modelrecord for field named \"" + name + "\"");
ModelField field = getModelRecord().getModelField(name);
if (field == null)
throw new IllegalArgumentException("Could not find model for field named \"" + name + "\"");
Object value = get(name);
if (value == null) {
return null;
}
String fieldType = field.type;
String str = null;
// first the custom types that need to be parsed
if (fieldType.equals("CustomTimestamp")) {
// a common timestamp format for flat files is with no separators: yyyyMMddHHmmss
SimpleDateFormat sdf = new SimpleDateFormat(field.format);
java.sql.Timestamp timestamp = (java.sql.Timestamp) value;
str = sdf.format(new Date(timestamp.getTime()));
} else if (fieldType.equals("CustomDate")) {
// a common date only format for flat files is with no separators: yyyyMMdd or MMddyyyy
SimpleDateFormat sdf = new SimpleDateFormat(field.format);
java.sql.Date date = (java.sql.Date) value;
str = sdf.format(new Date(date.getTime()));
} else if (fieldType.equals("CustomTime")) {
// a common time only format for flat files is with no separators: HHmmss
SimpleDateFormat sdf = new SimpleDateFormat(field.format);
java.sql.Time time = (java.sql.Time) value;
str = sdf.format(new Date(time.getTime()));
} else if (fieldType.equals("FixedPointDouble")) {
// this custom type will parse a fixed point number according to the number
// of decimal places in the formatting string then place it in a Double
double decimalPlaces = Double.parseDouble(field.format);
double multiplier = Math.pow(10.0, decimalPlaces);
double dnum = multiplier * ((Double) value).doubleValue();
long number = Math.round(dnum);
str = padFrontZeros(Long.toString(number), field.length);
// if (Debug.infoOn()) Debug.logInfo("[Record.getFixedString] FixedPointDouble: multiplier=" + multiplier + ", value=" + value + ", dnum=" + dnum + ", number=" + number + ", str=" + str, module);
} // standard types
else if (fieldType.equals("java.lang.String") || fieldType.equals("String"))
str = value.toString();
else if (fieldType.equals("java.sql.Timestamp") || fieldType.equals("Timestamp"))
str = value.toString();
else if (fieldType.equals("java.sql.Time") || fieldType.equals("Time"))
str = value.toString();
else if (fieldType.equals("java.sql.Date") || fieldType.equals("Date"))
str = value.toString();
// for all numbers, pad front with zeros if field length is specified
else if (fieldType.equals("java.lang.Integer") || fieldType.equals("Integer"))
str = padFrontZeros(value.toString(), field.length);
else if (fieldType.equals("java.lang.Long") || fieldType.equals("Long"))
str = padFrontZeros(value.toString(), field.length);
else if (fieldType.equals("java.lang.Float") || fieldType.equals("Float"))
str = padFrontZeros(value.toString(), field.length);
else if (fieldType.equals("java.lang.Double") || fieldType.equals("Double"))
str = padFrontZeros(value.toString(), field.length);
else {
throw new IllegalArgumentException("Field type " + fieldType + " not currently supported. Sorry.");
}
if (str != null && field.length > 0 && str.length() < field.length) {
// pad the end with spaces
StringBuilder strBuf = new StringBuilder(str);
while (strBuf.length() < field.length)
strBuf.append(' ');
str = strBuf.toString();
}
return str;
}
public String writeLineString(ModelDataFile modelDataFile) throws DataFileException {
ModelRecord modelRecord = getModelRecord();
boolean isFixedRecord = ModelDataFile.SEP_FIXED_RECORD.equals(modelDataFile.separatorStyle);
boolean isFixedLength = ModelDataFile.SEP_FIXED_LENGTH.equals(modelDataFile.separatorStyle);
boolean isDelimited = ModelDataFile.SEP_DELIMITED.equals(modelDataFile.separatorStyle);
StringBuilder lineBuf = new StringBuilder();
for (ModelField modelField: modelRecord.fields) {
String data = this.getFixedString(modelField.name);
if (isDelimited && null != modelDataFile.textDelimiter) {
lineBuf.append(modelDataFile.textDelimiter);
}
// if field is null (not set) then assume we want to pad the field
char PAD_CHAR = ' ';
if (data == null) {
StringBuilder sb = new StringBuilder("");
for (int i = 0; i < modelField.length; i++)
sb.append(PAD_CHAR);
data = sb.toString();
}
// Pad the record
if (isFixedRecord) {
while (modelField.position > lineBuf.length())
lineBuf.append(" ");
}
// if (Debug.infoOn()) Debug.logInfo("Field: " + modelField.name + " Position: " + modelField.position + " BufLen: " + lineBuf.length(), module);
// if (Debug.infoOn()) Debug.logInfo("Got data \"" + data + "\" for field " + modelField.name + " in record " + modelRecord.name, module);
if (modelField.length > 0 && data.length() != modelField.length)
throw new DataFileException("Got field length " + data.length() + " but expected field length is " + modelField.length + " for field \"" +
modelField.name + "\" of record \"" + modelRecord.name + "\" data is: \"" + data + "\"");
lineBuf.append(data);
if (isDelimited) {
if (null != modelDataFile.textDelimiter) {
lineBuf.append(modelDataFile.textDelimiter);
}
lineBuf.append(modelDataFile.delimiter);
}
}
if (isDelimited) {
// just remove the last delimiter to finish clean, otherwise shows as extra column
lineBuf.setLength(lineBuf.length() - 1);
}
if ((isFixedRecord || isFixedLength) && modelDataFile.recordLength > 0 && lineBuf.length() != modelDataFile.recordLength)
throw new DataFileException("Got record length " + lineBuf.length() + " but expected record length is " + modelDataFile.recordLength +
" for record \"" + modelRecord.name + "\" data line is: \"" + lineBuf + "\"");
// for convenience, insert the type-code in where it is looked for, if exists
if (modelRecord.tcPosition > 0 && modelRecord.typeCode.length() > 0) {
lineBuf.replace(modelRecord.tcPosition, modelRecord.tcPosition + modelRecord.tcLength, modelRecord.typeCode);
}
if (isFixedLength || isDelimited)
lineBuf.append('\n');
return lineBuf.toString();
}
String padFrontZeros(String str, int totalLength) {
if (totalLength > 0 && str.length() < totalLength) {
// pad the front with zeros
StringBuilder zeros = new StringBuilder();
int numZeros = totalLength - str.length();
for (int i = 0; i < numZeros; i++)
zeros.append('0');
zeros.append(str);
return zeros.toString();
} else
return str;
}
public Record getParentRecord() {
return parentRecord;
}
public List<Record> getChildRecords() {
return childRecords;
}
public void addChildRecord(Record record) {
childRecords.add(record);
}
/** Creates new Record
* @param modelRecord
* @throws DataFileException Exception thown for various errors, generally has a nested exception
* @return return the Record Object created
*/
public static Record createRecord(ModelRecord modelRecord) throws DataFileException {
Record record = new Record(modelRecord);
return record;
}
/** Creates new Record from existing fields Map
* @param modelRecord
* @param fields
* @throws DataFileException Exception thown for various errors, generally has a nested exception
* @return return the Record Object created
*/
public static Record createRecord(ModelRecord modelRecord, Map<String, Object> fields) throws DataFileException {
Record record = new Record(modelRecord, fields);
return record;
}
/**
* @param line
* @param lineNum
* @param modelRecord
* @throws DataFileException Exception thown for various errors, generally has a nested exception
* @return return the Record Object created
*/
public static Record createRecord(String line, int lineNum, ModelRecord modelRecord) throws DataFileException {
Record record = new Record(modelRecord);
for (ModelField modelField: modelRecord.fields) {
String strVal = null;
try {
strVal = line.substring(modelField.position, modelField.position + modelField.length);
} catch (IndexOutOfBoundsException ioobe) {
throw new DataFileException("Field " + modelField.name + " from " + modelField.position +
" for " + modelField.length + " chars could not be read from a line (" + lineNum + ") with only " +
line.length() + " chars.", ioobe);
}
try {
record.setString(modelField.name, strVal);
} catch (java.text.ParseException e) {
throw new DataFileException("Could not parse field " + modelField.name + ", format string \"" + modelField.format + "\" with value " + strVal +
" on line " + lineNum, e);
} catch (java.lang.NumberFormatException e) {
throw new DataFileException("Number not valid for field " + modelField.name + ", format string \"" + modelField.format + "\" with value " +
strVal + " on line " + lineNum, e);
}
}
return record;
}
/**
* @param line
* @param lineNum
* @param modelRecord
* @param delimiter
* @throws DataFileException Exception thown for various errors, generally has a nested exception
* @return return a Record Object
*/
public static Record createDelimitedRecord(String line, int lineNum, ModelRecord modelRecord, char delimiter, String textDelimiter) throws DataFileException {
Record record = new Record(modelRecord);
StringTokenizer st = null;
if (line.endsWith(String.valueOf(delimiter))) {
st = new StringTokenizer(line + " ", "" + delimiter, true);
}
else {
st = new StringTokenizer(line, "" + delimiter, true);
}
for (ModelField modelField: modelRecord.fields) {
String strVal = null;
if (modelField.expression) {
if (UtilValidate.isNotEmpty(modelField.refField)) {
strVal = record.getString(modelField.refField);
}
if (strVal == null) {
strVal = (String)modelField.defaultValue;
}
} else {
//some input lines may be less than the header model.
if (st.hasMoreTokens()) {
try {
strVal = st.nextToken();
if (strVal.equals("" + delimiter)) {
strVal = null;
} else if (st.hasMoreTokens()) {
st.nextToken();
}
} catch (NoSuchElementException nsee) {
throw new DataFileException("Field " + modelField.name + " could not be read from a line (" + lineNum + ") with only " +
line.length() + " chars.", nsee);
}
}
else { //if input line is less than the header model then pad with null
strVal = null;
}
}
try {
if (textDelimiter != null && strVal != null && (strVal.startsWith(textDelimiter) && (!strVal.endsWith(textDelimiter) || strVal.length()==1))) {
strVal = strVal.concat(""+delimiter);
while (!strVal.endsWith(textDelimiter)) {
strVal = strVal.concat(st.nextToken());
}
st.nextToken();
}
if (textDelimiter != null && strVal != null && (strVal.startsWith(textDelimiter) && strVal.endsWith(textDelimiter))) {
strVal = strVal.substring(textDelimiter.length(), strVal.length() - textDelimiter.length());
}
record.setString(modelField.name, strVal);
} catch (java.text.ParseException e) {
throw new DataFileException("Could not parse field " + modelField.name + ", format string \"" + modelField.format + "\" with value " + strVal +
" on line " + lineNum, e);
} catch (java.lang.NumberFormatException e) {
throw new DataFileException("Number not valid for field " + modelField.name + ", format string \"" + modelField.format + "\" with value " +
strVal + " on line " + lineNum, e);
}
}
return record;
}
}