/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *             http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-server-core:0.2.2
 *   Bundle      : ldp4j-server-core-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.server.controller;

import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Variant;

import org.ldp4j.application.data.DataSet;
import org.ldp4j.application.engine.context.Capabilities;
import org.ldp4j.application.engine.context.ContentPreferences;
import org.ldp4j.application.engine.context.EntityTag;
import org.ldp4j.application.engine.context.PublicBasicContainer;
import org.ldp4j.application.engine.context.PublicDirectContainer;
import org.ldp4j.application.engine.context.PublicIndirectContainer;
import org.ldp4j.application.engine.context.PublicRDFSource;
import org.ldp4j.application.engine.context.PublicResource;
import org.ldp4j.application.ext.Query;
import org.ldp4j.application.vocabulary.LDP;
import org.ldp4j.application.vocabulary.Term;
import org.ldp4j.server.config.Configuration;
import org.ldp4j.server.utils.VariantUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.collect.Lists;

public final class EndpointControllerUtils {

	public abstract static class ResponseEnricher implements Function<ResponseBuilder,ResponseBuilder> {

		@Override
		public final ResponseBuilder apply(ResponseBuilder builder) {
			enrich(builder);
			return builder;
		}

		protected abstract void enrich(ResponseBuilder builder);

	}

	private static final Logger LOGGER=LoggerFactory.getLogger(EndpointControllerUtils.class);

	private static final String NL=System.lineSeparator();

	private EndpointControllerUtils() {
	}

	private static void addAllowedMethodHeader(ResponseBuilder builder, String method, boolean flag) {
		if(flag) {
			builder.header(MoreHttp.ALLOW_HEADER,method);
		}
	}

	private static void addProtocolEndorsedHeaders(ResponseBuilder builder, Date lastModified, EntityTag entityTag) {
		builder.header(MoreHttp.LAST_MODIFIED_HEADER,lastModified);
		builder.header(MoreHttp.ENTITY_TAG_HEADER,entityTag);
	}

	// LDP 1.0 - 5.2.1.4 : "LDP servers exposing LDPCs must advertise
	// their LDP support by exposing a HTTP Link header with a target
	// URI matching the type of container (see below) the server
	// supports, and a link relation type of type (that is, rel='type')
	// in all responses to requests made to the LDPC's HTTP Request-URI"
	// LDP 1.0 - 5.2.1.4 : "LDP servers may provide additional HTTP
	// Link: rel='type' headers"
	private static void addProtocolSpecificHeaders(ResponseBuilder builder, Class<? extends PublicResource> clazz) {
		List<Term> types=new ArrayList<Term>();
		if(PublicRDFSource.class.isAssignableFrom(clazz)) {
			types.add(LDP.RESOURCE);
		}
		if(PublicBasicContainer.class.isAssignableFrom(clazz)) {
			types.add(LDP.BASIC_CONTAINER);
		} else if(PublicDirectContainer.class.isAssignableFrom(clazz)) {
			types.add(LDP.DIRECT_CONTAINER);
		} else if(PublicIndirectContainer.class.isAssignableFrom(clazz)) {
			types.add(LDP.INDIRECT_CONTAINER);
		}
		for(Term type:types) {
			builder.header(MoreHttp.LINK_HEADER,MoreHttp.createLink(type, "type"));
		}
	}

	static Variant textResponseVariant() {
		return
			Variant.
				languages(Locale.ENGLISH).
				mediaTypes(MediaType.TEXT_PLAIN_TYPE.withCharset(StandardCharsets.UTF_8.name())).
				add().
				build().
					get(0);
	}

	static Variant errorResponseVariant() {
		return textResponseVariant();
	}

	/**
	 * Get a text/plain representation that indicates the acceptable media types
	 * and charsets for the specified resource. The current implementation
	 * <b>only</b> takes care of the media type, nor language, nor encodings are
	 * taken into consideration for generating the acceptable content.
	 *
	 * @param variants
	 *            The acceptable variants
	 * @param resourceLocation
	 *            The location of the resource
	 * @param supportedCharsets
	 *            The supported charsets
	 * @return A content instance that outlines the acceptable media types
	 *         together with the locations from where the resource contents for
	 *         those media types can be retrieved, and the charsets supported.
	 */
	static String getAcceptableContent(
			List<Variant> variants,
			URI resourceLocation,
			List<String> supportedCharsets) {
		StringBuilder builder = new StringBuilder();
		for (Variant variant : variants) {
			MediaType mediaType = variant.getMediaType();
			builder.append(mediaType).append(" : %1$s%n");
			for(String supportedCharset:supportedCharsets) {
				builder.append(mediaType.withCharset(supportedCharset)).append(" : %1$s%n");
			}
		}
		return String.format(builder.toString(),resourceLocation);
	}

	static String retrievalLog(OperationContext context) {
		StringBuilder builder=new StringBuilder();
		Query query=context.getQuery();
		if(query.isEmpty()) {
			builder.append("Executing resource retrieval:").append(NL);
		} else {
			builder.append("Executing resource query:").append(NL);
			builder.append("  - Parameters:").append(NL);
			for(String parameter:query.parameterNames()) {
			  builder.append("    + ").append(parameter).append(" : ").append(query.getParameter(parameter).rawValues());
			}
		}
		ContentPreferences preferences=context.contentPreferences();
		if(preferences!=null) {
			builder.append("  - Using preferences: ").append(preferences).append(NL);
		} else {
			builder.append("  - Using default preferences: ").append(ContentPreferences.defaultPreferences()).append(NL);
		}
		return builder.toString();
	}

	static String retrievalResultLog(DataSet entity) {
		return String.format("  - Data set to serialize:%n%s",entity);
	}

	static void populateRequiredHeaders(ResponseBuilder builder, OperationContext context) {
		PublicResource resource = context.resource();
		addProtocolEndorsedHeaders(builder,resource.lastModified(),resource.entityTag());
		addProtocolSpecificHeaders(builder,resource.getClass());

	}

	static void populateResponseBody(ResponseBuilder builder, String entity, Variant variant, boolean includeEntity) {
		MediaType mediaType = variant.getMediaType();

		String charsetName=mediaType.getParameters().get(MediaType.CHARSET_PARAMETER);
		Charset charset=StandardCharsets.UTF_8;
		if(charsetName!=null && !charsetName.isEmpty() && Charset.isSupported(charsetName)) {
			charset=Charset.forName(charsetName);
		} else {
			LOGGER.error("Missing of invalid charset information {}",mediaType);
			charsetName=charset.name();
		}

		MediaType target=
			Configuration.includeCharsetInformation()?
				mediaType.withCharset(charsetName):
				new MediaType(mediaType.getType(),mediaType.getSubtype());

		byte[] bytes = entity.getBytes(charset);
		builder.
			type(target).
			header(MoreHttp.CONTENT_LENGTH_HEADER,bytes.length);

		if(variant.getLanguage()!=null) {
			builder.language(variant.getLanguage());
		}

		if(includeEntity) {
			builder.entity(new ByteArrayInputStream(bytes));
		}
	}

	static String createQueryOfLink(Object uriRef, Query query) {
		String strQuery = toString(query);
		try {
			return MoreHttp.createLink(uriRef, "queryOf")+"; parameters=\""+URLEncoder.encode(strQuery,"UTF-8")+"\"";
		} catch (UnsupportedEncodingException e) {
			throw new AssertionError("UTF-8 encoding should always be supported",e);
		}
	}

	static String toString(Query query) {
		List<String> parameters=Lists.newArrayList();
		for(String paramName:query.parameterNames()) {
			for(String rawValue:query.getParameter(paramName).rawValues()) {
				parameters.add(paramName+"="+rawValue);
			}
		}
		StringBuilder builder=new StringBuilder();
		Iterator<String> iterator=parameters.iterator();
		while(iterator.hasNext()) {
			builder.append(iterator.next());
			if(iterator.hasNext()) {
				builder.append("&");
			}
		}
		return builder.toString();
	}

	public static void populateAllowedHeaders(ResponseBuilder builder, Capabilities capabilities) {
		/**
		 * LDP 1.0 - 4.2.8.2 : "LDP servers must indicate their support for HTTP
		 * Methods by responding to a HTTP OPTIONS request on the LDPR’s URL
		 * with the HTTP Method tokens in the HTTP response header Allow."
		 */
		addAllowedMethodHeader(builder,"OPTIONS",true);
		addAllowedMethodHeader(builder,"HEAD",true);
		addAllowedMethodHeader(builder,"GET",true);
		addAllowedMethodHeader(builder,"PUT",capabilities.isModifiable());
		addAllowedMethodHeader(builder,"PATCH",capabilities.isPatchable());
		addAllowedMethodHeader(builder,"DELETE",capabilities.isDeletable());
		addAllowedMethodHeader(builder,"POST",capabilities.isFactory());
		if(capabilities.isFactory()) {
			/**
			 * LDP 1.0 - 5.2.3.13: "LDP servers that support POST must include
			 * an Accept-Post response header on HTTP OPTIONS responses, listing
			 * POST request media type(s) supported by the server.
			 *
			 * LDP 1.0 - 7.1.2 : "The Accept-Post HTTP header should appear in
			 * the OPTIONS response for any resource that supports the use of
			 * the POST method."
			 */
			for(Variant variant:VariantUtils.defaultVariants()) {
				builder.header(MoreHttp.ACCEPT_POST_HEADER,variant.getMediaType());
			}
		}
	}

	public static Response prepareErrorResponse(DiagnosedException throwable, ResponseEnricher... enrichers) {
		Diagnosis diagnosis = throwable.getDiagnosis();
		ResponseBuilder builder=Response.status(diagnosis.statusCode());
		populateResponseBody(builder,diagnosis.diagnostic(), errorResponseVariant(), diagnosis.mandatory());
		addProtocolEndorsedHeaders(builder,throwable.resourceLastModified(),throwable.resourceEntityTag());
		addProtocolSpecificHeaders(builder,throwable.resourceClass());
		for(Function<ResponseBuilder, ResponseBuilder> enricher:enrichers) {
			builder=enricher.apply(builder);
		}
		return builder.build();
	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *             http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-engine-api:0.2.2
 *   Bundle      : ldp4j-application-engine-api-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.engine;

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.ReflectPermission;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.ldp4j.application.engine.context.ApplicationContext;
import org.ldp4j.application.engine.lifecycle.ApplicationEngineLifecycleListener;
import org.ldp4j.application.engine.lifecycle.ApplicationEngineState;
import org.ldp4j.application.engine.util.ListenerManager;
import org.ldp4j.application.engine.util.Notification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Maps;

/**
 * ApplicationEngine is an abstract facade class for the LDP4j Application
 * Engine API. The class defines the default behavior of the LDP4j Application
 * Engine and provides an extension point for pluging-in alternative
 * implementations of the LDP4j Application Engine API. These extension points
 * are designed for use by other LDP4j Application Engine classes and are not
 * intended to be called directly by applications using the LDP4j Application
 * Engine API.

 *

 *
 * An implementation of LPD4j Application Engine API MUST provide a concrete
 * subclass of {@code ApplicationEngine}. Using the supplied
 * {@code ApplicationEngine} this can be achieved in one of two ways:

 *

 *
An instance of {@code ApplicationEngine} can be instantiated and injected
 * using its static method {@link ApplicationEngine#setEngine(ApplicationEngine) setEngine}. In this case the
 * implementation is responsible for creating the instance; this option is
 * intended for use with implementations based on IoC frameworks.

 *
The class to be used can be configured, (see {@link ApplicationEngine#engine() below}). In this case the
 * LDP4j Application Engine is responsible for instantiating an instance of the
 * class. The configured class MUST have a public constructor which takes no
 * arguments.

 *

 *
 * A LDP4j Application Engine API implementation may rely on a particular
 * implementation of the {@code ApplicationEngine} being used, however applications
 * SHOULD NOT depend on this nor override the supplied {@code ApplicationEngine}
 * instance with an application-supplied alternative as doing so may cause
 * unexpected problems.

 *
 * @author Miguel Esteban Gutiérrez
 * @since 1.0.0
 * @version 1.0
 */
public abstract class ApplicationEngine {

	private static final String DISABLE = "disable";

	/**
	 * Name of the system property for controlling the resolution of the
	 * {@code ApplicationEngine} runtime delegate instance leveraging the
	 * {@link java.util.ServiceLoader} mechanism.
	 *
	 * The absence of this property or any value but {@value #DISABLE}, despite
	 * the case, will enable the enable the usage of this mechanism.
	 */
	public static final String LDP4J_APPLICATION_ENGINE_FINDER = "org.ldp4j.application.engine.finder";

	/**
	 * Name of the configuration file where the
	 * {@value #LDP4J_APPLICATION_ENGINE_PROPERTY} property that
	 * identifies the {@link ApplicationEngine} implementation to be returned from
	 * {@link ApplicationEngine#engine()} can be defined.
	 */
	public static final String LDP4J_APPLICATION_ENGINE_CFG = "ldp4j-application-engine.properties";

	/**
	 * Name of the property identifying the {@link ApplicationEngine} implementation
	 * to be returned from {@link ApplicationEngine#engine()}.
	 */
	public static final String LDP4J_APPLICATION_ENGINE_PROPERTY = "org.ldp4j.application.ApplicationEngine";

	private static final String INSTANTIATE_ACTION = "instantiate";

	private static final Logger LOGGER=LoggerFactory.getLogger(ApplicationEngine.class);

	private static final AtomicReference<ApplicationEngine> CACHED_DELEGATE=new AtomicReference<ApplicationEngine>();

	private static final ReflectPermission SUPPRESS_ACCESS_CHECKS_PERMISSION = new ReflectPermission("suppressAccessChecks");

	private static final ListenerManager<ApplicationEngineLifecycleListener> LISTENERS=ListenerManager.newInstance();

	private final Lock read;
	private final Lock write;
	private final Map<String,ApplicationContext> contexts;
	private final Deque<String> loadedContexts;
	private final AtomicReference<ApplicationContext> currentContext;

	private ApplicationEngineState state;

	private final Deployment deployment;

	protected abstract static class ApplicationContextManager<T extends ApplicationContext> {

		private final Class<? extends T> managedClass;

		protected ApplicationContextManager(final Class<? extends T> managedClazz) {
			checkNotNull(managedClazz);
			this.managedClass=managedClazz;
		}

		protected final boolean isManaged(final ApplicationContext instance) {
			return this.managedClass.isInstance(instance);
		}

		protected abstract T createContext(String applicationClassName) throws ApplicationContextCreationException;

		protected final boolean disposeContext(final ApplicationContext applicationContext) throws ApplicationContextTerminationException {
			return doDisposeContext(this.managedClass.cast(applicationContext));
		}

		protected abstract boolean doDisposeContext(T applicationContext) throws ApplicationContextTerminationException ;

	}

	/**
	 * Allows custom implementations to extend the {@code ApplicationEngine} class.
	 */
	protected ApplicationEngine() {
		this.contexts=Maps.newLinkedHashMap();
		final ReadWriteLock lock = new ReentrantReadWriteLock();
		this.read=lock.readLock();
		this.write=lock.writeLock();
		this.currentContext=new AtomicReference<ApplicationContext>();
		this.state=ApplicationEngineState.UNAVAILABLE;
		this.loadedContexts=new LinkedList<String>();
		this.deployment=Deployment.newInstance();
	}

	private void refreshCurrentContext() {
		final String current=this.loadedContexts.isEmpty()?null:this.loadedContexts.peek();
		this.currentContext.set(this.contexts.get(current));
	}

	private boolean isApplicationContextLoaded(final ApplicationContext context) {
		return this.contexts.containsKey(context.applicationClassName()) && this.contexts.containsValue(context);
	}

	private void unsafeDisposeContext(final ApplicationContext applicationContext) throws ApplicationContextTerminationException {
		applicationContextManager().disposeContext(applicationContext);
		this.loadedContexts.remove(applicationContext.applicationClassName());
		this.contexts.remove(applicationContext.applicationClassName());
	}

	private void setState(final ApplicationEngineState newState) {
		this.state=newState;
		ApplicationEngine.notifyStateChange(newState);
	}

	private synchronized void checkApplicationEngineActive() {
		if(!ApplicationEngineState.STARTED.equals(this.state)) {
			throw new ApplicationEngineRuntimeException("Application engine is not active ("+this.state+")");
		}
	}

	public synchronized ApplicationEngine withContextPath(final String contextPath) {
		if(!ApplicationEngineState.AVAILABLE.equals(this.state)) {
			throw new ApplicationEngineRuntimeException("Application engine is not available ("+this.state+")");
		}
		this.deployment.withContextPath(contextPath);
		return this;
	}

	public synchronized ApplicationEngine withTemporalDirectory(final File temporalDirectory) {
		if(!ApplicationEngineState.AVAILABLE.equals(this.state)) {
			throw new ApplicationEngineRuntimeException("Application engine is not available ("+this.state+")");
		}
		this.deployment.withTemporalDirectory(temporalDirectory);
		return this;
	}

	public synchronized String contextPath() {
		return this.deployment.contextPath();
	}

	public synchronized File temporalDirectory() {
		return this.deployment.temporalDirectory();
	}

	public synchronized ApplicationEngineState state() {
		return this.state;
	}

	public final synchronized void start() throws ApplicationEngineLifecycleException {
		if(ApplicationEngineState.STARTED.equals(this.state)) {
			return;
		}
		ApplicationEngineState newState=ApplicationEngineState.UNDEFINED;
		try {
			setUp();
			newState=ApplicationEngineState.STARTED;
		} finally {
			setState(newState);
		}
	}

	public final synchronized void shutdown() throws ApplicationEngineLifecycleException {
		if(!ApplicationEngineState.STARTED.equals(this.state)) {
			return;
		}
		this.currentContext.set(null);
		for(final ApplicationContext ctx:this.contexts.values()) {
			try {
				unsafeDisposeContext(ctx);
			} catch (final ApplicationContextTerminationException e) {
				if(LOGGER.isErrorEnabled()) {
					LOGGER.error("Could not terminate context", e);
				}
			}
		}
		try {
			cleanUp();
		} finally {
			setState(ApplicationEngineState.SHUTDOWN);
		}
	}

	public final ApplicationContext load(final String applicationClassName) throws ApplicationContextCreationException {
		checkNotNull(applicationClassName,"Application class name cannot be null");
		checkApplicationEngineActive();
		this.write.lock();
		try {
			if(this.loadedContexts.contains(applicationClassName)) {
				throw new IllegalStateException("Application class '"+applicationClassName+"' is already been loaded");
			}
			final ApplicationContext context=applicationContextManager().createContext(applicationClassName);
			this.contexts.put(applicationClassName, context);
			this.loadedContexts.push(applicationClassName);
			refreshCurrentContext();
			return context;
		} finally {
			this.write.unlock();
		}
	}

	public final ApplicationContext findByName(final String applicationName) {
		checkNotNull(applicationName,"Application name cannot be null");
		checkApplicationEngineActive();
		this.read.lock();
		try {
			for(final ApplicationContext ctx:this.contexts.values()) {
				if(ctx.applicationName().equals(applicationName)) {
					return ctx;
				}
			}
			return null;
		} finally {
			this.read.unlock();
		}
	}

	public final ApplicationContext findByClassName(final String applicationClassName) {
		checkNotNull(applicationClassName,"Application class name cannot be null");
		checkApplicationEngineActive();
		this.read.lock();
		try {
			return this.contexts.get(applicationClassName);
		} finally {
			this.read.unlock();
		}
	}

	public final boolean dispose(final ApplicationContext applicationContext) throws ApplicationContextTerminationException {
		checkNotNull(applicationContext,"Application context cannot be null");
		checkApplicationEngineActive();
		this.write.lock();
		try {
			if(!applicationContextManager().isManaged(applicationContext)) {
				throw new ApplicationContextTerminationException("Invalid application context class "+applicationContext.getClass().getName());
			}
			if(!isApplicationContextLoaded(applicationContext)) {
				return false;
			}
			unsafeDisposeContext(applicationContext);
			refreshCurrentContext();
			return true;
		} finally {
			this.write.unlock();
		}
	}

	protected abstract ApplicationContextManager<? extends ApplicationContext> applicationContextManager();

	protected void setUp() throws ApplicationEngineInitializationException {
		// To be overriden by implementations
	}

	protected void cleanUp() throws ApplicationEngineTerminationException {
		// To be overriden by implementations
	}

	/**
	 * Obtain an {@code ApplicationEngine} instance using the method described
	 * in {@link ApplicationEngine#engine()}.
	 *
	 * @return an instance of {@code ApplicationEngine} if available, or null if
	 *         a default implementation class is to be selected.
	 */
	private static ApplicationEngine findDelegate() {
		try {
			ApplicationEngine result=createApplicationEngineFromSPI();
			if(result==null) {
				result=createApplicationEngineFromConfigurationFile();
			}

			if(result==null) {
				final String delegateClassName = System.getProperty(LDP4J_APPLICATION_ENGINE_PROPERTY);
				if(delegateClassName!=null) {
					result=createApplicationEngineForClassName(delegateClassName);
				}
			}

			return result;
		} catch (final Exception ex) {
			throw new IllegalStateException("Could not find application engine",ex);
		}
	}

	private static ApplicationEngine createApplicationEngineFromConfigurationFile() {
		ApplicationEngine result=null;
		final File configFile = getConfigurationFile();
		if(configFile.canRead()) {
			InputStream is=null;
			try {
				is=new FileInputStream(configFile);
				final Properties configProperties=new Properties();
				configProperties.load(is);
				final String delegateClassName=configProperties.getProperty(LDP4J_APPLICATION_ENGINE_PROPERTY);
				if(delegateClassName!=null) {
					result=createApplicationEngineForClassName(delegateClassName);
				}
				if(delegateClassName==null && LOGGER.isWarnEnabled()) {
					LOGGER.warn("Configuration file '"+configFile.getAbsolutePath()+"' does not define a delegate class name");
				}
			} catch(final FileNotFoundException e) {
				if(LOGGER.isDebugEnabled()) {
					LOGGER.debug("Could not find runtime instance configuration file '"+configFile.getAbsolutePath()+"'",e);
				}
			} catch(final IOException e) {
				if(LOGGER.isWarnEnabled()) {
					LOGGER.warn("Could not load runtime instance configuration file '"+configFile.getAbsolutePath()+"'",e);
				}
			} finally {
				closeQuietly(is, "Could not close configuration properties");
			}
		}
		return result;
	}

	/**
	 * Get the configuration file for the Application Engine, that is, the a
	 * file named {@value #LDP4J_APPLICATION_ENGINE_CFG} in the lib
	 * directory of current JAVA_HOME.
	 *
	 * @return The configuration file for the runtime instance.
	 */
	private static File getConfigurationFile() {
		return new File(new File(System.getProperty("java.home")),"lib"+File.separator+LDP4J_APPLICATION_ENGINE_CFG);
	}

	/**
	 * Close an input stream logging possible failures.
	 * @param is The input stream that is to be closed.
	 * @param message The message to log in case of failure.
	 */
	private static void closeQuietly(final InputStream is, final String message) {
		if(is!=null) {
		try {
			is.close();
		} catch (final Exception e) {
			if(LOGGER.isWarnEnabled()) {
				LOGGER.warn(message,e);
			}
		}
		}
	}

	private static ApplicationEngine createApplicationEngineFromSPI() {
		if(!DISABLE.equalsIgnoreCase(System.getProperty(LDP4J_APPLICATION_ENGINE_FINDER))) {
			try {
				for (final ApplicationEngine delegate : ServiceLoader.load(ApplicationEngine.class)) {
					return delegate;
				}
			} catch (final ServiceConfigurationError ex) {
				LOGGER.error("Could not load LDP4j Application Engine service. Full stacktrace follows:",ex);
			}
		}
		return null;
	}

	private static ApplicationEngine createApplicationEngineForClassName(final String delegateClassName) {
		ApplicationEngine result = null;
		try {
			final Class<?> delegateClass = Class.forName(delegateClassName);
			if(ApplicationEngine.class.isAssignableFrom(delegateClass)) {
				final Object impl = delegateClass.newInstance();
				result = ApplicationEngine.class.cast(impl);
			}
		} catch (final ClassNotFoundException e) {
			handleFailure(delegateClassName, "find", e);
		} catch (final InstantiationException e) {
			handleFailure(delegateClassName, INSTANTIATE_ACTION, e);
		} catch (final IllegalAccessException e) {
			handleFailure(delegateClassName, INSTANTIATE_ACTION, e);
		}
		return result;
	}

	private static void handleFailure(final String delegateClassName, final String action, final Exception failure) {
		if(LOGGER.isWarnEnabled()) {
			LOGGER.warn("Could not "+action+" delegate class "+delegateClassName,failure);
		}
	}

	private static final void notifyStateChange(final ApplicationEngineState newState) {
		LISTENERS.notify(
			new Notification<ApplicationEngineLifecycleListener>() {
				@Override
				public void propagate(final ApplicationEngineLifecycleListener listener) {
					listener.stateChanged(newState);
				}
			}
		);
	}

	/**
	 * Obtain a {@code ApplicationEngine} instance. If an instance had not
	 * already been created and set via {@link #setEngine(ApplicationEngine)},
	 * the first invocation will create an instance which will then be cached
	 * for future use.
	 *
	 *

	 * The algorithm used to locate the {@code ApplicationEngine} subclass to
	 * use consists of the following steps:
	 *

	 *

	 *

	 * If a resource with the name of
	 * {@code META-INF/services/org.ldp4j.application.engine.ApplicationEngine}
	 * exists, then its first line, if present, is used as the UTF-8 encoded
	 * name of the implementation class.

	 *

	 * If a file named {@value #LDP4J_APPLICATION_ENGINE_CFG} in the
	 * lib directory of current JAVA_HOME exists and it is readable
	 * by the {@code java.util.Properties.load(InputStream)} method and it
	 * contains an entry whose key is
	 * {@value #LDP4J_APPLICATION_ENGINE_PROPERTY}, then the value of that entry
	 * is used as the name of the implementation class.

	 *

	 * If a system property named {@value #LDP4J_APPLICATION_ENGINE_PROPERTY} is
	 * defined, then its value is used as the name of the implementation class.

	 *

	 * Finally, a default implementation class name is used.

	 *

	 *
	 * @return an instance of {@code ApplicationEngine}.
	 */
	public static ApplicationEngine engine() {
		ApplicationEngine result = ApplicationEngine.CACHED_DELEGATE.get();
		if (result != null) {
			return result;
		}
		synchronized(ApplicationEngine.CACHED_DELEGATE) {
			result=ApplicationEngine.CACHED_DELEGATE.get();
			if(result==null) {
				result=findDelegate();
				ApplicationEngineState state=ApplicationEngineState.AVAILABLE;
				if(result==null) {
					result=new DefaultApplicationEngine();
					state=ApplicationEngineState.UNDEFINED;
				}
				ApplicationEngine.CACHED_DELEGATE.set(result);
				result.setState(state);
			}
			return result;
		}
	}

	/**
	 * Set the application engine that will be used by clients. If this method
	 * is not called prior to {@link ApplicationEngine#engine()} then an
	 * implementation will be sought as described in
	 * {@link ApplicationEngine#engine()}.
	 *
	 * @param delegate
	 *            the {@code ApplicationEngine} runtime delegate instance.
	 * @throws SecurityException
	 *             if there is a security manager and the permission
	 *             ReflectPermission("suppressAccessChecks") has not been
	 *             granted.
	 */
	public static void setEngine(final ApplicationEngine delegate) {
		final SecurityManager security = System.getSecurityManager();
		if (security != null) {
			security.checkPermission(SUPPRESS_ACCESS_CHECKS_PERMISSION);
		}
		synchronized(ApplicationEngine.CACHED_DELEGATE) {
			final ApplicationEngine current=ApplicationEngine.CACHED_DELEGATE.get();
			if(current!=null) {
				try {
					current.shutdown();
				} catch (final ApplicationEngineLifecycleException e) {
					LOGGER.error("Shutdown of previous engine failed. Full stacktrace follows:",e);
				}
			}
			ApplicationEngine.CACHED_DELEGATE.set(delegate);
			if(delegate!=null) {
				delegate.setState(ApplicationEngineState.AVAILABLE);
			}
		}
	}

	public static void registerLifecycleListener(final ApplicationEngineLifecycleListener listener) {
		LISTENERS.registerListener(listener);
	}

	public static void deregisterLifecycleListener(final ApplicationEngineLifecycleListener listener) {
		LISTENERS.deregisterListener(listener);
	}

	/**
	 * Return an object of the specified type to allow access to the
	 * provider-specific API. If the provider's ApplicationEngine implementation
	 * does not support the specified class, the ApplicationEngineException is
	 * thrown.
	 *
	 * @param <T> the type of object to be returned
	 * @param clazz
	 *            the class of the object to be returned. This is normally
	 *            either the underlying {@code ApplicationEngine} implementation
	 *            class or an interface that it implements.
	 * @return an instance of the specified class
	 * @throws ApplicationEngineException
	 *             if the provider does not support the call
	 */
	public <T> T unwrap(final Class<? extends T> clazz) throws ApplicationEngineException {
		checkNotNull(clazz,"Target class cannot be null");
		if(!clazz.isInstance(this)) {
			throw new ApplicationEngineException("Application Engine implementation is not compatible with "+clazz.getCanonicalName());
		}
		return clazz.cast(this);
	}

	private static class DefaultApplicationEngine extends ApplicationEngine {

		private static final String ERROR_MESSAGE=
				String.format(
					"No implementation for class '%s' could be found",
					ApplicationEngine.class.getName());

		@Override
		protected ApplicationContextManager<ApplicationContext> applicationContextManager() {
			throw new ApplicationEngineRuntimeException(ERROR_MESSAGE);
		}

	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *             http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.commons:ldp4j-commons-http:0.2.2
 *   Bundle      : ldp4j-commons-http-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.http;

import static com.google.common.base.Preconditions.checkArgument;

import java.util.BitSet;

final class RFC6838MediaRangeValidator implements MediaRangeValidator {

	private static final long serialVersionUID = -6377052968246092604L;

	private static final BitSet RESTRICTED_NAME_CHARS;
	private static final BitSet RESTRICTED_NAME_FIRST;

	static {
		RESTRICTED_NAME_FIRST=new BitSet(0xFF);
		RESTRICTED_NAME_FIRST.set(0x41, 0x5B); // A-Z
		RESTRICTED_NAME_FIRST.set(0x61, 0x7B); // a-z
		RESTRICTED_NAME_FIRST.set(0x30, 0x3A); // 0-9

		RESTRICTED_NAME_CHARS=new BitSet(0xFF);
		RESTRICTED_NAME_CHARS.set(0x41, 0x5B); // A-Z
		RESTRICTED_NAME_CHARS.set(0x61, 0x7B); // a-z
		RESTRICTED_NAME_CHARS.set(0x30, 0x3A); // 0-9
		RESTRICTED_NAME_CHARS.set(0x30, 0x3A); // 0-9
		RESTRICTED_NAME_CHARS.set('!');
		RESTRICTED_NAME_CHARS.set('#');
		RESTRICTED_NAME_CHARS.set('$');
		RESTRICTED_NAME_CHARS.set('&');
		RESTRICTED_NAME_CHARS.set('-');
		RESTRICTED_NAME_CHARS.set('^');
		RESTRICTED_NAME_CHARS.set('_');
		RESTRICTED_NAME_CHARS.set('.');
		RESTRICTED_NAME_CHARS.set('+');
	}

	RFC6838MediaRangeValidator() {
	}

	/**
	 * Checks the given {@code type} for illegal characters, as defined in RFC
	 * 6838, section 4.2. NOTE: '*' is accepted as a valid type.
	 *
	 * @param type
	 *            the string to validate
	 * @throws IllegalArgumentException
	 *             in case of illegal characters
	 * @see Media Type
	 *      Specifications and Registration Procedures, Section 4.2
	 */
	@Override
	public void checkType(final String type) {
		validateLength(type,"Type");
		validateFirstChar(type,"type");
		validateOtherChars(type,"type");
	}

	/**
	 * Checks the given {@code subtype} for illegal characters, as defined in RFC
	 * 6838, section 4.2. NOTE: '*' is accepted as a valid subtype.
	 *
	 * @param subType
	 *            the string to validate
	 * @throws IllegalArgumentException
	 *             in case of illegal characters
	 * @see Media Type
	 *      Specifications and Registration Procedures, Section 4.2
	 */
	@Override
	public void checkSubType(final String subType) {
		validateLength(subType,"Subtype");
		validateFirstChar(subType,"subtype");
		validateOtherChars(subType,"subtype");
	}

	/**
	 * Checks the given {@code suffix} for illegal characters, as defined in RFC
	 * 6838, section 4.2.
	 *
	 * @param suffix
	 *            the string to validate
	 * @throws IllegalArgumentException
	 *             in case of illegal characters
	 * @see Media Type
	 *      Specifications and Registration Procedures, Section 4.2
	 */
	@Override
	public void checkSuffix(final String suffix) {
		validateOtherChars(suffix,"suffix");
	}

	private void validateLength(final String value, final String name) {
		checkArgument(value.length()<128,"%s length cannot be greater than 127 ('%s' has %s)",name,value,value.length());
	}

	private void validateFirstChar(final String value, String name) {
		final char ch = value.charAt(0);
		boolean check=false;
		if(ch=='*') {
			check=value.length()==1;
		} else {
			check=RESTRICTED_NAME_FIRST.get(ch);
		}
		checkArgument(check,"Invalid character '%s' in %s '%s' at 0",ch,name,value);
	}

	private void validateOtherChars(final String value, final String name) {
		for(int i=1;i<value.length();i++) {
			final char ch = value.charAt(i);
			checkArgument(RESTRICTED_NAME_CHARS.get(ch),"Invalid character '%s' in %s '%s' at %s",ch,name,value,i);
		}
	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *             http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-kernel-core:0.2.2
 *   Bundle      : ldp4j-application-kernel-core-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.kernel.engine;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.ldp4j.application.data.DataSet;
import org.ldp4j.application.data.ExternalIndividual;
import org.ldp4j.application.data.Individual;
import org.ldp4j.application.data.IndividualVisitor;
import org.ldp4j.application.data.LocalIndividual;
import org.ldp4j.application.data.ManagedIndividual;
import org.ldp4j.application.data.ManagedIndividualId;
import org.ldp4j.application.data.NewIndividual;
import org.ldp4j.application.data.Property;
import org.ldp4j.application.data.RelativeIndividual;
import org.ldp4j.application.data.Value;
import org.ldp4j.application.data.validation.ValidationConstraint;
import org.ldp4j.application.data.validation.ValidationConstraintFactory;
import org.ldp4j.application.data.validation.Validator.ValidatorBuilder;
import org.ldp4j.application.engine.context.ApplicationExecutionException;
import org.ldp4j.application.engine.context.ContentPreferences;
import org.ldp4j.application.engine.context.CreationPreferences;
import org.ldp4j.application.engine.context.PublicContainer;
import org.ldp4j.application.engine.context.PublicResource;
import org.ldp4j.application.engine.context.UnsupportedInteractionModelException;
import org.ldp4j.application.engine.context.ContentPreferences.Preference;
import org.ldp4j.application.engine.context.CreationPreferences.InteractionModel;
import org.ldp4j.application.kernel.endpoint.Endpoint;
import org.ldp4j.application.kernel.resource.Container;
import org.ldp4j.application.kernel.resource.Member;
import org.ldp4j.application.kernel.resource.Resource;
import org.ldp4j.application.kernel.resource.ResourceId;
import org.ldp4j.application.kernel.template.BasicContainerTemplate;
import org.ldp4j.application.kernel.template.ContainerTemplate;
import org.ldp4j.application.kernel.template.DirectContainerTemplate;
import org.ldp4j.application.kernel.template.IndirectContainerTemplate;
import org.ldp4j.application.kernel.template.MembershipAwareContainerTemplate;
import org.ldp4j.application.kernel.template.ResourceTemplate;
import org.ldp4j.application.kernel.template.TemplateVisitor;
import org.ldp4j.application.vocabulary.LDP;
import org.ldp4j.application.vocabulary.RDF;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

abstract class DefaultPublicContainer<T extends ContainerTemplate> extends DefaultPublicRDFSource implements PublicContainer {

	private static final class AcceptableInteractionModelCollector implements
			TemplateVisitor {
		private final Set<InteractionModel> acceptable;

		private AcceptableInteractionModelCollector(
				Set<InteractionModel> acceptable) {
			this.acceptable = acceptable;
		}

		@Override
		public void visitResourceTemplate(ResourceTemplate template) {
			acceptable.add(InteractionModel.RESOURCE);
		}

		@Override
		public void visitIndirectContainerTemplate(IndirectContainerTemplate template) {
			acceptable.add(InteractionModel.INDIRECT_CONTAINER);
		}

		@Override
		public void visitDirectContainerTemplate(DirectContainerTemplate template) {
			acceptable.add(InteractionModel.DIRECT_CONTAINER);
		}

		@Override
		public void visitBasicContainerTemplate(BasicContainerTemplate template) {
			acceptable.add(InteractionModel.BASIC_CONTAINER);
		}

		@Override
		public void visitMembershipAwareContainerTemplate(MembershipAwareContainerTemplate template) {
			throw new AssertionError("We should not arrive here");
		}

		@Override
		public void visitContainerTemplate(ContainerTemplate template) {
			throw new AssertionError("We should not arrive here");
		}
	}

	private final class IndividualFilter implements IndividualVisitor {
		private final List<Individual<?, ?>> toRemove;

		private IndividualFilter(List<Individual<?, ?>> toRemove) {
			this.toRemove = toRemove;
		}

		@Override
		public void visitManagedIndividual(ManagedIndividual individual) {
			ManagedIndividualId id = individual.id();
			if(!isSelf(id)) {
				this.toRemove.add(individual);
			}
		}

		@Override
		public void visitLocalIndividual(LocalIndividual individual) {
			this.toRemove.add(individual);
		}

		@Override
		public void visitExternalIndividual(ExternalIndividual individual) {
			this.toRemove.add(individual);
		}

		@Override
		public void visitRelativeIndividual(RelativeIndividual individual) {
			this.toRemove.add(individual);
		}

		@Override
		public void visitNewIndividual(NewIndividual individual) {
			this.toRemove.add(individual);
		}
	}

	private final Class<? extends T> templateClass;

	protected DefaultPublicContainer(DefaultApplicationContext applicationContext, Endpoint endpoint, Class<? extends T> templateClass) {
		super(applicationContext, endpoint);
		this.templateClass = templateClass;
	}

	protected final T containerTemplate() {
		ResourceTemplate template = template();
		if(!this.templateClass.isInstance(template)) {
			throw new IllegalStateException("Invalid container template exception: expected an instance of class "+this.templateClass.getCanonicalName()+" but got an instance of "+template.getClass().getCanonicalName());
		}
		return this.templateClass.cast(template);
	}

	@Override
	protected final DataSet resourceData(ContentPreferences contentPreferences) throws ApplicationExecutionException {
		DataSet dataSet = super.resourceData(contentPreferences);
		if(contentPreferences.isMinimalInclusionRequired()) {
			List<Individual<?,?>> toClean=new ArrayList<Individual<?,?>>();
			for(Individual<?,?> individual:dataSet) {
				individual.accept(new IndividualFilter(toClean));
			}
			for(Individual<?,?> individual:toClean) {
				cleanIndividual(individual);
			}
		}
		return dataSet;
	}

	@Override
	protected void fillInMetadata(ContentPreferences contentPreferences,Individual<?, ?> individual, Context ctx) {
		super.fillInMetadata(contentPreferences,individual,ctx);
		individual.
			addValue(
				ctx.property(RDF.TYPE),
				ctx.reference(LDP.CONTAINER));
		if(contentPreferences.mayInclude(Preference.CONTAINMENT_TRIPLES)) {
			for(PublicResource member:members()) {
				individual.addValue(
					ctx.property(LDP.CONTAINS),
					ctx.newIndividual(member));
			}
		}
	}

	@Override
	protected void configureValidationConstraints(ValidatorBuilder builder, Individual<?, ?> individual, DataSet metadata) {
		super.configureValidationConstraints(builder, individual, metadata);
		URI propertyId = LDP.CONTAINS.as(URI.class);
		Property property = individual.property(propertyId);
		ValidationConstraint<Property> constraint=null;
		if(property!=null) {
			constraint=ValidationConstraintFactory.readOnlyProperty(property);
		} else {
			constraint=ValidationConstraintFactory.readOnlyProperty(propertyId);
		}
		builder.withPropertyConstraint(constraint);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public final Collection<PublicResource> members() {
		List<PublicResource> members=Lists.newArrayList();
		for(Member member:resolveAs(Container.class).members()) {
			members.add(createResource(member.memberId()));
		}
		return Collections.unmodifiableList(members);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public DefaultPublicResource createResource(DataSet dataSet, CreationPreferences preferences) throws ApplicationExecutionException {
		verifyInteractionModel(preferences.getInteractionModel());
		Resource resource=applicationContext().createResource(endpoint(),dataSet,preferences.getPath());
		return createResource(resource.id());
	}

	private void cleanIndividual(Individual<?,?> individual) {
		for(Property property:individual) {
			URI propertyId = property.predicate();
			for(Value value:property) {
				individual.removeValue(propertyId,value);
			}
		}
	}

	private boolean isSelf(ManagedIndividualId id) {
		ResourceId resourceId = super.endpoint().resourceId();
		return id.managerId().equals(resourceId.templateId()) && id.name().equals(resourceId.name());
	}

	/**
	 * NOTE: For the time being containers will always behave as containers.
	 * Whenever we support downgrading containers to resources the visitor will
	 * have to be updated to reflect that.
	 */
	private void verifyInteractionModel(InteractionModel interactionModel) {
		if(interactionModel==null) {
			return;
		}
		Set<InteractionModel> acceptable=Sets.newHashSet();
		containerTemplate().memberTemplate().accept(new AcceptableInteractionModelCollector(acceptable));
		if(!acceptable.contains(interactionModel)) {
			throw new UnsupportedInteractionModelException(interactionModel,acceptable);
		}

	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *             http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-examples:0.2.2
 *   Bundle      : ldp4j-application-examples-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.example;

import static org.ldp4j.application.data.IndividualReferenceBuilder.newReference;

import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.ldp4j.application.data.DataDSL;
import org.ldp4j.application.data.DataSet;
import org.ldp4j.application.data.Name;
import org.ldp4j.application.data.NamingScheme;
import org.ldp4j.application.ext.Application;
import org.ldp4j.application.ext.ApplicationInitializationException;
import org.ldp4j.application.ext.ApplicationSetupException;
import org.ldp4j.application.ext.Configuration;
import org.ldp4j.application.session.ContainerSnapshot;
import org.ldp4j.application.session.ResourceSnapshot;
import org.ldp4j.application.session.WriteSession;
import org.ldp4j.application.session.WriteSessionException;
import org.ldp4j.application.setup.Bootstrap;
import org.ldp4j.application.setup.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

/**
 * An example LDP4j application.
 */
public class MyApplication extends Application<Configuration> {

	static final class LoggedUncaughtExceptionHandler implements UncaughtExceptionHandler {

		private final Logger logger; // NOSONAR

		LoggedUncaughtExceptionHandler(Logger logger) {
			this.logger = logger;
		}

		@Override
		public void uncaughtException(Thread t, Throwable e) {
			this.logger.error(String.format("Thread %s died",t.getName()),e);
		}
	}

	private static final String PERSON_CONTAINER_NAME    = "PersonContainer";
	private static final String PERSON_RESOURCE_NAME     = "PersonResource";
	private static final String RELATIVE_CONTAINER_NAME  = "RelativeContainer";
	private static final String QUERYABLE_RESOURCE_NAME  = "QueryableResource";
	private static final String DYNAMIC_RESOURCE_NAME    = "DynamicResource";
	private static final String BAD_DATA_RESOURCE_NAME   = "BadDataResource";

	/**
	 * The path for a root person resource
	 */
	public static final String ROOT_PERSON_RESOURCE_PATH    = "rootPersonResource/";

	/**
	 * The path for a root person container resource
	 */
	public static final String ROOT_PERSON_CONTAINER_PATH   = "rootPersonContainer/";

	/**
	 * The path for a root queryable resource
	 */
	public static final String ROOT_QUERYABLE_RESOURCE_PATH = "rootQueryableResource/";

	/**
	 * The path for a root dynamic  resource
	 */
	public static final String ROOT_DYNAMIC_RESOURCE_PATH   = "rootDynamicResource/";

	/**
	 * The path for a root bad data resource
	 */
	public static final String ROOT_BAD_DATA_RESOURCE_PATH  = "rootBadDataResource/";

	private static final Logger LOGGER=LoggerFactory.getLogger(MyApplication.class);

	private final Name<String> personResourceName;
	private final Name<String> personContainerName;
	private final Name<String> relativeContainerResourceName;
	private final Name<String> queryableResourceName;
	private final Name<String> dynamicResourceName;
	private final Name<String> badDataResourceName;

	private DynamicResourceHandler dynamicResourceHandler;
	private ScheduledExecutorService executorService;

	/**
	 * Create a new instance
	 */
	public MyApplication() {
		this.personResourceName = NamingScheme.getDefault().name(PERSON_RESOURCE_NAME);
		this.personContainerName = NamingScheme.getDefault().name(PERSON_CONTAINER_NAME);
		this.relativeContainerResourceName = NamingScheme.getDefault().name(RELATIVE_CONTAINER_NAME);
		this.queryableResourceName = NamingScheme.getDefault().name(QUERYABLE_RESOURCE_NAME);
		this.dynamicResourceName = NamingScheme.getDefault().name(DYNAMIC_RESOURCE_NAME);
		this.badDataResourceName = NamingScheme.getDefault().name(BAD_DATA_RESOURCE_NAME);
	}

	private DataSet getInitialData(String templateId, String name) {
		return
			DataDSL.
				dataSet().
					individual(newReference().toManagedIndividual(templateId).named(name)).
						hasProperty("http://www.ldp4j.org/vocabulary/example#creationDate").
							withValue(new Date()).
						hasProperty("http://www.ldp4j.org/vocabulary/example#age").
							withValue(34).
						hasLink("http://www.ldp4j.org/vocabulary/example#hasFather").
							toIndividual(newReference().toLocalIndividual().named("Michel")).
								hasLink("http://www.ldp4j.org/vocabulary/example#hasWife").
									referringTo(newReference().toLocalIndividual().named("Consuelo")).
						build();
	}

	/** Exposed for testing */
	protected final Name<String> personResourceName() {
		return this.personResourceName;
	}

	/** Exposed for testing */
	protected final Name<String> personContainerName() {
		return this.personContainerName;
	}

	/** Exposed for testing */
	protected final Name<String> relativeContainerResourceName() {
		return this.relativeContainerResourceName;
	}

	/** Exposed for testing */
	protected final Name<String> dynamicResourceName() {
		return this.dynamicResourceName;
	}

	/** Exposed for testing */
	protected final Name<String> badDataResourceName() {
		return this.badDataResourceName;
	}

	/** Exposed for testing */
	protected final DynamicResourceHandler dynamicResourceHandler() {
		return this.dynamicResourceHandler;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setup(Environment environment, Bootstrap<Configuration> bootstrap) throws ApplicationSetupException {
		LOGGER.info("Configuring application: {}, {}",environment,bootstrap);
		PersonHandler resourceHandler = new PersonHandler();
		PersonContainerHandler containerHandler=new PersonContainerHandler();
		RelativeContainerHandler relativesHandler=new RelativeContainerHandler();
		QueryableResourceHandler queryableHandler=new QueryableResourceHandler();
		this.dynamicResourceHandler = new DynamicResourceHandler();
		BadDataResourceHandler badDataHandler=new BadDataResourceHandler();

		containerHandler.setHandler(resourceHandler);
		relativesHandler.setHandler(resourceHandler);

		resourceHandler.add(this.personResourceName, getInitialData(PersonHandler.ID,PERSON_RESOURCE_NAME));
		containerHandler.add(this.personContainerName, getInitialData(PersonContainerHandler.ID,PERSON_CONTAINER_NAME));
		relativesHandler.add(this.relativeContainerResourceName, getInitialData(RelativeContainerHandler.ID,RELATIVE_CONTAINER_NAME));
		queryableHandler.add(this.queryableResourceName, getInitialData(QueryableResourceHandler.ID,QUERYABLE_RESOURCE_NAME));
		this.dynamicResourceHandler.add(this.dynamicResourceName, getInitialData(DynamicResourceHandler.ID,DYNAMIC_RESOURCE_NAME));
		badDataHandler.add(this.badDataResourceName, badDataHandler.getRepresentation());

		bootstrap.addHandler(resourceHandler);
		bootstrap.addHandler(containerHandler);
		bootstrap.addHandler(relativesHandler);
		bootstrap.addHandler(queryableHandler);
		bootstrap.addHandler(this.dynamicResourceHandler);
		bootstrap.addHandler(badDataHandler);

		environment.publishResource(this.personResourceName, PersonHandler.class, ROOT_PERSON_RESOURCE_PATH);
		environment.publishResource(this.personContainerName, PersonContainerHandler.class, ROOT_PERSON_CONTAINER_PATH);
		environment.publishResource(this.queryableResourceName, QueryableResourceHandler.class, ROOT_QUERYABLE_RESOURCE_PATH);
		environment.publishResource(this.dynamicResourceName, DynamicResourceHandler.class, ROOT_DYNAMIC_RESOURCE_PATH);
		environment.publishResource(this.badDataResourceName, BadDataResourceHandler.class, ROOT_BAD_DATA_RESOURCE_PATH);

		this.executorService =
			Executors.
				newScheduledThreadPool(
					1,
					new ThreadFactoryBuilder().
						setNameFormat("daemon-updater-thread-%d").
						setUncaughtExceptionHandler(new LoggedUncaughtExceptionHandler(LOGGER)).
						build());
		LOGGER.info("Configuration completed.");
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void initialize(WriteSession session) throws ApplicationInitializationException {
		LOGGER.info("Initializing application: {}",session);
		ResourceSnapshot person = session.find(ResourceSnapshot.class,this.personResourceName,PersonHandler.class);
		LOGGER.info("Root resource.......: "+person);
		LOGGER.info("Root basic container: "+session.find(ResourceSnapshot.class,this.personContainerName,PersonContainerHandler.class));

		ContainerSnapshot relativesContainer = person.createAttachedResource(ContainerSnapshot.class, PersonHandler.RELATIVES_ID, this.relativeContainerResourceName, RelativeContainerHandler.class);
		LOGGER.info("Attached resource...: "+relativesContainer);
		try {
			session.saveChanges();
		} catch (WriteSessionException e) {
			throw new ApplicationInitializationException("Could not initialize application",e);
		}
		this.executorService.
		scheduleAtFixedRate(
			new DynamicResourceUpdater(this.dynamicResourceHandler,this.dynamicResourceName),
			1,
			15,TimeUnit.SECONDS);
		this.executorService.
			schedule(
				new DynamicResourceResolver(this.dynamicResourceHandler,this.dynamicResourceName),
				3,TimeUnit.SECONDS);
		LOGGER.info("Initialization completed.");
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void shutdown() {
		LOGGER.info("Shutting down application...");
		this.executorService.shutdown();
		boolean finished=this.executorService.isTerminated();
		while(!finished) {
			try {
				this.executorService.awaitTermination(100, TimeUnit.MILLISECONDS);
				finished=this.executorService.isTerminated();
			} catch (InterruptedException e) {
				finished=true;
			}
		}
		LOGGER.info("Shutdown completed.");
	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *             http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.commons:ldp4j-commons-core:0.2.2
 *   Bundle      : ldp4j-commons-core-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.net;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

public final class Path {

	private static final char     CURRENT_CHAR   = '.';
	private static final String   SLASH          = "/";
	private static final String   PARENT         = "..";
	private static final String   CURRENT        = ".";
	private static final Path     EMPTY_PATH     = Path.create("");

	private static final String[] EMPTY_SEGMENTS = new String[0];
	private static final String[] ROOT_SEGMENTS   = new String[] {""};

	private String directory;
	private String fileName;
	private String fileExtension;

	private Path() {
	}

	private Path(final Path path) {
		setDirectory(path.getDirectory());
		setFile(path.getFile());
	}

	private void setDirectory(final String directory) {
		this.directory = directory;
	}

	private void setFile(final String file) {
		String tFileName=null;
		String tFileExtension=null;
		if(file!=null && !file.isEmpty()) {
			final int ext = file.lastIndexOf(CURRENT_CHAR);
			if(ext>=0) {
				tFileName=file.substring(0,ext);
				tFileExtension=file.substring(ext+1);
			} else {
				tFileName=file;
			}
		}
		setFileName(tFileName);
		setFileExtension(tFileExtension);
	}

	private void setFileExtension(final String fileExtension) {
		this.fileExtension=fileExtension;
	}

	private void setFileName(final String fileName) {
		this.fileName=fileName;
	}

	private String nullable(final String string) {
		return string==null?"":string;
	}

	private String file() {
		final StringBuilder builder=new StringBuilder();
		builder.append(nullable(this.fileName));
		if(this.fileExtension!=null) {
			builder.append(CURRENT);
			builder.append(this.fileExtension);
		}
		return builder.toString();
	}

	private String normalizePath(final String[] segments, final String file) {
		final Deque<String> buffer=new LinkedList<String>();
		for(final String segment:segments) {
			if(segment.equals(CURRENT)) {
				// do nothing
			} else if(segment.equals(PARENT)) {
				processParentSegment(buffer);
			} else {
				buffer.addLast(segment);
			}
		}
		return assembleSegments(buffer, file);
	}

	private void processParentSegment(final Deque<String> buffer) {
		if(!buffer.isEmpty()) {
			if(buffer.peekLast().equals(PARENT)) {
				buffer.addLast(PARENT);
			} else if(buffer.peekLast().isEmpty()){
				buffer.addLast(PARENT);
			} else {
				buffer.removeLast();
			}
		} else {
			buffer.addLast(PARENT);
		}
	}

	private String assembleSegments(final Deque<String> segments, final String file) {
		final StringBuilder builder=new StringBuilder();
		for(final Iterator<String> it=segments.iterator();it.hasNext();) {
			final String segment=it.next();
			builder.append(segment);
			if(it.hasNext() || !isDotSegment(segment) || file!=null) {
				builder.append(SLASH);
			}
		}
		if(file!=null) {
			builder.append(file);
		}
		return builder.toString();
	}

	private boolean isDotSegment(final String segment) {
		return segment.equals(CURRENT) || segment.equals(PARENT);
	}

	private String[] segments() {
		if(this.directory==null) {
			return EMPTY_SEGMENTS;
		}
		if(SLASH.equals(this.directory)) {
			return ROOT_SEGMENTS;
		} else {
			return this.directory.split(SLASH);
		}
	}

	/**
	 * Checks if the path is empty.
	 *
	 * @return {@code true}, if is empty, {@code false} otherwise
	 */
	public boolean isEmpty() {
		return this.directory==null && this.fileName==null && this.fileExtension==null;
	}

	/**
	 * Checks if the path is root.
	 *
	 * @return {@code true}, if is root, {@code false} otherwise
	 */
	public boolean isRoot() {
		return this.directory!=null && this.directory.startsWith(SLASH);
	}

	/**
	 * Checks if the path is out of the scope.
	 *
	 * @return {@code true}, if is out of the scope, {@code false} otherwise
	 */
	public boolean isOutOfScope() {
		// Files are always on scope
		if(this.directory==null) {
			return false;
		}

		// First, normalize
		final Path normalize=normalize();

		// If now we are a file, we are in scope
		if(normalize.directory==null) {
			return false;
		}

		// If we find a segment which is '..' we are out of scope
		final String[] segments=normalize.segments();
		boolean result=false;
		for(int i=0;i<segments.length && !result;i++) {
			result=isDotSegment(segments[i]);
		}
		return result;
	}

	/**
	 * Checks if the path is a directory.
	 *
	 * @return {@code true}, if is a directory, {@code false} otherwise
	 */
	public boolean isDirectory() {
		return this.directory!=null && this.fileName==null && this.fileExtension==null;
	}

	/**
	 * Checks if the path is a file.
	 *
	 * @return {@code true}, if is a file, {@code false} otherwise
	 */
	public boolean isFile() {
		return !isDirectory();
	}

	/**
	 * Gets the directory.
	 *
	 * @return the directory
	 */
	public String getDirectory() {
		return this.directory;
	}

	/**
	 * Gets the file.
	 *
	 * @return the file
	 */
	public String getFile() {
		final String file=file();
		return file.isEmpty()?null:file;
	}

	/**
	 * Gets the file name.
	 *
	 * @return the file name
	 */
	public String getFileName() {
		return this.fileName;
	}

	/**
	 * Gets the file extension.
	 *
	 * @return the file extension
	 */
	public String getFileExtension() {
		return this.fileExtension;
	}

	/**
	 * Create a new path with a given directory.
	 *
	 * @param directory the directory for the path
	 * @return the new path
	 */
	public Path withDirectory(final String directory) {
		final Path result = new Path(this);
		result.setDirectory(directory);
		return result;
	}

	/**
	 * Create a new path with a given file.
	 *
	 * @param file the file for the path
	 * @return the new path
	 */
	public Path withFile(final String file) {
		final Path result = new Path(this);
		result.setFile(file);
		return result;
	}

	public Path unroot() {
		if(!isRoot()) {
			return this;
		}
		return Path.create(toString().substring(1));
	}

	public Path normalize() {
		final String[] segments=
			this.directory==null?
				EMPTY_SEGMENTS:
				SLASH.equals(this.directory)?
					ROOT_SEGMENTS:
					this.directory.split(SLASH);
		return Path.create(normalizePath(segments, getFile()));
	}

	/**
	 * Computes relative relative path to reference "target" from "base". Uses
	 * ".." if needed, in contrast to {@link URI#relativize(URI)}.
	 *
	 * @param path the path to be relativized
	 * @return the relative path
	 */
	public Path relativize(final Path path) {
		Objects.requireNonNull("Path cannot be null");

		final Path base=this;

		// By default, we return the normalized form of the input path
		final Path defaultRelative = path.normalize();

		// Either root or not
		if(base.isRoot()!=path.isRoot()) {
			return defaultRelative;
		}

		// If the path is not root, return its normalized base
		if(!path.isRoot()) {
			return defaultRelative;
		}

		// Beyond this point we need the normalized form
		final Path nBase = base.normalize();

		// If we are out of the scope...
		if(base.isOutOfScope()) {
			// ... we must return the input path because it will define the
			// scope
			Path relative=defaultRelative;
			if(nBase.equals(relative)) {
				// ... unless the path is the same as ourselves, in which
				// case it is safe to return the empty path
				relative=EMPTY_PATH;
			}
			return relative;
		}

		// Can only compare for equality iff normalized.
		final Path unBase=nBase.unroot();
		final Path unPath=defaultRelative.unroot();

		// If the base and the target are the same, return the empty path
		if(unBase.equals(unPath)) {
			return EMPTY_PATH;
		}

		return
			assembleRelativeSegments(
				path,
				base,
				getRelativeSegments(
					unBase.segments(),
					unPath.segments()));
	}

	/**
	 * If there are no segments in the resolved path, and we are trying to
	 * resolve a directory coming from a path, we have to make explicit that we
	 * want the directory
	 */
	private Path assembleRelativeSegments(final Path path, final Path base, final Deque<String> segments) {
		if(segments.isEmpty() && path.isDirectory() && base.isFile()) {
			segments.add(CURRENT);
		}

		return Path.create(assembleSegments(segments,path.getFile()));
	}

	private Deque<String> getRelativeSegments(final String[] baseSegments,final String[] targetSegments) {
		final Deque<String> segments=new LinkedList<String>();
		// Look for index of last common segment
		final int commonSegments=countCommonSegments(baseSegments, targetSegments);
		// For each different segment of the base path, add '..' to the
		// segments of the relative path
		addParentSegments(segments,baseSegments,commonSegments);
		// Add each different segment of the input path to the segments of
		// the relative path
		addChildSegments(segments,targetSegments,commonSegments);
		return segments;
	}

	private void addChildSegments(final Deque<String> segments, final String[] targetSegments, final int commonSegments) {
		for(int i=commonSegments;i<targetSegments.length;i++) {
			segments.add(targetSegments[i]);
		}
	}

	private void addParentSegments(final Deque<String> segments, final String[] baseSegments, final int commonSegments) {
		for(int i=commonSegments;i<baseSegments.length;i++) {
			segments.add(PARENT);
		}
	}

	private int countCommonSegments(final String[] s1, final String[] s2) {
		final int comparableSegments=Math.min(s1.length,s2.length);
		int commonSegments=0;
		for(int i=0;i<comparableSegments;i++) {
			if(!s1[i].equals(s2[i])) {
				break;
			}
			commonSegments++;
		}
		return commonSegments;
	}

	public Path resolve(final Path path) {
		if(path==null) {
			throw new NullPointerException("Target path cannot be null");
		}
		if(path.equals(Path.create(""))) {
			return this.normalize();
		}
		if(path.isEmpty()) {
			return this.normalize();
		}
		if(path.isRoot()) {
			return path.normalize();
		}
		final Path base=normalize();
		final Path relative=path.normalize();

		final List<String> baseSegments=new ArrayList<String>(Arrays.asList(base.segments()));
		baseSegments.addAll(Arrays.asList(relative.segments()));
		final String[] segments = baseSegments.toArray(new String[baseSegments.size()]);
		final String resolved=normalizePath(segments,path.getFile());
		return Path.create(resolved);
	}

	@Override
	public int hashCode() {
		return Objects.hash(this.directory,this.fileName,this.fileExtension);
	}

	@Override
	public boolean equals(final Object obj) {
		boolean result = false;
		if(obj instanceof Path) {
			final Path that=(Path)obj;
			result=
				Objects.equals(this.directory,that.directory) &&
				Objects.equals(this.fileName,that.fileName) &&
				Objects.equals(this.fileExtension,that.fileExtension);
		}
		return result;
	}

	@Override
	public String toString() {
		final StringBuilder builder=new StringBuilder();
		if(this.directory!=null) {
			builder.append(this.directory);
		}
		builder.append(file());
		return builder.toString();
	}
	public static Path create(final java.net.URI jdkURI) {
		return create(jdkURI.getPath());
	}

	public static Path create(final String path) {
		Path result=null;
		if(path!=null) {
			String directory=null;
			String file=null;
			final int lastSegmentSeparator = path.lastIndexOf('/');
			if(lastSegmentSeparator<0) {
				if(CURRENT.equals(path)) {
					directory=CURRENT;
				} else if(PARENT.equals(path)) {
					directory=PARENT;
				} else {
					file=path;
				}
			} else if(lastSegmentSeparator==path.length()-1) {
				directory=path;
			} else {
				directory=path.substring(0,lastSegmentSeparator+1);
				final String segment=path.substring(lastSegmentSeparator+1);
				if(CURRENT.equals(segment)) {
					directory+=CURRENT;
				} else if(PARENT.equals(segment)) {
					directory+=PARENT;
				} else {
					file=segment;
				}
			}
			result=new Path();
			result.setDirectory(directory);
			result.setFile(file);
		}
		return result;
	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *             http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-server-core:0.2.2
 *   Bundle      : ldp4j-server-core-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.server.controller;

import java.net.URI;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.Variant;

import org.ldp4j.application.data.DataSet;
import org.ldp4j.application.engine.context.ApplicationContextException;
import org.ldp4j.application.engine.context.ApplicationExecutionException;
import org.ldp4j.application.engine.context.ContentPreferences;
import org.ldp4j.application.engine.context.OperationPrecondititionException;
import org.ldp4j.application.engine.context.PublicContainer;
import org.ldp4j.application.engine.context.PublicResource;
import org.ldp4j.application.engine.context.UnsupportedInteractionModelException;
import org.ldp4j.application.ext.ApplicationRuntimeException;
import org.ldp4j.application.ext.InvalidContentException;
import org.ldp4j.application.ext.InvalidQueryException;
import org.ldp4j.application.ext.Parameter;
import org.ldp4j.application.ext.Query;
import org.ldp4j.application.ext.UnknownResourceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Optional;
import com.google.common.base.Supplier;

final class ExistingEndpointController implements EndpointController {

	private static final class DefaultContentPreferencesSupplier implements Supplier<ContentPreferences> {
		@Override
		public ContentPreferences get() {
			return ContentPreferences.defaultPreferences();
		}
	}

	private static final String NO_CONSTRAINT_REPORT_ID_DEFINED_ERROR = "No constraint report identifier defined. Full stacktrace follows";

	private static final Logger LOGGER=LoggerFactory.getLogger(ExistingEndpointController.class);

	ExistingEndpointController() {
	}

	private void addOptionsMandatoryHeaders(OperationContext context, ResponseBuilder builder) {
		EndpointControllerUtils.populateRequiredHeaders(builder, context);
		EndpointControllerUtils.populateAllowedHeaders(builder, context.resource().capabilities());
	}

	private ResponseBuilder prepareRetrievalResponse(
			OperationContext context,
			Variant variant,
			DataSet entity,
			boolean includeEntity) {
		String body=
			context.serialize(
				entity,
				NamespacesHelper.
					constraintReportNamespaces(
						context.applicationNamespaces()),
				variant.getMediaType());
		ResponseBuilder builder=Response.ok();
		EndpointControllerUtils.
			populateResponseBody(
				builder,
				body,
				variant,
				includeEntity);
		return builder;
	}

	private Response prepareResourceRetrievalResponse(
			OperationContext context,
			Variant variant,
			DataSet entity,
			boolean includeEntity) {
		ResponseBuilder builder=prepareRetrievalResponse(context, variant, entity, includeEntity);
		addOptionsMandatoryHeaders(context, builder);

		ContentPreferences preferences = context.contentPreferences();
		if(preferences!=null) {
			builder.
				header(
					ContentPreferencesUtils.PREFERENCE_APPLIED_HEADER,
					ContentPreferencesUtils.asPreferenceAppliedHeader(preferences));
		}

		Query query=context.getQuery();
		if(!query.isEmpty()) {
			builder.
				header(
					HttpHeaders.LINK,
					EndpointControllerUtils.
						createQueryOfLink(
							context.base().resolve(context.path()),
							query));
		}
		return builder.build();
	}

	private Response prepareConstraintReportRetrievalResponse(
			OperationContext context,
			Variant variant,
			DataSet report,
			boolean includeEntity) {
		return
			prepareRetrievalResponse(context,variant,report,includeEntity).
				build();
	}

	private Response handleRetrieval(OperationContext context, boolean includeEntity) {
		final Variant variant=context.expectedVariant();
		context.
			checkOperationSupport().
			checkPreconditions();
		switch(RetrievalScenario.forContext(context)) {
			case MIXED_QUERY:
				throw new MixedQueryNotAllowedException(context,includeEntity);
			case QUERY_NOT_SUPPORTED:
				throw new QueryingNotSupportedException(context,includeEntity);
			case CONSTRAINT_REPORT_RETRIEVAL:
				return handleConstraintReportRetrieval(context,includeEntity,variant);
			default:
				return handleResourceRetrieval(context,includeEntity,variant);
		}
	}

	private Response handleResourceRetrieval(OperationContext context, boolean includeEntity, Variant variant) {
		try {
			if(LOGGER.isDebugEnabled()) {
				LOGGER.debug(EndpointControllerUtils.retrievalLog(context));
			}
			PublicResource resource=context.resource();
			Query query=context.getQuery();
			ContentPreferences preferences =
				Optional.
					fromNullable(context.contentPreferences()).
					or(new DefaultContentPreferencesSupplier());
			DataSet entity=
				query.isEmpty()?
					resource.entity(preferences):
					resource.query(query,preferences);
			if(LOGGER.isTraceEnabled()) {
				LOGGER.trace(EndpointControllerUtils.retrievalResultLog(entity));
			}
			return prepareResourceRetrievalResponse(context,variant,entity,includeEntity);
		} catch (ApplicationExecutionException e) {
			throw diagnoseApplicationExecutionException(context, e);
		} catch (ApplicationContextException e) {
			throw new InternalServerException(context,e);
		}
	}

	private Response handleConstraintReportRetrieval(OperationContext context, boolean includeEntity, Variant variant) {
		Parameter parameter=RetrievalScenario.constraintReportId(context);
		if(parameter.cardinality()!=1) {
			throw new InvalidConstraintReportRetrievalException(context,parameter.rawValues(),includeEntity);
		}
		String constraintReportId = parameter.rawValue();
		try {
			LOGGER.debug("Retrieving constraint report: {}",constraintReportId);

			PublicResource resource=context.resource();
			DataSet report=resource.getConstraintReport(constraintReportId);
			if(report==null) {
				throw new UnknownConstraintReportException(context,constraintReportId,includeEntity);
			}

			LOGGER.trace("Constraint report to serialize:\n{}",report);
			return prepareConstraintReportRetrievalResponse(context,variant,report,includeEntity);
		} catch (ApplicationExecutionException e) {
			throw diagnoseApplicationExecutionException(context, e);
		} catch (ApplicationContextException e) {
			throw new InternalServerException(context,e);
		}
	}

	private OperationContextException diagnoseApplicationExecutionException(OperationContext context, ApplicationExecutionException exception) {
		OperationContextException result = null;
		final Throwable rootCause=exception.getCause();
		if(rootCause instanceof InvalidContentException) {
			final InvalidContentException ice = (InvalidContentException)exception.getCause();
			if(ice.getConstraintsId()==null) {
				result=new InternalServerException(context,NO_CONSTRAINT_REPORT_ID_DEFINED_ERROR,ice);
			} else {
				result=new InvalidContentDiagnosedException(context,ice);
			}
		} else if (rootCause instanceof UnknownResourceException) {
			throw new WebApplicationException(Status.NOT_FOUND);
		} else if (rootCause instanceof InvalidQueryException) {
			result=new InvalidQueryDiagnosedException(context, (InvalidQueryException)rootCause);
		} else if (rootCause instanceof ApplicationRuntimeException) {
			result=new InternalServerException(context,rootCause);
		} else {
			result=new InternalServerException(context,exception);
		}
		return result;
	}

	@Override
	public Response options(OperationContext context) {
		ResponseBuilder builder=Response.ok();
		addOptionsMandatoryHeaders(context, builder);
		return builder.build();
	}

	@Override
	public Response head(OperationContext context) {
		return handleRetrieval(context, false);
	}

	@Override
	public Response getResource(OperationContext context) {
		return handleRetrieval(context, true);
	}

	/**
	 * TODO: This could be improved by returning an OK with an additional
	 * description of all the resources that were deleted as a side effect.
	 */
	@Override
	public Response deleteResource(OperationContext context) {
		context.
			checkOperationSupport().
			checkPreconditions();
		try {
			context.resource().delete();
			return Response.noContent().build();
		} catch (ApplicationExecutionException e) {
			throw diagnoseApplicationExecutionException(context, e);
		} catch (ApplicationContextException e) {
			throw new InternalServerException(context,e);
		}
	}

	/**
	 * TODO: This could be improved by returning an OK with an additional
	 * description of all the resources that were modified (updated, created,
	 * deleted) as a side effect.
	 */
	@Override
	public Response modifyResource(OperationContext context) {
		context.
			checkOperationSupport().
			checkContents().
			checkPreconditions();
		try {
			context.resource().modify(context.dataSet());
			ResponseBuilder builder=Response.noContent();
			EndpointControllerUtils.populateRequiredHeaders(builder, context);
			return builder.build();
		} catch (ApplicationExecutionException e) {
			throw diagnoseApplicationExecutionException(context, e);
		} catch (ApplicationContextException e) {
			throw new InternalServerException(context,e);
		}
	}

	@Override
	public Response patchResource(OperationContext context) {
		// Verify that we can carry out the operation
		context.
			checkOperationSupport().
			checkContents().
			checkPreconditions();
		// Fail as we do not support PATCH
		throw new NotImplementedException(context);
	}

	@Override
	public Response createResource(OperationContext context) {
		context.
			checkOperationSupport().
			checkContents().
			checkPreconditions();
		try {
			PublicContainer container=context.container();
			PublicResource newResource =
				container.createResource(context.dataSet(),context.creationPreferences());
			URI location = context.resolve(newResource);
			ResponseBuilder builder=Response.created(location);
			EndpointControllerUtils.populateRequiredHeaders(builder, context);
			EndpointControllerUtils.
				populateResponseBody(
					builder,
					location.toString(),
					EndpointControllerUtils.textResponseVariant(),
					true);
			return builder.build();
		} catch (ApplicationExecutionException e) {
			throw diagnoseApplicationExecutionException(context, e);
		} catch (UnsupportedInteractionModelException e) {
			throw new UnsupportedInteractionModelDiagnosedException(context, e);
		} catch (OperationPrecondititionException e) {
			throw new OperationPrecondititionModelDiagnosedException(context, e);
		} catch (ApplicationContextException e) {
			throw new InternalServerException(context,e);
		}
	}
}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *             http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-data:0.2.2
 *   Bundle      : ldp4j-application-data-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.data;

import java.io.Serializable;
import java.net.URI;
import java.util.List;

import com.google.common.collect.Lists;

final class DataSetHelperImpl extends DataSetHelper {

	private final class IndividualRenamer implements TripleConsumer {

		private final Individual<?, ?> from;
		private final Individual<?, ?> to;

		private IndividualRenamer(final Individual<?, ?> from, final Individual<?, ?> to) {
			this.from = from;
			this.to = to;
		}

		@Override
		public void consume(final Individual<?, ?> subject, final URI predicate, final Literal<?> object) {
			if(subject==this.from) {
				this.to.addValue(predicate,object);
			}
		}

		@Override
		public void consume(final Individual<?, ?> subject, final URI predicate, final Individual<?, ?> object) {
			if(subject==this.from) {
				Value value=object;
				if(value==this.from) {
					value=this.to;
				}
				this.to.addValue(predicate,value);
			} else if(subject!=this.to && object==this.from) {
				subject.removeValue(predicate, object);
				subject.addValue(predicate,this.to);
			}
		}
	}

	private static class DataSetIterator{

		private static final class TripleConsumerAdapter implements ValueVisitor {
			private final Individual<?, ?> individual;
			private final DataSetHelperImpl.TripleConsumer consumer;
			private final URI predicate;

			private TripleConsumerAdapter(final Property property, final Individual<?, ?> individual, final DataSetHelperImpl.TripleConsumer consumer) {
				this.predicate  = property.predicate();
				this.individual = individual;
				this.consumer   = consumer;
			}

			@Override
			public void visitLiteral(final Literal<?> value) {
				this.consumer.consume(this.individual,this.predicate,value);
			}

			@Override
			public void visitIndividual(final Individual<?, ?> value) {
				this.consumer.consume(this.individual,this.predicate,value);
			}

		}

		private final DataSet dataSet;

		private DataSetIterator(final DataSet dataSet) {
			this.dataSet = dataSet;
		}

		void iterate(final DataSetHelperImpl.TripleConsumer processor) {
			for(final Individual<?,?> individual:this.dataSet) {
				for(final Property property:individual) {
					final DataSetIterator.TripleConsumerAdapter visitor = new TripleConsumerAdapter(property,individual, processor);
					for(final Value value:property) {
						value.accept(visitor);
					}
				}
			}
		}

	}

	private static final class NewIndividualIdCollector implements IndividualVisitor {

		private final List<URI> newIds=Lists.newArrayList();
		private final ManagedIndividualId id;

		private boolean hasSelf=false;
		private boolean idInUse=false;

		private NewIndividualIdCollector(final ManagedIndividualId id) {
			this.id = id;
		}

		@Override
		public void visitManagedIndividual(final ManagedIndividual individual) {
			this.idInUse=this.id.equals(individual.id());
		}

		@Override
		public void visitRelativeIndividual(final RelativeIndividual individual) {
			// Nothing to do
		}

		@Override
		public void visitLocalIndividual(final LocalIndividual individual) {
			// Nothing to do
		}

		@Override
		public void visitExternalIndividual(final ExternalIndividual individual) {
			// Nothing to do
		}

		@Override
		public void visitNewIndividual(final NewIndividual individual) {
			final URI path=individual.path();
			this.hasSelf=this.hasSelf || path.equals(SELF);
			this.newIds.add(path);
		}

		List<URI> getCollectedIds() throws DataSetModificationException {
			if(!this.hasSelf) {
				throw new DataSetModificationException("No default new individual defined");
			}
			return this.newIds;
		}

		void collect(final Individual<?, ?> individual) throws DataSetModificationException {
			individual.accept(this);
			if(this.idInUse) {
				throw new DataSetModificationException("The data set already has an individual identified as '"+this.id+"'");
			}
		}

	}

	private final DataSet dataSet;

	DataSetHelperImpl(final DataSet dataSet) {
		this.dataSet = dataSet;
	}

	private void rename(final Individual<?,?> from, final Individual<?,?> to) {
		final DataSetIterator iterator = new DataSetIterator(this.dataSet);
		final TripleConsumer consumer = new IndividualRenamer(from, to);
		iterator.iterate(consumer);
		this.dataSet.remove(from);
	}

	private List<URI> getNewIds(final ManagedIndividualId id) throws DataSetModificationException {
		final NewIndividualIdCollector collector=new NewIndividualIdCollector(id);
		for(final Individual<?,?> individual:this.dataSet) {
			collector.collect(individual);
		}
		return collector.getCollectedIds();
	}

	@Override
	public <T extends Serializable, S extends Individual<T, S>>  S replace(final Serializable from, final T to, final Class<? extends S> clazz) {
		S target=null;
		final Individual<?, ?> src=this.dataSet.individualOfId(from);
		if(src!=null) {
			target=this.dataSet.individual(to, clazz);
			rename(src,target);
		}
		return target;

	}

	@Override
	public ManagedIndividual manage(final ManagedIndividualId id) throws DataSetModificationException {
		final List<URI> newIds=getNewIds(id);
		newIds.remove(SELF);
		final ManagedIndividual self=replace(SELF,id,ManagedIndividual.class);
		for(final URI newId:newIds) {
			final RelativeIndividualId relativeId=RelativeIndividualId.createId(id, newId);
			replace(newId,relativeId,RelativeIndividual.class);
		}
		return self;
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T extends Individual<URI,T>> T self() {
		return (T)relative(SELF);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T extends Individual<URI,T>> T relative(final URI path) {
		return (T)this.dataSet.individualOfId(path);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public IndividualHelper managedIndividual(final Name<?> name, final String managerId) {
		final ManagedIndividualId individualId = ManagedIndividualId.createId(name,managerId);
		final ManagedIndividual individual = this.dataSet.individual(individualId, ManagedIndividual.class);
		return new IndividualHelperImpl(individual);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public IndividualHelper relativeIndividual(final Name<?> name, final String managerId, final URI path) {
		final ManagedIndividualId parentId = ManagedIndividualId.createId(name,managerId);
		final RelativeIndividualId individualId=RelativeIndividualId.createId(parentId, path);
		final RelativeIndividual individual = this.dataSet.individual(individualId, RelativeIndividual.class);
		return new IndividualHelperImpl(individual);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public IndividualHelper localIndividual(final Name<?> name) {
		@SuppressWarnings("rawtypes")
		final
		LocalIndividual individual = this.dataSet.individual((Name)name, LocalIndividual.class);
		return new IndividualHelperImpl(individual);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public IndividualHelper externalIndividual(final URI uri) {
		Individual<?, ?> individual=null;
		if(uri.isAbsolute()) {
			individual=this.dataSet.individual(uri, ExternalIndividual.class);
		} else {
			individual=this.dataSet.individual(uri, NewIndividual.class);
		}
		return new IndividualHelperImpl(individual);
	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *             http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-engine-sdk:0.2.2
 *   Bundle      : ldp4j-application-engine-sdk-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.sdk.internal;

import org.ldp4j.application.sdk.spi.ObjectFactory;
import org.ldp4j.application.sdk.spi.ObjectParseException;

/**
 * Object factory for parsing and formatting Character objects.
 */
public class CharacterObjectFactory implements ObjectFactory<Character> {

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Class<? extends Character> targetClass() {
		return Character.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Character fromString(String rawValue) {
		try {
			return PrimitiveObjectFactory.parseCharacter(rawValue);
		} catch (IllegalArgumentException e) {
			throw new ObjectParseException(e,targetClass(),rawValue);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString(Character value) {
		return value.toString();
	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *             http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-api:0.2.2
 *   Bundle      : ldp4j-application-api-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.ext;

import static com.google.common.base.Preconditions.checkNotNull;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;

/**
 * Helper methods for class type parameters.
 *
 * @see <a
 *      href="http://gafter.blogspot.com/2006/12/super-type-tokens.html">Super
 *      Type Tokens</a>
 */
final class Generics {

	private Generics() {
		/* singleton */
	}

	/**
	 * Finds the type parameter for the given class.
	 *
	 * @param klass
	 *            a parameterized class
	 * @return the class's type parameter
	 */
	static Class<?> getTypeParameter(Class<?> klass) {
		return getTypeParameter(klass, Object.class);
	}

	/**
	 * Finds the type parameter for the given class which is assignable to the
	 * bound class.
	 *
	 * @param clazz
	 *            a parameterized class
	 * @param bound
	 *            the type bound
	 * @param <T>
	 *            the type bound
	 * @return the class's type parameter
	 */
	static <T> Class<T> getTypeParameter(Class<?> clazz, Class<? super T> bound) {
		Type t = checkNotNull(clazz);
		while (t instanceof Class<?>) {
			t = ((Class<?>) t).getGenericSuperclass();
		}
		/**
		 * This is not guaranteed to work for all cases with convoluted piping
		 * of type parameters: but it can at least resolve straight-forward
		 * extension with single type parameter (as per [Issue-89]). And when it
		 * fails to do that, will indicate with specific exception.
		 */
		if(t instanceof ParameterizedType) {
			Class<T> result=processParameterizedType(bound, (ParameterizedType) t);
			if(result!=null) {
				return result;
			}
		}
		throw new IllegalStateException("Cannot figure out type parameterization for "+ clazz.getName());
	}

	protected static <T> Class<T> processParameterizedType(Class<? super T> bound, ParameterizedType parameterizedType) {
		// should typically have one of type parameter (first one) that matches:
		for(Type param : parameterizedType.getActualTypeArguments()) {
			Class<T> cls = null;
			if(param instanceof Class<?>) {
				cls=determineClass(bound, param);
			} else if(param instanceof TypeVariable) {
				cls=processTypeVariable(bound, (TypeVariable<?>)param);
			}
			if(cls!=null) {
				return cls;
			}
		}
		return null;
	}

	protected static <T> Class<T> processTypeVariable(Class<? super T> bound, TypeVariable<?> typeVariable) {
		for(Type paramBound:typeVariable.getBounds()) {
			if(paramBound instanceof Class<?>) {
				Class<T >cls=determineClass(bound,paramBound);
				if(cls!=null) {
					return cls;
				}
			}
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	private static <T> Class<T> determineClass(Class<? super T> bound, Type candidate) {
		if (candidate instanceof Class<?>) {
			final Class<?> cls = (Class<?>) candidate;
			if (bound.isAssignableFrom(cls)) {
				return (Class<T>) cls;
			}
		}
		return null;
	}
}