package com.framsticks.parsers; import com.framsticks.params.*; import org.apache.log4j.Logger; import java.io.FileNotFoundException; import java.io.IOException; import java.util.*; public class MultiParamLoader { private final static Logger logger = Logger .getLogger(MultiParamLoader.class.getName()); /** * The class which name was recently found in the file, to which execution * should be passed. */ public AccessInterface getLastAccessInterface() { return lastAccessInterface; } /** TODO does it support multilines? */ /** * Specifies the condition to break execution. */ public enum Status { None, Finished, BeforeObject, AfterObject, BeforeUnknown, OnComment, OnError, Loading } /** * Specifies the action that should be taken inside loops. */ private enum LoopAction { Nothing, Break, Continue } protected AccessInterface lastAccessInterface; /** * Empty Param representing unknown classes - used to omit unknown * objects in the file. */ protected AccessInterface emptyParam = new PropertiesAccess(new FramsClass()); /** * Last comment found in the file. */ protected String lastComment; /** * Set of break conditions. */ private EnumSet breakConditions = EnumSet.of(Status.None); /** * Status of current execution. */ private Status status; /** * File from which data should be read. */ private SourceInterface currentSource; /** * All the files read by the loader (there could be many of them because of * '#|include'). */ private Stack fileStack = new Stack(); /** * A map that specifies connection between the getName of the file and the * actual reader. */ private Map fileMap = new HashMap(); /** * List of known classes. */ private Map knownParamInterfaces = new HashMap(); /** * Last unknown object found in the file. */ private String lastUnknownObjectName; public MultiParamLoader() { } /** * Starts reading the file. */ public Status go() throws Exception { logger.trace("go"); while (!isFinished()) { // check if we are before some known or unknown object LoopAction loopAction = tryReadObject(); if (loopAction == LoopAction.Break) { break; } else if (loopAction == LoopAction.Continue) { continue; } // read data String line = currentSource.readLine(); // end of file if (line == null) { if (!returnFromIncluded()) { finish(); break; } else { continue; } } // empty line if (line.length() == 0) { continue; } // check if some file should be included if (isIncludeLine(line) == LoopAction.Continue) { continue; } // check if should break on comment if (isCommentLine(line) == LoopAction.Break) { break; } // get class getName if (changeCurrentParamInterface(line) == LoopAction.Break) { break; } } return status; } /** * Checks whether the reader found a known or unknown object and execution * should be passed to it. */ private LoopAction tryReadObject() throws Exception { if (status == Status.BeforeObject || (status == Status.BeforeUnknown && lastAccessInterface != null)) { // found object - let it load data if (lastAccessInterface.getSelected() == null) { lastAccessInterface.select(lastAccessInterface.createAccessee()); } lastAccessInterface.load(currentSource); if (isBreakCondition(Status.AfterObject)) { // break after object creation return LoopAction.Break; } return LoopAction.Continue; } else if (status == Status.BeforeUnknown) { logger.info("Omitting unknown object: " + lastUnknownObjectName); // found unknown object emptyParam.load(currentSource); status = Status.AfterObject; return LoopAction.Continue; } return LoopAction.Nothing; } /** * Checks whether some additional file shouldn't be included. */ private LoopAction isIncludeLine(String line) throws FileNotFoundException { try { // found comment if (line.charAt(0) == '#') { // maybe we should include something if (line.substring(1, 8).equals("include")) { int beg = line.indexOf('\"'); if (beg == -1) { logger.info("Wanted to include some file, but the format is incorrect"); return LoopAction.Continue; } String includeFileName = line.substring(beg + 1); int end = includeFileName.indexOf('\"'); if (end == -1) { logger.info("Wanted to include some file, but the format is incorrect"); return LoopAction.Continue; } includeFileName = includeFileName.substring(0, end); include(includeFileName); return LoopAction.Continue; } } } catch (IndexOutOfBoundsException ex) { // value after # sign is shorter than expected 7 characters - do // nothing } return LoopAction.Nothing; } /** * Checks whether execution shouldn't break on comment. */ private LoopAction isCommentLine(String line) { if (line.charAt(0) == '#' && isBreakCondition(Status.OnComment)) { // it's a simple comment - maybe we should break? lastComment = line; return LoopAction.Break; } return LoopAction.Nothing; } /** * Gets the getName of the class from line read from file. */ private LoopAction changeCurrentParamInterface(String line) { // found key - value line if (line.charAt(line.length() - 1) == ':') { String typeName = line.substring(0, line.length() - 1); lastAccessInterface = knownParamInterfaces.get(typeName); if (lastAccessInterface != null) { if (isBreakCondition(Status.BeforeObject)) { logger.info("Breaking before object"); return LoopAction.Break; } } else { lastUnknownObjectName = typeName; if (isBreakCondition(Status.BeforeUnknown)) { logger.info("Breaking before unknown"); return LoopAction.Break; } } } return LoopAction.Nothing; } /** * Adds another break condition. */ public void addBreakCondition(Status condition) { breakConditions.add(condition); } /** * Removes break condition. */ public void removeBreakCondition(Status condition) { breakConditions.remove(condition); } /** * Adds another class. */ public void addAccessInterface(AccessInterface accessInterface) { /**TODO: by id or by name? rather by id, because from file is always lowercase*/ knownParamInterfaces.put(accessInterface.getId(), accessInterface); } /** * Checks whether execution is finished. */ private boolean isFinished() { return (status == Status.Finished); } private void finish() { if (currentSource != null) { currentSource.close(); } status = Status.Finished; } /** * Opens selected file. */ public boolean setNewSource(SourceInterface source) { logger.debug("switching current source to " + source.getFilename() + "..."); currentSource = source; status = Status.Loading; return true; } /** * Includes specified file. */ private void include(String includeFilename) { includeFilename = currentSource.demangleInclude(includeFilename); if (includeFilename == null) { return; } // check if it is already included and break if it is if (isAlreadyIncluded(includeFilename)) { logger.debug("circular reference ignored (" + includeFilename + ")"); return; } logger.info("including file " + includeFilename + "..."); SourceInterface newSource = currentSource.openInclude(includeFilename); if (newSource == null) { return; } fileStack.add(currentSource.getFilename()); fileMap.put(currentSource.getFilename(), currentSource); setNewSource(newSource); } /** * Checks whether selected file was already included. */ private boolean isAlreadyIncluded(String filename) { for (String file : fileStack) { if (filename.equals(file)) { logger.warn("file " + filename + " was already included"); return true; } } return false; } /** * Returns from included file. */ private boolean returnFromIncluded() throws IOException { if (fileStack.size() == 0) { return false; } if (currentSource != null) { currentSource.close(); } String filename = fileStack.pop(); currentSource = fileMap.get(filename); fileMap.remove(filename); return true; } /** * Checks whether execution should break on selected condition. */ private boolean isBreakCondition(Status condition) { status = condition; return breakConditions.contains(condition); } public Object returnObject() { Object result = lastAccessInterface.getSelected(); if (result == null) { return null; } lastAccessInterface.select(null); return result; } }