SimpleEnvironmentBuilder.java

package fr.sii.ogham.core.builder.env;

import static fr.sii.ogham.core.CoreConstants.DEFAULT_CLASSPATH_PROPERTY_PRIORITY;
import static fr.sii.ogham.core.CoreConstants.DEFAULT_FILE_PROPERTY_PRIORITY;
import static fr.sii.ogham.core.CoreConstants.DEFAULT_MANUAL_PROPERTY_PRIORITY;
import static fr.sii.ogham.core.CoreConstants.DEFAULT_SYSTEM_PROPERTY_PRIORITY;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;

import fr.sii.ogham.core.builder.env.props.AbstractProps;
import fr.sii.ogham.core.builder.env.props.Props;
import fr.sii.ogham.core.builder.env.props.PropsBuilder;
import fr.sii.ogham.core.builder.env.props.PropsPath;
import fr.sii.ogham.core.convert.Converter;
import fr.sii.ogham.core.convert.DefaultConverter;
import fr.sii.ogham.core.env.FirstExistingPropertiesResolver;
import fr.sii.ogham.core.env.JavaPropertiesResolver;
import fr.sii.ogham.core.env.PropertyResolver;
import fr.sii.ogham.core.fluent.AbstractParent;
import fr.sii.ogham.core.util.BuilderUtils;

/**
 * Builds the {@link PropertyResolver}:
 * <ul>
 * <li>If a custom resolver is defined, use it directly</li>
 * <li>If no custom resolver, use the {@link JavaPropertiesResolver} with merged
 * {@link Properties} and converter instance provided by
 * {@link ConverterBuilder#build()}</li>
 * </ul>
 * 
 * @author Aurélien Baudet
 *
 * @param <P>
 *            the type of the parent builder (when calling {@link #and()}
 *            method)
 */
public class SimpleEnvironmentBuilder<P> extends AbstractParent<P> implements EnvironmentBuilder<P> {
	private PropertyResolver resolver;
	private ConverterBuilder<EnvironmentBuilder<P>> converterBuilder;
	private List<AbstractProps> props;
	private int currentIndex;

	/**
	 * Initializes the builder with the provided parent (parent is used when
	 * calling {@link #and()} method).
	 * 
	 * @param parent
	 *            the parent instance
	 */
	public SimpleEnvironmentBuilder(P parent) {
		super(parent);
		props = new ArrayList<>();
		currentIndex = 0;
	}

	@Override
	public EnvironmentBuilder<P> override() {
		props.clear();
		return this;
	}

	@Override
	public EnvironmentBuilder<P> properties(String path) {
		int priority = DEFAULT_CLASSPATH_PROPERTY_PRIORITY;
		if(path.startsWith("file:")) {
			priority = DEFAULT_FILE_PROPERTY_PRIORITY;
		}
		return properties(path, priority);
	}

	@Override
	public EnvironmentBuilder<P> properties(String path, int priority) {
		props.add(new PropsPath(path, priority, currentIndex));
		currentIndex++;
		return this;
	}

	@Override
	public SimpleEnvironmentBuilder<P> properties(Properties properties) {
		return properties(properties, DEFAULT_MANUAL_PROPERTY_PRIORITY);
	}

	@Override
	public SimpleEnvironmentBuilder<P> properties(Properties properties, int priority) {
		props.add(new Props(properties, priority, currentIndex));
		currentIndex++;
		return this;
	}

	@Override
	public SimpleEnvironmentBuilder<P> systemProperties() {
		return systemProperties(DEFAULT_SYSTEM_PROPERTY_PRIORITY);
	}

	@Override
	public SimpleEnvironmentBuilder<P> systemProperties(int priority) {
		props.add(new Props(BuilderUtils.getDefaultProperties(), priority, currentIndex));
		currentIndex++;
		return this;
	}

	@Override
	public ConverterBuilder<EnvironmentBuilder<P>> converter() {
		if (converterBuilder == null) {
			converterBuilder = new SimpleConverterBuilder<>(this);
		}
		return converterBuilder;
	}

	@Override
	public SimpleEnvironmentBuilder<P> resolver(PropertyResolver resolver) {
		this.resolver = resolver;
		return this;
	}

	@Override
	public PropertiesBuilder<EnvironmentBuilder<P>> properties() {
		return properties(DEFAULT_MANUAL_PROPERTY_PRIORITY);
	}

	@Override
	public PropertiesBuilder<EnvironmentBuilder<P>> properties(int priority) {
		PropertiesBuilder<EnvironmentBuilder<P>> propsBuilder = new SimplePropertiesBuilder<>(this);
		props.add(new PropsBuilder(propsBuilder, priority, currentIndex));
		currentIndex++;
		return propsBuilder;
	}

	/**
	 * Build the {@link PropertyResolver}:
	 * <ul>
	 * <li>If a custom resolver is defined, use it directly</li>
	 * <li>If no custom resolver, use the {@link JavaPropertiesResolver} with
	 * merged {@link Properties} and converter instance provided by
	 * {@link ConverterBuilder#build()}</li>
	 * </ul>
	 */
	@Override
	public PropertyResolver build() {
		if (resolver != null) {
			return resolver;
		}
		Converter converter = buildConverter();
		List<PropertyResolver> delegates = buildProperties(converter);
		return new FirstExistingPropertiesResolver(delegates);
	}

	private Converter buildConverter() {
		if (converterBuilder != null) {
			return converterBuilder.build();
		}
		return new DefaultConverter();
	}

	private List<PropertyResolver> buildProperties(Converter converter) {
		// sort by priority and then by registration order
		// highest priority comes first
		List<AbstractProps> orderedProps = new ArrayList<>(props);
		Collections.sort(orderedProps, new PriorityComparator());
		// build the resolvers separately
		List<PropertyResolver> builtProperties = new ArrayList<>();
		for (AbstractProps prop : orderedProps) {
			builtProperties.add(new JavaPropertiesResolver(prop.getProps(), converter));
		}
		return builtProperties;
	}

	private static class PriorityComparator implements Comparator<AbstractProps> {

		@Override
		public int compare(AbstractProps o1, AbstractProps o2) {
			if (o1.getPriority() == o2.getPriority()) {
				return o1.getIndex() <= o2.getIndex() ? -1 : 1;
			}
			return o1.getPriority() <= o2.getPriority() ? 1 : -1;
		}

	}
}