ConfigTools.java

package eu.javaexperience.config;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;
import java.util.Map.Entry;

import eu.javaexperience.Global;
import eu.javaexperience.arrays.ArrayTools;
import eu.javaexperience.collection.enumerations.EnumTools;
import eu.javaexperience.environment.Mode;
import eu.javaexperience.io.file.FileTools;
import eu.javaexperience.log.JavaExperienceLoggingFacility;
import eu.javaexperience.log.LogLevel;
import eu.javaexperience.log.Loggable;
import eu.javaexperience.log.Logger;
import eu.javaexperience.log.LoggingTools;
import eu.javaexperience.parse.ParsePrimitive;
import eu.javaexperience.reflect.CastTo;

public class ConfigTools
{
	public static final Logger LOG = JavaExperienceLoggingFacility.getLogger(new Loggable("ConfigTools"));
	
	/**
	 * tries to load the first one found config file from the given list.
	 * If file found it loads everything into the {@link Global} facility,
	 * all options now available there.
	 * 
	 *  Otherwise false returned.
	 *  
	 *  Note: this method logs which file loaded (on {@link LogLevel#INFO})
	 *  with it's canonical path. On {@link LogLevel#DEBUG} it's log every
	 *  attempts (file not exists or Exception raised).
	 *  
	 *  It uses the {@link JavaExperienceLoggingFacility#DEFAULT_LOGGER} for
	 *  logging because usually there's no another logging module loaded at this
	 *  stage of a program. (eg. Loglevels specified in config, used with
	 *  {@link JavaExperienceLoggingFacility#setFutureModuleDefaultLoglevel(String, eu.javaexperience.log.LoggingDetailLevel)})
	 *  
	 *  And there's an automatic function for this:
	 *  {@link JavaExperienceLoggingFacility#loadDefaultFacilityLogLevelsFromPropertyWithPrefix(Properties, String)}
	 *  whith {@link Global#getAllPropertySet()}
	 *  
	 * */
	
	public static Properties loadFirstConfig(String... files)
	{
		if(null == files || 0 == files.length)
		{
			LoggingTools.tryLogSimple
			(
				LOG,
				LogLevel.DEBUG,
				"No config files given, spoofing default files for config loader."
			);
				
			return loadFirstConfig
			(
				new String[]
				{
					"settings.properties",
					"setting.properties",
					"config.properties",
					
					"srv/settings.properties",
					"srv/setting.properties",
					"srv/config.properties",
					
					"bin/settings.properties",
					"bin/setting.properties",
					"bin/config.properties",
				}
			);
		}
		else
		{
			if(LOG.mayLog(LogLevel.DEBUG))
			{
				try
				{
					LoggingTools.tryLogFormat
					(
						LOG,
						LogLevel.DEBUG,
						"Config load stage, current working directory is %s",
						new File(".").getCanonicalFile()
					);
				}
				catch(Exception e)
				{
					LoggingTools.tryLogSimple
					(
						LOG,
						LogLevel.DEBUG,
						"Unable to log Current Working Directory",
						e
					);
				}
			}
			
			for(String file:files)
			{
				File f = new File(file);
				if(!f.exists())
				{
					LoggingTools.tryLogFormat
					(
						LOG,
						LogLevel.DEBUG,
						"Result of config file loading `%s`: file doesn't exists (canonical file is: %s)",
						file,
						FileTools.tryGetCanonicalFile(f)
					);
					
					continue;
				}
				
				try(InputStream is = new FileInputStream(f))
				{
					Properties p = new Properties();
					p.load(new BufferedReader(new InputStreamReader(is)));
					
					LoggingTools.tryLogFormat
					(
						LOG,
						LogLevel.INFO,
						"Config file `%s` successfully loaded  (canonical file is: %s).",
						file,
						FileTools.tryGetCanonicalFile(f)
					);
					
					return p;
				}
				catch(Exception e)
				{
					LoggingTools.tryLogFormat
					(
						LOG,
						LogLevel.DEBUG,
						"Loading config file `%s` (canonical file is: %s) refused by Exception: ",
						file,
						FileTools.tryGetCanonicalFile(f)
					);
					continue;
				}
			}
		}
		
		LoggingTools.tryLogSimple
		(
			LOG,
			LogLevel.DEBUG,
			"No config files are loaded in this stage."
		);
		return null;
	}
	
	public static boolean loadConfig(String... files)
	{
		Properties p = loadFirstConfig(files);
		if(null != p)
		{
			for(Entry<Object, Object> kv:p.entrySet())
			{
				Global.putProperty(kv.getKey(), kv.getValue());
			}
			return true;
		}
		
		return false;
	}
	
	protected static void notNullOrThrow(Object subject, String key)
	{
		if(null == subject)
		{
			throw new RuntimeException("No configuration value specified under key: "+key);
		}
	}
	
	public static <E extends Enum<E>> E getGlobalValuableEnum(Class<? extends E> cls, String key)
	{
		Object prop = Global.getProperty(key);
		notNullOrThrow(prop, key);
		E ret = EnumTools.recogniseSymbol(cls, prop);
		if(null == ret)
		{
			throw new RuntimeException("Can't recognise enum: `"+prop+"` under key: `"+key+"`. Possible values: "+ArrayTools.toString(cls.getEnumConstants()));
		}
		return ret;
	}

	public static String getGlobalValuableString(String key)
	{
		Object prop = Global.getProperty(key);
		notNullOrThrow(prop, key);
		return String.valueOf(prop.toString());
	}


	public static int getGlobalValuableInt(String key)
	{
		Object prop = Global.getProperty(key);
		notNullOrThrow(prop, key);
		Integer ret = ParsePrimitive.tryParseInt(prop.toString());
		if(null == ret)
		{
			throw new RuntimeException("Can't recognise integer number: `"+prop+"` under key: `"+key+"`.");
		}
		return ret;
	}
	
	public static double getGlobalValuableDouble(String key)
	{
		Object prop = Global.getProperty(key);
		notNullOrThrow(prop, key);
		Double ret = ParsePrimitive.tryParseDouble(prop.toString());
		if(null == ret)
		{
			throw new RuntimeException("Can't recognise double float number: `"+prop+"` under key: `"+key+"`.");
		}
		return ret;
	}


	public static boolean getGlobalValuableBoolean(String key)
	{
		Object prop = Global.getProperty(key);
		notNullOrThrow(prop, key);
		Boolean ret = (Boolean) CastTo.Boolean.cast(prop);
		if(null == ret)
		{
			throw new RuntimeException("Can't recognise boolean: `"+prop+"` under key: `"+key+"`.");
		}
		return ret;
	}
}