All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.gluebooster.java.booster.basic.mvc.Layer Maven / Gradle / Ivy

Go to download

Basic classes to support the development of applications. There should be as few dependencies on other frameworks as possible.

The newest version!
package net.sf.gluebooster.java.booster.basic.mvc;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import net.sf.gluebooster.java.booster.basic.transformation.CallableByCalling;
import net.sf.gluebooster.java.booster.basic.transformation.CallableByCopying;
import net.sf.gluebooster.java.booster.basic.transformation.CallableBySelecting;
import net.sf.gluebooster.java.booster.basic.transformation.CallableChain;
import net.sf.gluebooster.java.booster.basic.transformation.CallableContainerFactory;
import net.sf.gluebooster.java.booster.essentials.eventsCommands.Callable;
import net.sf.gluebooster.java.booster.essentials.eventsCommands.CallableAbstraction;
import net.sf.gluebooster.java.booster.essentials.eventsCommands.CallableByConstant;
import net.sf.gluebooster.java.booster.essentials.eventsCommands.CallableByReflection;
import net.sf.gluebooster.java.booster.essentials.eventsCommands.MultiListener;
import net.sf.gluebooster.java.booster.essentials.meta.objects.ObjectAttributes;
import net.sf.gluebooster.java.booster.essentials.utils.Check;
import net.sf.gluebooster.java.booster.essentials.utils.Constants;
import net.sf.gluebooster.java.booster.essentials.utils.ContainerBoostUtils;
import net.sf.gluebooster.java.booster.essentials.utils.ReflectionBoostUtils;
import net.sf.gluebooster.java.booster.essentials.utils.XmlBoostUtils;

/**
 * A layer of a multitier architecture. A layer may contain a model that is needed for the invocation of the next (backend) layer. Calls of a layer always
 * contain the model of the previous layer.
 * 
 * Example: Display Layer: Swing, JavaFX, JSF Beans for html View Layer: User 1, User 2 Backend Layer: Business Logic
 * 
 * @defaultParamText name the name of the instance
 * 
 * @author cbauer
 *
 */
public class Layer extends CallableAbstraction {

	/**
	 * The backend layer. Example: The backend of a view is a controller; the backend of a display is a view
	 */
	private Layer backend;

	/**
	 * The model with which the next (backend) layer is called and (after the call) which values are processed (e.g. displayed).
	 */
	private Map modelOfBackendLayer;

	/**
	 * May be used when this layer has an own model, independent of the models that are propagated during the calls. Example: The number of logged in persons
	 */
	private Map modelOfThisLayer;

	/**
	 * Commands to be executed. The preferable result of the callable is Boolean (success = true, failure = false). If no Boolean is returned, then success is
	 * assumed.
	 */
	private Map callablePerCommand = new HashMap();

	public Map getModelOfBackendLayer() {
		if (modelOfBackendLayer == null) {
			modelOfBackendLayer = new ObjectAttributes("backendmodel of " + getName());
		}
		return modelOfBackendLayer;
	}

	public void setModelOfBackendLayer(Map modelOfNextLayer) {
		this.modelOfBackendLayer = modelOfNextLayer;
	}

	public Layer(String name) throws Exception {
		this(name, null);
	}

	/**
	 * 
	 * @param modelOfNextLayer
	 *            the model of the backend layer
	 */
	public Layer(String name, Map modelOfNextLayer) throws Exception {
		super(name);
		this.modelOfBackendLayer = modelOfNextLayer;
		// default setting: nothing MUST be done at start
	}

	/**
	 * Declares that this layer must do nothing to start.
	 * 
	 * @throws Exception
	 * 
	 */
	public void noStartNecessary() throws Exception {
		setCommandCallable(Constants.START, new CallableByConstant("no start sequence"));

	}

	/**
	 * Declares that this layer must do nothing when stopping
	 * 
	 * @throws Exception
	 * 
	 */
	public void noStopNecessary() throws Exception {
		setCommandCallable(Constants.STOP, new CallableByConstant("no stop sequence"));

	}

	public Map getModelOfThisLayer() {
		if (modelOfThisLayer == null) {
			modelOfThisLayer = new ObjectAttributes("model of " + getName());
		}
		return modelOfThisLayer;
	}

	public void setModelOfThisLayer(Map modelOfThisLayer) {
		this.modelOfThisLayer = modelOfThisLayer;
	}

	/**
	 * Connects a command with a callable
	 * 
	 * @param command
	 *            the command which should invoke the callable
	 * @param callable
	 *            parameters are command, commandParameters, map model
	 * @throws Exception
	 */
	public void setCommandCallable(Object command, Callable callable) throws Exception {
		// if the callable returns no boolean true is returned
		// Condition postcondition = callable.getPostcondition();
		// Collection isBoolean = postcondition.implies(new ValueDescription<>(Boolean.class));
		// if (isBoolean != null && !isBoolean.isEmpty()) {
		// throw new IllegalStateException("result must be Boolean, but is " + postcondition);
		// }

		callablePerCommand.put(command, callable);
	}

	/**
	 * Sets the callable that is invoked when starting/stopping.
	 * 
	 * @param listener
	 *            parameters are command (Constants.START/STOP), commandParameters, map model
	 * @throws Exception
	 */
	public void setStartStopCallable(Callable listener) throws Exception {
		setCommandCallable(Constants.START, listener);// start repeater
		setCommandCallable(Constants.STOP, listener);// stop repeater

	}

	public Map getCallablePerCommand() {
		return callablePerCommand;
	}

	public void setCallablePerCommand(Map callablePerCommand) {
		this.callablePerCommand = callablePerCommand;
	}


	/**
	 * @param parameters
	 *            command name, command parameters, model map
	 * @return model map
	 */
	@Override
	protected final Map callImpl(Object... parameters) throws Exception {
		Object commandName = null;
		Object commandParameters = null;
		Map model = null;
		if (parameters != null) {
			int size = parameters.length;
			if (size > 0) {
				commandName = parameters[0];
			}
			if (size > 1) {
				commandParameters = parameters[1];
			}
			if (size > 2) {
				model = (Map) parameters[2];
			}
		}

		Object partResult = null;
		if (callablePerCommand.containsKey(commandName)) {
			Callable callable = callablePerCommand.get(commandName);
			partResult = callable.call(commandName, commandParameters, model);
		} else if (!exec(commandName, commandParameters, model)) {
			throw new IllegalStateException("command " + commandName + " not supported by " + this + " (" + getClass().getSimpleName() + ")");
		}

		return model;

	}

	/**
	 * Selects the model (if the call parameters are "command" (ignored), "commandParameters" (ignored), model).
	 * 
	 * @return the created callable that selects the model
	 */
	public Callable selectModel() {
		return new CallableBySelecting("select model. ", 2);// TODO precondition arraysize
	}

	/**
	 * Selects the parameters (if the call parameters are "command" (ignored), "commandParameters", model (ignored)).
	 * 
	 * @return the created callable that selects the parameters
	 */
	public Callable selectParameters() {
		return new CallableBySelecting("select parameters ", 1);// TODO precondition arraysize
	}

	/**
	 * Selects the command (if the call parameters are "command" , "commandParameters" (ignored), model (ignored)).
	 * 
	 * @return the created callable that selects the command
	 */
	public Callable selectCommand() {
		return new CallableBySelecting("select command", 0);
	}

	/**
	 * Selects the model (if the call parameters are "command" (ignored), "commandParameters" (ignored), model). See ContainerBoostUtils.putIntoMap to put a
	 * value into the model.
	 * 
	 * @param key
	 *            the key of the field
	 * @return the created callable that selects the field of the model
	 */
	public CallableAbstraction selectModelField(Object key) {
		return new CallableChain("select model field '" + key + "'", selectModel(), new CallableBySelecting("select original text", key));
	}


	/**
	 * Implementation of the command execution. Must be overwritten or the setCommand-Method must be used.
	 * 
	 * @param commandName
	 *            the name of the command.
	 * @param parameter
	 *            the parameter of the command
	 * @param model
	 *            the current model. It may be modified during execution
	 * @return true if the command has been fully handled
	 */
	protected boolean exec(Object commandName, Object parameter, Map model) throws Exception {
		return false;
	}

	public Layer getBackend() {
		return backend;
	}

	public void setBackend(Layer backend) {
		this.backend = backend;
	}

	/**
	 * Create an action listener that extracts the command from the action and calls this (without model).
	 * 
	 * @return the created listener
	 */
	public ActionListener createActionListener(/* Map model */) throws Exception {
		return new MultiListener(new CallableChain("invoke event command on view",
				// "actionPerformed", actionEvent
				new CallableContainerFactory("extract event", Constants.ARRAY, new int[] { 1 }),
				// actionEvent
				new CallableByReflection("extract command", null, ActionEvent.class.getMethod(Constants.GETACTIONCOMMAND)),
				// command
				new CallableByCalling("call " + this, this, new Object[] { null, null, null/* model */ }, new int[] { 0 })
		// executes this.call(command, null, null /* model*/)
		));

	}

	/**
	 * Create a callable to which commands are passed
	 * 
	 * @param backend
	 *            will receive the calls
	 * @param useModelOfBackendLayer
	 *            should the model of the backend layer be used as command parameter
	 * @return the created callable
	 */
	public CallableAbstraction passTo(Callable backend, boolean useModelOfBackendLayer) throws Exception {
		Map backendModel = useModelOfBackendLayer ? getModelOfBackendLayer() : null;
		return new CallableByCalling("pass to backend layer", backend, new Object[] { null, null, backendModel });
	}

	/**
	 * Execute a command by the controller.
	 * 
	 * @param command
	 *            the command to be invoked
	 * @param parameter
	 *            parameter of the command
	 * @return the result of the command
	 */
	protected Map callBackend(Object command, Object... parameter) throws Exception {
		return getBackend().call(command, parameter, getModelOfBackendLayer());
	}

	/**
	 * If the parameter is an object array with only 1 element, it returns the element. Otherwise the parameter is returned.
	 * 
	 * @param parameter
	 *            will be inspected
	 * @return parameter or parameter[0]
	 */
	protected  Result getSingleParameter(Object parameter) {
		if (parameter != null && parameter.getClass().isArray() && Array.getLength(parameter) == 1) {
			return (Result) Array.get(parameter, 0);
		} else {
			return (Result) parameter;
		}
	}



	/**
	 * Creates a callable that calls this instance with a given command and parameters
	 * 
	 * @param command
	 *            the command
	 * @param parameters
	 *            parameters of the command
	 * @param model
	 *            model to be used
	 * @return the created callable
	 */
	public CallableAbstraction createExecuteCommandCallable(Object command, Object parameters, Map model) throws Exception {
		Object name = getName();
		if (name == null) {
			name = "";
		}
		Object commandName = "";
		if (command != null && !Constants.REPLACE.equals(command)) {
			commandName = command + " ";
		}

		return new CallableByReflection("invoke " + commandName + "on the controller " + name, this,
				ReflectionBoostUtils.getMethod(Callable.class, Constants.CALL), command, parameters, model);

	}

	/**
	 * Creates a callable that passes the command and its parameters to this instance
	 * 
	 * @param model
	 *            used as command parameter
	 * @return the created callable
	 */
	public CallableAbstraction passCommand(Map model) throws Exception {
		return createExecuteCommandCallable(Constants.REPLACE, null, model);// replace the first parameter with the actual command

	}

	/**
	 * Copies some entries from the backend model into another model (example: frontend model)
	 * 
	 * @param otherModel
	 *            will be modified
	 * @param keys
	 *            the keys of the entries
	 */
	protected final void copyBackendModelTo(Map otherModel, Collection keys) throws Exception {
		Map controllerModel = getModelOfBackendLayer();
		ContainerBoostUtils.copy(controllerModel, otherModel, keys, true);
	}

	/**
	 * Copies some entries from the backend model to another model
	 * 
	 * @param otherModel
	 *            will be modified
	 * @param keys
	 *            the keys of the entries
	 */
	protected final void copyBackendModelTo(Map otherModel, Object... keys) throws Exception {
		Map controllerModel = getModelOfBackendLayer();
		ContainerBoostUtils.copyVararg(controllerModel, otherModel, true, keys);
	}

	/**
	 * Create a callable that copies some field into another model. The argument of call is the source model from which the values are copied.
	 * 
	 * @param targetModel
	 *            will be modified
	 * @param fieldNames
	 *            the names of the fields that will be copied.
	 * @return the created callable
	 */
	public static Callable copyIntoModel(Map targetModel, Object... fieldNames) {
		return new CallableByCopying("copyIntoDisplayModel", null, targetModel, fieldNames);
	}

	/**
	 * Lets the backend execute a command with some fields as parameters (map)
	 * 
	 * @param command
	 *            the command to be executed
	 * @param fieldnames
	 *            the names of the fields to be transferred (as map) to the controller
	 * @param allFields
	 *            some fields will be selected
	 * @return the result of the command
	 */
	protected final Map backendCommandWithFields(Object command, Collection fieldnames, Map allFields) throws Exception {
		Map selectedFields = new HashMap();
		ContainerBoostUtils.copy(allFields, selectedFields, fieldnames, true);
		return callBackend(command, selectedFields);

	}

	/**
	 * Creates a callable that expects (command, File as parameter, model) and serializes an field of the model into the file denoted by the parameter (format:
	 * UTF-8 xml)
	 * 
	 * @param callableName
	 *            name of the result
	 * @param modelKey
	 *            the field to be serialized
	 * @return the created callable
	 */
	public final CallableAbstraction serializeModelField(String callableName, Object modelKey) throws Exception {
		// save cards
		Callable createOutputStream = new CallableChain("create output stream for " + callableName, selectParameters(),
				new CallableByReflection("FileOutputStream constructor", FileOutputStream.class.getConstructor(File.class)));

		Callable getFlashcards = selectModelField(modelKey);

		CallableByCalling parameters = new CallableByCalling<>("parameters for writeAsUtf8Xml", new Object[] { getFlashcards, createOutputStream, true, 0 });

		CallableChain result = new CallableChain<>(callableName, parameters, XmlBoostUtils.writeAsUtf8XmlCallable("writer of " + callableName));
		// TODO put feedback into model

		return result;
	}

	/**
	 * Creates a callable that expects (command, File as parameter, model) and deserializes an field of the model from the the file denoted by the parameter
	 * (format: UTF-8 xml)
	 * 
	 * @param callableName
	 *            the name of the result
	 * @param modelKey
	 *            the name of the field
	 * @return the created callable
	 */
	public final CallableAbstraction deserializeIntoModelField(String callableName, Object modelKey) throws Exception {
		// save cards
		CallableAbstraction createStream = new CallableChain("create input stream for " + callableName, selectParameters(),
				new CallableByReflection("FileInputStream constructor", FileInputStream.class.getConstructor(File.class))
				, XmlBoostUtils.readFromUtf8XmlCallable("deserialize " + callableName), Check.notNullChecker("deserialized " + modelKey));
		createStream.setBreakpoint();

		CallableAbstraction parameters = new CallableByCalling<>("create [model,inputstream] ", new Object[] { selectModel(), createStream });
		// parameters.setBreakpoint();

		CallableChain result = new CallableChain<>(callableName, parameters, ContainerBoostUtils.putIntoMap("put " + modelKey + " into model", modelKey));

		return result;
	}

}