/* * file: CriteriaReader.java * author: Jon Iles * copyright: (c) Packwood Software 2010 * date: 2010-05-06 */ /* * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation; either version 2.1 of the License, or (at your * option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ package net.sf.mpxj.mpp; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import net.sf.mpxj.FieldType; import net.sf.mpxj.FieldTypeClass; import net.sf.mpxj.GenericCriteria; import net.sf.mpxj.GenericCriteriaPrompt; import net.sf.mpxj.ProjectProperties; import net.sf.mpxj.TestOperator; /** * This class allows criteria definitions to be read from an MPP file. */ public abstract class CriteriaReader { /** * Retrieves the offset of the start of the criteria data. * * @return criteria start offset */ protected abstract int getCriteriaStartOffset(); /** * Retrieves the criteria block size. * * @return criteria block size */ protected abstract int getCriteriaBlockSize(); /** * Retrieves the child of the current block. * * @param block parent block * @return child block */ protected abstract byte[] getChildBlock(byte[] block); /** * Retrieves the next list sibling of this block. * * @param block current block * @return next sibling list block */ protected abstract byte[] getListNextBlock(byte[] block); /** * Retrieves the offset of the start of the text block. * * @param block current block * @return text block start offset */ protected abstract int getTextOffset(byte[] block); /** * Retrieves the offset of the prompt text. * * @param block current block * @return prompt text offset */ protected abstract int getPromptOffset(byte[] block); /** * Retrieves the offset of the field value. * * @return field value offset */ protected abstract int getValueOffset(); /** * Retrieves the offset of the time unit field. * * @return time unit field offset */ protected abstract int getTimeUnitsOffset(); /** * Retrieves offset of value which determines the start of the text block. * * @return criteria text start offset */ protected abstract int getCriteriaTextStartOffset(); /** * Retrieves a field type value. * * @param block criteria block * @return field type value */ protected abstract FieldType getFieldType(byte[] block); /** * Main entry point to read criteria data. * * @param properties project properties * @param data criteria data block * @param dataOffset offset of the data start within the larger data block * @param entryOffset offset of start node for walking the tree * @param prompts optional list to hold prompts * @param fields optional list of hold fields * @param criteriaType optional array representing criteria types * @return first node of the criteria */ public GenericCriteria process(ProjectProperties properties, byte[] data, int dataOffset, int entryOffset, List<GenericCriteriaPrompt> prompts, List<FieldType> fields, boolean[] criteriaType) { m_properties = properties; m_prompts = prompts; m_fields = fields; m_criteriaType = criteriaType; m_dataOffset = dataOffset; if (m_criteriaType != null) { m_criteriaType[0] = true; m_criteriaType[1] = true; } m_criteriaBlockMap.clear(); m_criteriaData = data; m_criteriaTextStart = MPPUtility.getShort(m_criteriaData, m_dataOffset + getCriteriaTextStartOffset()); // // Populate the map // int criteriaStartOffset = getCriteriaStartOffset(); int criteriaBlockSize = getCriteriaBlockSize(); //System.out.println(); //System.out.println(MPPUtility.hexdump(data, dataOffset, criteriaStartOffset, false)); if (m_criteriaData.length <= m_criteriaTextStart) { return null; // bad data } while (criteriaStartOffset + criteriaBlockSize <= m_criteriaTextStart) { byte[] block = new byte[criteriaBlockSize]; System.arraycopy(m_criteriaData, m_dataOffset + criteriaStartOffset, block, 0, criteriaBlockSize); m_criteriaBlockMap.put(Integer.valueOf(criteriaStartOffset), block); //System.out.println(Integer.toHexString(criteriaStartOffset) + ": " + MPPUtility.hexdump(block, false)); criteriaStartOffset += criteriaBlockSize; } if (entryOffset == -1) { entryOffset = getCriteriaStartOffset(); } List<GenericCriteria> list = new LinkedList<GenericCriteria>(); processBlock(list, m_criteriaBlockMap.get(Integer.valueOf(entryOffset))); GenericCriteria criteria; if (list.isEmpty()) { criteria = null; } else { criteria = list.get(0); } return criteria; } /** * Process a single criteria block. * * @param list parent criteria list * @param block current block */ private void processBlock(List<GenericCriteria> list, byte[] block) { if (block != null) { if (MPPUtility.getShort(block, 0) > 0x3E6) { addCriteria(list, block); } else { switch (block[0]) { case (byte) 0x0B: { processBlock(list, getChildBlock(block)); break; } case (byte) 0x06: { processBlock(list, getListNextBlock(block)); break; } case (byte) 0xED: // EQUALS { addCriteria(list, block); break; } case (byte) 0x19: // AND case (byte) 0x1B: { addBlock(list, block, TestOperator.AND); break; } case (byte) 0x1A: // OR case (byte) 0x1C: { addBlock(list, block, TestOperator.OR); break; } } } } } /** * Adds a basic LHS OPERATOR RHS block. * * @param list parent criteria list * @param block current block */ private void addCriteria(List<GenericCriteria> list, byte[] block) { byte[] leftBlock = getChildBlock(block); byte[] rightBlock1 = getListNextBlock(leftBlock); byte[] rightBlock2 = getListNextBlock(rightBlock1); TestOperator operator = TestOperator.getInstance(MPPUtility.getShort(block, 0) - 0x3E7); FieldType leftValue = getFieldType(leftBlock); Object rightValue1 = getValue(leftValue, rightBlock1); Object rightValue2 = rightBlock2 == null ? null : getValue(leftValue, rightBlock2); GenericCriteria criteria = new GenericCriteria(m_properties); criteria.setLeftValue(leftValue); criteria.setOperator(operator); criteria.setRightValue(0, rightValue1); criteria.setRightValue(1, rightValue2); list.add(criteria); if (m_criteriaType != null) { m_criteriaType[0] = leftValue.getFieldTypeClass() == FieldTypeClass.TASK; m_criteriaType[1] = !m_criteriaType[0]; } if (m_fields != null) { m_fields.add(leftValue); } processBlock(list, getListNextBlock(block)); } /** * Adds a logical operator block. * * @param list parent criteria list * @param block current block * @param operator logical operator represented by this block */ private void addBlock(List<GenericCriteria> list, byte[] block, TestOperator operator) { GenericCriteria result = new GenericCriteria(m_properties); result.setOperator(operator); list.add(result); processBlock(result.getCriteriaList(), getChildBlock(block)); processBlock(list, getListNextBlock(block)); } /** * Retrieves the value component of a criteria expression. * * @param field field type * @param block block data * @return field value */ private Object getValue(FieldType field, byte[] block) { Object result = null; switch (block[0]) { case 0x07: // Field { result = getFieldType(block); break; } case 0x01: // Constant value { result = getConstantValue(field, block); break; } case 0x00: // Prompt { result = getPromptValue(field, block); break; } } return result; } /** * Retrieves a constant value. * * @param type field type * @param block criteria data block * @return constant value */ private Object getConstantValue(FieldType type, byte[] block) { Object value; switch (type.getDataType()) { case DURATION: { value = MPPUtility.getAdjustedDuration(m_properties, MPPUtility.getInt(block, getValueOffset()), MPPUtility.getDurationTimeUnits(MPPUtility.getShort(block, getTimeUnitsOffset()))); break; } case NUMERIC: { value = Double.valueOf(MPPUtility.getDouble(block, getValueOffset())); break; } case PERCENTAGE: { value = Double.valueOf(MPPUtility.getShort(block, getValueOffset())); break; } case CURRENCY: { value = Double.valueOf(MPPUtility.getDouble(block, getValueOffset()) / 100); break; } case STRING: { int textOffset = getTextOffset(block); value = MPPUtility.getUnicodeString(m_criteriaData, m_dataOffset + m_criteriaTextStart + textOffset); break; } case BOOLEAN: { int intValue = MPPUtility.getShort(block, getValueOffset()); value = (intValue == 1 ? Boolean.TRUE : Boolean.FALSE); break; } case DATE: { value = MPPUtility.getTimestamp(block, getValueOffset()); break; } default: { value = null; break; } } return value; } /** * Retrieves a prompt value. * * @param field field type * @param block criteria data block * @return prompt value */ private GenericCriteriaPrompt getPromptValue(FieldType field, byte[] block) { int textOffset = getPromptOffset(block); String value = MPPUtility.getUnicodeString(m_criteriaData, m_criteriaTextStart + textOffset); GenericCriteriaPrompt prompt = new GenericCriteriaPrompt(field.getDataType(), value); if (m_prompts != null) { m_prompts.add(prompt); } return prompt; } private ProjectProperties m_properties; private byte[] m_criteriaData; private boolean[] m_criteriaType; private int m_criteriaTextStart; private int m_dataOffset; private List<GenericCriteriaPrompt> m_prompts; private List<FieldType> m_fields; protected Map<Integer, byte[]> m_criteriaBlockMap = new TreeMap<Integer, byte[]>(); }