public class TerminalEvent extends EventObject {

	//--------------------------------------------------------------------------
	// constants
	//--------------------------------------------------------------------------

	/** represents key F1 */
	public final static int KEY_F01 = -1;

	/** represents key F2 */
	public final static int KEY_F02 = -2;

	/** represents key F3 */
	public final static int KEY_F03 = -3;

	/** represents key F4 */
	public final static int KEY_F04 = -4;

	/** represents key F5 */
	public final static int KEY_F05 = -5;

	/** represents key F6 */
	public final static int KEY_F06 = -6;

	/** represents key F7 */
	public final static int KEY_F07 = -7;

	/** represents key F8 */
	public final static int KEY_F08 = -8;

	/** represents key F9 */
	public final static int KEY_F09 = -9;

	/** represents key F10 */
	public final static int KEY_F10 = -10;

	/** represents key F11 */
	public final static int KEY_F11 = -11;

	/** represents key F12 */
	public final static int KEY_F12 = -12;

	/** represents key Enter */
	public final static int KEY_ENTER = -100;

	/** represents key Tabulator */
	public final static int KEY_TAB = -101;

	/** represents key-komination Shift+Tabulator */
	public final static int KEY_TAB_BACK = -102;

	/** represents key Pause (usually only on pc-keyboard present) */
	public final static int KEY_PAUSE = -103;

	/** represents key Backspace */
	public final static int KEY_BACKSPACE = -104;

	/** represents key Cursor Up */
	public final static int KEY_UP = -200;

	/** represents key Cursor Down */
	public final static int KEY_DOWN = -201;

	/** represents key Cursor Right */
	public final static int KEY_RIGHT = -202;

	/** represents key Cursor Left */
	public final static int KEY_LEFT = -203;

	/** represents key Insert */
	public final static int KEY_PASTE = -300;

	/** represents key Delete */
	public final static int KEY_DEL = -301;

	/** represents key Home (Pos1) */
	public final static int KEY_HOME = -302;

	/** represents key End */
	public final static int KEY_END = -303;

	/** represents key Page Up */
	public final static int KEY_PAGE_UP = -304;

	/** represents key Page Down */
	public final static int KEY_PAGE_DOWN = -305;

	/** Representiert eine unbekannte und undefineirte Taste */
	public final static int KEY_UNDEFINED = -10000;

	//--------------------------------------------------------------------------
	// class variables
	//--------------------------------------------------------------------------

	/** log4j reference */
	public final static Logger log = Logger.getLogger(Configuration.class);

	//--------------------------------------------------------------------------
	// instance variables
	//--------------------------------------------------------------------------

	/** flag indicates if event represents barcode */
	private boolean barcode = false;

	/** data of barcode (filled only if condition <code>barcode == true</code> */
	private String barcodeString;

	/** Code for a pressed key */
	private int key = KEY_UNDEFINED;

	/** indicates if this event allready is evaluated */
	private boolean consumed = false;


	//--------------------------------------------------------------------------
	// constructors
	//--------------------------------------------------------------------------

	/** Creates an TerminalEvent object as barcode-representation */
	public TerminalEvent(Object source, String barcode) {
		super(source);
		log.debug("Creating barcode-instance of TerminalEvent");
		this.barcode = true;
		barcodeString = barcode;
	}


	/** Creates an TerminalEvent object as key-representation */
	public TerminalEvent(Object source, int key) {
		super(source);
		log.debug("Creating key-instance of TerminalEvent");
		this.key = key;
	}


	/**
	 * Creates an TerminalEvent object as key-representation (mostly for
	 * printable characters)
	 */
	public TerminalEvent(Object source, char key) {
		super(source);
		log.debug("Creating char-instance of TerminalEvent");
		this.key = key;
	}


	//--------------------------------------------------------------------------
	// public methods
	//--------------------------------------------------------------------------

	/** Tells us, if this event represents an barcode */
	public boolean isBarcode() {
		return barcode;
	}


	/**
	 * Returns the barcode (only if this event is a barcode, elsewise an empty
	 * String)
	 */
	public String getBarcode() {
		if (barcodeString != null) {
			return barcodeString;
		}

		return "";
	}


	/** Returns the code of an pressed key (only of no barcode) */
	public int getKey() {
		return key;
	}


	/** Gibt bei druckbaren Zeichen dieses als String zur�ck */
	public String getKeyAsString() {
		if (!isPrintable()) {
			return "";
		}
		return String.valueOf((char)key);
	}


	/** Returns the key charater if it is printable, elsewise an space character */
	public char getKeyAsChar() {
		if (!isPrintable()) {
			return ' ';
		}
		return (char)key;
	}


	/** Tells us, if this character is printable */
	public boolean isPrintable() {
		return key > 0;
	}


	/** Tells us, if this event represents a functionkey (Fnn) */
	public boolean isFunctionKey() {
		return (key <= KEY_F01) && (key >= KEY_F12);
	}


	/** Returns if the event is allready evaluated */
	public boolean isConsumed() {
		return consumed;
	}


	/** Evaluated (Consumes) the event. */
	public void consume() {
		consumed = true;
	}

} public interface TerminalEventListener extends EventListener {

	//--------------------------------------------------------------------------
	// public methods
	//--------------------------------------------------------------------------

	/** event barcode is scanned */
	public void barcodeReceived(TerminalEvent e);


	/** event key is pressed */
	public void keyPressed(TerminalEvent e);

} public interface TerminalIOInterface {

	//--------------------------------------------------------------------------
	// constants
	//--------------------------------------------------------------------------

	/** represents the color Black */
	public final static int COLOR_BLACK = 1;

	/** represents the color red */
	public final static int COLOR_RED = 2;

	/** represents the color green */
	public final static int COLOR_GREEN = 3;

	/** represents the color yellow */
	public final static int COLOR_YELLOW = 4;

	/** represents the color blue */
	public final static int COLOR_BLUE = 5;

	/** represents the color magenta */
	public final static int COLOR_MAGENTA = 6;

	/** represents the color cyan */
	public final static int COLOR_CYAN = 7;

	/** represents the color white */
	public final static int COLOR_WHITE = 8;

	/** direction of the cursor on relative movement: up */
	public final static int CURSOR_DIRECTION_UP = 1;

	/** direction of the cursor on relative movement: down */
	public final static int CURSOR_DIRECTION_DOWN = 2;

	/** direction of the cursor on relative movement: right */
	public final static int CURSOR_DIRECTION_RIGHT = 3;

	/** direction of the cursor on relative movement: left */
	public final static int CURSOR_DIRECTION_LEFT = 4;

	/** on line operations: horizontal alignment */
	public final static int ORIENTATION_HORIZONTAL = 1;

	/** on line operations: vertical alignment */
	public final static int ORIENTATION_VERTIKAL = 2;


	//--------------------------------------------------------------------------
	// public methods
	//--------------------------------------------------------------------------

	/** absolut cursor movement on a terminal display */
	public void cursorMoveAbsolut(int line, int column) throws IOException;


	/** relative cursor movement on a terminal display */
	public void cursorMoveRelative(int direction, int steps) throws IOException;


	/** writes <code>text</code> to the current cursor position */
	public void writeText(String text) throws IOException;


	/** writes <code>text</code> to the specific position */
	public void writeText(String text, int posLine, int posColumn) throws IOException;


	/** writes <code>text</code> invers to the current cursor position */
	public void writeInverseText(String text) throws IOException;


	/** writes <code>text</code> invers to the specific position */
	public void writeInverseText(String text, int posLine, int posColumn) throws IOException;


	/** sets the bold text style */
	public void setBold(boolean bold) throws IOException;


	/** sets the underline text style */
	public void setUnderlined(boolean underlined) throws IOException;


	/** sets the blink text style */
	public void setBlink(boolean blink) throws IOException;


	/** sets the invers text style */
	public void setReverse(boolean reverse) throws IOException;


	/** sets the foreground color according to it's constants */
	public void setForeground(int color) throws IOException;


	/** sets the background color according to it's constants */
	public void setBackground(int color) throws IOException;


	/** creates manually line break */
	public void newLine() throws IOException;


	/** clears the screen */
	public void clearScreen() throws IOException;


	/** draws a line */
	public void drawLine(int line, int column, int orientation, int length) throws IOException;


	/** draws an not filled rectangle */
	public void drawRectangle(int fromLine, int fromColumn, int toLine, int toColumn) throws IOException;


	/** clears a line */
	public void clearLine(int line, int column, int orientation, int length) throws IOException;


	/** writes content of the writer buffer */
	public void flush() throws IOException;


	/** closes the IO */
	public void close() throws IOException;


	/** Returns the internet-adress associated with the terminal */
	public InetAddress getInetAdress();


	/** Returns the port associated with the terminal */
	public int getPort();


	/** reads the next event from the reader */
	public TerminalEvent readNextEvent() throws IOException;


	/** Makes an errortone */
	public void beepError() throws IOException;


	/** Makes an errortone */
	public void beepError(int number) throws IOException;

	/** @todo public void enableLED(int led) throws IOException; */
	/** @todo public void disableLEDs() throws IOException; */

	/** @todo public static int getWidth(); */
	/** @todo public static int getHeight(); */
} public interface GlobalObject {

	//--------------------------------------------------------------------------
	// public methods
	//--------------------------------------------------------------------------

	/**
	 * This method is called when Jalita is going to be closed and
	 * should be implemted if some finalisation should be done.
	 */
	public void finish();

} public class Session implements Runnable {

	//--------------------------------------------------------------------------
	// class variables
	//--------------------------------------------------------------------------

	/** log4j reference */
	public final static Logger log = Logger.getLogger(Configuration.class);

	/** Jalita configuration-properties */
	private static Configuration config = Configuration.getConfiguration();

	private final Map attributes = new HashMap();

	//--------------------------------------------------------------------------
	// instance variables
	//--------------------------------------------------------------------------

	/** Access to an terminal */
	private TerminalIOInterface io;

	/** Associated global SessionManager */
	private final SessionManager sessionManager = SessionManager.getSessionManager();

	/**
	 * FormManager, which administrates an stack of FormAutomationSets and other
	 * elements
	 */
	private FormManager formManager;

	/** true if session is closed normally */
	private boolean finished = false;

	/** list of TerminalEvents objects */
	private final Vector eventListener = new Vector(1, 1);

	/** time index of last event happend. used to catch timeouts */
	private long lastActivity;

	/** the user defined object, associated with this session */
	private SessionObject sessionObject = null;


	//--------------------------------------------------------------------------
	// constructors
	//--------------------------------------------------------------------------

	/** Creates a new Session object */
	public Session(Socket socket) {
		log.debug("Creating instance of Session");
		bindSocket(socket);
		formManager = new FormManager(this);
		addTerminalEventListener(formManager);
		lastActivity = System.currentTimeMillis();
	}


	public Session(Session session) {

	}


	//--------------------------------------------------------------------------
	// private & protected methods
	//--------------------------------------------------------------------------

	/** Inform listeners about recived barcode */
	private void fireBarcodeEvent(TerminalEvent event) {
		for (int i = 0; i < eventListener.size(); i++) {
			((TerminalEventListener)eventListener.elementAt(i)).barcodeReceived(event);
		}
	}


	/** Inform listeners about recived key */
	private void fireKeyEvent(TerminalEvent event) {
		for (int i = 0; i < eventListener.size(); i++) {
			((TerminalEventListener)eventListener.elementAt(i)).keyPressed(event);
		}
	}


	//--------------------------------------------------------------------------
	// public methods
	//--------------------------------------------------------------------------

	/**
	 * Binds a socket to this Session, create therewith a new TerminalIO and
	 * informs
	 * the FormManager. Furthermore an existing but eventually not closed
	 * Session,
	 * thru e.g. time out, will be closed (with the sameIP).
	 */
	public void bindSocket(Socket socket) {
		lastActivity = System.currentTimeMillis();

		if (io != null) {
			sessionManager.closeBrokenSession(this);
		}

		try {
			/**
			 * TODO negotiate terminal type here later. maybe better in the
			 * Dispatcher transmitted thru via int consts?
			 */
			io = new VT100TerminalIO(socket);

			// redraw
			try {
				// io.clearScreen();
			}
			catch (Exception ex) {
				// ignore..
			}

			// inform formManager
			if (formManager != null) {
				formManager.ioChanged();
			}
		}
		catch (IOException ex) {
			handleIOException(ex);
		}
	}


	/** Starts the Session as seperate Thread */
	public void startSession() {
		lastActivity = System.currentTimeMillis();

		Thread sessionThread = new Thread(this, io.toString());
		sessionThread.start();
	}


	/**
	 * Returns the current IO as String, or as "broken IO" if Connection is
	 * interrupted
	 */
	@Override
	public String toString() {
		if (io != null) {
			return io.toString();
		}

		return "brocken IO";
	}


	/** Returns the current IO from the terminal */
	public TerminalIOInterface getIO() {
		return io;
	}


	/**
	 * Shuts the Session down, informs the FormManager, which in turn informs
	 * the finite automatons.
	 * At the end the session object will be finished
	 */
	public void finish() {
		finished = true;

		try {
			io.close();
		}
		catch (IOException ex) {
			// ignore, because lokalIO.readNextEvent() in run() throws an exception
		}

		formManager.finish();

		if (sessionObject != null) {
			sessionObject.finish();
		}
	}


	/** Tells if the Session is allready finished */
	public boolean isFinished() {
		return finished;
	}


	/** Calculates if there is an timeOut of this Session */
	public boolean isTimeout() {
		return lastActivity < System.currentTimeMillis() - config.getSessionTimeOut();
	}


	/**
	 * Registers a TerminalEventListener, which can respond to barcodes and keys
	 */
	public void addTerminalEventListener(TerminalEventListener listener) {
		eventListener.add(listener);
	}


	/** Removes a TerminalEventListener */
	public void removeTerminalEventListener(TerminalEventListener listener) {
		eventListener.remove(listener);
	}


	/**
	 * Responds commonly to an IOException during an existing connection, to
	 * remove this
	 */
	public void handleIOException(Exception ex) {
		sessionManager.registerBrokenSession(this.io);
		log.debug(ex);
	}


	/** Sets the sesionObject, which is only in this session visible */
	public void setSessionObject(SessionObject obj) {
		sessionObject = obj;
	}


	/** Returns the SessionObject */
	public SessionObject getSessionObject() {
		return sessionObject;
	}


	//--------------------------------------------------------------------------
	// implementation of the interface Runnable
	//--------------------------------------------------------------------------

	/** This runs the Session as parallel Thread */
	public void run() {
		TerminalIOInterface lokalIO = this.io;
		log.info("Connection created and SessionThread started on Node " + lokalIO);

		boolean broken = false;
		try {
			// endless loop, could only be interrupted by finish() or a TimeOut
			while (true) {
				lastActivity = System.currentTimeMillis();

				TerminalEvent event = lokalIO.readNextEvent();

				if (event == null) {
					// could (should) never happen
					throw new IOException("TerminalEvent was null!");
				}

				if (event.isBarcode()) {
					fireBarcodeEvent(event);
				}
				else {
					fireKeyEvent(event);
				}
			}
		}
		catch (IOException ex) {
			broken = true;
		}

		if (!isTimeout() && !finished) {
			if (broken) {
				log.error("Connection lost on Node " + lokalIO);
				sessionManager.registerBrokenSession(lokalIO);
			}
			else {
				log.info("SessionThread finished on Node " + lokalIO);
				sessionManager.registerClosedSession(this);
			}
		}

		log.info("Session-Thread closed on Node " + lokalIO);
	}


	public void setAttribute(String name, Object value) {
		attributes.put(name.toLowerCase().trim(), value);
	}


	public Object getAttribute(String name) {
		return attributes.get(name.toLowerCase().trim());
	}
} public class SessionManager implements Runnable {

	//--------------------------------------------------------------------------
	// constants
	//--------------------------------------------------------------------------

	/** Lock-object for the cleanup */
	private static final Object cleanupLockObject = new Object();

	//--------------------------------------------------------------------------
	// class variables
	//--------------------------------------------------------------------------

	/** log4j reference */
	public final static Logger log = Logger.getLogger(Configuration.class);

	/** Jalita configuration-properties */
	private static Configuration config = Configuration.getConfiguration();

	/** Singleton-object for the SessionManagers */
	private static SessionManager sessionManager = null;

	/**
	 * Locates the Session via IP.
	 * Key = InetAdress-Object(IP-Adresse); Value = Session-Object
	 * Consider that the Key may change on conversion to IPv6.
	 */
	private static Hashtable sessions = new Hashtable();

	/**
	 * Contains TerminalIOInterface-Objects which are interrupted, but are not
	 * closed
	 */
	private static Vector brokenSessions = new Vector();

	/** Contains Session-Objects wich are cloed, for e.g. by time-out */
	private static Vector closedSessions = new Vector();

	/** Globale SessionObject which all Sessions can access */
	private static GlobalObject globalObject;


	//--------------------------------------------------------------------------
	// class methods
	//--------------------------------------------------------------------------

	/** Returns the Singleton */
	public static SessionManager getSessionManager() {
		if (sessionManager == null) {
			sessionManager = new SessionManager();
		}
		return sessionManager;
	}

	//--------------------------------------------------------------------------
	// instance variables
	//--------------------------------------------------------------------------

	/** Tells us if the cleanup Thread is running in background */
	private boolean cleanupActive = true;


	//--------------------------------------------------------------------------
	// constructors
	//--------------------------------------------------------------------------

	/** Creates a new SessionManager Object */
	private SessionManager() {
		log.debug("Creating instance of SessionManager");

		// Starts the cleanup thread in background, which manages broken and closed connections, ass well as time outs
		Thread cleanupThread = new Thread(this, "SessionManager");
		cleanupThread.start();
	}


	//--------------------------------------------------------------------------
	// private & protected methods
	//--------------------------------------------------------------------------

	/** Validates connections from the Sessions and frees resources */
	private void cleanup() {
		synchronized (cleanupLockObject) {
			checkSessions();
			cleanupBrokenSession();
			cleanupClosedSession();
		}
	}


	/** Clearing Sessions which connections are broken */
	private void cleanupBrokenSession() {
		// to call ".size()" in the loop (processing FIFO)
		boolean brokenObjectsLeft = true;

		// cleanup loop
		while (brokenObjectsLeft) {
			synchronized (brokenSessions) {
				if (brokenSessions.size() > 0) {
					// retrieve next connection
					Object next = brokenSessions.firstElement();

					if (next instanceof TerminalIOInterface) {
						TerminalIOInterface nextIO = (TerminalIOInterface)next;
						log.debug("Closing Socket from broken Session: " + nextIO);

						closeTerminalIO(nextIO);
					}

					// remove from the brokenSession-List, if available
					brokenSessions.removeElement(next);
				}
				else {
					brokenObjectsLeft = false;
				}
			}
		}
	}


	/** Clearing Sessions which are closed normally */
	private void cleanupClosedSession() {
		// cleanup loop
		while (closedSessions.size() != 0) {
			// get a died one
			Session nextOne = (Session)closedSessions.firstElement();
			log.debug("Closing Socket from closed Session: " + nextOne);

			closeTerminalIO(nextOne.getIO());

			nextOne.finish();

			// remove it from the closedSession-List and list of the running Sessions
			closedSessions.removeElementAt(0);
		}
	}


	/**
	 * Validates the Sessions if they are broken (interrupted), closed or have a
	 * timeout.
	 */
	private void checkSessions() {
		Enumeration enumSessions = sessions.elements();
		Enumeration enumKeys = sessions.keys();

		while (enumSessions.hasMoreElements()) {
			Session nextSession = (Session)enumSessions.nextElement();
			String nextKey = (String)enumKeys.nextElement();

			if (nextSession.isTimeout()) {
				log.debug("Session-Timeout for '" + nextSession + "'");
				sessions.remove(nextKey);

				if (nextSession.getIO() != null) {
					brokenSessions.remove(nextSession.getIO());
					registerClosedSession(nextSession);
				}
			}
		}
	}


	/** Closes IO to a terminal. */
	private void closeTerminalIO(TerminalIOInterface io) {
		try {
			io.close();
		}
		catch (IOException ex) {
			log.warn("Could not close TerminalIO: " + io);
		}
	}


	//--------------------------------------------------------------------------
	// public methods
	//--------------------------------------------------------------------------

	/** Adds a new Sesion. One IP is associated to one Session. */
	public void addSession(Socket socket) {
		Session newSession = null;
		String sessionID = socket.getInetAddress().getHostAddress() + ":" + socket.getPort();

		if (config.getServerMaxSessionsPerHost() == 1) {
			sessionID = socket.getInetAddress().getHostAddress();
		}
		Session oldSession = (Session)sessions.get(sessionID);
		int sessionCount = 0;

		// New Session to not registered IP
		if (oldSession == null) {
			for (Iterator<String> iter = sessions.keySet().iterator(); iter.hasNext();) {
				String tmpKey = iter.next();
				if (tmpKey.startsWith(socket.getInetAddress().getHostAddress() + ":")) {
					sessionCount++;
				}
			}
			if (sessionCount >= config.getServerMaxSessionsPerHost()) {
				log.debug("IP '" + sessionID + "' exceeds maximum connections --> drop");
				try {
					socket.close();
				}
				catch (IOException e) {
					//ignore
				}
				return;
			}
			else {
				log.debug("IP '" + sessionID + "' is not registered -> new Session");
				newSession = new Session(socket);
				sessions.put(sessionID, newSession);
			}
		}

		// IP exists, but is closed -> close and create new Session
		else if (oldSession.isFinished()) {
			log.debug("IP '" + sessionID + "' exists, but is closed -> closing and new Session");
			registerClosedSession(oldSession);
			newSession = new Session(socket);
			sessions.put(sessionID, newSession);
		}

		// IP exists, will be reused
		else {
			log.debug("IP '" + sessionID + "' exists -> reusing");
			oldSession.bindSocket(socket);
			newSession = oldSession;
		}

		// Start the new Session (as Thread)
		newSession.startSession();
	}


	public Enumeration sessions() {
		return sessions.elements();
	}


	/**
	 * Closes the broken Session immediately. In difference to
	 * registerBrokenSession
	 * the IO of the Session will not be put in a Queue before closing, this
	 * happens here
	 * immediatly.
	 */
	public void closeBrokenSession(Session session) {
		synchronized (brokenSessions) {
			closeTerminalIO(session.getIO());

			// remove it from the brokenSession-List, if available
			brokenSessions.removeElement(session.getIO());
		}
	}


	/** Placed an interrupted Connection in the broken Session List */
	public void registerBrokenSession(TerminalIOInterface brokenIO) {
		if (!brokenSessions.contains(brokenIO)) {
			brokenSessions.addElement(brokenIO);
		}
	}


	/**
	 * Placed a closed Session in the closed Session List, and removes it from
	 * the running Session List
	 */
	public void registerClosedSession(Session session) {
		sessions.remove(session.getIO().getInetAdress() + ":" + session.getIO().getPort());
		if (!closedSessions.contains(session)) {
			closedSessions.addElement(session);
		}
	}


	/** Shuts all Sessions down */
	public void shutdown() {
		// no automatic cleanup here anymore
		cleanupActive = false;

		// inform sessions (-> IO will be closed, as well as finish() in the SessionObject)
		Enumeration enumSessions = sessions.elements();
		while (enumSessions.hasMoreElements()) {
			Session selectedSession = (Session)enumSessions.nextElement();
			selectedSession.finish();
		}

		// finish global object
		if (globalObject != null) {
			globalObject.finish();
		}

		// execute cleanup last time
		cleanup();
	}


	/** Returns the global object to which all Session have access. */
	public GlobalObject getGlobalObject() {
		return SessionManager.globalObject;
	}


	/** Sets the global object. */
	public void setGlobalObject(GlobalObject globalObject) {
		SessionManager.globalObject = globalObject;
	}


	//--------------------------------------------------------------------------
	// implementation of the interface Runnable
	//--------------------------------------------------------------------------

	/** Cleans broken, closed as well as timed out Connections. */
	public void run() {
		while (cleanupActive) {
			try {
				cleanup();

				try {
					Thread.sleep(config.getServerCleanupInterval());
				}
				catch (InterruptedException ex) {
					log.error(ex);
				}
			}
			catch (Exception ex) {
				log.error(ex);
			}
		}
	}

} public interface SessionObject {

	//--------------------------------------------------------------------------
	// public methods
	//--------------------------------------------------------------------------

	/**
	 * This method is called when the Session will be closed and
	 * should be implemted if some finalisation should be done.
	 * The TerminalIOInterface is allready closed at this moment.
	 */
	public void finish();

} public abstract class FormAutomationSet {

	//--------------------------------------------------------------------------
	// constants
	//--------------------------------------------------------------------------

	/**
	 * This state lets the FormManager remove the current FormAutomationSet from
	 * the stack
	 */
	public final static int STATE_FINISHED = -2;

	/** This state shows the current WaitScreenForm */
	public final static int STATE_WAIT = -3;

	/** This state shows the current ErrorForm */
	public final static int STATE_ERROR = -4;

	/** This state shows the current dialog */
	public final static int STATE_DIALOG = -5;

	/** Carriage Return - LineFeed */
	protected final static String CRLF = "\r\n";

	//--------------------------------------------------------------------------
	// class variables
	//--------------------------------------------------------------------------

	/** log4j reference */
	public final static Logger log = Logger.getLogger(Configuration.class);

	//--------------------------------------------------------------------------
	// instance variables
	//--------------------------------------------------------------------------

	/**
	 * This is the initial state, and should be set in initAutomationSet() with
	 * setInitState(int state)
	 */
	public int STATE_INIT = -1;

	/** Initial automaton state */
	private int state = STATE_INIT;

	/** Maps the states to forms. Key:(int)State / Value:(Form)Form */
	protected Hashtable stateToForm = new Hashtable();

	/** automaton owner */
	protected FormManager owner;

	/** Current Wait-Form, which should be shown */
	private final WaitScreenForm wait;

	/** Form (State) which was shown before the WaitScreen */
	private int stateBeforeWaitScreenForm;

	/** Tells us if the Wait-Forms is visible */
	private boolean waitScreenActive = false;


	//--------------------------------------------------------------------------
	// constructors
	//--------------------------------------------------------------------------

	/** Creates a FormAutomationSet object */
	public FormAutomationSet() {
		log.debug("Creating instance of FormAutomationSet");

		/**
		 * TODO set object on sessionlevel later; now on FormAutomationSet-Level
		 */
		wait = new WaitScreenForm(this, "");
		addForm(STATE_WAIT, wait);

		initAutomationSet();
	}


	//--------------------------------------------------------------------------
	// private & protected methods
	//--------------------------------------------------------------------------

	/**
	 * Initialise the following in your implementatopm of the Automaton:
	 * <ul>
	 * <li>1. Create the Form-objects
	 * <li>2.1. Register the created Form-Objects thru addForm(..)
	 * <li>2.2. Maybe register SubAutomatons thru AddFormAutomation(..)
	 * <li>3. Set the initial state
	 * </ul>
	 */
	protected abstract void initAutomationSet();


	/**
	 * Modifies the automaton state, and sets the registered Form with this
	 * state
	 * to the current.
	 */
	protected final void setState(int state) {
		log.debug("call form left on state " + this.state);
		getCurrentForm().formLeft();

		if (state == STATE_FINISHED) {
			owner.detachFormAutomationSet();
			return;
		}

		Object obj = stateToForm.get(new Integer(state));

		// Different BasicForm
		if (obj instanceof BasicForm) {
			this.state = state;

			log.debug("call form entered on state " + state);
			if (waitScreenActive)
				waitScreenActive = false;
			BasicForm bf = (BasicForm)stateToForm.get(new Integer(state));
			bf.formEntered();
			bf.markFormDirty();
		}

		// push FormAutomationSet
		else if (obj instanceof Class) {
			try {
				// reset saved Form
				if (waitScreenActive) {
					this.state = stateBeforeWaitScreenForm;
					waitScreenActive = false;
				}
				Class classAutomation = (Class)obj;
				FormAutomationSet set = (FormAutomationSet)classAutomation.newInstance();
				owner.attachFormAutomationSet(set);
				set.getCurrentForm().formEntered();
				set.getCurrentForm().markFormDirty();
			}
			catch (Exception ex) {
				/** TODO default formautomationset... */
				log.error(ex);
			}
		}

	}


	//--------------------------------------------------------------------------
	// public methods
	//--------------------------------------------------------------------------

	/**
	 * This is the entry point for the controller, you have to implement a
	 * dispatcher, which evaluates the action and delegates to the corresponding
	 * business object. Later you have to response in a way like setState(..),
	 * showErrorForm(..), etc.. See examples.
	 */
	public abstract void doAction(int action);


	/** Override this Method on potential clean up */
	public abstract void finish();


	/** Returns the current Form from the automaton */
	public BasicForm getCurrentForm() {
		Object obj = stateToForm.get(new Integer(state));
		if ((obj != null) && (obj instanceof BasicForm)) {
			return (BasicForm)obj;
		}

		/** TODO Standarderror Form */
		return null;
	}


	/** Sets the initial state */
	public void setInitState(int state) {
		this.state = state;
		STATE_INIT = state;
	}


	/** Returns the current State */
	public int getState() {
		return state;
	}


	/** Override to return the name of the current FormAutomationSet */
	@Override
	public String toString() {
		/** TODO via reflection */
		return "FormAutomationSet";
	}


	/** Sets the FormManager, to which this automaton is registered */
	public void setOwner(FormManager owner) {
		this.owner = owner;
	}


	/** Returns the FormManager, where this automaton is registered */
	public FormManager getOwner() {
		return owner;
	}


	/** Returns the terminal's IO-Interface */
	public TerminalIOInterface getIO() {
		return owner.getIO();
	}


	/** Registers a Form to this automaton */
	public void addForm(int state, BasicForm form) {
		Integer intState = new Integer(state);

		if (stateToForm.contains(intState)) {
			stateToForm.remove(intState);
		}

		stateToForm.put(intState, form);
	}


	/** Registers a FormAutomationSet to this automaton */
	public void addFormAutomation(int state, Class automation) {
		Integer intState = new Integer(state);

		if (stateToForm.contains(intState)) {
			stateToForm.remove(intState);
		}

		stateToForm.put(intState, automation);
	}


	/** Shows the annoying wait screen with a tranquilising message */
	public void showWaitScreen(String text) {
		try {
			// Save Form (state) before showing Warte-Form
			if (!(getCurrentForm() instanceof WaitScreenForm)) {
				stateBeforeWaitScreenForm = getState();
				waitScreenActive = true;
			}
			wait.setMessage(text);
			setState(STATE_WAIT);

			getCurrentForm().paint(true);
		}
		catch (Exception ex) {
			// ignore, will be catched later, action should run till the end
			// if action is still running, the WaitScreenForm will be still shown
		}
	}


	/** Convenice method, which shows a ErrorForm with a bothering message */
	public void showErrorForm(String message, FormAutomationSet owner, int exitState) {
		showErrorForm(message, owner, exitState, null);
	}


	/** Convenice method, which shows a ErrorForm and makes logger output */
	public void showErrorForm(String message, FormAutomationSet owner, int exitState, Exception ex) {
		ErrorForm errorForm = new ErrorForm(message, owner, exitState);
		addForm(STATE_ERROR, errorForm);
		setState(STATE_ERROR);
		if (ex != null) {
			log.error(ex);
		}
		try {
			getIO().beepError();
		}
		catch (IOException ioex) {
			log.error("Beep could not be send", ioex);
		}
	}


	/** Convenice method, which shows a simple message ans makes logger output */
	public void showMessageForm(String headline, String message, FormAutomationSet owner, int exitState) {
		ErrorForm errorForm = new ErrorForm(headline, message, owner, exitState);
		addForm(STATE_ERROR, errorForm);
		setState(STATE_ERROR);
	}


	/** Exits an ErrorForm, used internally */
	public void exitErrorState(int exitState) {
		/** TODO scope? */
		if (getState() == STATE_ERROR) {
			setState(exitState);
		}
	}


	/** Shows a DialogForm */
	public void showOptionForm(FormAutomationSet owner, String message, int okLabel, int abortLabel, int okAction, int abortAction) {
		OptionForm dialogForm = new OptionForm(owner, message, okLabel, abortLabel, okAction, abortAction);
		addForm(STATE_DIALOG, dialogForm);
		setState(STATE_DIALOG);
	}


	/** Returns the Session */
	public Session getSession() {
		return getOwner().getSession();
	}

} public abstract class BasicForm implements TerminalEventListener {

	//--------------------------------------------------------------------------
	// constants
	//--------------------------------------------------------------------------

	/** Carriage Return / LineFeed */
	protected final static String CRLF = "\r\n";

	//--------------------------------------------------------------------------
	// class variables
	//--------------------------------------------------------------------------

	/** log4j reference */
	public final static Logger log = Logger.getLogger(Configuration.class);

	//--------------------------------------------------------------------------
	// instance variables
	//--------------------------------------------------------------------------

	/** FormAutomationSet, this Forms belongs to */
	protected FormAutomationSet owner;

	/** All widgets of this Form. Order is simultaneously tab-order. */
	private final Vector widgets = new Vector(5, 2);

	/** Widget with current focus, if there is no focusable widget null. */
	private BasicWidget focusedWidget;

	/** Header row */
	private HeaderWidget header;

	/** Before the next drawing should the screen be cleared? */
	private boolean clearScreen = true;

	/**
	 * All KeylabelListener. Key: TerminalEvent-Constant from the HotKeys /
	 * Object: KeyLabelWidget
	 */
	private final Hashtable keyLabels = new Hashtable();


	//--------------------------------------------------------------------------
	// constructors
	//--------------------------------------------------------------------------

	/** Creates a new BasicForm-Object */
	public BasicForm(FormAutomationSet owner, String headerText) {
		log.debug("Creating instance of BasicForm");
		this.owner = owner;

		initWidgets();

		setTitle(headerText);

		focusFirstPossibleWidget();
	}


	/** Creates a new BasicForm-Object */
	public BasicForm(FormAutomationSet owner) {
		log.debug("Creating instance of BasicForm");
		this.owner = owner;

		initWidgets();

		focusFirstPossibleWidget();
	}


	//--------------------------------------------------------------------------
	// private & protected methods
	//--------------------------------------------------------------------------

	/**
	 * Sets all widgets in the Form to dirty, so that all widgets will be
	 * repainted on next call of paint
	 */
	private void setWidgetsDirty() {
		Enumeration enumWidgets = widgets.elements();
		while (enumWidgets.hasMoreElements()) {
			BasicWidget bw = (BasicWidget)enumWidgets.nextElement();
			bw.setDirty(true);
		}
	}


	/** Returns the Session object */
	protected SessionObject getSessionObject() {
		return owner.getOwner().getSessionObject();
	}


	/** Sets the Session object */
	protected void setSessionObject(SessionObject obj) {
		owner.getOwner().setSessionObject(obj);
	}


	//--------------------------------------------------------------------------
	// public methods
	//--------------------------------------------------------------------------

	/** Returns the owner of this Form */
	public FormAutomationSet getOwner() {
		return owner;
	}


	/** Returns the TerminalInterface */
	public TerminalIOInterface getIO() {
		return owner.getIO();
	}


	/** Provokes the Form to a whole repaint on next call of paint */
	public void markFormDirty() {
		clearScreen = true;
		setWidgetsDirty();
	}


	/** Paints the form */
	public void paint(boolean fullRedraw) throws IOException {
		if (fullRedraw) {
			markFormDirty();
		}
		paint();
	}


	/** Returns the current focused widget, on no focused widget null. */
	public BasicWidget getFocusedWidget() {
		return focusedWidget;
	}


	/** Focuses the first possible widget and returns it */
	public BasicWidget focusFirstPossibleWidget() {
		Enumeration enumWidgets = widgets.elements();

		if (focusedWidget != null) {
			focusedWidget.focusLeft();
		}

		focusedWidget = null;
		while (enumWidgets.hasMoreElements()) {
			BasicWidget bw = (BasicWidget)enumWidgets.nextElement();
			if (bw.isFocusable()) {
				focusedWidget = bw;
				focusedWidget.focusEntered();
				break;
			}
		}

		return focusedWidget;
	}


	/** Focuses the last possible widget an returns it */
	public BasicWidget focusLastPossibleWidget() {
		if (focusedWidget != null) {
			focusedWidget.focusLeft();
		}

		focusedWidget = null;
		for (int i = widgets.size(); i > 0; i--) {
			BasicWidget bw = (BasicWidget)widgets.elementAt(i - 1);
			if (bw.isFocusable()) {
				focusedWidget = bw;
				focusedWidget.focusEntered();
				break;
			}
		}

		return focusedWidget;
	}


	/** Focuses the next possible widget and returns it */
	public BasicWidget focusNextPossibleWidget() {
		if (focusedWidget == null) {
			return focusFirstPossibleWidget();
		}
		else {
			focusedWidget.focusLeft();
		}

		int indexFocused = widgets.indexOf(focusedWidget);

		for (int i = indexFocused + 1; i < widgets.size(); i++) {
			BasicWidget bw = (BasicWidget)widgets.elementAt(i);
			if (bw.isFocusable()) {
				focusedWidget = bw;
				focusedWidget.focusEntered();
				return focusedWidget;
			}
		}

		// no one found, take firstpossible (or null)
		return focusFirstPossibleWidget();
	}


	/** Focuses the previous widget and returns it */
	public BasicWidget focusPreviousPossibleWidget() {
		if (focusedWidget == null) {
			return focusFirstPossibleWidget();
		}
		else {
			focusedWidget.focusLeft();
		}

		int indexFocused = widgets.indexOf(focusedWidget);

		for (int i = indexFocused - 1; i >= 0; i--) {
			BasicWidget bw = (BasicWidget)widgets.elementAt(i);
			if (bw.isFocusable()) {
				focusedWidget = bw;
				focusedWidget.focusEntered();
				return focusedWidget;
			}
		}

		// noe one found, take first possible (or null)
		return focusLastPossibleWidget();
	}


	/** Paint-routine to draw the dirty widgets */
	public void paint() throws IOException {
		// bildschirm falls ben�tigt l�schen
		if (clearScreen) {
			clearScreen = false;
			getIO().clearScreen();
		}

		// paint widgets
		Enumeration enumWidgets = widgets.elements();
		while (enumWidgets.hasMoreElements()) {
			BasicWidget bw = (BasicWidget)enumWidgets.nextElement();
			if (bw.isDirty()) {
				bw.redraw();
			}
		}

		// set cursor
		BasicWidget bw = getFocusedWidget();
		if (bw != null) {
			getIO().cursorMoveAbsolut(bw.getCursor().getLine(), bw.getCursor().getColumn());
		}

		getIO().flush();

	}


	/** Adds and registers a widget to the form */
	public void addWidget(BasicWidget widget) {
		if (widget != null) {
			if (widgets.contains(widget)) {
				widgets.remove(widget);
			}

			widgets.add(widget);

			// Globale KeyListener for the KeyLabelWidgets (aka "return of the ToolbarButtons" ;)
			if ((widget instanceof KeyLabelWidget) && (((KeyLabelWidget)widget).getKey() != TerminalEvent.KEY_UNDEFINED)) {
				if (keyLabels.contains(widget)) {
					keyLabels.remove(widget);
				}

				keyLabels.put(new Integer(((KeyLabelWidget)widget).getKey()), widget);
			}
		}
	}


	/** Queries the widget if it has the focus */
	public boolean hasWidgetFocus(BasicWidget widget) {
		return focusedWidget.equals(widget);
	}


	/** In this method all widgets should be created and registered */
	public abstract void initWidgets();


	/** Catches the scanned barcodes in the form for further evaluation */
	public abstract void processBarcodeReceived(TerminalEvent e);


	/** Catches the pressed key in the form for further evaluation */
	public abstract void processKeyPressed(TerminalEvent e);


	/** Catches the leaving from a Form for further evaluation */
	public abstract void formLeft();


	/** Catches the entering in a Form for further evaluation */
	public abstract void formEntered();


	//--------------------------------------------------------------------------
	// implementation of the interface TerminalKeyListener
	//--------------------------------------------------------------------------

	/** Catches a recived barcode */
	public final void barcodeReceived(TerminalEvent e) {
		log.info("Barcode received in BasicForm -> Barcode [" + e.getBarcode() + "]");

		processBarcodeReceived(e);
	}


	/** Catches a recived keypress */
	public final void keyPressed(TerminalEvent e) {
		if (e.isPrintable()) {
			log.debug("Keypress received in BasicForm -> Key (printable): [" + e.getKeyAsString() + "]");
		}
		else if (e.isFunctionKey()) {
			log.debug("Keypress received in BasicForm -> FunktionKey: [" + java.lang.Math.abs(e.getKey()) + "]");
		}
		else {
			log.debug("Keypress received in BasicForm -> Key (non-printable): [" + e.getKey() + "]");
		}

		if (e.getKey() == TerminalEvent.KEY_TAB) {
			focusNextPossibleWidget();
			return;
		}
		else if (e.getKey() == TerminalEvent.KEY_TAB_BACK) {
			focusPreviousPossibleWidget();
			return;
		}

		processKeyPressed(e);

		if (!e.isConsumed()) {
			Integer intKeyLabel = new Integer(e.getKey());

			if (keyLabels.containsKey(intKeyLabel)) {
				KeyLabelWidget klw = (KeyLabelWidget)keyLabels.get(intKeyLabel);
				klw.fireActionPerformed(e);
			}

			else if (focusedWidget != null) {
				focusedWidget.keyPressed(e);
			}
		}
	}


	public String getTitle() {
		if (header != null)
			return header.getText();
		else
			return null;
	}


	public void setTitle(String title) {
		if (header != null) {
			header.setText(title);
		}
		else {
			header = new HeaderWidget(this, title);
			addWidget(header);
		}
	}


	public Enumeration<BasicWidget> widgets() {
		return widgets.elements();
	}


	public HeaderWidget getHeader() {
		return header;
	}
} public class Configuration {

	//--------------------------------------------------------------------------
	// constants
	//--------------------------------------------------------------------------

	// globale definitions
	private final static String APP_NAME = "Jalita";
	private final static String APP_VERSION = "1.0";

	// configuration definitions
	private final static String CONFIG_FILE = "jalita.properties";
	private final static String CONFIG_HEAD = APP_NAME + " Version " + APP_VERSION + " - Configurationfile";

	// property attribute
	private final static String PROP_SERVER_PORT = "server.port";
	private final static String PROP_SERVER_CLEANUP_INTERVAL = "server.cleanup.interval";
	private final static String PROP_SESSION_TIMEOUT = "session.timeout";
	private final static String PROP_SESSION_INIT_FORMAUTOMATION = "session.init.formautomation";
	private final static String PROP_SESSION_INIT_TIMEBEFORECLEARBUFFER = "session.init.timebeforeclearbuffer";
	private final static String PROP_SESSION_WIDTH = "session.init.width";
	private final static String PROP_SESSION_HEIGHT = "session.init.height";

	private final static String PROP_SERVER_MAXSESSIONS_PER_HOST = "server.maxsessions.perhost";

	// common
	public final static String KEY_CRLF = "\r\n";

	//--------------------------------------------------------------------------
	// class variables
	//--------------------------------------------------------------------------

	/** log4j reference */
	public final static Logger log = Logger.getLogger(Configuration.class);

	/** singleton-object */
	private static Configuration configuration = null;

	/** configurationfile */
	private static Properties properties = null;


	//--------------------------------------------------------------------------
	// class methods
	//--------------------------------------------------------------------------

	/** Returns the singleton **/
	public static Configuration getConfiguration() {
		if (configuration == null) {
			configuration = new Configuration();
		}
		return configuration;
	}


	//--------------------------------------------------------------------------
	// constructors
	//--------------------------------------------------------------------------

	/** Creates a configuration object */
	private Configuration() {
		log.debug("Creating instance of Configuration");

		// create properties with defaultvalues
		properties = new Properties(getDefaultProperties());

		try {
			// create default configuration property file if not allready existings
			File f = new File(CONFIG_FILE);
			if (!f.exists()) {
				try {
					FileOutputStream fout = new FileOutputStream(f);
					getDefaultProperties().store(fout, CONFIG_HEAD);
				}
				catch (Exception ex) {
					log.error("Defaultconfiguration-file could not be created!", ex);
				}
			}

			// read properties from file
			FileInputStream fin = new FileInputStream(CONFIG_FILE);
			properties.load(fin);
		}
		catch (Exception ex) {
			log.error(ex);
		}

	}


	//--------------------------------------------------------------------------
	// private & protected methods
	//--------------------------------------------------------------------------

	/**
	 * Here are the Defaultproperties, which will be used if the key is not
	 * present
	 * in the file. Additionally the defaultproperties will be created here if
	 * the
	 * propertyfile does not exist.
	 */
	private Properties getDefaultProperties() {
		Properties defaultProperties = new Properties();

		// Defaultport for the serverapplication
		defaultProperties.setProperty(PROP_SERVER_PORT, "6040");

		// Timeout for a session [ms]
		defaultProperties.setProperty(PROP_SESSION_TIMEOUT, "3600000"); // 60 Min.

		// Interval, the background cleanup thread pauses
		defaultProperties.setProperty(PROP_SERVER_CLEANUP_INTERVAL, "1000"); // 1 Sec.

		// FormAutomationSet, which will be used for a new session
		defaultProperties.setProperty(PROP_SESSION_INIT_FORMAUTOMATION, "net.sourceforge.jalita.konzentrator.test.TestAutomation");

		// on new connection the timeinterval which will be waited until possibly
		// buffered, but unwanted data comes from the terminal, that should be
		// deleted. Initially needed for Putty, which sends some of its own codes
		defaultProperties.setProperty(PROP_SESSION_INIT_TIMEBEFORECLEARBUFFER, "500");

		return defaultProperties;
	}


	//--------------------------------------------------------------------------
	// public methods
	//--------------------------------------------------------------------------

	/**
	 * Saves the configuration in a propertyfile
	 * Attention: An existing file will be overriden (including formatting and
	 * usercomments)
	 */
	public void save() {
		try {
			File f = new File(CONFIG_FILE);
			FileOutputStream fout = new FileOutputStream(f);
			getDefaultProperties().store(fout, CONFIG_HEAD);
		}
		catch (Exception ex) {
			log.error("Configurationfile could not be saved!", ex);
		}
	}


	//--------------------------------------------------------------------------
	// Getter/Setter of Constant-values
	//--------------------------------------------------------------------------

	public String getApplicationName() {
		return APP_NAME;
	}


	public String getAppliationVersion() {
		return APP_VERSION;
	}


	//--------------------------------------------------------------------------
	// Getter/Setter of Property-values
	//--------------------------------------------------------------------------

	/** port, where the server is listening to new connections */
	public int getServerPort() {
		String port = properties.getProperty(PROP_SERVER_PORT);
		return Integer.parseInt(port);
	}


	/** sets the port, where the server is listening to new connections */
	public void setServerPort(int port) {
		properties.setProperty(PROP_SERVER_PORT, String.valueOf(port));
	}


	/** maximum number of sessions per single host */
	public int getServerMaxSessionsPerHost() {
		String maxSessions = properties.getProperty(PROP_SERVER_MAXSESSIONS_PER_HOST);
		return Integer.parseInt(maxSessions);
	}


	/** maximum number of sessions per single host */
	public void setServerMaxSessionsPerHost(int maxSessions) {
		properties.setProperty(PROP_SERVER_MAXSESSIONS_PER_HOST, String.valueOf(maxSessions));
	}


	public int getSessionHeight() {
		String height = properties.getProperty(PROP_SESSION_HEIGHT);
		if (height == null)
			height = "7";
		return Integer.parseInt(height);
	}


	/** sets the default height of the forms in client display */
	public void setSessionHeight(int height) {
		properties.setProperty(PROP_SESSION_HEIGHT, String.valueOf(height));
	}


	public int getSessionWidth() {
		String width = properties.getProperty(PROP_SESSION_WIDTH);
		if (width == null)
			width = "20";
		return Integer.parseInt(width);
	}


	/** sets the default width of the forms in client display */
	public void setSessionWidth(int width) {
		properties.setProperty(PROP_SESSION_WIDTH, String.valueOf(width));
	}


	/**
	 * time for keeping an session without activity connected, independent of an
	 * physically connection [ms]
	 */
	public int getSessionTimeOut() {
		String timeout = properties.getProperty(PROP_SESSION_TIMEOUT);
		return Integer.parseInt(timeout);
	}


	/**
	 * sets the time for keeping an session without activity connected,
	 * independent of an physically connection [ms]
	 */
	public void setSessionTimeOut(int timeout) {
		properties.setProperty(PROP_SESSION_TIMEOUT, String.valueOf(timeout));
	}


	/**
	 * this is the time slice of the cyclic session-managment-thread, tells him
	 * how long to wait until clean up should be started [ms]
	 */
	public int getServerCleanupInterval() {
		String interval = properties.getProperty(PROP_SERVER_CLEANUP_INTERVAL);
		return Integer.parseInt(interval);
	}


	/**
	 * sets the time slice of the cyclic session-managment-thread, tells him
	 * how long to wait until clean up should be started [ms]
	 */
	public void setServerCleanupInterval(int interval) {
		properties.setProperty(PROP_SERVER_CLEANUP_INTERVAL, String.valueOf(interval));
	}


	/** The class of the initially FormAutomationSet */
	public Class getSessionInitFormAutomation() {
		String init = properties.getProperty(PROP_SESSION_INIT_FORMAUTOMATION);
		Class c = null;

		try {
			c = Class.forName(init);
		}
		catch (ClassNotFoundException ex) {
			// Simply return null
		}

		return c;
	}


	/** Sets the initially FormAutomationSet class */
	public void setSessionInitFormAutomation(Class init) {
		properties.setProperty(PROP_SESSION_INIT_FORMAUTOMATION, init.toString());
	}


	/**
	 * on new connection the timeinterval [ms] which will be waited until
	 * possibly
	 * buffered, but unwanted data comes from the terminal, that should be
	 * deleted. Initially needed for Putty, which sends some of its own codes
	 */
	public int getTimeBeforeClearBuffer() {
		String time = properties.getProperty(PROP_SESSION_INIT_TIMEBEFORECLEARBUFFER);
		return Integer.parseInt(time);
	}


	/**
	 * sets the timeinterval [ms] for a pause before the buffer will be cleared
	 * on a new connection
	 */
	public void setTimeBeforeClearBuffer(int time) {
		properties.setProperty(PROP_SESSION_INIT_TIMEBEFORECLEARBUFFER, String.valueOf(time));
	}


	/**
	 * Returns an userdefined configuration setting.
	 * These are settings which start with an 'user.' in the settingsfile
	 */
	public String getUserProperty(String key, String fallback) {
		return properties.getProperty("user." + key, fallback);
	}


	/** Sets an userdefined configuration setting. */
	public void setUserProperty(String key, String value) {
		properties.setProperty("user." + key, value);
	}

}
import net.sf.jalita.server.SessionObject;
import net.sf.jalita.ui.automation.FormAutomationSet;
import net.sf.jalita.ui.forms.BasicForm;
import net.sf.jalita.util.Configuration;
import org.apache.log4j.Logger;
import java.io.IOException;
import java.util.Stack;
import net.sf.jalita.io.TerminalEvent;
import net.sf.jalita.io.TerminalEventListener;
import net.sf.jalita.io.TerminalIOInterface;
import net.sf.jalita.server.GlobalObject;
import net.sf.jalita.server.Session;
import net.sf.jalita.server.SessionManager;
/***********************************************************************
 * This software is published under the terms of the LGPL
 * version 2.1, a copy of which has been included with this
 * distribution in the 'lgpl.txt' file.
 * Copyright (C) 2004 Daniel Galán y Martins
 * Author: Daniel Galán y Martins
 * Creation date: 02.05.2003
 * Revision: $Revision: 1.4 $
 * Checked in by: $Author: danielgalan $
 * Last modified: $Date: 2009/12/02 21:53:58 $
 * $Log: FormManager.java,v $
 * Revision 1.4  2009/12/02 21:53:58  danielgalan
 * naming
 *
 * Revision 1.3 2008/10/09 12:11:05 ilgian
 * Added getSession method
 * Revision 1.2 2005/05/23 18:10:20 danielgalan
 * some cleaning and removing some cycles (not all removed yet)
 * Revision 1.1 2004/07/26 21:40:29 danielgalan
 * Jalita initial cvs commit :)
 **********************************************************************/
package net.sf.jalita.ui;






/**
 * Manages all Forms associated with a Session, as well as a Stack with
 * FormAutomationSets.
 * 
 * @author Daniel Galán y Martins
 * @version $Revision: 1.4 $
 */
public class FormManager implements TerminalEventListener {

	//--------------------------------------------------------------------------
	// class variables
	//--------------------------------------------------------------------------

	/** log4j reference */
	public final static Logger log = Logger.getLogger(Configuration.class);

	/** Jalita configuration-properties */
	private static Configuration config = Configuration.getConfiguration();

	/** SessionManager */
	private final SessionManager sessionManager = SessionManager.getSessionManager();

	//--------------------------------------------------------------------------
	// instance variables
	//--------------------------------------------------------------------------

	/** Stack with FormAutomationSet objects */
	private final Stack formAutomationStack = new Stack();

	/** Session from this FormManager */
	private final Session owner;


	//--------------------------------------------------------------------------
	// constructors
	//--------------------------------------------------------------------------

	/** Creates a FormManager object */
	public FormManager(Session owner) {
		log.debug("Creating instance of FormManager");
		this.owner = owner;

		try {
			formAutomationStack.push(config.getSessionInitFormAutomation().newInstance());
			getCurrentAutomationSet().setOwner(this);
		}
		catch (Exception ex) {
			log.error(ex);
		}

		try {
			ioChanged();
		}
		catch (IOException ex) {
			log.error(ex);
		}

	}


	//--------------------------------------------------------------------------
	// private & protected methods
	//--------------------------------------------------------------------------

	/** Redraws all marked widgets in the Form */
	protected void repaintCurrentForm() throws IOException {
		repaintCurrentForm(false);
	}


	/**
	 * Draws all marked Widgets im Form neu, if <code>fullRedraw</code> all
	 * Widgets in any case
	 */
	protected void repaintCurrentForm(boolean fullRedraw) throws IOException {
		log.debug("Redraw " + (fullRedraw ? "all" : "dirty") + " widgets in current Form");
		getCurrentAutomationSet().getCurrentForm().paint(fullRedraw);
	}


	/** Returns the current running FormAutomationSet */
	protected FormAutomationSet getCurrentAutomationSet() {
		return (FormAutomationSet)formAutomationStack.peek();
	}


	//--------------------------------------------------------------------------
	// public methods
	//--------------------------------------------------------------------------

	/**
	 * If IO has changed, the current Form from the current FormAutomationSet
	 * will be redrawn
	 */
	public void ioChanged() throws IOException {
		repaintCurrentForm(true);
	}


	/**
	 * Returns the TerminalIOInterface, which represents the connection to the
	 * terminal
	 */
	public TerminalIOInterface getIO() {
		return owner.getIO();
	}


	/**
	 * Places a FormAutomationSet on the Stack, which will become the
	 * currentFormAutomationSet
	 */
	public void attachFormAutomationSet(FormAutomationSet set) {
		set.setOwner(this);
		formAutomationStack.push(set);
	}


	/**
	 * Removes the current FormAutomationSet from the Stack and passes the
	 * control to the following.
	 * The initial Form will allways persist on the stack
	 */
	public void detachFormAutomationSet() {
		// never get an empty Stackn -> java.util.EmptyStackException
		if (formAutomationStack.size() > 1) {
			formAutomationStack.pop();
			BasicForm bf = getCurrentAutomationSet().getCurrentForm();
			bf.formEntered();
			bf.markFormDirty();
		}
		else {
			log.warn("Internal Error (intercepted): Attempt to remove last FormAutomationSet");
		}
	}


	/** Returns the Session object */
	public SessionObject getSessionObject() {
		return owner.getSessionObject();
	}


	/** Sets the Session object */
	public void setSessionObject(SessionObject obj) {
		owner.setSessionObject(obj);
	}


	/** Returns the global object */
	public GlobalObject getGlobalObject() {
		return sessionManager.getGlobalObject();
	}


	/** Sets the global object */
	public void setGlobalObject(GlobalObject obj) {
		sessionManager.setGlobalObject(obj);
	}


	/**
	 * Will be called on Session-closing (not on broken Sessions), to
	 * clean up FormAutomationSets.
	 */
	public void finish() {
		while (formAutomationStack.size() > 0) {
			FormAutomationSet fas = (FormAutomationSet)formAutomationStack.pop();
			fas.finish();
		}
	}


	//--------------------------------------------------------------------------
	// implementation of the interface TerminalKeyListener
	//--------------------------------------------------------------------------

	/** Delegates the Barcode-Event to the current Form */
	public void barcodeReceived(TerminalEvent e) {