org.anc.constants.Constants Maven / Gradle / Ivy
Show all versions of common Show documentation
/*-
* Copyright 2011 The American National Corpus
*
* 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.
*
*/
package org.anc.constants;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* The base class used for declaring project wide constant values.
*
* Each sub-class defines {@code public final} fields that will be
* initialized with values from a properties file. The default values to use
* if the properties file does not exist are specified with @Default annotations on each
* field.
*
* package org.anc.example;
* public class MyConstants extends Constants
* {
* @Default("Hello world.")
* public final String HELLO_WORLD = null;
* @Default("8")
* public final Integer NTHREADS = null;
*
* public MyConstants()
* {
* super.init();
* }
* }
*
* Each public field in the subclass must be initialized to null and the subclass
* constructor(s) must call {@code super.init()}. The Constants subclass can contain
* fields of type String, Boolean, Integer, Float, and Double. However, the value specified
* by the @Default annotation is always a String.
*
*
An instance of the subclass can then be created (usually as a static final
* field of the application object) to access the defined constants.
*
* public class Application
* {
* public static final MyConstants CONST = new MyConstants();
*
* public void run()
* {
* System.out.println(CONST.HELLO_WORLD);
* }
* }
*
*
* The {@code Constants} class will look for the properties file {@code conf/machineName/class.name.properties}
* where:
*
* - machineName is the value of the environment variable HOSTNAME (Unix based OSes) or COMPUTERNAME (Windows)
* - class.name is the fully qualified Java class name.
*
* The {@code Constants} class will first look for the properties file on the
* file system and then on the class path. If a properties file can not be found
* the fields will be initialized with the values from the @Default annotations.
*
* A sub-class can also specify the properties file to use with the {@code init(String)}
* method. The String parameter passed to the {@code init} method should be the name of a
* Java system property or an OS environmental variable. The {@code Constants} class
* will first try {@code System.getProperty} and then {@code System.getenv} to obtain
* a file name. If neither property has been set the above method is used to locate
* the properties file. For example,
*
* // In MyConstants.java
* package org.anc.example;
* public class MyConstants extends Constants
* {
* @Default("Hello world")
* public final String HELLO_WORLD = null;
*
* public MyConstants()
* {
* super.init("org.anc.hello");
* }
*
* public static void main(String[] args)
* {
* MyConstants constants = new MyConstants();
* System.out.println(constants.HELLO_WORLD);
* }
* }
*
* # In /home/anc/hello.properties
* HELLO_WORLD=Bonjour le monde.
*
* # From the command line:
* > java -cp MyConstants.jar -Dorg.anc.hello=/home/anc/hello.properties org.anc.example.MyConstants
* > Bonjour le monde
*
* A properties file containing the default values can be generated by
* creating an instance of the class and calling the {@code save()} method. This is
* convenient when you want to create properties files for use with other machines.
*
* public static void main(String[] args)
* {
* MyConstants constants = new MyConstants();
* constants.save();
* }
*
*
*
* @author Keith Suderman
*
*/
public abstract class Constants
{
private static final long serialVersionUID = 1L;
/**
* Symbol table used to resolve variable names during initialization.
* After initialization is complete this should be set back to null to
* release the memory.
*/
private Map variables = null;
/**
* Annotation used to provide a default value for constants.
*
* @author Keith Suderman
*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Default
{
String value();
}
public void save() throws IOException
{
String name = getName();
File file = new File(name);
File parent = file.getParentFile();
if (!parent.exists())
{
if (!parent.mkdirs())
{
throw new FileNotFoundException("Unable to create " + parent.getPath());
}
}
save(file);
}
public void save(String path) throws IOException
{
save(new File(path));
}
public void save(File file) throws IOException
{
Properties props = new Properties();
Class extends Constants> subclass = this.getClass();
for (Field field : subclass.getDeclaredFields())
{
String name = field.getName();
if (isPublicFinalString(field) ||
isPublicFinalInteger(field) ||
isPublicFinalFloat(field) ||
isPublicFinalDouble(field) ||
isPublicFinalBoolean(field))
{
try
{
props.put(name, field.get(this).toString());
}
catch (Exception e)
{
throw new IOException("Unable to save field : " + name, e);
}
}
}
OutputStream os = new FileOutputStream(file);
try
{
System.out.println("Wrote " + file.getPath());
props.store(os, "Constants.");
}
finally
{
os.close();
}
}
protected Properties getProperties(String propName) throws FileNotFoundException, IOException
{
Properties props = new Properties();
String propValue = null;
if (propName != null)
{
propValue = System.getProperty(propName);
if (propValue != null)
{
props.load(new FileReader(propValue));
return props;
}
propValue = System.getenv(propName);
if (propValue != null)
{
props.load(new FileReader(propValue));
return props;
}
}
propValue = getName();
InputStream in = null;
// First try to find the properties file on the file system.
File propFile = new File(propValue);
if (propFile.exists())
{
in = new FileInputStream(propFile);
}
// Then try the class path.
if (in == null)
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null)
{
loader = Constants.class.getClassLoader();
}
in = loader.getResourceAsStream(propValue);
if (in == null)
{
//throw new FileNotFoundException("Properties " + propValue + " not found.");
return new Properties();
}
}
// 'in' can not be null or an exception would have been thrown above.
props.load(in);
return props;
}
protected String getName()
{
Class extends Constants> subclass = this.getClass();
String name = null;
if (name == null)
{
name = System.getenv("COMPUTERNAME");
}
if (name == null)
{
name = System.getenv("HOSTNAME");
}
if (name == null)
{
name = System.getProperty("user.name");
}
if (name == null)
{
name = "constants";
}
return "conf/" + name.toLowerCase() + "/" + subclass.getName() + ".properties";
}
protected void init()
{
init(null);
}
protected void init(String propertyName)
{
variables = new HashMap();
Properties props;
try
{
props = getProperties(propertyName);
}
catch (Exception e)
{
System.err.println("Unable to load properties from " + propertyName);
e.printStackTrace();
props = new Properties();
}
Class extends Constants> subclass = this.getClass();
Field[] fields = subclass.getDeclaredFields();
for (Field field : fields)
{
String value = getInitValue(props, field);
if (value == null)
{
continue;
}
if (isPublicFinalString(field))
{
set(field, value);
}
else if (isPublicFinalInteger(field))
{
set(field, new Integer(value));
}
else if (isPublicFinalFloat(field))
{
set(field, new Float(value));
}
else if (isPublicFinalDouble(field))
{
set(field, new Double(value));
}
else if (isPublicFinalBoolean(field))
{
set(field, new Boolean(value));
}
}
variables = null;
}
private String getInitValue(Properties props, Field field)
{
String sValue = props.getProperty(field.getName());
if (sValue == null)
{
Default defaultValue = field.getAnnotation(Default.class);
if (defaultValue == null)
{
// This is definitely a programming error.
//throw new RuntimeException("Missing @Default annotation on "
// + field.getName());
// NO, it may not be a programming error. Groovy adds fields to
// classes which will not contain Default annotations.
return null;
}
sValue = defaultValue.value();
}
return replaceVariables(sValue);
}
private String replaceVariables(String input)
{
int index = input.indexOf('$');
while (index >= 0)
{
int end = input.indexOf('/', index);
if (end < index)
{
end = input.length();
}
String key = input.substring(index + 1, end);
String value = variables.get(key);
if (value != null)
{
String prefix = input.substring(0, index);
input = prefix + value + input.substring(end);
index = input.indexOf('$', prefix.length());
}
else
{
index = input.indexOf('$', end);
}
}
return input;
}
private void set(Field field, Object value)
{
try
{
field.setAccessible(true);
field.set(this, value);
if (value instanceof String)
{
variables.put(field.getName(), value.toString());
}
}
catch (IllegalArgumentException e)
{
e.printStackTrace();
}
catch (IllegalAccessException e)
{
e.printStackTrace();
}
}
protected static boolean isPublicFinalString(Field field)
{
return isType(String.class, field);
}
protected static boolean isPublicFinalInteger(Field field)
{
return isType(Integer.class, field);
}
protected static boolean isPublicFinalDouble(Field field)
{
return isType(Double.class, field);
}
protected static boolean isPublicFinalFloat(Field field)
{
return isType(Float.class, field);
}
protected static boolean isPublicFinalBoolean(Field field)
{
return isType(Boolean.class, field);
}
private static boolean isType(Class> theClass, Field field)
{
int flags = field.getModifiers();
return field.getType().equals(theClass) && Modifier.isPublic(flags)
&& Modifier.isFinal(flags) && !Modifier.isStatic(flags);
}
}