/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.sys.batch;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.log4j.Logger;
/**
* Class which tracks the current state of parsing - particularly which object should currently be parsed into
*/
public class FlatFileParseTrackerImpl implements FlatFileParseTracker {
static Logger LOG = Logger.getLogger(FlatFileParseTrackerImpl.class);
protected FlatFileSpecification classIdentifier;
protected Stack<Object> parseStack;
protected List<Object> parsedParentObjects;
protected Map<Class<?>, FlatFileChildMapEntry> childrenMap;
protected int completedLineCount = 0;
/**
* Initializes a new FlatFileParseTracker
* @param flatFileSpecification the FlatFileSpecificationBase instance which will determine which object should be instantiated for a given line
* @param specifications the specifications for all objects that will be parsed into, to build a parent/child map out of
*/
@Override
public void initialize(FlatFileSpecification flatFileClassIdentifier) {
this.classIdentifier = flatFileClassIdentifier;
this.parseStack = new Stack<Object>();
this.parsedParentObjects = new ArrayList<Object>();
constructChildrenMap();
}
/**
* Builds a parent/child map out of the given specifications
* @param specifications the specifications for the parse
*/
protected void constructChildrenMap() {
childrenMap = new HashMap<Class<?>, FlatFileChildMapEntry>();
for (FlatFileObjectSpecification specification : classIdentifier.getObjectSpecifications()) {
if (specification.getParentBusinessObjectClass() != null) {
final FlatFileChildMapEntry entry = new FlatFileChildMapEntry(specification.getParentBusinessObjectClass(), specification.getParentTargetProperty());
childrenMap.put(specification.getBusinessObjectClass(), entry);
}
}
}
/**
* Determines which class should be parsed into and returns an instance of that
* @param lineToParse the line which is going to be parsed
* @return the object to parse into
*/
@Override
public Object getObjectToParseInto(String lineToParse) {
final Class<?> lineClass = classIdentifier.determineClassForLine(lineToParse);
if (lineClass == null) {
// the prefix was insignificant; skip it
return null;
}
try {
Object parseIntoObject = lineClass.newInstance();
parseStack.push(parseIntoObject);
return parseIntoObject;
} catch (InstantiationException ie) {
throw new RuntimeException("Could not instantiate object of class "+lineClass.getName()+" in FlatFileParse", ie);
} catch (IllegalAccessException iae) {
throw new RuntimeException("Illegal access attempting to instantiate object of class "+lineClass.getName()+" in FlatFileParse", iae);
}
}
/**
* Called when a line has completed parsing. Throws an exception if a proper parent
* is not found for the line being parsed
*/
@Override
@SuppressWarnings("unchecked")
public void completeLineParse() {
completedLineCount += 1;
if (LOG.isDebugEnabled()) {
LOG.debug("Completing parse of line: "+completedLineCount);
}
Object currentObject = parseStack.pop();
final FlatFileChildMapEntry entry = getEntryForParsedIntoObject(currentObject);
final Class<?> parentClass = (entry == null) ? null : entry.getParentBeanClass();
final String propertyName = (entry == null) ? null : entry.getPropertyName();
while (!parseStack.isEmpty()) {
Object checkingObject = parseStack.pop();
if (parentClass != null && parentClass.isAssignableFrom(checkingObject.getClass())) {
try {
if (Collection.class.isAssignableFrom(PropertyUtils.getPropertyType(
checkingObject, propertyName))) {
Collection childrenList = ((Collection) PropertyUtils.getProperty(
checkingObject, propertyName));
childrenList.add(currentObject);
} else {
PropertyUtils.setProperty(checkingObject, propertyName,currentObject);
}
parseStack.push(checkingObject);
parseStack.push(currentObject);
return;
} catch (Exception e) {
LOG.error(e.getMessage() + "occured when completing line parse; attempting to set object of type "+currentObject.getClass().getName()+" to the following parent: "+parentClass.getName()+"#"+propertyName, e);
throw new RuntimeException(e.getMessage() + "occured when completing line parse; attempting to set object of type "+currentObject.getClass().getName()+" to the following parent: "+parentClass.getName()+"#"+propertyName,e);
}
}
}
if (parentClass == null) {
parseStack.push(currentObject);
parsedParentObjects.add(currentObject);
} else {
throw new IllegalStateException("A line of class "+currentObject.getClass().getName()+" cannot exist without a proper parent");
}
}
/**
* Looks up the FlatFileChildMapEntry for the given object
* @param parsedIntoObject the object which has just completed being parsed into
* @return the FlatFileChildMapEntry which has the given object as a child object, or null if the object is a base object
*/
public FlatFileChildMapEntry getEntryForParsedIntoObject(Object parsedIntoObject) {
final FlatFileChildMapEntry entry = childrenMap.get(parsedIntoObject.getClass());
return entry;
}
/**
* @return the List of parsed parent objects
*/
@Override
public List<Object> getParsedObjects() {
return parsedParentObjects;
}
/**
* Inner class to make holding parent/child relationships easier
*
*/
private class FlatFileChildMapEntry {
protected Class<?> parentBeanClass;
protected String propertyName;
public FlatFileChildMapEntry() {
super();
}
public FlatFileChildMapEntry(Class<?> parentBeanClass, String propertyName) {
this();
this.parentBeanClass = parentBeanClass;
this.propertyName = propertyName;
}
public Class<?> getParentBeanClass() {
return parentBeanClass;
}
public String getPropertyName() {
return propertyName;
}
}
}