org.qxsched.doc.afp.AfpStructuredFieldDefinitions.java Source code

Java tutorial

Introduction

Here is the source code for org.qxsched.doc.afp.AfpStructuredFieldDefinitions.java

Source

package org.qxsched.doc.afp;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/*
 * 
 * Copyright 2009, 2010, 2011 Vincenzo Zocca
 * 
 * This file is part of Java library org.qxsched.doc.afp.
 *
 * Java library org.qxsched.doc.afp is free software: you can redistribute
 * it and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Java library org.qxsched.doc.afp 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Java library org.qxsched.doc.afp.
 * If not, see <http://www.gnu.org/licenses/>.
 * 
 */

/**
 * Class {@link AfpStructuredFieldDefinitions} contains AFP structured
 * definition codes.
 * 
 * The codes, abbreviations and descriptions are from IBM document
 * "S544-3884-04/Programming Guide and Line Data Reference" and are stored in a
 * property file named identical to this java file -except for the file
 * extension of course.
 * 
 * @author Vincenzo Zocca
 * 
 */
public class AfpStructuredFieldDefinitions {

    private static AfpStructuredFieldDefinitions instance = null;
    private static Logger LOG = Logger.getLogger(AfpStructuredFieldDefinitions.class);

    /**
     * Returns the supplied integer as hexadecimal string. The string starts
     * with <code>"0x"</code>. The hexadecimal string is padded with
     * <code>'0'</code> characters to get to the required width.
     * 
     * @param val
     *            the integer value.
     * @param width
     *            the qequired width, without considering the leading
     *            <code>"0x"</code>.
     * @return the supplied integer as hexadecimal string.
     */
    public static String hexString(Integer val, int width) {
        return "0x" + StringUtils.leftPad(Integer.toString(val, 16).toUpperCase(), width, '0');
    }

    /**
     * Returns the singleton instance.
     * 
     * @return the singleton instance.
     * @throws AfpException
     *             if an exception occurs when reading the codes table;
     */
    public synchronized static AfpStructuredFieldDefinitions instance() throws AfpException {

        // Instantiate
        if (instance == null) {
            instance = new AfpStructuredFieldDefinitions();
        }

        // Return if already loaded
        return instance;
    }

    private Map<String, Integer> abbrev2code = new HashMap<String, Integer>();
    private Map<Integer, Integer> begin2end = new HashMap<Integer, Integer>();
    private Map<Integer, String> code2abbrev = new TreeMap<Integer, String>();
    private Map<Integer, String> code2desc = new HashMap<Integer, String>();
    private Map<Integer, Integer> end2begin = new HashMap<Integer, Integer>();

    private AfpStructuredFieldDefinitions() throws AfpException {

        // Get resource
        String resourceName = AfpStructuredFieldDefinitions.class.getName().replace('.', '/') + ".properties";
        if (LOG.isTraceEnabled()) {
            LOG.trace("Loading resource " + resourceName);
        }

        // Map properties object
        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName);
        Properties props = new Properties();
        try {
            props.load(is);
        } catch (IOException e) {
            throw new AfpException(e.getMessage(), e);
        }

        // Fill maps
        Set<Object> keys = props.keySet();
        for (Object keyObj : keys) {

            // Cast key
            String key = (String) keyObj;
            String keyLc = key.toLowerCase();

            // Check key
            if (!keyLc.matches("^0x[0-9a-f]+$")) {
                LOG.error("Illegal key '" + key + "' in resource " + resourceName);
            }

            // Make integer key
            Integer keyInt = Integer.decode(key);

            // Get value and split
            String val = props.getProperty(key);
            String[] valSplit = val.split("\\W+", 2);

            // Make abbreviation mapping
            code2abbrev.put(keyInt, valSplit[0].toUpperCase());
            abbrev2code.put(valSplit[0].toUpperCase(), keyInt);

            // Make description string
            if (valSplit.length > 1) {
                code2desc.put(keyInt, valSplit[1]);
            }
        }

        // Make begin/end maps
        // Loop through codes
        Set<Integer> codeSet = code2abbrev.keySet();
        for (Integer codeBegin : codeSet) {

            // Get abbreviation and description
            String abbrev = code2abbrev.get(codeBegin);
            String desc = code2desc.get(codeBegin);

            // Do nothing if not begin
            if (!desc.toLowerCase().startsWith("begin")) {
                continue;
            }

            // Make "end" abbreviation
            String abbrevEnd = abbrev.replaceFirst("^[A-Z]", "E");
            abbrevEnd = abbrevEnd.replaceFirst("^[a-z]", "e");

            // Throw exception if "end" abbreviation does not exist
            Integer codeEnd = abbrev2code.get(abbrevEnd);
            if (codeEnd == null) {
                throw new AfpException("Did not find \"end\" abbreviation for \"begin\" abbreviation " + abbrev
                        + " (" + desc + ")");
            }
            if (LOG.isTraceEnabled()) {
                LOG.trace("code-bigin/code-end:" + hexString(codeBegin, 6) + "/" + hexString(codeEnd, 6));
            }

            // Assert begin2end
            if (begin2end.containsKey(codeBegin)) {
                throw new AfpException("Unexpected state. Code " + hexString(codeBegin, 6)
                        + " defined miltiple times in begin-to-end map.");
            }
            begin2end.put(codeBegin, codeEnd);

            // Assert end2begin
            Integer codeBeginError = end2begin.get(codeEnd);
            if (codeBeginError != null) {

                // Get abbreviations
                String abbrevBegin = code2abbrev.get(codeBegin);
                String abbrevBeginError = code2abbrev.get(codeBeginError);

                throw new AfpException("End-code '" + abbrevEnd + "'/" + hexString(codeEnd, 1)
                        + " matches multiple begin-codes: '" + abbrevBeginError + "'/"
                        + hexString(codeBeginError, 6) + " and '" + abbrevBegin + "'/" + hexString(codeBegin, 6));
            }
            end2begin.put(codeEnd, codeBegin);
        }
    }

    /**
     * Return all known structured definition abbreviation strings in the
     * object.
     * 
     * @return all known structured definition abbreviation strings in the
     *         object.
     */
    public Set<String> getAbbrev() {
        return abbrev2code.keySet();
    }

    /**
     * Returns the abbreviation string for the supplied structured definition
     * code. If code is unknown, <code>null</code> is returned.
     * 
     * @param code
     *            the structured definition code.
     * @return the abbreviation string for the supplied structured definition
     *         code.
     */
    public String getAbbreviation(Integer code) {
        return code2abbrev.get(code);
    }

    /**
     * Returns the structured definition code for the supplied abbreviation
     * string. If the abbreviation is unknown, <code>null</code> is returned.
     * 
     * @param abbrev
     *            the structured definition code.
     * @return the description string for the supplied structured definition
     *         code.
     */
    public Integer getCode(String abbrev) {
        return abbrev2code.get(abbrev.toUpperCase());
    }

    /**
     * Return all known structured definition codes in the object.
     * 
     * @return all known structured definition codes in the object.
     */
    public Set<Integer> getCodes() {
        return code2abbrev.keySet();
    }

    /**
     * Returns the description string for the supplied structured definition
     * code. If code is unknown, <code>null</code> is returned.
     * 
     * @param code
     *            the structured definition code.
     * @return the description string for the supplied structured definition
     *         code.
     */
    public String getDescription(Integer code) {
        return code2desc.get(code);
    }

    /**
     * Returns the matching begin-code for the supplied end-group code. If no
     * begin-code is found -e.g. when the supplied code is not an end- group
     * code- then <code>null</code> is returned.
     * 
     * @param code
     *            the end-group code.
     * @return the matching begin-code for the supplied end-group code.
     */
    public Integer getGroupBegin(Integer code) {
        return end2begin.get(code);
    }

    /**
     * Returns the matching end-code for the supplied begin-group code. If no
     * end-code is found -e.g. when the supplied code is not a begin-group code-
     * then <code>null</code> is returned.
     * 
     * @param code
     *            the begin-group code.
     * @return the matching end-code for the supplied begingroup -code.
     */
    public Integer getGroupEnd(Integer code) {
        return begin2end.get(code);
    }

    /**
     * Returns <code>true</code> if the code belongs to a 'begin-group' AFP
     * record and <code>false</code> otherwise.
     * 
     * @param code
     *            the code to test.
     * @return <code>true</code> if the code belongs to a 'begin-group' AFP
     *         record and <code>false</code> otherwise.
     */
    public Boolean isGroupBegin(Integer code) {
        return begin2end.containsKey(code);
    }

    /**
     * Returns <code>true</code> if the code belongs to a 'end-group' AFP record
     * and <code>false</code> otherwise.
     * 
     * @param code
     *            the code to test.
     * @return <code>true</code> if the code belongs to a 'end-group' AFP record
     *         and <code>false</code> otherwise.
     */
    public Boolean isGroupEnd(Integer code) {
        return end2begin.containsKey(code);
    }

}