package js.http;

import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import js.lang.SyntaxException;
import js.log.Log;
import js.log.LogFactory;
import js.util.Files;
import js.util.Params;
import js.util.Strings;

/**
 * HTTP content type instance and public constants. This class has a single constructor that parses a string value and
 * initializes internal immutable state. Provides getters for ContentType components: type, subtype and parameters. Also this
 * class provides predefined constant values for well known content types.
 *

 * Here is described expected syntax for string value provided to constructor. If this syntax is not respected
 * {@link #ContentType(String)} throws syntax exception.
 *
 *

 * content-type = mime-type *(OWS ";" OWS parameter)
 * mime-type = type "/" subtype
 * parameter = name "=" value
 *
 * type = string
 * subtype = string
 * name = string
 * value = string "/" quoted-string
 *
 * ; optional white space
 * OWS = *(SP / HTAB)
 *
 * ; string contains any char less delimiters
 * ; quoted-string is string between double quotes
 *

 *

 * This class provides factory methods for creating content type instances from string value or one suitable to represent a file
 * content. Content type to file mapping is based on file extension and cover only couple of, widely used, types.
 *

 * Two ContentType instances are considered equal if have the the same {@link #mime} value, that is, parameters are not used for
 * equality test. Also hash code is based on the same mime field.
 *
 * @author Iulian Rotaru
 * @version final
 */
public class ContentType {
	/** Class logger. */
	private static final Log log = LogFactory.getLog(ContentType.class);

	/** Default value for textual files. A textual file should be human-readable and must not contain binary data. */
	public static final ContentType TEXT_PLAIN = new ContentType("text/plain;charset=UTF-8");
	/** HyperText Markup Language (HTML) */
	public static final ContentType TEXT_HTML = new ContentType("text/html;charset=UTF-8");
	/** Extensible Markup Language (XML) */
	public static final ContentType TEXT_XML = new ContentType("text/xml;charset=UTF-8");
	/** Cascading Style Sheet (CSS) */
	public static final ContentType TEXT_CSS = new ContentType("text/css;charset=UTF-8");
	/** JavaScript */
	public static final ContentType APPLICATION_JAVASCRIPT = new ContentType("application/javascript;charset=UTF-8");
	/** Multipart MIME data streams for HTML forms that contain binary files. */
	public static final ContentType MULTIPART_FORM = new ContentType("multipart/form-data");
	/**
	 * Mixed is the primary subtype for multipart, and is intended for use when the body parts are independent and intended to
	 * be displayed serially.
	 */
	public static final ContentType MULTIPART_MIXED = new ContentType("multipart/mixed");
	/** HTTP form represented as list of name / value pairs of strings. */
	public static final ContentType URLENCODED_FORM = new ContentType("application/x-www-form-urlencoded");
	/**
	 * JSON format. Excerpt from https://www.iana.org/assignments/media-types/application/json: No "charset" parameter is
	 * defined for this registration. Adding one really has no effect on compliant recipients.
	 */
	public static final ContentType APPLICATION_JSON = new ContentType("application/json");
	/** Archive document (multiple files embedded). */
	public static final ContentType APPLICATION_STREAM = new ContentType("application/octet-stream");
	/** Portable Network Graphics */
	public static final ContentType IMAGE_PNG = new ContentType("image/png");
	/** JPEG images */
	public static final ContentType IMAGE_JPEG = new ContentType("image/jpeg");
	/** Graphics Interchange Format (GIF) */
	public static final ContentType IMAGE_GIF = new ContentType("image/gif");
	/** Tagged Image File Format (TIFF) */
	public static final ContentType IMAGE_TIFF = new ContentType("image/tiff");
	/** Scalable Vector Graphics (SVG) */
	public static final ContentType IMAGE_SVG = new ContentType("image/svg+xml");

	/** Content type for widespread file extensions. */
	public static final Map<String, ContentType> FILE_TYPES = new HashMap<>();
	static {
		FILE_TYPES.put("html", TEXT_HTML);
		FILE_TYPES.put("htm", TEXT_HTML);
		FILE_TYPES.put("xml", TEXT_XML);
		FILE_TYPES.put("css", TEXT_CSS);
		FILE_TYPES.put("js", APPLICATION_JAVASCRIPT);
		FILE_TYPES.put("json", APPLICATION_JSON);
		FILE_TYPES.put("png", IMAGE_PNG);
		FILE_TYPES.put("jpg", IMAGE_JPEG);
		FILE_TYPES.put("jpeg", IMAGE_JPEG);
		FILE_TYPES.put("gif", IMAGE_GIF);
		FILE_TYPES.put("tiff", IMAGE_TIFF);
		FILE_TYPES.put("svg", IMAGE_SVG);
	}

	/**
	 * Create a content type instance suitable to represent requested file. It is a trivial a approach using a map of common
	 * used file extension. Recognizes only couple, most used types; if extension is not recognized returns {@link #TEXT_HTML}.
	 *
	 * @param file source file.
	 * @return content type discovered for requested file.
	 * @throws IllegalArgumentException if file parameter is null.
	 */
	public static ContentType forFile(File file) {
		Params.notNull(file, "File");
		ContentType contentType = FILE_TYPES.get(Files.getExtension(file));
		if (contentType == null) {
			log.debug("Unknown content type for |%s|. Replace with default |%s|.", file, TEXT_HTML);
			contentType = TEXT_HTML;
		}
		return contentType;
	}

	/**
	 * Parses content type value and returns newly created instance. Given value should obey syntax described by this class.
	 * This factory method just delegates {@link #ContentType(String)}. If value argument is null uses
	 * {@link #APPLICATION_JSON} as default.
	 *
	 * @param value content type string value with syntax described by this class, possible null.
	 * @return content type instance for given value.
	 * @throws SyntaxException if value does not respect syntax described by this class.
	 */
	public static ContentType valueOf(String value) {
		if (value == null) {
			return ContentType.APPLICATION_JSON;
		}
		return new ContentType(value);
	}

	/** Content mime type. */
	private final String type;
	/** Content mime subtype. */
	private final String subtype;
	/** Cache this immutable instance mime value. */
	private final String mime;
	/** Optional content type parameters, usually charset. Null if missing. */
	private final Map<String, String> parameters;
	/** Full content type value, that is, mime type and parameters. */
	private final String value;

	/**
	 * Construct content type from string value. Value should be formatted as specified by HTTP Content-Type header, that is, it
	 * should have mime type, both type and subtype and optional parameters separated by mime type with semicolon.
	 *
	 * @param value content type value.
	 * @throws IllegalArgumentException if value argument is null or empty.
	 * @throws SyntaxException if value does not respect syntax described by this class.
	 */
	public ContentType(String value) {
		Params.notNullOrEmpty(value, "Content type value");

		int parametersSeparatorIndex = value.indexOf(';');
		if (parametersSeparatorIndex != -1) {
			parameters = parseParameters(value.substring(parametersSeparatorIndex + 1).trim());
			value = value.substring(0, parametersSeparatorIndex).trim();
		} else {
			parameters = Collections.emptyMap();
		}

		int typesSeparatorIndex = value.indexOf('/');
		if (typesSeparatorIndex == -1) {
			throw new SyntaxException("Invalid content type value |%s|. Missing subtype separator.", value);
		}
		this.type = value.substring(0, typesSeparatorIndex).trim();
		this.subtype = value.substring(typesSeparatorIndex + 1).trim();
		this.mime = Strings.concat(type, '/', subtype);

		StringBuilder valueBuilder = new StringBuilder();
		valueBuilder.append(this.type);
		valueBuilder.append('/');
		valueBuilder.append(this.subtype);
		for (Map.Entry<String, String> parameter : parameters.entrySet()) {
			valueBuilder.append(';');
			valueBuilder.append(parameter.getKey());
			valueBuilder.append('=');
			valueBuilder.append(parameter.getValue());
		}
		this.value = valueBuilder.toString();
	}

	/**
	 * Get mime type for this content type instance.
	 *
	 * @return mime type.
	 * @see #type
	 */
	public String getType() {
		return type;
	}

	/**
	 * Get mime subtype for this content type instance.
	 *
	 * @return mime subtype.
	 * @see #subtype
	 */
	public String getSubtype() {
		return subtype;
	}

	/**
	 * MIME value is just type and subtype, that is, content type without parameters.
	 *
	 * @return mime value.
	 */
	public String getMIME() {
		return mime;
	}

	/**
	 * Test if this content type has the named parameter.
	 *
	 * @param name parameter name.
	 * @return true if this content type has the names parameter.
	 * @throws IllegalArgumentException if name argument is null or empty.
	 */
	public boolean hasParameter(String name) {
		Params.notNullOrEmpty(name, "Parameter name");
		return parameters.containsKey(name);
	}

	/**
	 * Test if content type has a parameter with requested name and value.
	 *
	 * @param name parameter name,
	 * @param value parameter value.
	 * @return true if content type has a parameter with requested name and value.
	 * @throws IllegalArgumentException if name argument is null or empty.
	 * @throws IllegalArgumentException if value argument is null or empty.
	 */
	public boolean hasParameter(String name, String value) {
		Params.notNullOrEmpty(name, "Parameter name");
		Params.notNullOrEmpty(value, "Parameter value");
		return value.equals(parameters.get(name));
	}

	/**
	 * Get parameter value or null if parameter does not exist.
	 *
	 * @param name parameter name.
	 * @return parameter value, possible null.
	 * @throws IllegalArgumentException if name argument is null or empty.
	 */
	public String getParameter(String name) {
		Params.notNullOrEmpty(name, "Parameter name");
		return parameters.get(name);
	}

	/**
	 * Return full content type value, that is, type, subtype and parameters.
	 *
	 * @return full content type value.
	 * @see #value
	 */
	public String getValue() {
		return value;
	}

	/**
	 * Test if this content type is of requested MIME type.
	 *
	 * @param mime requested MIME type.
	 * @return true if this content type is of requested MIME type.
	 */
	public boolean isMIME(String mime) {
		return getMIME().equals(mime);
	}

	/**
	 * Test if this content type is HTML.
	 *
	 * @return true if this content type is HTML.
	 */
	public boolean isHTML() {
		return TEXT_HTML.equals(this);
	}

	/**
	 * Test if this content type is XML.
	 *
	 * @return true if this content type is XML.
	 */
	public boolean isXML() {
		return TEXT_XML.equals(this);
	}

	/**
	 * Test if this content type is JSON.
	 *
	 * @return true if this content type is JSON.
	 */
	public boolean isJSON() {
		return APPLICATION_JSON.equals(this);
	}

	/**
	 * Test if this content type is multipart form.
	 *
	 * @return true if this content type is multipart form.
	 */
	public boolean isMultipartForm() {
		return MULTIPART_FORM.equals(this);
	}

	/**
	 * Test if this content type is mixed multipart.
	 *
	 * @return true if this content type is mixed multipart.
	 */
	public boolean isMultipartMixed() {
		return MULTIPART_MIXED.equals(this);
	}

	/**
	 * Test if this content type is URL encoded.
	 *
	 * @return true if this content type is URL encoded.
	 */
	public boolean isUrlEncodedForm() {
		return URLENCODED_FORM.equals(this);
	}

	/**
	 * Test if this content type is bytes stream.
	 *
	 * @return true if this content type is bytes stream.
	 */
	public boolean isByteStream() {
		return APPLICATION_STREAM.equals(this);
	}

	// --------------------------------------------------------------------------------------------
	// OBJECT

	/**
	 * Get content type instance string representation.
	 *
	 * @return string representation.
	 */
	@Override
	public String toString() {
		return value;
	}

	/**
	 * Get hash code for this content type instance. This class hash code is based on {@link #mime} field.
	 *
	 * @return hash code.
	 */
	@Override
	public int hashCode() {
		return mime.hashCode();
	}

	/**
	 * Predicates that test if given object equals this content type instance. Two content type instances are considered equal
	 * if they have the same {@link #mime} value.
	 *
	 * @param obj another object to test for equality, possible null.
	 * @return true if given object is a content type with the same mime as this instance.
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		return this.mime.equals(((ContentType) obj).mime);
	}

	// --------------------------------------------------------------------------------------------
	// UTILITY METHODS

	/**
	 * Parse content type parameters. Given parameters expression should be valid accordingly grammar from class description; it
	 * should not start with parameters separator, that is, semicolon.
	 *
	 * @param expression content type parameters expression.
	 * @return non empty parameters map.
	 * @throws SyntaxException if parameters expression is not valid.
	 */
	private static Map<String, String> parseParameters(String expression) {
		// charset = UTF-8

		Map<String, String> parameters = new HashMap<>();
		int parametersSeparatorIndex = 0;
		for (;;) {
			int valueSeparatorIndex = expression.indexOf('=', parametersSeparatorIndex);
			if (valueSeparatorIndex == -1) {
				break;
			}
			String name = expression.substring(parametersSeparatorIndex, valueSeparatorIndex).trim();

			++valueSeparatorIndex;
			parametersSeparatorIndex = expression.indexOf(';', valueSeparatorIndex);
			if (parametersSeparatorIndex == -1) {
				parametersSeparatorIndex = expression.length();
			}
			if (valueSeparatorIndex == parametersSeparatorIndex) {
				throw new SyntaxException("Invalid content type parameters |%s|. Value is empty.", expression);
			}

			if (parameters.put(name, expression.substring(valueSeparatorIndex, parametersSeparatorIndex).trim()) != null) {
				throw new SyntaxException("Invalid content type parameters |%s|. Name override |%s|.", expression, name);
			}
			++parametersSeparatorIndex;
		}

		if (parameters.isEmpty()) {
			throw new SyntaxException("Invalid content type parameters |%s|. Missing name/value separator.", expression);
		}

		return parameters;
	}
}


package js.container;

import js.core.AppFactory;
import js.lang.BugError;
import js.lang.Config;
import js.lang.ConfigException;
import js.lang.ManagedPreDestroy;
import js.log.Log;
import js.log.LogFactory;
import js.transaction.Transaction;
import js.transaction.TransactionContext;
import js.transaction.TransactionManager;

/**
 * Transactional resource implementation. See {@link TransactionalResource} interface for transactional resource concept
 * description.
 *

 * Current implementation keeps a reference to {@link TransactionManager} and delegates it for transactions creation. Also has a
 * thread local storage, {@link #sessionStorage} that keeps session object references. Application code can retrieve session
 * object using {@link TransactionContext#getSession()} and execute service API on it while {@link ManagedProxyHandler} takes
 * care to provide transaction boundaries.
 *

 * Because session objects are stored on an inheritable thread local storage, {@link InheritableThreadLocal}, it is legal for
 * application logic to create child threads while still being able to access session object. Anyway, keep in mind that when
 * return from transactional method container destroy transaction and session object. If method creates child threads, perhaps
 * for some parallel computation, it should wait for all child threads end before returning. Fail to do so may result in not
 * specified behavior.
 *
 * @author Iulian Rotaru
 * @version final
 */
final class TransactionalResourceImpl implements TransactionalResource, OptionalConfigurable, ManagedPreDestroy {
	/** Class logger. */
	private static final Log log = LogFactory.getLog(TransactionalResource.class);

	/**
	 * Transaction manager. Since is legal to have runtimes without database support, e.g. embedded systems, transaction manager
	 * is optional. In fact, transaction manager is not even implemented by this library but provided as service.
	 */
	private final TransactionManager transactionManager;

	/**
	 * Keep currently executing transactional session object on thread local storage. Uses inheritable thread local so that
	 * child threads can still access session object.
	 */
	private final ThreadLocal<Object> sessionStorage = new InheritableThreadLocal<>();

	/**
	 * Construct transactional resource for application served by given factory.
	 *
	 * @param appFactory application factory.
	 */
	public TransactionalResourceImpl(AppFactory appFactory) {
		log.trace("TransactionalResource(AppFactory)");
		// uses AppFactory instead of Classes.loadService to acquire transaction manager since need scope management
		this.transactionManager = appFactory.getOptionalInstance(TransactionManager.class);
		if (this.transactionManager == null) {
			throw new BugError("Transaction manager service not found. Ensure there is |%s| service provider on run-time.", TransactionManager.class);
		}
	}

	/**
	 * Configure underlying transaction manager.
	 *
	 * @param config configuration object.
	 * @throws ConfigException if given configuration object is not valid, as requested by transaction manager implementation.
	 * @throws Exception if configuration of the transaction manager implementation fails.
	 */
	@Override
	public void config(Config config) throws Exception {
		log.trace("config(Config.Element)");
		log.debug("Configure transaction manager |%s|.", transactionManager.getClass());
		transactionManager.config(config);
	}

	/** Destroy transaction manager and release all resources like caches and connection pools. */
	@Override
	public void preDestroy() throws Exception {
		log.trace("preDestroy()");
		transactionManager.destroy();
	}

	@Override
	public TransactionManager getTransactionManager() {
		return transactionManager;
	}

	// --------------------------------------------------------------------------------------------
	// TRANSACTIONAL SESSION

	/**
	 * Store currently executing session on thread local variable.
	 *
	 * @param session currently executing session.
	 */
	@Override
	public void storeSession(Object session) {
		sessionStorage.set(session);
	}

	/** After transaction completes release session from thread local storage. */
	@Override
	public void releaseSession() {
		sessionStorage.set(null);
	}

	/**
	 * Get session object bound to current thread. Session object methods are executed inside transaction boundaries, also bound
	 * to current thread.
	 *
	 * @return current thread session.
	 */
	@SuppressWarnings("unchecked")
	@Override
	public <T> T getSession() {
		return (T) sessionStorage.get();
	}

	// --------------------------------------------------------------------------------------------
	// TRANSACTION CREATION

	/**
	 * Delegates {@link TransactionManager#createTransaction()}. This class constructor ensure transactional service provider
	 * exists on run-time.
	 *
	 * @return created transaction.
	 */
	@Override
	public Transaction createTransaction() {
		return transactionManager.createTransaction();
	}

	/**
	 * Delegates {@link TransactionManager#createReadOnlyTransaction()}. This class constructor ensure transactional service
	 * provider exists on run-time.
	 *
	 * @return created read-only transaction.
	 */
	@Override
	public Transaction createReadOnlyTransaction() {
		return transactionManager.createReadOnlyTransaction();
	}
}


/**
 * Copyright (C) 2015 Michael Schnell. All rights reserved.
 * http://www.fuin.org/
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 3 of the License, or (at your option) any
 * later version.
 *
 * This library 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 Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library. If not, see http://www.gnu.org/licenses/.
 */
package org.fuin.srcgen4j.core.base;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.validation.constraints.NotNull;

import org.fuin.objects4j.common.Contract;
import org.fuin.srcgen4j.commons.Folder;
import org.fuin.srcgen4j.commons.GenerateException;
import org.fuin.srcgen4j.commons.GeneratedArtifact;
import org.fuin.srcgen4j.commons.Generator;
import org.fuin.srcgen4j.commons.GeneratorConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Base class for concrete generators.
 *
 * @param <MODEL>
 *            Type of the model.
 * @param <CONFIG>
 *            Type of the generator specific configuration - Use 'Object' if the generator does not require any configuration.
 */
public abstract class AbstractGenerator<MODEL, CONFIG> implements Generator<MODEL> {

    private static final Logger LOG = LoggerFactory.getLogger(AbstractGenerator.class);

    private String name;

    private GeneratorConfig config;

    private MODEL model;

    /**
     * Default constructor.
     */
    public AbstractGenerator() {
        super();
    }

    @Override
    public final void initialize(final GeneratorConfig config) {

        Contract.requireArgNotNull("config", config);
        Contract.requireValid(config);

        this.config = config;
        name = config.getName();

        LOG.debug("Initialize generator: {}", name);

        final Object obj = config.getConfig().getConfig();
        if (getSpecificConfigClass() == null) {
            if (obj != null) {
                throw new IllegalStateException("No configuration is expected, but was: " + obj.getClass());
            }
        } else {
            if (obj == null) {
                throw new IllegalStateException(
                        "The configuration is expected to be of type '" + getSpecificConfigClass().getName() + "', but was: null");
            }
            if (!getSpecificConfigClass().isAssignableFrom(obj.getClass())) {
                throw new IllegalStateException("The configuration is expected to be of type '" + getSpecificConfigClass().getName()
                        + "', but was: " + obj.getClass().getName());
            }
        }

        init();

    }

    /**
     * Extension point for specific initialization. Sub classes can use this to initialize something after the main initialization process
     * is done. Does nothing by default.
     */
    protected void init() {
        // May be overwritten by sub classes
    }

    @Override
    public final void generate(final MODEL model, final boolean incremental) throws GenerateException {

        Contract.requireArgNotNull("model", model);
        Contract.requireValid(model);

        this.model = model;

        LOG.info("Starting generation: {}", name);
        generate(incremental);
        LOG.info("Generation finished: {}", name);

    }

    /**
     * Returns the configuration.
     *
     * @return Configuration.
     */
    public final GeneratorConfig getGeneratorConfig() {
        return config;
    }

    /**
     * Returns the model.
     *
     * @return Model.
     */
    public final MODEL getModel() {
        return model;
    }

    /**
     * Returns the class of the expected configuration class. This method can be overridden to make sure the configuration is valid.
     *
     * @return Specific configuration class or NULL if no configuration is required.
     */
    // CHECKSTYLE:OFF Empty methods do not violate the 'design for extension'
    // principle
    public Class<CONFIG> getSpecificConfigClass() {
        // CHECKSTYLE:ON
        return null;
    }

    /**
     * Returns the specific configuration. This is a shortcut for getConfig().getConfig().getConfig().
     *
     * @return Generator configuration or NULL.
     */
    @SuppressWarnings("unchecked")
    public final CONFIG getSpecificConfig() {
        return (CONFIG) config.getConfig().getConfig();
    }

    /**
     * Returns the target file for a given artifact type and filename. This method takes care about eventually creating non existing
     * directories or protect existing files to be overridden.
     *
     * @param artifactName
     *            Type of the artifact to create - This has to refer to an existing entry in the configuration (Like '<artifact
     *            name="artifactName" ..>').
     * @param filename
     *            Name of the target file (without a path).
     * @param logInfo
     *            Addition information to log.
     *
     * @return File to write to or NULL if nothing should be written.
     */
    protected final GeneratedFile getTargetFile(final String artifactName, final String filename, final String logInfo) {

        final Folder folder = getGeneratorConfig().findTargetFolder(artifactName);
        final File dir = folder.getCanonicalDir();
        final File file = new File(dir, filename);

        // Make sure the folder exists
        if (!dir.exists()) {
            if (folder.isCreate()) {
                dir.mkdirs();
            } else {
                throw new IllegalStateException("Directory '" + dir + "' does not exist, but configuration does not allow creation: "
                        + "<folder name=\"" + folder.getName() + "\" create=\"false\" ... />");
            }
        }
        // Make sure the parent directory for the file exists
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        if (file.exists() && !folder.overrideAllowed(file)) {
            // Skip file because override is not allowed
            return new GeneratedFile(file, logInfo, true);
        }

        return new GeneratedFile(file, logInfo);

    }

    /**
     * Writes a generated artifact to a file.
     *
     * @param artifact
     *            Artifact to persist.
     *
     * @throws GenerateException
     *             Error writing the artifact.
     */
    protected final void write(@NotNull final GeneratedArtifact artifact) throws GenerateException {

        Contract.requireArgNotNull("artifact", artifact);

        final GeneratedFile genFile = getTargetFile(artifact.getName(), artifact.getPathAndName(), null);
        if (genFile.isSkip()) {
            LOG.debug("Omitted already existing file: {} [{}]", genFile, artifact);
        } else {
            LOG.debug("Writing file:  {} [{}]", genFile, artifact);
            try {
                final OutputStream out = new BufferedOutputStream(new FileOutputStream(genFile.getTmpFile()));
                try {
                    out.write(artifact.getData());
                } finally {
                    out.close();
                }
                genFile.persist();
            } catch (final IOException ex) {
                throw new GenerateException("Error writing artifact '" + artifact + "' to '" + artifact.getPathAndName() + "'!", ex);
            }
        }
    }

    /**
     * Generates something using the given configuration and model.
     *
     * @param incremental
     *            If this is an incremental build TRUE, else FALSE (full build).
     *
     * @throws GenerateException
     *             Error when generating.
     */
    protected abstract void generate(boolean incremental) throws GenerateException;

}


package js.container;

import java.util.List;

import js.converter.Converter;
import js.converter.ConverterException;
import js.lang.BugError;
import js.lang.Config;
import js.util.Classes;

/**
 * Initialize instance fields from managed class configuration object. Instance initializer reads name / value pairs from
 * instance-field configuration element, see sample below. String value is converted to instance field type using
 * {@link Converter#asObject(String, Class)} utility. This means that configured value should be convertible to field type,
 * otherwise {@link ConverterException} is thrown.
 *

 * In sample there is a person managed instance that has a configuration section. Configuration declares three
 * instance fields that will be initialized with defined values every time a new js.app.Person instance is created.
 *
 *

 * <managed-classes>
 * 	<person class="js.app.Person" />
 * </managed-classes>
 * ...
 * <person>
 * 	<instance-field name='name' value='John Doe' />
 * 	<instance-field name='age' value='54' />
 * 	<instance-field name='married' value='false' />
 * </person>
 * ...
 * class Person {
 * 	private String name;
 * 	private int age;
 * 	private boolean married;
 * }
 *

 *
 * Instance initialization can be performed only on {@link InstanceType#POJO} types. Not obeying this constrain is considered a
 * bug and {@link BugError} is thrown. Finally, if managed class has no configuration object this processor does nothing, that
 * is, returns given instance as it is.
 *
 * @author Iulian Rotaru
 * @version final
 */
final class InstanceFieldsInitializationProcessor implements InstanceProcessor {
	/**
	 * Initialize instance fields from managed class configuration object.
	 *
	 * @param managedClass managed class,
	 * @param instance instance of managed class.
	 * @throws ConverterException if configured value cannot be converted to field type.
	 * @throws BugError if attempt to assign instance field to not POJO type.
	 */
	@Override
	public void postProcessInstance(ManagedClassSPI managedClass, Object instance) {
		Config config = managedClass.getConfig();
		if (config == null) {
			return;
		}
		List<Config> fields = config.findChildren("instance-field");
		if (!fields.isEmpty() && !InstanceType.POJO.equals(managedClass.getInstanceType())) {
			throw new BugError("Cannot assign instance field on non %s type.", InstanceType.POJO);
		}
		for (Config field : fields) {
			Classes.setFieldValue(instance, field.getAttribute("name"), field.getAttribute("value"));
		}
	}
}


package js.container;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import js.annotation.Asynchronous;
import js.annotation.ContextParam;
import js.annotation.Controller;
import js.annotation.Cron;
import js.annotation.Inject;
import js.annotation.Intercepted;
import js.annotation.Local;
import js.annotation.Private;
import js.annotation.Public;
import js.annotation.Remote;
import js.annotation.RequestPath;
import js.annotation.Service;
import js.annotation.Test;
import js.converter.Converter;
import js.converter.ConverterException;
import js.core.AppFactory;
import js.lang.BugError;
import js.lang.Config;
import js.lang.ConfigException;
import js.lang.Configurable;
import js.lang.ManagedLifeCycle;
import js.log.Log;
import js.log.LogFactory;
import js.transaction.Immutable;
import js.transaction.Mutable;
import js.transaction.Transactional;
import js.util.Classes;
import js.util.Types;

/**
 * Managed class resemble Java {@link Class} and is configurable from external class descriptor and by annotations. Class
 * descriptor is a configuration element from managed-classes section from application descriptor. Managed class
 * parses and validates class descriptor and annotations and initialize its state accordingly; managed class is immutable. Also
 * takes care to parse managed class configuration, if exist into application descriptor, see below Descriptor
 * section. Anyway, managed class has no means to create instances, like Java class has. This is because instance creation
 * algorithm is implemented into container and exposes by {@link AppFactory} interface.
 *

 * As stated, managed class parses given class descriptor and scans all interfaces and implementation classes annotations in
 * order to initialize its internal state. Since is immutable, initialization is fully completed on construction, there is no
 * setters. If constructor fails to validate its internal state throws {@link ConfigException} and container creation aborts.
 *

 * Managed class holds information used by container for managed instances life cycle, that is, instances creation or reusing,
 * initialization and instance declarative services. Here are managed class core properties and related class descriptor
 * attributes, used by container for instances life cycle:
 *

 *
{@link #interfaceClasses} used to identify managed instances - initialized from interface attribute or child
 * element,
 *
{@link #implementationClass} used to create instances when managed class is local - initialized from class
 * attribute,
 *
{@link #instanceType} selector for {@link InstanceFactory} strategy - initialized from type attribute,
 *
{@link #instanceScope} selector for {@link ScopeFactory} strategy - initialized from scope attribute.
 *

 *

 * Container uses interface classes to identify managed classes in classes pool. For this reason is not possible to use the same
 * interface class for two different managed classes.
 *

 * Managed class has an interface and associated implementation. Usually there is only one interface but support for multiple
 * interface exists. Note that interface concept is not identical with Java interface. It is in fact the class used
 * to identify managed class and related instances; interfaceClass parameter from
 * {@link AppFactory#getInstance(Class, Object...)} refers to this interface. In most cases it is indeed a Java
 * interface but can be abstract class or even standard Java class. Implementation class is optional depending on
 * {@link #instanceType}. Anyway, if implementation exists it must be an instantiable class, package private accepted.
 *
 *
Class Descriptor
 Managed classes use class descriptors to declare managed class interface(s), implementation, type
 * and scope. In application descriptor there is a predefined managed-classes section and inside it all managed
 * classes are declared, an element per class - this configuration element is the class descriptor. Every managed class
 * descriptor has a name, i.e. the element tag; this name is used to declare managed class configuration section.
 *

 * Lets consider a UserManagerImpl class that implements UserManager interface. Into application descriptor there is
 * managed-classes section used to declare all managed classes. Below user-manager section is managed
 * class specific configuration.
 *
 *

 * class UserManagerImpl implements UserManager {
 * 	static File USERS_PATH;
 * }
 *
 * // application descriptor
 * ...
 * // managed classes section
 * <managed-classes>
 * 	// class descriptor for managed class
 * 	<user-manager interface="js.admin.UserManager" implementation="js.admin.UserManagerImpl" type="POJO" scope="APPLICATION" />
 * 	...
 * </managed-classes>
 *
 * // managed class configuration
 * <user-manager>
 * 	<static-field name="USERS_PATH" value="/usr/share/tomcat/conf/users" />
 * </manager>
 *

 *
 * In above sample code, descriptor class for js.admin.UserManager has user-manager name. This name is used to
 * declare managed class configuration - the below section with the same name. Class configuration section is supplied to
 * {@link Configurable#config(Config)}, of course if managed class implements {@link Configurable}. Every managed class
 * configuration section has a specific content that has meaning only to owning managed class.
 *

 * Managed classes support multiple interfaces. In sample, transactional resource is a single POJO instance with application
 * scope. It is accessible by both its own class and by transaction context interface. No mater which interface is used to
 * retrieve the managed instance, the same managed class is used in the end.
 *
 *

 * <data-source class="js.core.TransactionalResource">
 * 	<interface name="js.core.TransactionalResource" />
 * 	<interface name="js.core.TransactionContext" />
 * </data-source>
 *

 *

 * Also, a managed instance can be loaded from a service using Java service loader. In this case managed class has no
 * implementation declared and has {@link InstanceType#SERVICE} type.
 *
 *

 * <transaction-manager interface="js.core.TransactionManager" type="SERVICE" />
 *

 *

 * Finally, a managed class could describe a managed instance deployed on a remote host. For this uses
 * {@link InstanceType#REMOTE} type and add url attribute with remote host address. Managed instances created for
 * this managed class will actually be a Java proxy that delegates method invocation to a HTTP-RMI client.
 *
 *

 * <weather-service interface="ro.bbnet.WeatherService" type="REMOTE" url="http://bbnet.ro" />
 *

 *
 * Even if url attribute is provided, when use {@link AppFactory#getInstance(String, Class, Object...)} to retrieve
 * named instances, container will enact a discovery process based on provided instance name that in this case should be unique
 * on local network.
 *
 *
Annotations

 *

 * Managed class supports declarative services via annotations. This implementation follows JSR-250 guidelines for annotations
 * inheritance. Basically annotations are searched in implementation class; this is true for method annotations which are
 * searched also in implementation class. For this reason only managed classes of types that require implementation may have
 * annotations, see {@link InstanceType#requiresImplementation()}.
 *

 * Optionally, managed class supports an extended annotations searching scope, something resembling JAX-RS, annotation
 * inheritance. If annotation is not found in implementation class it is searched in super class or implemented interface.
 *

 * For your convenience here is the list of supported annotations. For details see annotations class description.
 *

 *

 * Annotation Class
 * Arguments
 * Description
 * Type
 * Method
 * Field
 *

 * {@link Remote}
 * Class request URI path
 * Grant remote access from client logic to managed methods deployed in container.
 * true
 * true
 * false
 *

 * {@link Local}
 * N/A
 * Forbid remote access to particular methods inside a {@link Remote} accessible managed instance.
 * false
 * true
 * false
 *

 * {@link Controller}
 * Class request URI path
 * A controller is a remote accessible managed class that has methods returning resources.
 * true
 * false
 * false
 *

 * {@link Service}
 * Class request URI path
 * A service is a managed class that has methods remotely accessible.
 * true
 * false
 * false
 *

 * {@link RequestPath}
 * Method request URI path
 * Managed method binding to particular resource path.
 * false
 * true
 * false
 *

 * {@link Private}
 * N/A
 * Private remote accessible managed methods, that cannot be invoked without authorization.
 * false
 * true
 * false
 *

 * {@link Public}
 * N/A
 * Remote accessible entity that do not require authorization.
 * true
 * true
 * false
 *

 * {@link Transactional}
 * N/A
 * Execute managed method into transactional scope.
 * true
 * true
 * false
 *

 * {@link Immutable}
 * N/A
 * Immutable, that is, read-only transaction.
 * true
 * true
 * false
 *

 * {@link Mutable}
 * N/A
 * Force mutable transaction on particular method inside a {@link Immutable} managed instance.
 * false
 * true
 * false
 *

 * {@link Asynchronous}
 * N/A
 * Execute managed method in a separated thread of execution.
 * true
 * true
 * false
 *

 * {@link Inject}
 * N/A
 * Managed instance field value injection.
 * false
 * false
 * true
 *

 * {@link Intercepted}
 * interceptor class
 * An intercepted managed method executes an interceptor cross-cutting logic whenever is invoked.
 * true
 * true
 * false
 *

 *
 * @author Iulian Rotaru
 * @version final
 */
final class ManagedClass implements ManagedClassSPI {
	/** Class logger. */
	private static final Log log = LogFactory.getLog(ManagedClass.class);

	/** Seed for managed classes key. */
	private static final AtomicInteger KEY_SEED = new AtomicInteger(0);

	/** Back reference to parent container. */
	private final Container container;

	/**
	 * Managed class key used to uniquely identifying this managed class. This key is incremental and can be used to sort
	 * managed classes in creation order.
	 */
	private final String key;

	/**
	 * Optional managed class configuration object. Every managed class has an name used in managed-classes section
	 * from application descriptor. This name can be used to name configuration section usable for every managed class that
	 * implements {@link Configurable} interface or for field initialization. This field store this configuration section parsed
	 * as a configuration object. It is null if configuration section is missing.
	 */
	private final Config config;

	/** Managed instance scope used for life span management. */
	private final InstanceScope instanceScope;

	/** Managed instance type used to select {@link InstanceFactory}. */
	private final InstanceType instanceType;

	/**
	 * Managed class interfaces, usually one but multiple supported. If class descriptor has only class attribute
	 * this field is initialized from {@link #implementationClass}.
	 */
	private final Class<?>[] interfaceClasses;

	/**
	 * Optional managed class implementation has value only if {@link InstanceType#requiresImplementation()} says so. It can be
	 * null if managed class does not require implementation, for example if is a remote class.
	 */
	private final Class<?> implementationClass;

	/** Cached implementation constructor or null if this managed class has no implementation. */
	private final Constructor<?> constructor;

	/** Fields tagged with {@link Inject} annotation, in no particular order. */
	private final Collection<Field> dependencies;

	/**
	 * Optional remote class implementation URL that can be used if this managed class is {@link InstanceType#REMOTE}. This
	 * field is loaded from url attribute from class descriptor.
	 */
	private final String implementationURL;

	/**
	 * Managed methods pool for managed classes of type {@link InstanceType#PROXY}. Used by {@link ManagedProxyHandler} to find
	 * out managed method bound to interface method.
	 */
	private final Map<Method, ManagedMethodSPI> methodsPool = new HashMap<>();

	/** Pool of net methods, that is, methods remotely accessible. */
	private final Map<String, ManagedMethodSPI> netMethodsPool = new HashMap<>();

	private final Map<String, Field> contextParamFields = new HashMap<>();

	private final List<ManagedMethodSPI> cronMethodsPool = new ArrayList<>();

	/** Cached value of managed class string representation, merely for logging. */
	private final String string;

	/**
	 * A managed class is remotely accessible, also known as net class, if is annotated with {@link Remote} or has at least one
	 * remotely accessible method.
	 */
	private boolean remotelyAccessible;

	/** A managed class is transactional if is annotated with {@link Transactional} or has at least one transactional method. */
	private boolean transactional;

	/**
	 * Request URI path for this managed class configured by {@link Remote}, {@link Controller} or {@link Service} annotations.
	 */
	private String requestPath;

	private boolean autoInstanceCreation;

	/**
	 * Loads this managed class state from class descriptor then delegates {@link #scanAnnotations()}. Annotations scanning is
	 * performed only if this managed class type requires implementation, see {@link InstanceType#requiresImplementation()}.
	 *
	 * @param container parent container,
	 * @param descriptor class descriptor from managed-class section.
	 * @throws ConfigException if configuration is invalid.
	 */
	public ManagedClass(Container container, Config descriptor) throws ConfigException {
		this.container = container;
		// if configuration section is missing this.config field remains null
		this.config = descriptor.getRoot().getChild(descriptor.getName());

		// loading order matters; do not change it
		this.instanceScope = loadInstanceScope(descriptor);
		this.instanceType = loadInstanceType(descriptor);
		this.implementationClass = loadImplementationClass(descriptor);
		this.interfaceClasses = loadInterfaceClasses(descriptor);
		this.implementationURL = loadImplementationURL(descriptor);

		// get declared constructor return null if no implementation class
		this.constructor = getDeclaredConstructor(this.implementationClass);
		// scan dependencies return empty collection if no implementation class
		this.dependencies = scanDependencies(this.implementationClass);

		if (this.instanceType.requiresImplementation()) {
			scanAnnotations();
			initializeStaticFields();
		}

		this.key = Integer.toString(KEY_SEED.getAndIncrement());
		this.string = buildStringRepresentation(descriptor);
	}

	/**
	 * This managed class string representation.
	 *
	 * @return string representation.
	 * @see #string
	 */
	@Override
	public String toString() {
		return string;
	}

	/**
	 * Scan annotations for managed classes that requires implementation. Annotations are processed only for managed classes
	 * that have {@link #implementationClass} that is primary source scanned for annotation. If an annotation is not present
	 * into implementation class try to find it on interface(s). See Annotations section from class
	 * description.
	 *
	 * @throws BugError for insane conditions.
	 */
	private void scanAnnotations() {
		// set remote type and request URI path from @Remote, @Controller or @Service
		boolean remoteType = false;
		Controller controllerAnnotation = getAnnotation(implementationClass, Controller.class);
		if (controllerAnnotation != null) {
			remoteType = true;
			requestPath = controllerAnnotation.value();
		}
		Service serviceAnnotation = getAnnotation(implementationClass, Service.class);
		if (serviceAnnotation != null) {
			remoteType = true;
			requestPath = serviceAnnotation.value();
		}

		Remote remoteAnnotation = getAnnotation(implementationClass, Remote.class);
		if (remoteAnnotation != null) {
			remoteType = true;
		}
		RequestPath requestPathAnnotation = getAnnotation(implementationClass, RequestPath.class);
		if (requestPathAnnotation != null) {
			requestPath = requestPathAnnotation.value();
		}

		if (requestPath != null && requestPath.isEmpty()) {
			requestPath = null;
		}
		if (remoteType) {
			remotelyAccessible = true;
		}

		// set transactional and immutable type
		boolean transactionalType = hasAnnotation(implementationClass, Transactional.class);
		boolean immutableType = hasAnnotation(implementationClass, Immutable.class);
		if (!transactionalType && immutableType) {
			throw new BugError("@Immutable annotation without @Transactional on class |%s|.", implementationClass.getName());
		}
		if (transactionalType && !instanceType.isPROXY()) {
			throw new BugError("@Transactional requires |%s| type but found |%s| on |%s|.", InstanceType.PROXY, instanceType, implementationClass);
		}

		Class<? extends Interceptor> classInterceptor = getInterceptorClass(implementationClass);
		boolean publicType = hasAnnotation(implementationClass, Public.class);

		// managed classes does not support public inheritance
		for (Method method : implementationClass.getDeclaredMethods()) {
			final int modifiers = method.getModifiers();
			if (Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) {
				// scans only public and non-static methods
				continue;
			}
			Method interfaceMethod = getInterfaceMethod(method);
			ManagedMethod managedMethod = null;

			boolean remoteMethod = hasAnnotation(method, Remote.class);
			if (!remoteMethod) {
				remoteMethod = remoteType;
			}
			if (hasAnnotation(method, Local.class)) {
				if (!remoteMethod) {
					throw new BugError("@Local annotation on not remote method |%s|.", method);
				}
				remoteMethod = false;
			}
			if (remoteMethod) {
				// if at least one owned managed method is remote this managed class become remote too
				remotelyAccessible = true;
			}

			// load method interceptor annotation and if missing uses class annotation; is legal for both to be null
			Class<? extends Interceptor> methodInterceptor = getInterceptorClass(method);
			if (methodInterceptor == null) {
				methodInterceptor = classInterceptor;
			}
			// if method is intercepted, either by method or class annotation, create intercepted managed method
			if (methodInterceptor != null) {
				if (!instanceType.isPROXY() && !remotelyAccessible) {
					throw new BugError("@Intercepted method |%s| supported only on PROXY type or remote accessible classes.", method);
				}
				managedMethod = new ManagedMethod(this, methodInterceptor, interfaceMethod);
			}

			// handle remote accessible methods

			boolean publicMethod = hasAnnotation(method, Public.class);
			if (publicMethod && !remotelyAccessible) {
				throw new BugError("@Public annotation on not remote method |%s|.", method);
			}
			if (!publicMethod) {
				publicMethod = publicType;
			}
			if (hasAnnotation(method, Private.class)) {
				if (!remotelyAccessible) {
					throw new BugError("@Private annotation on not remote method |%s|.", method);
				}
				publicMethod = false;
			}
			RequestPath methodPath = getAnnotation(method, RequestPath.class);
			if (!remotelyAccessible && methodPath != null) {
				throw new BugError("@MethodPath annotation on not remote method |%s|.", method);
			}
			if (remoteMethod) {
				if (managedMethod == null) {
					managedMethod = new ManagedMethod(this, interfaceMethod);
				}
				managedMethod.setRequestPath(methodPath != null ? methodPath.value() : null);
				managedMethod.setRemotelyAccessible(remoteMethod);
				managedMethod.setAccess(publicMethod ? Access.PUBLIC : Access.PRIVATE);
			}

			// handle declarative transaction
			// 1. allow transactional only on PROXY
			// 2. do not allow immutable on method if not transactional
			// 3. do not allow mutable on method if not transactional
			// 4. if PROXY create managed method if not already created from above remote method logic

			if (!transactionalType) {
				transactionalType = hasAnnotation(method, Transactional.class);
			}
			if (transactionalType) {
				transactional = true;
				if (!instanceType.isPROXY()) {
					throw new BugError("@Transactional requires |%s| type but found |%s|.", InstanceType.PROXY, instanceType);
				}
			}

			boolean immutable = hasAnnotation(method, Immutable.class);
			if (immutable && !transactional) {
				log.debug("@Immutable annotation without @Transactional on method |%s|.", method);
			}
			if (!immutable) {
				immutable = immutableType;
			}
			if (hasAnnotation(method, Mutable.class)) {
				if (!transactional) {
					log.debug("@Mutable annotation without @Transactional on method |%s|.", method);
				}
				immutable = false;
			}

			if (instanceType.isPROXY() && managedMethod == null) {
				managedMethod = new ManagedMethod(this, interfaceMethod);
			}
			if (transactional) {
				managedMethod.setTransactional(true);
				managedMethod.setImmutable(immutable);
			}

			// handle asynchronous mode
			// 1. instance type should be PROXY or managed class should be flagged for remote access
			// 2. transactional method cannot be executed asynchronously
			// 3. asynchronous method should be void

			boolean asynchronousMethod = hasAnnotation(method, Asynchronous.class);
			if (asynchronousMethod) {
				if (!instanceType.isPROXY() && !remotelyAccessible) {
					throw new BugError("Not supported instance type |%s| for asynchronous method |%s|.", instanceType, method);
				}
				if (transactional) {
					throw new BugError("Transactional method |%s| cannot be executed asynchronous.", method);
				}
				if (!Types.isVoid(method.getReturnType())) {
					throw new BugError("Asynchronous method |%s| must be void.", method);
				}

				// at this point either instance type is PROXY or remote flag is true; checked by above bug error
				// both conditions has been already processed with managed method creation
				// so managed method must be already created
				managedMethod.setAsynchronous(asynchronousMethod);
			}

			Cron cronMethod = getAnnotation(method, Cron.class);
			if (cronMethod != null) {
				// if (!instanceType.isPROXY()) {
				// throw new BugError("Not supported instance type |%s| for cron method |%s|.", instanceType, method);
				// }
				if (remotelyAccessible) {
					throw new BugError("Remote accessible method |%s| cannot be executed by cron.", method);
				}
				if (transactional) {
					throw new BugError("Transactional method |%s| cannot be executed by cron.", method);
				}
				if (!Types.isVoid(method.getReturnType())) {
					throw new BugError("Cron method |%s| must be void.", method);
				}

				if (managedMethod == null) {
					managedMethod = new ManagedMethod(this, interfaceMethod);
				}
				managedMethod.setCronExpression(cronMethod.value());
				cronMethodsPool.add(managedMethod);
				autoInstanceCreation = true;
			}

			// store managed method, if created, to managed methods pool
			if (managedMethod != null) {
				methodsPool.put(interfaceMethod, managedMethod);
				if (managedMethod.isRemotelyAccessible() && netMethodsPool.put(method.getName(), managedMethod) != null) {
					throw new BugError("Overloading is not supported for net method |%s|.", managedMethod);
				}
			}
		}

		for (Field field : implementationClass.getDeclaredFields()) {
			ContextParam contextParam = field.getAnnotation(ContextParam.class);
			if (contextParam != null) {
				field.setAccessible(true);
				contextParamFields.put(contextParam.value(), field);
			}
		}
	}

	// --------------------------------------------------------------------------------------------
	// MANAGED CLASS SPI

	@Override
	public ContainerSPI getContainer() {
		return container;
	}

	@Override
	public String getKey() {
		return key;
	}

	@Override
	public Config getConfig() {
		return config;
	}

	@Override
	public Class<?>[] getInterfaceClasses() {
		return interfaceClasses;
	}

	@Override
	public Class<?> getInterfaceClass() {
		if (interfaceClasses.length > 1) {
			throw new BugError("Attempt to treat multiple interfaces as a single one.");
		}
		return interfaceClasses[0];
	}

	@Override
	public Class<?> getImplementationClass() {
		return implementationClass;
	}

	@Override
	public Constructor<?> getConstructor() {
		return constructor;
	}

	@Override
	public Iterable<Field> getDependencies() {
		return dependencies;
	}

	@Override
	public Iterable<ManagedMethodSPI> getManagedMethods() {
		return methodsPool.values();
	}

	@Override
	public Iterable<ManagedMethodSPI> getCronManagedMethods() {
		return cronMethodsPool;
	}

	@Override
	public ManagedMethodSPI getManagedMethod(Method method) throws NoSuchMethodException {
		if (!instanceType.equals(InstanceType.PROXY)) {
			throw new BugError("Managed method getter can be used only on |%s| types.", InstanceType.PROXY);
		}
		ManagedMethodSPI managedMethod = methodsPool.get(method);
		if (managedMethod == null) {
			throw new NoSuchMethodException(String.format("Missing managed method |%s#%s|.", implementationClass.getName(), method.getName()));
		}
		return managedMethod;
	}

	@Override
	public ManagedMethodSPI getNetMethod(String methodName) {
		ManagedMethodSPI managedMethod = netMethodsPool.get(methodName);
		if (managedMethod == null) {
			log.error("Missing remote method |%s| from |%s|.", methodName, implementationClass);
			return null;
		}
		return managedMethod;
	}

	@Override
	public InstanceScope getInstanceScope() {
		return instanceScope;
	}

	@Override
	public InstanceType getInstanceType() {
		return instanceType;
	}

	@Override
	public boolean isTransactional() {
		return transactional;
	}

	@Override
	public boolean isRemotelyAccessible() {
		return remotelyAccessible;
	}

	@Override
	public String getRequestPath() {
		if (!remotelyAccessible) {
			throw new BugError("Attempt to retrive request URI path for local managed class |%s|.", this);
		}
		return requestPath;
	}

	@Override
	public String getImplementationURL() {
		return implementationURL;
	}

	@Override
	public Map<String, Field> getContextParamFields() {
		return contextParamFields;
	}

	// --------------------------------------------------------------------------------------------
	// CLASS DESCRIPTOR UTILITY METHODS

	@Override
	public boolean isAutoInstanceCreation() {
		return autoInstanceCreation;
	}

	/**
	 * Load optional implementation class from class descriptor and applies insanity checks. Load implementation class from
	 * class attribute from descriptor. If class attribute not present return null.
	 *

	 * Beside loading implementation class this utility method performs sanity checks and throws configuration exception if:
	 *

	 *
instance type requires implementation but class attribute is missing,
	 *
instance type does not require implementation but class attribute is present,
	 *
class not found on run-time class path,
	 *
implementation class is a Java interface,
	 *
implementation class is abstract,
	 *
implementation class implements managed life cycle but instance scope is not {@link InstanceScope#APPLICATION}.
	 *

	 *
	 * @param descriptor class descriptor.
	 * @return loaded implementation class or null.
	 * @throws ConfigException if sanity check fails.
	 */
	private Class<?> loadImplementationClass(Config descriptor) throws ConfigException {
		String implementationName = descriptor.getAttribute("class");
		if (implementationName == null) {
			if (instanceType.requiresImplementation()) {
				throw new ConfigException("Managed type |%s| requires <class> attribute. See class descriptor |%s|.", instanceType, descriptor);
			}
			return null;
		}
		if (!instanceType.requiresImplementation()) {
			throw new ConfigException("Managed type |%s| forbids <class> attribute. See class descriptor |%s|.", instanceType, descriptor);
		}

		Class<?> implementationClass = Classes.forOptionalName(implementationName);
		if (implementationClass == null) {
			throw new ConfigException("Managed class implementation |%s| not found.", implementationName);
		}

		if (implementationClass.isInterface()) {
			throw new ConfigException("Managed class implementation |%s| cannot be an interface. See class descriptor |%s|.", implementationClass, descriptor);
		}
		int implementationModifiers = implementationClass.getModifiers();
		if (Modifier.isAbstract(implementationModifiers)) {
			throw new ConfigException("Managed class implementation |%s| cannot be abstract. See class descriptor |%s|.", implementationClass, descriptor);
		}
		if (Types.isKindOf(implementationClass, ManagedLifeCycle.class) && !InstanceScope.APPLICATION.equals(instanceScope)) {
			throw new ConfigException("Bad scope |%s| used with managed life cycle. See class descriptor |%s|.", instanceScope, descriptor);
		}

		return implementationClass;
	}

	/**
	 * Load interface classes from class descriptor. Attempt to load interface classes from interface attribute or
	 * child elements. If none found returns implementation class that should be already initialized.
	 *

	 * Perform sanity checks on loaded interface classes and throws configuration exception is:
	 *

	 *
instance type requires interface but none found,
	 *
name attribute is missing from child element,
	 *
interface class not found on run-time class path,
	 *
implementation class is not implemented by already configured implementation class.
	 *

	 *

	 * This utility method should be loaded after {@link #loadImplementationClass(Config)} otherwise behavior is not defined.
	 *
	 * @param descriptor class descriptor.
	 * @return interface classes array.
	 * @throws ConfigException if sanity check fails.
	 */
	private Class<?>[] loadInterfaceClasses(Config descriptor) throws ConfigException {
		List<String> interfaceNames = new ArrayList<>();

		if (!descriptor.hasChildren()) {
			if (!descriptor.hasAttribute("interface")) {
				if (instanceType.requiresInterface()) {
					throw new ConfigException("Managed type |%s| requires <interface> attribute. See class descriptor |%s|.", instanceType, descriptor);
				}
				// if interface is not required and is missing uses implementation class
				return new Class<?>[] { implementationClass };
			}
			interfaceNames.add(descriptor.getAttribute("interface"));

			if ("REMOTE".equals(descriptor.getAttribute("type"))) {
				String url = descriptor.getAttribute("url");
				if (url == null || url.isEmpty()) {
					throw new ConfigException("Managed type REMOTE requires <url> attribute. See class descriptor |%s|.", descriptor);
				}
				if (url.startsWith("${")) {
					throw new ConfigException("Remote implementation <url> property not resolved. See class descriptor |%s|.", descriptor);
				}
			}
		} else {
			for (int i = 0; i < descriptor.getChildrenCount(); ++i) {
				String interfaceName = descriptor.getChild(i).getAttribute("name");
				if (interfaceName == null) {
					throw new ConfigException("Missing <name> attribute from interface declaration. See class descriptor |%s|.", descriptor);
				}
				interfaceNames.add(interfaceName);
			}
		}

		Class<?>[] interfaceClasses = new Class<?>[interfaceNames.size()];
		for (int i = 0; i < interfaceNames.size(); ++i) {
			final String interfaceName = interfaceNames.get(i);
			final Class<?> interfaceClass = Classes.forOptionalName(interfaceName);

			if (interfaceClass == null) {
				throw new ConfigException("Managed class interface |%s| not found.", interfaceName);
			}
			if (Types.isKindOf(interfaceClass, ManagedLifeCycle.class)) {
				autoInstanceCreation = true;
			}
			if (instanceType.requiresInterface() && !interfaceClass.isInterface()) {
				throw new ConfigException("Managed type |%s| requires interface to make Java Proxy happy but got |%s|.", instanceType, interfaceClass);
			}
			if (implementationClass != null && !Types.isKindOf(implementationClass, interfaceClass)) {
				throw new ConfigException("Implementation |%s| is not a kind of interface |%s|.", implementationClass, interfaceClass);
			}

			interfaceClasses[i] = interfaceClass;
		}
		return interfaceClasses;
	}

	/**
	 * Return instance scope loaded from class descriptor, scope attribute. If scope is not defined use
	 * {@link InstanceScope#APPLICATION} as default value.
	 *
	 * @param descriptor managed class descriptor.
	 * @return loaded instance scope.
	 * @throws ConfigException if there is no {@link ScopeFactory} registered for loaded instance scope.
	 */
	private InstanceScope loadInstanceScope(Config descriptor) throws ConfigException {
		InstanceScope instanceScope = descriptor.getAttribute("scope", InstanceScope.class, InstanceScope.APPLICATION);
		if (!container.hasScopeFactory(instanceScope)) {
			throw new ConfigException("Not registered managed instance scope value |%s|. See class descriptor |%s|.", instanceScope, descriptor);
		}
		return instanceScope;
	}

	/**
	 * Return instance type loaded from class descriptor, type attribute. If type is not defined uses
	 * {@link InstanceType#POJO} as default value.
	 *
	 * @param descriptor managed class descriptor.
	 * @return loaded instance type.
	 * @throws ConfigException if there is no {@link InstanceFactory} registered for loaded instance type.
	 */
	private InstanceType loadInstanceType(Config descriptor) throws ConfigException {
		InstanceType instanceType = descriptor.getAttribute("type", InstanceType.class, InstanceType.POJO);
		if (!container.hasInstanceFactory(instanceType)) {
			throw new ConfigException("Not registered managed instance type value |%s|. See class descriptor |%s|.", instanceType, descriptor);
		}
		return instanceType;
	}

	/**
	 * Load remote class URL from class descriptor, url attribute. This getter does not perform URL validation; it
	 * returns URL value as declared by attribute.
	 *
	 * @param descriptor class descriptor.
	 * @return remote class URL value.
	 * @throws ConfigException if instance type is {@link InstanceType#REMOTE} and url attribute is missing.
	 */
	private String loadImplementationURL(Config descriptor) throws ConfigException {
		String implementationURL = descriptor.getAttribute("url");
		if (instanceType.equals(InstanceType.REMOTE) && implementationURL == null) {
			throw new ConfigException("Remote managed class requires <url> attribute. See class descriptor |%s|.", descriptor);
		}
		return implementationURL;
	}

	/**
	 * Get implementation class constructor. Managed class mandates a single constructor with parameters, no matter if private
	 * or formal parameters count. If both default constructor and constructor with parameters are defined this method returns
	 * constructor with parameters. Returns null if implementation class is missing.
	 *
	 * @param implementationClass implementation class, possible null.
	 * @return implementation class constructor or null if given implementation class is null.
	 * @throws BugError if implementation class is an interface, primitive or array and has no constructor at all.
	 * @throws BugError if implementation class has more constructors with parameters.
	 */
	private static Constructor<?> getDeclaredConstructor(Class<?> implementationClass) {
		if (implementationClass == null) {
			return null;
		}
		Constructor<?>[] declaredConstructors = (Constructor<?>[]) implementationClass.getDeclaredConstructors();
		if (declaredConstructors.length == 0) {
			throw new BugError("Invalid implementation class |%s|. Missing constructor.", implementationClass);
		}
		Constructor<?> constructor = null;

		for (Constructor<?> declaredConstructor : declaredConstructors) {
			// synthetic constructors are created by compiler to circumvent JVM limitations, JVM that is not evolving with
			// the same speed as the language; for example, to allow outer class to access private members on a nested class
			// compiler creates a constructor with a single argument of very nested class type
			if (declaredConstructor.isSynthetic()) {
				continue;
			}
			if (declaredConstructor.getParameterTypes().length == 0) {
				continue;
			}
			if (declaredConstructor.getAnnotation(Test.class) != null) {
				continue;
			}
			if (constructor != null) {
				throw new BugError("Implementation class |%s| has not a single constructor with parameters.", implementationClass);
			}
			constructor = declaredConstructor;
		}

		if (constructor == null) {
			constructor = declaredConstructors[0];
		}
		constructor.setAccessible(true);
		return constructor;
	}

	/**
	 * Scan class dependencies declared by {@link Inject} annotation. This method scans all fields, no matter private, protected
	 * or public. Anyway it is considered a bug if inject annotation is found on final or static field.
	 *

	 * Returns a collection of reflective fields with accessibility set but in not particular order. If given class argument is
	 * null returns empty collection.
	 *
	 * @param clazz class to scan dependencies for, null tolerated.
	 * @return dependencies collection, in no particular order.
	 * @throws BugError if inject annotation is used on final or static field.
	 */
	private static Collection<Field> scanDependencies(Class<?> clazz) {
		if (clazz == null) {
			return Collections.emptyList();
		}
		Collection<Field> dependencies = new ArrayList<>();
		for (Field field : clazz.getDeclaredFields()) {
			if (!field.isAnnotationPresent(Inject.class)) {
				continue;
			}
			if (Modifier.isFinal(field.getModifiers())) {
				throw new BugError("Attempt to inject final field |%s|.", field.getName());
			}
			if (Modifier.isStatic(field.getModifiers())) {
				throw new BugError("Attempt to inject static field |%s|.", field.getName());
			}
			field.setAccessible(true);
			dependencies.add(field);
		}
		return dependencies;
	}

	/**
	 * Initialize implementation class static, not final, fields. Static initializer reads name / value pairs from
	 * static-field configuration element, see sample below. String value is converted to instance field type using
	 * {@link Converter#asObject(String, Class)} utility. This means that configured value should be convertible to field type,
	 * otherwise {@link ConverterException} is thrown.
	 *

	 * In sample there is a person managed instance that has a configuration section. Configuration declares three
	 * static fields that will be initialized with defined values when managed class is created.
	 *
	 *

	 * <managed-classes>
	 * 	<person class="js.app.Person" />
	 * </managed-classes>
	 * ...
	 * <person>
	 * 	<static-field name='name' value='John Doe' />
	 * 	<static-field name='age' value='54' />
	 * 	<static-field name='married' value='false' />
	 * </person>
	 * ...
	 * class Person {
	 * 	private static String name;
	 * 	private static int age;
	 * 	private static boolean married;
	 * }
	 *

	 *
	 * @throws ConfigException if static field descriptor is invalid, field is missing or is instance field.
	 */
	private void initializeStaticFields() throws ConfigException {
		if (config == null) {
			return;
		}
		for (Config config : config.findChildren("static-field")) {
			String fieldName = config.getAttribute("name");
			if (fieldName == null) {
				throw new ConfigException("Missing <name> attribute from static field initialization |%s|.", config.getParent());
			}
			if (!config.hasAttribute("value")) {
				throw new ConfigException("Missing <value> attribute from static field initialization |%s|.", config.getParent());
			}

			Field field = Classes.getOptionalField(implementationClass, fieldName);
			if (field == null) {
				throw new ConfigException("Missing managed class static field |%s#%s|.", implementationClass, fieldName);
			}
			int modifiers = field.getModifiers();
			if (!Modifier.isStatic(modifiers)) {
				throw new ConfigException("Attempt to execute static initialization on instance field |%s#%s|.", implementationClass, fieldName);
			}

			Object value = config.getAttribute("value", field.getType());
			log.debug("Intialize static field |%s#%s| |%s|", implementationClass, fieldName, value);
			Classes.setFieldValue(null, field, config.getAttribute("value", field.getType()));
		}
	}

	/**
	 * Build and return this managed class string representation.
	 *
	 * @param descriptor managed class descriptor.
	 * @return this managed class string representation.
	 */
	private String buildStringRepresentation(Config descriptor) {
		StringBuilder builder = new StringBuilder();
		builder.append(descriptor.getName());
		builder.append(':');
		if (implementationClass != null) {
			builder.append(implementationClass.getName());
			builder.append(':');
		}
		for (Class<?> interfaceClass : interfaceClasses) {
			builder.append(interfaceClass.getName());
			builder.append(':');
		}
		builder.append(instanceType);
		builder.append(':');
		builder.append(instanceScope);
		builder.append(':');
		builder.append(remotelyAccessible ? "NET" : "LOCAL");
		if (implementationURL != null) {
			builder.append(':');
			builder.append(implementationURL);
		}
		return builder.toString();
	}

	// --------------------------------------------------------------------------------------------
	// ANNOTATIONS SCANNER UTILITY METHODS

	/**
	 * Get class annotation or null if none found. This getter uses extended annotation searching scope: it searches first on
	 * given class then tries with all class interfaces. Note that only interfaces are used as alternative for annotation
	 * search. Super class is not included.
	 *

	 * Returns null if no annotation found on base class or interfaces.
	 *
	 * @param clazz base class to search for annotation,
	 * @param annotationClass annotation to search for.
	 * @param <T> annotation type.
	 * @return annotation instance or null if none found.
	 */
	private static <T extends Annotation> T getAnnotation(Class<?> clazz, Class<T> annotationClass) {
		T annotation = clazz.getAnnotation(annotationClass);
		if (annotation == null) {
			for (Class<?> interfaceClass : clazz.getInterfaces()) {
				annotation = interfaceClass.getAnnotation(annotationClass);
				if (annotation != null) {
					break;
				}
			}
		}
		return annotation;
	}

	/**
	 * Test if class has requested annotation. This predicate uses extended annotation searching scope: it searches first on
	 * given class then tries with all class interfaces. Note that only interfaces are used as alternative for annotation
	 * search. Super class is not included.
	 *

	 * Returns false if no annotation found on base class or interfaces.
	 *
	 * @param clazz base class to search for annotation,
	 * @param annotationClass annotation to search for.
	 * @return true if annotation found.
	 */
	private static boolean hasAnnotation(Class<?> clazz, Class<? extends Annotation> annotationClass) {
		Annotation annotation = clazz.getAnnotation(annotationClass);
		if (annotation != null) {
			return true;
		}
		for (Class<?> interfaceClass : clazz.getInterfaces()) {
			annotation = interfaceClass.getAnnotation(annotationClass);
			if (annotation != null) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Get method annotation or null if none found. This getter uses extended annotation searching scope: it searches first on
	 * given method declaring class then tries with all class interfaces. Note that only interfaces are used as alternative for
	 * method annotation search. Super class is not included.
	 *

	 * Returns null if no method annotation found on base class or interfaces.
	 *
	 * @param method method to search for annotation,
	 * @param annotationClass annotation to search for.
	 * @param <T> annotation type.
	 * @return annotation instance or null if none found.
	 */
	private static <T extends Annotation> T getAnnotation(Method method, Class<T> annotationClass) {
		T annotation = method.getAnnotation(annotationClass);
		if (annotation == null) {
			for (Class<?> interfaceClass : method.getDeclaringClass().getInterfaces()) {
				try {
					annotation = interfaceClass.getMethod(method.getName(), method.getParameterTypes()).getAnnotation(annotationClass);
					if (annotation != null) {
						return annotation;
					}
				} catch (NoSuchMethodException unused) {
				}
			}
		}
		return annotation;
	}

	/**
	 * Test if method has requested annotation. This predicate uses extended annotation searching scope: it searches first on
	 * given method declaring class then tries with all class interfaces. Note that only interfaces are used as alternative for
	 * annotation search. Super class is not included.
	 *

	 * Returns false if no method annotation found in declaring class or interfaces.
	 *
	 * @param method method to search for annotation,
	 * @param annotationClass annotation to search for.
	 * @return true if annotation found.
	 */
	private static boolean hasAnnotation(Method method, Class<? extends Annotation> annotationClass) {
		Annotation annotation = method.getAnnotation(annotationClass);
		if (annotation != null) {
			return true;
		}
		for (Class<?> interfaceClass : method.getDeclaringClass().getInterfaces()) {
			try {
				annotation = interfaceClass.getMethod(method.getName(), method.getParameterTypes()).getAnnotation(annotationClass);
				if (annotation != null) {
					return true;
				}
			} catch (NoSuchMethodException unused) {
			}
		}
		return false;
	}

	/**
	 * Get Java reflective method from interface. This getter attempt to locate a method with the same signature as requested
	 * base class method in any interface the declaring class may have. If no method found in interfaces or no interface present
	 * return given base class method. If requested base class method is declared in multiple interfaces this getter returns the
	 * first found but there is no guarantee for order.
	 *
	 * @param method base class method, not null.
	 * @return interface method or given base class method if no interface method found.
	 */
	private static Method getInterfaceMethod(Method method) {
		for (Class<?> interfaceClass : method.getDeclaringClass().getInterfaces()) {
			try {
				return interfaceClass.getMethod(method.getName(), method.getParameterTypes());
			} catch (NoSuchMethodException unused) {
			}
		}
		return method;
	}

	/**
	 * Get class value of intercepted annotation of a given class. Returns null if given class has no {@link Intercepted}
	 * annotation.
	 *
	 * @param clazz annotated class.
	 * @return interceptor class or null if intercepted annotation is missing.
	 */
	private static Class<? extends Interceptor> getInterceptorClass(Class<?> clazz) {
		Intercepted intercepted = getAnnotation(clazz, Intercepted.class);
		return intercepted != null ? intercepted.value() : null;
	}

	/**
	 * Get class value of intercepted annotation of a given method. Returns null if given method has no {@link Intercepted}
	 * annotation.
	 *
	 * @param method annotated method.
	 * @return interceptor class or null if intercepted annotation is missing.
	 */
	private static Class<? extends Interceptor> getInterceptorClass(Method method) {
		Intercepted intercepted = getAnnotation(method, Intercepted.class);
		return intercepted != null ? intercepted.value() : null;
	}
}


/*
 * Copyright (c) 2002 Cunningham & Cunningham, Inc.
 * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence
 *
 * This file is part of FitGoodies.
 *
 * FitGoodies is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FitGoodies 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FitGoodies.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.cologneintelligence.fitgoodies.typehandler;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class TypeHandlerFactory {
	private final Map<Class<?>, Class<? extends TypeHandler<?>>> adapters = new HashMap<>();

	public TypeHandlerFactory() {
		register(BigDecimalTypeHandler.class);
		register(BigIntegerTypeHandler.class);
		register(BooleanTypeHandler.class);
		register(ByteTypeHandler.class);
		register(CharTypeHandler.class);
		register(DateTypeHandler.class);
		register(DoubleTypeHandler.class);
		register(FloatTypeHandler.class);
		register(IntTypeHandler.class);
		register(LongTypeHandler.class);
		register(ObjectTypeHandler.class);
		register(ScientificDoubleTypeHandler.class);
		register(ShortTypeHandler.class);
		register(SqlDateTypeHandler.class);
		register(SqlTimestampTypeHandler.class);
		register(StringBufferTypeHandler.class);
		register(StringBuilderTypeHandler.class);
		register(StringTypeHandler.class);
		register(TestableStringTypeHandler.class);

		adapters.put(Boolean.TYPE, BooleanTypeHandler.class);
		adapters.put(Character.TYPE, CharTypeHandler.class);
		adapters.put(Byte.TYPE, ByteTypeHandler.class);
		adapters.put(Double.TYPE, DoubleTypeHandler.class);
		adapters.put(Float.TYPE, FloatTypeHandler.class);
		adapters.put(Integer.TYPE, IntTypeHandler.class);
		adapters.put(Long.TYPE, LongTypeHandler.class);
		adapters.put(Short.TYPE, LongTypeHandler.class);
	}

	/**
	 * Returns a new instance of <class>typeAdapter</class>, passing
	 * <class>baseTypeAdapter</class> as the only constructor argument.
	 *
	 * @param typeAdapter subclass of {@link TypeHandler}
	 *                    which is used to create the new adapter.
	 * @param parameter   column/row parameter
	 * @return an instance of {@code typeAdapter}
	 */
	private TypeHandler<?> instantiate(Class<? extends TypeHandler<?>> typeAdapter, String parameter) {
		try {
			final Constructor<? extends TypeHandler<?>> constructor = typeAdapter.getConstructor(String.class);
			return constructor.newInstance(parameter);
		} catch (final Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Instantiates a new {@code TypeAdapter} which is capable of handling
	 * {@code type}.
	 *

	 * If no such adapter exists, the old one is returned.
	 *
	 * @param type      {@code Class} to process
	 * @param parameter Column/row parameter
	 * @return an instance of {@code TypeAdapter}, either a specialized one,
	 * or {@code typeAdapter} itself.
	 */
	public TypeHandler getHandler(Class<?> type, String parameter) {
		if (type == null) {
			return null;
		} else if (type.isArray()) {
			return new ArrayTypeHandler(parameter, getHandler(type.getComponentType(), parameter));
		} else if (adapters.containsKey(type)) {
			return instantiate(adapters.get(type), parameter);
		} else {
			throw new IllegalArgumentException("Unknown type: " + type.getName());
		}
	}

	/**
	 * Registers a new {@code AbstractTypeAdapter}.
	 *

	 * After registering an adapter, {@link #getHandler(Class, String)}
	 * is able to return it.
	 *
	 * @param adapterClass subclass of {@code AbstractTypeAdapter} to register
	 */
	public void register(Class<? extends TypeHandler<?>> adapterClass) {
		final Class<?> targetType = ((TypeHandler<?>) instantiate(adapterClass, null)).getType();
		adapters.put(targetType, adapterClass);
	}
}


/*
 * Copyright (c) 2002 Cunningham & Cunningham, Inc.
 * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence
 *
 * This file is part of FitGoodies.
 *
 * FitGoodies is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FitGoodies 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FitGoodies.  If not, see <http://www.gnu.org/licenses/>.
*/

package de.cologneintelligence.fitgoodies.runners;

import de.cologneintelligence.fitgoodies.Counts;
import de.cologneintelligence.fitgoodies.FixtureRunner;
import de.cologneintelligence.fitgoodies.file.FileSystemDirectoryHelper;
import de.cologneintelligence.fitgoodies.htmlparser.FitDocument;
import de.cologneintelligence.fitgoodies.util.DependencyManager;

import java.io.*;
import java.util.Date;

/**
 * Runs a test file using most of fit's default component.
 */
public class FitFileRunner implements Runner {
	private String encoding;

	@Override
	public void setEncoding(final String fileEncoding) {
		this.encoding = fileEncoding;
	}

	/**
	 * Processes {@code inputFile}, write output to {@code outputFile}
	 * and return the resulting counts using most of fit's default components.
	 *
	 * @param inputFile  file to process
	 * @param outputFile file to write output to
	 * @return resulting counts
	 */
	@Override
	public final Counts run(final File inputFile, final File outputFile) {
		Counts result = null;

		FileSystemDirectoryHelper dirHelper = new FileSystemDirectoryHelper();
		RunnerHelper currentRunner = DependencyManager.getOrCreate(RunnerHelper.class);
		RunnerHelper helper = new RunnerHelper();

		DependencyManager.inject(RunnerHelper.class, helper);

		helper.setFile(dirHelper.rel2abs(System.getProperty("user.dir"),
				inputFile.toString()));
		helper.setResultFile(dirHelper.rel2abs(System.getProperty("user.dir"),
				outputFile.toString()));
		helper.setRunner(this);
		helper.setHelper(dirHelper);

		try {
			result = process(inputFile, outputFile);
		} catch (Exception e) {
			System.err.printf("%s while processing %s -> %s%n", e, inputFile, outputFile);
			System.err.println(e.getMessage());
			e.printStackTrace();
		} finally {
			DependencyManager.inject(RunnerHelper.class, currentRunner);
		}

		return result;
	}

	@Override
	public final String getEncoding() {
		return encoding;
	}

	private Counts process(final File inputFile, final File outputFile) throws IOException {
		FixtureRunner fixtureRunner;
        fixtureRunner = prepareFixture(inputFile, outputFile);

        FitDocument document;
        try(InputStream is = new FileInputStream(inputFile)) {
            document = FitDocument.parse(is, encoding);
        }

        String result;
        try {
            fixtureRunner.doDocument(document);
            result = document.getHtml();
        } catch (Exception e) {
            result = "<title>Error</title>" +
                    "
Unable to parse input. Input ignored
";

            fixtureRunner.counts.right = 0;
            fixtureRunner.counts.wrong = 0;
            fixtureRunner.counts.ignores = 0;
            fixtureRunner.counts.exceptions = 1;
        }

        try (PrintWriter output = new PrintWriter(outputFile, encoding)) {
            output.print(result);
        }

		return fixtureRunner.counts;
	}

	@SuppressWarnings("unchecked")
	private FixtureRunner prepareFixture(final File in, final File out) {
		FixtureRunner fixtureRunner = new FixtureRunner();
		fixtureRunner.summary.put("input file", in.getAbsolutePath());
		fixtureRunner.summary.put("input update", new Date(in.lastModified()));
		fixtureRunner.summary.put("output file", out.getAbsolutePath());
		return fixtureRunner;
	}
}