org.bitsofinfo.util.address.usps.ais.USPSUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.bitsofinfo.util.address.usps.ais.USPSUtils.java

Source

/*****************************************************************************
* Copyright 2012  bitsofinfo.g [at] gmail [dot] com 
* 
* Licensed 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
* 
* Author: bitsofinfo.g [at] gmail [dot] com 
* @see bitsofinfo.wordpress.com
*****************************************************************************/
package org.bitsofinfo.util.address.usps.ais;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.ConvertUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.bitsofinfo.util.address.usps.ais.annotations.USPSIdentifierField;
import org.bitsofinfo.util.address.usps.ais.annotations.USPSKeyField;
import org.bitsofinfo.util.address.usps.ais.annotations.USPSRecordContext;
import org.bitsofinfo.util.address.usps.ais.loader.GenericEnumConverter;
import org.bitsofinfo.util.reflection.ClassFinder;

/**
 * USPSUtils provides utility methods
 * for working with USPSRecord annotations and other
 * utility operations in the USPS AIS package
 * suite of classes.
 * 
 * This is primarily used by the usps.ais.loader package
 * 
 * @author bitsofinfo.g [at] gmail [dot] com
 *
 */
public class USPSUtils {

    // cache of USPSRecord classes to all Fields annotated by a specific Annotation type within it
    // the key is ClassName+AnnotationClassName
    private Map<String, List<Field>> class2AnnotatedFieldCache = new Hashtable<String, List<Field>>();

    // cache of USPSRecord classes to field names annotated with USPSKeyField in that class
    private Map<Class<? extends USPSRecord>, String[]> keyFieldCache = new Hashtable<Class<? extends USPSRecord>, String[]>();

    // cache of USPSRecord classes to field names annotated with USPSIdentifierField in that class
    private Map<Class<? extends USPSRecord>, String[]> idFieldCache = new Hashtable<Class<? extends USPSRecord>, String[]>();

    // cache of USPSRecord classes to declared field names 
    private Map<Class<? extends USPSRecord>, String[]> declaredFieldNamesCache = new Hashtable<Class<? extends USPSRecord>, String[]>();

    // cache of USPSRecord classes to declared field names (stored as Set>
    private Map<Class<? extends USPSRecord>, Set<String>> declaredFieldNamesAsSetCache = new Hashtable<Class<? extends USPSRecord>, Set<String>>();

    // cache of USPSRecord classes to declared fields 
    private Map<Class<? extends USPSRecord>, Field[]> declaredFieldsCache = new Hashtable<Class<? extends USPSRecord>, Field[]>();

    // cache of record (copyrightDetailCodes+recordLengths) -> target USPSRecord classes 
    private Map<String, Class<? extends USPSRecord>> recordLengthMap = new Hashtable<String, Class<? extends USPSRecord>>();

    /**
     * This is a cache which maps:
     * 
     * USPSProductType -> Supported CopyrightDetailCodes -> USPSRecord class to handle that code
     * 
     */
    private Map<USPSProductType, Map<CopyrightDetailCode, Class<? extends USPSRecord>>> productTypeMapCache = new Hashtable<USPSProductType, Map<CopyrightDetailCode, Class<? extends USPSRecord>>>();

    @Autowired
    private ClassFinder classFinder;
    private String uspsAisPackage;

    public void setClassFinder(ClassFinder classFinder) {
        this.classFinder = classFinder;
    }

    public void setUspsAisPackage(String uspsAisPackage) {
        this.uspsAisPackage = uspsAisPackage;
    }

    /**
     * For a given raw USPS data file record. This will return the target USPSRecord
     * class that it should be applied against
     * 
     * @param record
     * @return USPSRecord class 
     * @throws Exception
     */
    public Class<? extends USPSRecord> getClassForRawRecord(String record) throws Exception {
        return getClassForRawRecord(record.substring(0, 1), record.length());
    }

    /**
     * Determine if the given fieldName exists within the target USPSRecord class
     * @param clazz
     * @param fieldName
     * @return true/false
     */
    public boolean fieldExists(Class<? extends USPSRecord> clazz, String fieldName) {
        if (this.declaredFieldNamesAsSetCache.get(clazz) == null) {
            synchronized (this.declaredFieldNamesAsSetCache) {
                if (this.declaredFieldNamesAsSetCache.get(clazz) == null) {
                    this.getAllDeclaredFieldNames(clazz);
                    for (Class<? extends USPSRecord> c : this.declaredFieldNamesCache.keySet()) {
                        Set<String> x = new HashSet<String>();
                        for (String fname : this.declaredFieldNamesCache.get(c)) {
                            x.add(fname);
                        }

                        this.declaredFieldNamesAsSetCache.put(c, x);
                    }
                }
            }
        }

        return this.declaredFieldNamesAsSetCache.get(clazz).contains(fieldName);
    }

    /**
     * For a given raw USPS data file record. This will return the target USPSRecord
     * class that it should be applied against
     * 
     * @param record
     * @return USPSRecord class 
     * @throws Exception
     */
    public Class<? extends USPSRecord> getClassForRawRecord(String copyrightDetailCode, int recordLength)
            throws Exception {
        String key = copyrightDetailCode + recordLength;
        Class<? extends USPSRecord> targetClass = recordLengthMap.get(key);

        if (targetClass == null) {
            synchronized (recordLengthMap) {
                targetClass = recordLengthMap.get(key);
                if (targetClass == null) {
                    USPSProductType type = USPSProductType.getTypeForRawRecord(recordLength);
                    Map<CopyrightDetailCode, Class<? extends USPSRecord>> map = this
                            .getTargetUSPSRecordClasses(type);
                    for (CopyrightDetailCode cdc : map.keySet()) {
                        Class cz = map.get(cdc);
                        recordLengthMap.put(cdc.toString() + recordLength, cz);
                    }

                    targetClass = recordLengthMap.get(key);
                }

            }
        }

        return targetClass;
    }

    /**
     * Get all Classes that can handle USPSRecord data
     * 
     * @return
     * @throws Exception
     */
    public Set<Class> getUSPSRecordClasses() throws Exception {
        return this.classFinder.findByTypeAnnotation(USPSRecordContext.class, this.uspsAisPackage);
    }

    /**
     * Given a Class and an List, this method will
     * populate the given List with all Field's for that Class and
     * all of its parent Classes in the inheritance tree
     * 
     * @param fields
     * @param type the Class to check
     * @param stopAtClass, if the current class is equal to this, we go no further up the parent chains
     * @param   annotationClass the class of the annotation which must be present in 
     */
    private void populateAllDeclaredFields(List<Field> fields, Class type, Class stopAtClass,
            Class<? extends Annotation> annotationClass) {
        for (Field field : type.getDeclaredFields()) {

            if (annotationClass != null) {
                if (field.isAnnotationPresent(annotationClass)) {
                    fields.add(field);
                }
            } else {
                fields.add(field);
            }

            if (field.getType().isEnum()) {
                ConvertUtils.register(new GenericEnumConverter(), field.getType());
            }

        }

        // this is as far as we go...
        if (stopAtClass != null && stopAtClass == type) {
            return;
        }

        if (type.getSuperclass() != null) {
            populateAllDeclaredFields(fields, type.getSuperclass(), stopAtClass, annotationClass);
        }
    }

    /**
     * Return all Fields in the given class [type] that have the given
     * annotation present for the field
     * 
     * @param type
     * @param annotationClass
     * @return List of Fields annotated by annotationClass within in the passed clazz
     */
    public List<Field> getFieldsByAnnotation(Class clazz, Class<? extends Annotation> annotationClass) {

        // get the list of all Fields known in this class (and parents)
        String lookupKey = clazz.getName() + annotationClass.getName();
        List<Field> allFields = class2AnnotatedFieldCache.get(lookupKey);

        if (allFields == null) {
            synchronized (class2AnnotatedFieldCache) {
                allFields = class2AnnotatedFieldCache.get(lookupKey);
                if (allFields == null) {
                    allFields = new ArrayList<Field>();
                    populateAllDeclaredFields(allFields, clazz, null, annotationClass);
                    class2AnnotatedFieldCache.put(lookupKey, allFields);
                }
            }
        }

        return allFields;
    }

    /**
     * For the given USPSRecord class return an Array 
     * of all field/property names declared within the USPSRecord class,
     * going no farther than the parent USPSRecord.class
     * 
     * @param clazz
     * @return String array of key fields for the class
     */
    public Field[] getAllDeclaredFields(Class<? extends USPSRecord> clazz) {
        Field[] allFields = declaredFieldsCache.get(clazz);

        if (allFields == null) {
            synchronized (declaredFieldsCache) {
                allFields = declaredFieldsCache.get(clazz);

                if (allFields == null) {
                    ArrayList<Field> tmpFields = new ArrayList<Field>();
                    populateAllDeclaredFields(tmpFields, clazz, USPSRecord.class, null);
                    allFields = tmpFields.toArray(new Field[1]);
                    declaredFieldsCache.put(clazz, allFields);

                }
            }
        }

        return allFields;
    }

    /**
     * For the given USPSRecord class return an Array 
     * of all field/property names declared within the USPSRecord class,
     * going no farther than the parent USPSRecord.class
     * 
     * @param clazz
     * @return String array of key fields for the class
     */
    public String[] getAllDeclaredFieldNames(Class<? extends USPSRecord> clazz) {
        String[] fieldNames = declaredFieldNamesCache.get(clazz);

        if (fieldNames == null) {
            synchronized (declaredFieldNamesCache) {
                fieldNames = declaredFieldNamesCache.get(clazz);

                if (fieldNames == null) {
                    Field[] allFields = getAllDeclaredFields(clazz);

                    fieldNames = new String[allFields.length];

                    for (int i = 0; i < allFields.length; i++) {
                        fieldNames[i] = allFields[i].getName();
                    }

                    declaredFieldNamesCache.put(clazz, fieldNames);
                }
            }
        }

        return fieldNames;
    }

    /**
     * For the given USPSRecord class return an Array 
     * of all field/property names that have are annotated
     * by the USPSIdentifierField annotation
     * 
     * @param clazz
     * @return String array of key fields for the class
     */
    public String[] getIdentifierFieldNames(Class<? extends USPSRecord> clazz) {
        String[] fieldNames = idFieldCache.get(clazz);

        if (fieldNames == null) {
            synchronized (clazz) {
                if (fieldNames == null) {
                    List<Field> idFields = getFieldsByAnnotation(clazz, USPSIdentifierField.class);
                    fieldNames = new String[idFields.size()];

                    for (int i = 0; i < idFields.size(); i++) {
                        fieldNames[i] = idFields.get(i).getName();
                    }

                    idFieldCache.put(clazz, fieldNames);
                }
            }
        }

        return fieldNames;
    }

    /**
     * For the given USPSRecord class return an Array 
     * of all field/property names that have are annotated
     * by the USPSIdentifierField annotation
     * 
     * @param record
     * @return String array of key fields for the class
     */
    public String[] getIdentifierFieldNames(USPSRecord record) {
        Class<? extends USPSRecord> clazz = record.getClass();
        return getIdentifierFieldNames(clazz);
    }

    /**
     * For the given USPSRecord class return an Array 
     * of all field/property names that have are annotated
     * by the USPSKeyField annotation
     * 
     * @param clazz
     * @return String array of key fields for the class
     */
    public String[] getKeyFieldNames(Class<? extends USPSRecord> clazz) {
        String[] fieldNames = keyFieldCache.get(clazz);

        if (fieldNames == null) {
            synchronized (clazz) {
                if (fieldNames == null) {
                    List<Field> keyFields = getFieldsByAnnotation(clazz, USPSKeyField.class);
                    fieldNames = new String[keyFields.size()];

                    for (int i = 0; i < keyFields.size(); i++) {
                        fieldNames[i] = keyFields.get(i).getName();
                    }

                    keyFieldCache.put(clazz, fieldNames);
                }
            }
        }

        return fieldNames;
    }

    /**
     * For the given USPSRecord class return an Array 
     * of all field/property names that have are annotated
     * by the USPSKeyField annotation
     * 
     * @param record
     * @return String array of key fields for the class
     */
    public String[] getKeyFieldNames(USPSRecord record) {
        Class<? extends USPSRecord> clazz = record.getClass();
        return getKeyFieldNames(clazz);
    }

    /**
     * Retrieve a Set of CopyrightDetailCodes -> USPSRecord classes for the given USPSProductType.
     * 
     * @param productType
     * @return a map that contains the mapping between CopyrightDetailCodes and their target USPSRecord class for the passed USPSProductType
     * 
     * @throws Exception
     */
    public Map<CopyrightDetailCode, Class<? extends USPSRecord>> getTargetUSPSRecordClasses(
            USPSProductType productType) throws Exception {

        // see if the map already exists for this product type
        Map<CopyrightDetailCode, Class<? extends USPSRecord>> classesForProduct = productTypeMapCache
                .get(productType);

        // if it does not exist
        if (classesForProduct == null) {

            synchronized (productTypeMapCache) {
                try {
                    // create container for the product type
                    classesForProduct = new Hashtable<CopyrightDetailCode, Class<? extends USPSRecord>>();

                    // located all classes which are annotated by @USPSProduct
                    Set<Class> productClasses = classFinder.findByTypeAnnotation(USPSRecordContext.class,
                            this.uspsAisPackage);

                    // for each class, determine if the requested USPSProductType is supported by the given class
                    for (Class clazz : productClasses) {

                        // huh? misconfig, USPSRecordContext annotations should only be used on derivatives of USPSRecord targets
                        if (!clazz.getSuperclass().equals(USPSRecord.class)
                                && !clazz.getSuperclass().equals(CopyrightedUSPSRecord.class)) {
                            throw new Exception("Class: " + clazz.getName()
                                    + " is annotated by @USPSRecordContext. Only derivatives of USPSRecord "
                                    + " can be annotated with @USPSRecordContext ");
                        }

                        // get the annotation
                        USPSRecordContext prodInfo = (USPSRecordContext) clazz
                                .getAnnotation(USPSRecordContext.class);

                        // if this class is applicable for the passed USPSProductType, create a map entry for it by 
                        // the annotations target copyright detail code
                        for (USPSProductType permissableType : prodInfo.productTypes()) {
                            CopyrightDetailCode cdc = prodInfo.copyrightDetailCode();
                            if (permissableType == productType) {

                                // already exists? this is a mis-config
                                if (classesForProduct.get(cdc) != null) {
                                    throw new Exception(
                                            "Mis-configuration: There are one or more classes that are annotated with @USPSRecordContext for the combination of USPSProductType: "
                                                    + productType.name() + " and CopyrightDetailCode of: "
                                                    + cdc.name());
                                } else {
                                    // add to the map, keyed by the CopyrightDetailCode
                                    classesForProduct.put(cdc, clazz);
                                }
                            }
                        }
                    }

                    // some other error
                } catch (Exception e) {
                    throw new Exception(
                            "There was a critical error locating USPSRecord classes annotated with USPSRecordContext...error:"
                                    + e.getMessage());
                }
            }
        }

        // return the map of detail codes -> classes which handle it
        return classesForProduct;

    }

}