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

org.refcodes.properties.PolyglotProperties Maven / Gradle / Ivy

// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// =============================================================================
// This code is copyright (c) by Siegfried Steiner, Munich, Germany, distributed
// on an "AS IS" BASIS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, and licen-
// sed under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// =============================================================================
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// together with the GPL linking exception applied; as being applied by the GNU
// Classpath ("http://www.gnu.org/software/classpath/license.html")
// =============================================================================
// Apache License, v2.0 ("http://www.apache.org/licenses/TEXT-2.0")
// =============================================================================
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////

package org.refcodes.properties;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.refcodes.data.Delimiter;
import org.refcodes.properties.JavaProperties.JavaPropertiesFactory;
import org.refcodes.properties.JsonProperties.JsonPropertiesFactory;
import org.refcodes.properties.PolyglotPropertiesBuilder.PolyglotPropertiesBuilderFactory;
import org.refcodes.properties.TomlProperties.TomlPropertiesFactory;
import org.refcodes.properties.XmlProperties.XmlPropertiesFactory;
import org.refcodes.properties.YamlProperties.YamlPropertiesFactory;
import org.refcodes.runtime.ConfigLocator;

/**
 * Implementation of the {@link ResourceProperties} interface with support of so
 * called "{@link PolyglotProperties}" (or just "properties"). For
 * {@link PolyglotProperties}, see "https://en.wikipedia.org/wiki/.properties".
 */
public class PolyglotProperties extends AbstractResourcePropertiesDecorator {

	// /////////////////////////////////////////////////////////////////////////
	// CONSTRUCTORS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Loads the {@link PolyglotProperties} from the given file's path.
	 *
	 * @param aResourceClass The class which's class loader is to take care of
	 *        loading the properties (from inside a JAR).
	 * @param aFilePath The file path of the class's resources from which to
	 *        load the properties.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( Class aResourceClass, String aFilePath ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory().toProperties( aResourceClass, aFilePath ) );
	}

	/**
	 * Loads the {@link PolyglotProperties} from the given file's path.
	 *
	 * @param aResourceClass The class which's class loader is to take care of
	 *        loading the properties (from inside a JAR).
	 * @param aFilePath The file path of the class's resources from which to
	 *        load the properties.
	 * @param aDocumentMetrics When set to true then unmarshaling
	 *        (XML) documents will preserve the preserve the root element
	 *        (envelope). As an (XML) document requires a root element, the root
	 *        element often is provided merely as of syntactic reasons and must
	 *        be omitted as of semantic reasons. Unmarshaling functionality
	 *        therefore by default skips the root elelemt, as this is considered
	 *        merely to serve as an envelope. This behavior can be overridden by
	 *        setting this property to true.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( Class aResourceClass, String aFilePath, DocumentMetrics aDocumentMetrics ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory( aDocumentMetrics ).toProperties( aResourceClass, aFilePath ) );
	}

	/**
	 * Loads the {@link PolyglotProperties} from the given file's path. A
	 * provided {@link ConfigLocator} describes the locations to additional
	 * crawl for the desired file. Finally (if nothing else succeeds) the
	 * properties are loaded by the provided class's class loader which takes
	 * care of loading the properties (in case the file path is a relative path,
	 * also the absolute path with a prefixed path delimiter "/" is probed).
	 *
	 * @param aResourceClass The class which's class loader is to take care of
	 *        loading the properties (from inside a JAR).
	 * @param aFilePath The file path of the class's resources from which to
	 *        load the properties.
	 * @param aConfigLocator The {@link ConfigLocator} describes the locations
	 *        to additional crawl for the desired file.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( Class aResourceClass, String aFilePath, ConfigLocator aConfigLocator ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory().toProperties( aResourceClass, aFilePath, aConfigLocator ) );
	}

	/**
	 * Loads the {@link PolyglotProperties} from the given file's path. A
	 * provided {@link ConfigLocator} describes the locations to additional
	 * crawl for the desired file. Finally (if nothing else succeeds) the
	 * properties are loaded by the provided class's class loader which takes
	 * care of loading the properties (in case the file path is a relative path,
	 * also the absolute path with a prefixed path delimiter "/" is probed).
	 *
	 * @param aResourceClass The class which's class loader is to take care of
	 *        loading the properties (from inside a JAR).
	 * @param aFilePath The file path of the class's resources from which to
	 *        load the properties.
	 * @param aConfigLocator The {@link ConfigLocator} describes the locations
	 *        to additional crawl for the desired file.
	 * @param aDocumentMetrics When set to true then unmarshaling
	 *        (XML) documents will preserve the preserve the root element
	 *        (envelope). As an (XML) document requires a root element, the root
	 *        element often is provided merely as of syntactic reasons and must
	 *        be omitted as of semantic reasons. Unmarshaling functionality
	 *        therefore by default skips the root elelemt, as this is considered
	 *        merely to serve as an envelope. This behavior can be overridden by
	 *        setting this property to true.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( Class aResourceClass, String aFilePath, ConfigLocator aConfigLocator, DocumentMetrics aDocumentMetrics ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory( aDocumentMetrics ).toProperties( aResourceClass, aFilePath, aConfigLocator ) );
	}

	/**
	 * Loads the {@link PolyglotProperties} from the given {@link File}.
	 *
	 * @param aFile The {@link File} from which to load the properties.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( File aFile ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory().toProperties( aFile ) );
	}

	/**
	 * Loads the {@link PolyglotProperties} from the given {@link File}.
	 *
	 * @param aFile The {@link File} from which to load the properties.
	 * @param aDocumentMetrics When set to true then unmarshaling
	 *        (XML) documents will preserve the preserve the root element
	 *        (envelope). As an (XML) document requires a root element, the root
	 *        element often is provided merely as of syntactic reasons and must
	 *        be omitted as of semantic reasons. Unmarshaling functionality
	 *        therefore by default skips the root elelemt, as this is considered
	 *        merely to serve as an envelope. This behavior can be overridden by
	 *        setting this property to true.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( File aFile, DocumentMetrics aDocumentMetrics ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory( aDocumentMetrics ).toProperties( aFile ) );
	}

	/**
	 * Loads or seeks the {@link PolyglotProperties} from the given
	 * {@link File}. A provided {@link ConfigLocator} describes the locations to
	 * additional crawl for the desired file.
	 * 
	 * @param aFile The {@link File} from which to load the properties.
	 * @param aConfigLocator The {@link ConfigLocator} describes the locations
	 *        to additional crawl for the desired file.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( File aFile, ConfigLocator aConfigLocator ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory().toProperties( aFile, aConfigLocator ) );
	}

	/**
	 * Loads or seeks the {@link PolyglotProperties} from the given
	 * {@link File}. A provided {@link ConfigLocator} describes the locations to
	 * additional crawl for the desired file.
	 *
	 * @param aFile The {@link File} from which to load the properties.
	 * @param aConfigLocator The {@link ConfigLocator} describes the locations
	 *        to additional crawl for the desired file.
	 * @param aDocumentMetrics When set to true then unmarshaling
	 *        (XML) documents will preserve the preserve the root element
	 *        (envelope). As an (XML) document requires a root element, the root
	 *        element often is provided merely as of syntactic reasons and must
	 *        be omitted as of semantic reasons. Unmarshaling functionality
	 *        therefore by default skips the root elelemt, as this is considered
	 *        merely to serve as an envelope. This behavior can be overridden by
	 *        setting this property to true.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( File aFile, ConfigLocator aConfigLocator, DocumentMetrics aDocumentMetrics ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory( aDocumentMetrics ).toProperties( aFile, aConfigLocator ) );
	}

	/**
	 * Reads the {@link PolyglotProperties} from the given {@link InputStream}.
	 *
	 * @param aInputStream The {@link InputStream} from which to read the
	 *        properties.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( InputStream aInputStream ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory().toProperties( aInputStream ) );
	}

	/**
	 * Reads the {@link PolyglotProperties} from the given {@link InputStream}.
	 *
	 * @param aInputStream The {@link InputStream} from which to read the
	 *        properties.
	 * @param aDocumentMetrics When set to true then unmarshaling
	 *        (XML) documents will preserve the preserve the root element
	 *        (envelope). As an (XML) document requires a root element, the root
	 *        element often is provided merely as of syntactic reasons and must
	 *        be omitted as of semantic reasons. Unmarshaling functionality
	 *        therefore by default skips the root elelemt, as this is considered
	 *        merely to serve as an envelope. This behavior can be overridden by
	 *        setting this property to true.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( InputStream aInputStream, DocumentMetrics aDocumentMetrics ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory( aDocumentMetrics ).toProperties( aInputStream ) );
	}

	/**
	 * Create a {@link PolyglotProperties} instance containing the elements of
	 * the provided {@link Map} instance using the default path delimiter "/"
	 * ({@link Delimiter#PATH}) for the path declarations.
	 *
	 * @param aMap the properties to be added.
	 */
	public PolyglotProperties( Map aMap ) {
		super( new PolyglotPropertiesFactory().toProperties( aMap ) );
	}

	/**
	 * Create a {@link PolyglotProperties} instance containing the elements of
	 * the provided {@link Map} instance using the default path delimiter "/"
	 * ({@link Delimiter#PATH}) for the path declarations.
	 *
	 * @param aMap the properties to be added.
	 * @param aDocumentMetrics When set to true then unmarshaling
	 *        (XML) documents will preserve the preserve the root element
	 *        (envelope). As an (XML) document requires a root element, the root
	 *        element often is provided merely as of syntactic reasons and must
	 *        be omitted as of semantic reasons. Unmarshaling functionality
	 *        therefore by default skips the root elelemt, as this is considered
	 *        merely to serve as an envelope. This behavior can be overridden by
	 *        setting this property to true.
	 */
	public PolyglotProperties( Map aMap, DocumentMetrics aDocumentMetrics ) {
		super( new PolyglotPropertiesFactory( aDocumentMetrics ).toProperties( aMap ) );
	}

	/**
	 * Create a {@link PolyglotProperties} instance containing the elements as
	 * of {@link MutablePathMap#insert(Object)} using the default path delimiter
	 * "/" ({@link Delimiter#PATH}) for the path declarations: "Inspects the
	 * given object and adds all elements found in the given object. Elements of
	 * type {@link Map}, {@link Collection} and arrays are identified and
	 * handled as of their type: The path for each value in a {@link Map} is
	 * appended with its according key. The path for each value in a
	 * {@link Collection} or array is appended with its according index of
	 * occurrence (in case of a {@link List} or an array, its actual index). In
	 * case of reflection, the path for each member is appended with its
	 * according mamber's name. All elements (e.g. the members and values) are
	 * inspected recursively which results in the according paths of the
	 * terminating values."
	 *
	 * @param aObj The object from which the elements are to be added.
	 */
	public PolyglotProperties( Object aObj ) {
		super( new PolyglotPropertiesFactory().toProperties( aObj ) );
	}

	/**
	 * Create a {@link PolyglotProperties} instance containing the elements as
	 * of {@link MutablePathMap#insert(Object)} using the default path delimiter
	 * "/" ({@link Delimiter#PATH}) for the path declarations: "Inspects the
	 * given object and adds all elements found in the given object. Elements of
	 * type {@link Map}, {@link Collection} and arrays are identified and
	 * handled as of their type: The path for each value in a {@link Map} is
	 * appended with its according key. The path for each value in a
	 * {@link Collection} or array is appended with its according index of
	 * occurrence (in case of a {@link List} or an array, its actual index). In
	 * case of reflection, the path for each member is appended with its
	 * according mamber's name. All elements (e.g. the members and values) are
	 * inspected recursively which results in the according paths of the
	 * terminating values."
	 *
	 * @param aObj The object from which the elements are to be added.
	 * @param aDocumentMetrics When set to true then unmarshaling
	 *        (XML) documents will preserve the preserve the root element
	 *        (envelope). As an (XML) document requires a root element, the root
	 *        element often is provided merely as of syntactic reasons and must
	 *        be omitted as of semantic reasons. Unmarshaling functionality
	 *        therefore by default skips the root elelemt, as this is considered
	 *        merely to serve as an envelope. This behavior can be overridden by
	 *        setting this property to true.
	 */
	public PolyglotProperties( Object aObj, DocumentMetrics aDocumentMetrics ) {
		super( new PolyglotPropertiesFactory( aDocumentMetrics ).toProperties( aObj ) );
	}

	/**
	 * Create a {@link PolyglotProperties} instance containing the elements of
	 * the provided {@link Properties} instance using the default path delimiter
	 * "/" ({@link Delimiter#PATH}) for the path declarations.
	 *
	 * @param aProperties the properties to be added.
	 */
	public PolyglotProperties( Properties aProperties ) {
		super( new PolyglotPropertiesFactory().toProperties( aProperties ) );
	}

	/**
	 * Create a {@link PolyglotProperties} instance containing the elements of
	 * the provided {@link Properties} instance using the default path delimiter
	 * "/" ({@link Delimiter#PATH}) for the path declarations.
	 *
	 * @param aProperties the properties to be added.
	 * @param aDocumentMetrics When set to true then unmarshaling
	 *        (XML) documents will preserve the preserve the root element
	 *        (envelope). As an (XML) document requires a root element, the root
	 *        element often is provided merely as of syntactic reasons and must
	 *        be omitted as of semantic reasons. Unmarshaling functionality
	 *        therefore by default skips the root elelemt, as this is considered
	 *        merely to serve as an envelope. This behavior can be overridden by
	 *        setting this property to true.
	 */
	public PolyglotProperties( Properties aProperties, DocumentMetrics aDocumentMetrics ) {
		super( new PolyglotPropertiesFactory( aDocumentMetrics ).toProperties( aProperties ) );
	}

	/**
	 * Create a {@link PolyglotProperties} instance containing the elements of
	 * the provided {@link PropertiesBuilder} instance using the default path
	 * delimiter "/" ({@link Delimiter#PATH}) for the path declarations.
	 *
	 * @param aPropertiesBuilder the properties to be added.
	 */
	public PolyglotProperties( PropertiesBuilder aPropertiesBuilder ) {
		super( new PolyglotPropertiesFactory().toProperties( aPropertiesBuilder ) );
	}

	/**
	 * Create a {@link PolyglotProperties} instance containing the elements of
	 * the provided {@link PropertiesBuilder} instance using the default path
	 * delimiter "/" ({@link Delimiter#PATH}) for the path declarations.
	 *
	 * @param aPropertiesBuilder the properties to be added.
	 * @param aDocumentMetrics When set to true then unmarshaling
	 *        (XML) documents will preserve the preserve the root element
	 *        (envelope). As an (XML) document requires a root element, the root
	 *        element often is provided merely as of syntactic reasons and must
	 *        be omitted as of semantic reasons. Unmarshaling functionality
	 *        therefore by default skips the root elelemt, as this is considered
	 *        merely to serve as an envelope. This behavior can be overridden by
	 *        setting this property to true.
	 */
	public PolyglotProperties( PropertiesBuilder aPropertiesBuilder, DocumentMetrics aDocumentMetrics ) {
		super( new PolyglotPropertiesFactory( aDocumentMetrics ).toProperties( aPropertiesBuilder ) );
	}

	/**
	 * Loads the {@link PolyglotProperties} from the given file's path.
	 *
	 * @param aFilePath The path to the file from which to load the properties.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( String aFilePath ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory().toProperties( aFilePath ) );
	}

	/**
	 * Loads the {@link PolyglotProperties} from the given file's path.
	 *
	 * @param aFilePath The path to the file from which to load the properties.
	 * @param aDocumentMetrics When set to true then unmarshaling
	 *        (XML) documents will preserve the preserve the root element
	 *        (envelope). As an (XML) document requires a root element, the root
	 *        element often is provided merely as of syntactic reasons and must
	 *        be omitted as of semantic reasons. Unmarshaling functionality
	 *        therefore by default skips the root elelemt, as this is considered
	 *        merely to serve as an envelope. This behavior can be overridden by
	 *        setting this property to true.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( String aFilePath, DocumentMetrics aDocumentMetrics ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory( aDocumentMetrics ).toProperties( aFilePath ) );
	}

	/**
	 * Loads the {@link PolyglotProperties} from the given file's path. A
	 * provided {@link ConfigLocator} describes the locations to additional
	 * crawl for the desired file.
	 * 
	 * @param aFilePath The path to the file from which to load the properties.
	 * @param aConfigLocator The {@link ConfigLocator} describes the locations
	 *        to additional crawl for the desired file.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( String aFilePath, ConfigLocator aConfigLocator ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory().toProperties( aFilePath, aConfigLocator ) );
	}

	/**
	 * Loads the {@link PolyglotProperties} from the given file's path. A
	 * provided {@link ConfigLocator} describes the locations to additional
	 * crawl for the desired file.
	 *
	 * @param aFilePath The path to the file from which to load the properties.
	 * @param aConfigLocator The {@link ConfigLocator} describes the locations
	 *        to additional crawl for the desired file.
	 * @param aDocumentMetrics When set to true then unmarshaling
	 *        (XML) documents will preserve the preserve the root element
	 *        (envelope). As an (XML) document requires a root element, the root
	 *        element often is provided merely as of syntactic reasons and must
	 *        be omitted as of semantic reasons. Unmarshaling functionality
	 *        therefore by default skips the root elelemt, as this is considered
	 *        merely to serve as an envelope. This behavior can be overridden by
	 *        setting this property to true.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( String aFilePath, ConfigLocator aConfigLocator, DocumentMetrics aDocumentMetrics ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory( aDocumentMetrics ).toProperties( aFilePath, aConfigLocator ) );
	}

	/**
	 * Loads the {@link PolyglotProperties} from the given {@link URL}.
	 *
	 * @param aUrl The {@link URL} from which to read the properties.
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( URL aUrl ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory().toProperties( aUrl ) );
	}

	/**
	 * Loads the {@link PolyglotProperties} from the given {@link URL}.
	 *
	 * @param aUrl The {@link URL} from which to read the properties.
	 * @param aDocumentMetrics Provides various metrics which may be tweaked
	 *        when marshaling or unmarshaling documents of various nations (such
	 *        as INI, XML, YAML, JSON, TOML, PROPERTIES, etc.).
	 * 
	 * @throws IOException thrown in case accessing or processing the properties
	 *         file failed.
	 * @throws ParseException Signals that an error has been reached
	 *         unexpectedly while parsing the data to be loaded.
	 */
	public PolyglotProperties( URL aUrl, DocumentMetrics aDocumentMetrics ) throws IOException, ParseException {
		super( new PolyglotPropertiesFactory( aDocumentMetrics ).toProperties( aUrl ) );
	}

	// /////////////////////////////////////////////////////////////////////////
	// INNER CLASSES:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * The {@link PolyglotPropertiesFactory} is a meta factory using a
	 * collection of {@link ResourcePropertiesFactory} instances to deliver
	 * {@link ResourceProperties} instances. In case a properties file for a
	 * filename was not found, then the according factories filename extension
	 * ({@link ResourcePropertiesFactory#getFilenameSuffixes()}) is append to
	 * the filename and probing is repeated. Any factory method such as
	 * {@link #toProperties(Map)}, {@link #toProperties(Object)},
	 * {@link #toProperties(Properties)} or
	 * {@link #toProperties(PropertiesBuilder)} will return
	 * {@link ResourceProperties} created by the first added
	 * {@link ResourcePropertiesFactory} instance.
	 */
	public static class PolyglotPropertiesFactory implements ResourcePropertiesFactory {

		// /////////////////////////////////////////////////////////////////////
		// VARIABLES:
		// /////////////////////////////////////////////////////////////////////

		private final List _factories = new ArrayList<>();

		// /////////////////////////////////////////////////////////////////////
		// CONSTRUCTORS:
		// /////////////////////////////////////////////////////////////////////

		/**
		 * Initializes the {@link PolyglotPropertiesBuilderFactory} with a
		 * predefined set of {@link ResourcePropertiesBuilderFactory} instances.
		 */
		public PolyglotPropertiesFactory() {
			this( DocumentNotation.DEFAULT );
		}

		/**
		 * Initializes the {@link PolyglotPropertiesFactory} with a predefined
		 * set of {@link ResourcePropertiesFactory} instances.
		 *
		 * @param aDocumentMetrics When set to true then
		 *        unmarshaling (XML) documents will preserve the preserve the
		 *        root element (envelope). As an (XML) document requires a root
		 *        element, the root element often is provided merely as of
		 *        syntactic reasons and must be omitted as of semantic reasons.
		 *        Unmarshaling functionality therefore by default skips the root
		 *        elelemt, as this is considered merely to serve as an envelope.
		 *        This behavior can be overridden by setting this property to
		 *        true.
		 */
		public PolyglotPropertiesFactory( DocumentMetrics aDocumentMetrics ) {
			_factories.add( new TomlPropertiesFactory( aDocumentMetrics ) );
			_factories.add( new YamlPropertiesFactory() );
			_factories.add( new XmlPropertiesFactory( aDocumentMetrics ) );
			_factories.add( new JsonPropertiesFactory() );
			_factories.add( new JavaPropertiesFactory( aDocumentMetrics ) ); // Last as them Java properties eat all notations 
		}

		/**
		 * Initializes the {@link PolyglotPropertiesFactory} with the given
		 * {@link ResourcePropertiesFactory} instances. Them factories will be
		 * queried in the order being provided.
		 * 
		 * @param aFactories The factories to be added.
		 */
		public PolyglotPropertiesFactory( ResourcePropertiesFactory... aFactories ) {
			for ( ResourcePropertiesFactory eFactory : aFactories ) {
				_factories.add( eFactory );
			}
		}

		// /////////////////////////////////////////////////////////////////////
		// METHODS:
		// /////////////////////////////////////////////////////////////////////

		/**
		 * Returns the filename extensions of the ResourcePropertiesFactory in
		 * the given order. {@inheritDoc}
		 */
		@Override
		public String[] getFilenameSuffixes() {
			final List theExtensions = new ArrayList<>();
			String[] eExtensions;
			for ( ResourcePropertiesFactory _factorie : _factories ) {
				eExtensions = _factorie.getFilenameSuffixes();
				if ( eExtensions != null ) {
					for ( String eExtension : eExtensions ) {
						theExtensions.add( eExtension );
					}
				}
			}
			return theExtensions.toArray( new String[theExtensions.size()] );
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public ResourceProperties toProperties( Class aResourceClass, String aFilePath, ConfigLocator aConfigLocator ) throws IOException, ParseException {
			Exception theCause = null;

			// Keep the precedence |-->
			try {
				return toPropertiesFromFilePath( aFilePath, aConfigLocator );
			}
			catch ( Exception e ) {
				if ( theCause == null ) {
					theCause = e;
				}
			}
			// Keep the precedence <--|

			for ( ResourcePropertiesFactory eFactory : _factories ) {
				try {
					return eFactory.toProperties( aResourceClass, aFilePath, aConfigLocator );
				}
				catch ( Exception e ) {
					if ( theCause == null ) {
						theCause = e;
					}
				}
			}
			// Test with the factorie's filename extension |-->
			String[] eExtensions;
			for ( ResourcePropertiesFactory eFactory : _factories ) {
				eExtensions = eFactory.getFilenameSuffixes();
				if ( eExtensions != null ) {
					for ( String eExtension : eExtensions ) {
						try {
							return eFactory.toProperties( aResourceClass, aFilePath + eExtension, aConfigLocator );
						}
						catch ( Exception e ) {
							if ( theCause == null ) {
								theCause = e;
							}
						}
					}
				}
			}
			// Test with the factorie's filename extension <--|
			if ( theCause instanceof IOException ) {
				throw (IOException) theCause;
			}
			if ( theCause instanceof ParseException ) {
				throw (ParseException) theCause;
			}
			throw new IOException( "No configuration file with name (scheme) <" + aFilePath + "> found, where a suffix (if applicable) might by one of the following: " + Arrays.toString( getFilenameSuffixes() ) );
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public ResourceProperties toProperties( File aFile, ConfigLocator aConfigLocator ) throws IOException, ParseException {
			Exception theCause = null;

			// Keep the precedence |-->
			try {
				return toPropertiesFromFilePath( aFile.getPath(), aConfigLocator );
			}
			catch ( Exception e ) {
				if ( theCause == null ) {
					theCause = e;
				}
			}
			// Keep the precedence <--|

			for ( ResourcePropertiesFactory eFactory : _factories ) {
				try {
					return eFactory.toProperties( aFile, aConfigLocator );
				}
				catch ( Exception e ) {
					if ( theCause == null ) {
						theCause = e;
					}
				}
			}
			// Test with the factorie's filename extension |-->
			File eFile;
			String[] eExtensions;
			for ( ResourcePropertiesFactory eFactory : _factories ) {
				eExtensions = eFactory.getFilenameSuffixes();
				if ( eExtensions != null ) {
					for ( String eExtension : eExtensions ) {
						eFile = new File( aFile.getAbsolutePath() + eExtension );
						try {
							return eFactory.toProperties( eFile, aConfigLocator );
						}
						catch ( Exception e ) {
							if ( theCause == null ) {
								theCause = e;
							}
						}
					}
				}
			}
			// Test with the factorie's filename extension <--|
			if ( theCause instanceof IOException ) {
				throw (IOException) theCause;
			}
			if ( theCause instanceof ParseException ) {
				throw (ParseException) theCause;
			}
			throw new IOException( "No configuration file with name (scheme) <" + aFile.toPath() + "> found, where a suffix (if applicable) might by one of the following: " + Arrays.toString( getFilenameSuffixes() ) );
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public ResourceProperties toProperties( InputStream aInputStream ) throws IOException, ParseException {
			Exception theCause = null;
			final byte[] theBytes = toByteArray( aInputStream );
			for ( ResourcePropertiesFactory eFactory : _factories ) {
				try {
					return eFactory.toProperties( new ByteArrayInputStream( theBytes ) );
				}
				catch ( Exception e ) {
					if ( theCause == null ) {
						theCause = e;
					}
				}
			}
			if ( theCause instanceof IOException ) {
				throw (IOException) theCause;
			}
			if ( theCause instanceof ParseException ) {
				throw (ParseException) theCause;
			}
			throw new IOException( "No configuration file for the given InputStream found!" );
		}

		/**
		 * Will return {@link ResourceProperties} created by the first added
		 * {@link ResourcePropertiesFactory} instance. {@inheritDoc}
		 */
		@Override
		public ResourceProperties toProperties( Map aProperties ) {
			return _factories.get( 0 ).toProperties( aProperties );
		}

		/**
		 * Will return {@link ResourceProperties} created by the first added
		 * {@link ResourcePropertiesFactory} instance. {@inheritDoc}
		 */
		@Override
		public ResourceProperties toProperties( Object aObj ) {
			return _factories.get( 0 ).toProperties( aObj );
		}

		/**
		 * Will return {@link ResourceProperties} created by the first added
		 * {@link ResourcePropertiesFactory} instance. {@inheritDoc}
		 */
		@Override
		public ResourceProperties toProperties( Properties aProperties ) {
			return _factories.get( 0 ).toProperties( aProperties );
		}

		/**
		 * Will return {@link ResourceProperties} created by the first added
		 * {@link ResourcePropertiesFactory} instance. {@inheritDoc}
		 */
		@Override
		public ResourceProperties toProperties( PropertiesBuilder aProperties ) {
			return _factories.get( 0 ).toProperties( aProperties );
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public ResourceProperties toProperties( String aFilePath, ConfigLocator aConfigLocator ) throws IOException, ParseException {
			return toProperties( null, aFilePath, aConfigLocator );
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public ResourceProperties toProperties( URL aUrl ) throws IOException, ParseException {
			Exception theCause = null;

			// Try file extension's factory first |-->
			try {
				final ResourcePropertiesFactory theFactory = fromFilenameExtension( aUrl.toExternalForm() );
				if ( theFactory != null ) {
					return theFactory.toProperties( aUrl );
				}
			}
			catch ( Exception e ) {
				if ( theCause == null ) {
					theCause = e;
				}
			}
			// Try file extension's factory first <--|

			for ( ResourcePropertiesFactory eFactory : _factories ) {
				try {
					return eFactory.toProperties( aUrl );
				}
				catch ( Exception e ) {
					if ( theCause == null ) {
						theCause = e;
					}
				}
			}
			// Test with the factorie's filename extension |-->
			URL eUrl;
			String[] eExtensions;
			for ( ResourcePropertiesFactory eFactory : _factories ) {
				eExtensions = eFactory.getFilenameSuffixes();
				if ( eExtensions != null ) {
					for ( String eExtension : eExtensions ) {
						eUrl = new URL( aUrl.toExternalForm() + eExtension );
						try {
							return eFactory.toProperties( eUrl );
						}
						catch ( Exception e ) {
							if ( theCause == null ) {
								theCause = e;
							}
						}
					}
				}
			}
			// Test with the factorie's filename extension <--|
			if ( theCause instanceof IOException ) {
				throw (IOException) theCause;
			}
			if ( theCause instanceof ParseException ) {
				throw (ParseException) theCause;
			}
			throw new IOException( "No configuration file with (base) URL <" + aUrl.toExternalForm() + "> found!" );
		}

		// /////////////////////////////////////////////////////////////////////
		// HELPER:
		// /////////////////////////////////////////////////////////////////////

		private ResourcePropertiesFactory fromFilenameExtension( String aFilePath ) {
			String[] eExtensions;
			for ( ResourcePropertiesFactory eFactory : _factories ) {
				eExtensions = eFactory.getFilenameSuffixes();
				if ( eExtensions != null ) {
					for ( String eExtension : eExtensions ) {
						if ( aFilePath.endsWith( eExtension ) ) {
							return eFactory;
						}
					}
				}
			}
			return null;
		}

		private ResourceProperties toPropertiesFromFilePath( String aFilePath, ConfigLocator aConfigLocator ) throws IOException, ParseException {
			Exception theCause = null;
			final File theFile = new File( aFilePath );
			File eFile;
			if ( theFile.exists() ) {
				try {
					final ResourcePropertiesFactory theFactory = fromFilenameExtension( theFile.getAbsolutePath() );
					if ( theFactory != null ) {
						return theFactory.toProperties( theFile, ConfigLocator.ABSOLUTE );
					}
				}
				catch ( IOException | ParseException e ) {
					if ( theCause == null ) {
						theCause = e;
					}
				}
			}
			else {
				for ( ResourcePropertiesFactory eFactory : _factories ) {
					for ( String eExtension : eFactory.getFilenameSuffixes() ) {
						eFile = new File( aFilePath + eExtension );
						if ( eFile.exists() ) {
							try {
								return eFactory.toProperties( eFile, ConfigLocator.ABSOLUTE );
							}
							catch ( IOException | ParseException e ) {
								if ( theCause == null ) {
									theCause = e;
								}
							}
						}
					}
				}
				if ( aConfigLocator != null && aConfigLocator != ConfigLocator.ABSOLUTE ) {
					for ( File eDir : aConfigLocator.getFolders() ) {
						for ( ResourcePropertiesFactory eFactory : _factories ) {
							for ( String eExtension : eFactory.getFilenameSuffixes() ) {
								eFile = new File( eDir, aFilePath + eExtension );
								if ( eFile.exists() ) {
									try {
										return eFactory.toProperties( eFile, ConfigLocator.ABSOLUTE );
									}
									catch ( IOException | ParseException e ) {
										if ( theCause == null ) {
											theCause = e;
										}
									}
								}
							}
						}
					}
				}
			}
			if ( theCause instanceof IOException ) {
				throw (IOException) theCause;
			}
			if ( theCause instanceof ParseException ) {
				throw (ParseException) theCause;
			}
			throw new IOException( "No configuration file with name (scheme) <" + aFilePath + "> found, where the suffix may by one of the following: " + Arrays.toString( getFilenameSuffixes() ) );
		}

		// /////////////////////////////////////////////////////////////////////
		// HOOKS:
		// /////////////////////////////////////////////////////////////////////

		/**
		 * Converts a byte array from an {@link InputStream}.
		 * 
		 * @param aInputStream The {@link InputStream} to be converted.
		 * 
		 * @return The byte array from the {@link InputStream}.
		 * 
		 * @throws IOException Thrown in case there were problems reading the
		 *         {@link InputStream}.
		 */
		public static byte[] toByteArray( InputStream aInputStream ) throws IOException {
			final ByteArrayOutputStream theBuffer = new ByteArrayOutputStream();
			int eRead;
			final byte[] theData = new byte[16384];
			while ( ( eRead = aInputStream.read( theData, 0, theData.length ) ) != -1 ) {
				theBuffer.write( theData, 0, eRead );
			}
			theBuffer.flush();
			return theBuffer.toByteArray();
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy