Right, so today am gonna teach you a nicer way to handle options/config/whatever you wanna call it. This requires that you have/know how to make:
- A module system
- Java Reflection
- Brains
- Basic Java knowledge
This method of managing options is annotation-based because I love annotations.
So first we need the option annotation. It looks like this:
Code:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@retention( RetentionPolicy.RUNTIME )
@target( ElementType.FIELD )
public @interface Option {
String name();
EnumOptionType type() default EnumOptionType.INT;
}
The Retention annotation tells the compiler to keep this annotation at runtime. The Target annotation tells the compiler that this annotation can only be placed on Fields.
The name() method is what the option will be called when you save the config file. The type method will tell what kind of option it is; this defaults to Integer.
Of course, this requires the EnumOptionType class, so here that is:
Code:
public enum EnumOptionType {
INT, FLOAT, DOUBLE, STRING, BOOLEAN;
}
Not much to say about this one.
Now we just need to be able to register classes that have options, and get the relevant data, and we'll be good. The class for that looks like this:
Code:
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
/**
* Scrapes/cleans options
*/
public class OptionsScraper {
private static OptionsScraper instance;
private final LinkedHashMap< Tuple< Object, String >, List< Field >> options;
private OptionsScraper() {
options = new LinkedHashMap<>();
}
public void registerOption( final Object opiner, final String name ) {
final List< Field > options = new ArrayList<>();
for( final Field f : opiner.getClass().getDeclaredFields() ) {
f.setAccessible( true );
if( f.isAnnotationPresent( Option.class ) ) {
options.add( f );
}
f.setAccessible( false );
}
if( options.size() == 0 ) {
return;
}
this.options.put( new Tuple< Object, String >( opiner, name ), options );
}
public List< String > scrapeOptions() {
final List< String > scrapes = new ArrayList<>();
for( final Entry< Tuple< Object, String >, List< Field >> en : options.entrySet() ) {
String opt = en.getKey().getKey();
for( final Field e : en.getValue() ) {
e.setAccessible( true );
final Option o = e.getAnnotation( Option.class );
opt += " " + o.name() + " " + o.type().name();
String val = "";
try {
val = e.get( en.getKey().getEntry() ).toString();
} catch( IllegalArgumentException | IllegalAccessException e1 ) {
e1.printStackTrace();
return null;
}
opt += " " + val;
e.setAccessible( false );
scrapes.add( opt );
}
}
return scrapes;
}
public static OptionsScraper getInstance() {
if( instance == null ) {
instance = new OptionsScraper();
}
return instance;
}
}
And the relevant Tuple class:
Code:
/**
* Stores 2 things, like a Tuple in Python.
* <p>
* And for what I use this for, it'd be a waste to make some kind of Map.
*
* @Author godshawk
*
* @param <K>
* @param <V>
*/
public final class Tuple< K, V > {
private K thing1;
private V thing2;
public Tuple( final K thing1, final V thing2 ) {
this.thing1 = thing1;
this.thing2 = thing2;
}
public K getEntry() {
return thing1;
}
public V getKey() {
return thing2;
}
public void setEntry( final K k ) {
this.thing1 = k;
}
public void setKey( final V v ) {
this.thing2 = v;
}
}
How it works:
- The options manager thingie has a HashMap that stores a Tuple and a List of Fields. The Tuple, in turn, stores a String for the name of the class that stores the options, and an instance of said class.
- When you register a new option class, it creates a new List of Fields. After this, it iterates through the list of fields declared in the class of the Object passed in
- For each field, it checks if it has the Option annotation
- If it does, it's added to the list.
- Once it's finished iterating over fields, it stores the information in the HashMap.
- When it goes to save the options, it iterates over the HashMap
- For each entry, it tries to get the value of the Field and the name from the Option annotation. If this succeeds, it adds the resulting String to a List of Strings it created at the start of the method.
- Once this is complete, it returns the List of Strings.
And you would use it like this:
Code:
public class ModuleTracers extends Module {
// Irrelevant stuff was here
@Option( name = "tracerWidth", type = EnumOptionType.FLOAT )
private float tracerWidth = 1.0F;
// Irrelevant stuff was here
}
And register it like this:
Code:
OptionsScraper.getInstance().registerOption(module, "MyModule");