org.yarnandtail.andhow.AndHow Maven / Gradle / Ivy
package org.yarnandtail.andhow;
import java.lang.reflect.Field;
import java.util.*;
import org.yarnandtail.andhow.api.*;
import org.yarnandtail.andhow.internal.AndHowCore;
import org.yarnandtail.andhow.util.AndHowUtil;
/**
* Central AndHow singleton class.
*
* This class is not directly constructed. The primary way to configure an
* instance is indirectly by creating a subclass of org.yarnandtail.andhow.AndHowInit.
* At startup, AndHow discovers your AndHowInit implementation and uses it to configure
* a single instance.
*
* For cases where the application needs to accept command line arguments or
* augment the configuration with fixed values, the configuration can have those
* parameters added like this:
* {@code
* AndHow.findConfig().setCmdLineArgs(myCmdLineArgs).build();
* }
* findConfig()
finds the AndHowConfiguration
that
* would be used if AndHow.instance()
was called. build()
* then causes the AndHow instance to be built with that modified configuration.
* The code above (or any method of AndHow initiation) can only be executed once
* during the life of the application.
*
* @author eeverman
*/
public class AndHow implements StaticPropertyConfiguration, ValidatedValues {
//
//A few app-wide constants
public static final String ANDHOW_INLINE_NAME = "AndHow";
public static final String ANDHOW_NAME = "AndHow!";
public static final String ANDHOW_URL = "https://github.com/eeverman/andhow";
public static final String ANDHOW_TAG_LINE = "strong.simple.valid.AppConfiguration";
private static volatile AndHow singleInstance;
private static final Object LOCK = new Object();
private volatile AndHowCore core;
private AndHow(AndHowConfiguration config) throws AppFatalException {
synchronized (LOCK) {
core = new AndHowCore(
config.getNamingStrategy(),
config.buildLoaders(),
config.getRegisteredGroups());
}
}
/**
* Finds and creates a new instance of the AndHowConfiguration
* that would be used if AndHow.instance()
was called.
*
* Note: If AndHow.instance()
is later called, a new
* AndHowConfiguration
will be returned, not this same instance.
*
* This method provides a way to add command line parameters or fixed values
* to the existing configuration and then immediately initiate AndHow
.
*
* Example usage:
*
{@code
* AndHow.findConfig().setCmdLineArgs(myCmdLineArgs).build();
* }
* The call to build()
at the end of that command string is
* just a convenience method to call AndHow.instance(thisConfiguration);
*
* @return
*/
public static AndHowConfiguration extends AndHowConfiguration> findConfig() {
return AndHowUtil.findConfiguration(StdConfig.instance());
}
/**
* Returns the current AndHow instance. If there is no instance, one is created
* using auto-discovered configuration.
*
* @return
* @throws AppFatalException
*/
public static AndHow instance() throws AppFatalException {
if (singleInstance != null && singleInstance.core != null) {
return singleInstance;
} else {
synchronized (LOCK) {
if (singleInstance == null || singleInstance.core == null) {
return instance(AndHowUtil.findConfiguration(StdConfig.instance()));
} else {
return singleInstance;
}
}
}
}
/**
* Builds a new AndHow instance using the specified configuration ONLY IF
* there is no existing AndHow instance.
*
* A fatal RuntimeException will be thrown if there is an existing AndHow
* instance. This method does not normally need to be used to initiate AndHow.
* See the AndHow class docs for typical configuration examples.
*
* @param config
* @return
* @throws AppFatalException
*/
public static AndHow instance(AndHowConfiguration config) throws AppFatalException {
synchronized (LOCK) {
if (singleInstance != null && singleInstance.core != null) {
throw new AppFatalException("Cannot request construction of new "
+ "AndHow instance when there is an existing instance.");
} else {
if (singleInstance == null) {
singleInstance = new AndHow(config);
} else if (singleInstance.core == null) {
/* This is a concession for testing. During testing the
core is deleted to force AndHow to reload. Its really an
invalid state (instance and core should be null/non-null
together, but its handled here to simplify testing. */
try {
AndHowCore newCore = new AndHowCore(
config.getNamingStrategy(),
config.buildLoaders(),
config.getRegisteredGroups());
Field coreField = AndHow.class.getDeclaredField("core");
coreField.setAccessible(true);
coreField.set(singleInstance, newCore);
} catch (Exception ex) {
if (ex instanceof AppFatalException) {
throw (AppFatalException) ex;
} else {
throwFatal("", ex);
}
}
}
return singleInstance;
}
} //end sync
}
/**
* Determine if AndHow is initialized or not w/out forcing AndHow to load.
*
* @return
*/
public static boolean isInitialize() {
return singleInstance != null && singleInstance.core != null;
}
//
//PropertyValues Interface
@Override
public boolean isExplicitlySet(Property> prop) {
return core.isExplicitlySet(prop);
}
@Override
public T getExplicitValue(Property prop) {
return core.getExplicitValue(prop);
}
@Override
public T getValue(Property prop) {
return core.getValue(prop);
}
//
//StaticPropertyConfiguration Interface
@Override
public List getAliases(Property> property) {
return core.getAliases(property);
}
@Override
public String getCanonicalName(Property> prop) {
return core.getCanonicalName(prop);
}
@Override
public GroupProxy getGroupForProperty(Property> prop) {
return core.getGroupForProperty(prop);
}
@Override
public NamingStrategy getNamingStrategy() {
return core.getNamingStrategy();
}
/**
* Builds and throws an AppFatalException. The stack trace is edited to
* remove 2 method calls, which should put the stacktrace at the user code
* of the build.
*
* @param message
*/
private static void throwFatal(String message, Throwable throwable) {
if (throwable instanceof AppFatalException) {
throw (AppFatalException) throwable;
} else {
AppFatalException afe = new AppFatalException(message, throwable);
StackTraceElement[] stes = afe.getStackTrace();
stes = Arrays.copyOfRange(stes, 2, stes.length);
afe.setStackTrace(stes);
throw afe;
}
}
}