| /******************************************************************************* |
| * 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.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.ofbiz.base.crypto.HashCrypt; |
| import org.ofbiz.base.util.UtilValidate; |
| import org.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.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; |
| } |
| |
| } |
| |