Java 枚举中的可配置值

Java 枚举中的可配置值,java,configuration,enums,extend,Java,Configuration,Enums,Extend,我经常在代码中使用这种设计来维护可配置的值。考虑这个代码: public enum Options { REGEX_STRING("Some Regex"), REGEX_PATTERN(Pattern.compile(REGEX_STRING.getString()), false), THREAD_COUNT(2), OPTIONS_PATH("options.config", false), DEBUG(true), ALWAYS_SAVE

我经常在代码中使用这种设计来维护可配置的值。考虑这个代码:

public enum Options {

    REGEX_STRING("Some Regex"),
    REGEX_PATTERN(Pattern.compile(REGEX_STRING.getString()), false),
    THREAD_COUNT(2),
    OPTIONS_PATH("options.config", false),
    DEBUG(true),
    ALWAYS_SAVE_OPTIONS(true),
    THREAD_WAIT_MILLIS(1000);

    Object value;
    boolean saveValue = true;

    private Options(Object value) {
        this.value = value;
    }

    private Options(Object value, boolean saveValue) {
        this.value = value;
        this.saveValue = saveValue;
    }

    public void setValue(Object value) {
        this.value = value;
    }

    public Object getValue() {
        return value;
    }

    public String getString() {
        return value.toString();
    }

    public boolean getBoolean() {
        Boolean booleanValue = (value instanceof Boolean) ? (Boolean) value : null;
        if (value == null) {
            try {
                booleanValue = Boolean.valueOf(value.toString());
            }
            catch (Throwable t) {
            }
        }

        // We want a NullPointerException here
        return booleanValue.booleanValue();
    }

    public int getInteger() {
        Integer integerValue = (value instanceof Number) ? ((Number) value).intValue() : null;
        if (integerValue == null) {
            try {
                integerValue = Integer.valueOf(value.toString());
            }
            catch (Throwable t) {
            }
        }
        return integerValue.intValue();
    }

    public float getFloat() {
        Float floatValue = (value instanceof Number) ? ((Number) value).floatValue() : null;
        if (floatValue == null) {
            try {
                floatValue = Float.valueOf(value.toString());
            }
            catch (Throwable t) {
            }
        }
        return floatValue.floatValue();
    }

    public static void saveToFile(String path) throws IOException {
        FileWriter fw = new FileWriter(path);
        Properties properties = new Properties();
        for (Options option : Options.values()) {
            if (option.saveValue) {
                properties.setProperty(option.name(), option.getString());
            }
        }
        if (DEBUG.getBoolean()) {
            properties.list(System.out);
        }
        properties.store(fw, null);
    }

    public static void loadFromFile(String path) throws IOException {
        FileReader fr = new FileReader(path);
        Properties properties = new Properties();
        properties.load(fr);

        if (DEBUG.getBoolean()) {
            properties.list(System.out);
        }
        Object value = null;
        for (Options option : Options.values()) {
            if (option.saveValue) {
                Class<?> clazz = option.value.getClass();
                try {
                    if (String.class.equals(clazz)) {
                        value = properties.getProperty(option.name());
                    }
                    else {
                        value = clazz.getConstructor(String.class).newInstance(properties.getProperty(option.name()));
                    }
                }
                catch (NoSuchMethodException ex) {
                    Debug.log(ex);
                }
                catch (InstantiationException ex) {
                    Debug.log(ex);
                }
                catch (IllegalAccessException ex) {
                    Debug.log(ex);
                }
                catch (IllegalArgumentException ex) {
                    Debug.log(ex);
                }
                catch (InvocationTargetException ex) {
                    Debug.log(ex);
                }

                if (value != null) {
                    option.setValue(value);
                }
            }
        }
    }
}
公共枚举选项{
正则表达式字符串(“某些正则表达式”),
REGEX_模式(PATTERN.compile(REGEX_STRING.getString()),false),
螺纹计数(2),
选项路径(“OPTIONS.config”,false),
调试(真),
始终保存选项(真),
线程_WAIT _MILLIS(1000);
目标价值;
布尔值=true;
私有选项(对象值){
这个值=值;
}
私有选项(对象值、布尔值){
这个值=值;
this.saveValue=saveValue;
}
公共无效设置值(对象值){
这个值=值;
}
公共对象getValue(){
返回值;
}
公共字符串getString(){
返回值.toString();
}
公共布尔getBoolean(){
布尔布尔值=(布尔值实例)?(布尔值)值:null;
如果(值==null){
试一试{
booleanValue=Boolean.valueOf(value.toString());
}
捕获(可丢弃的t){
}
}
//我们需要一个NullPointerException
返回booleanValue.booleanValue();
}
public int getInteger(){
Integer integerValue=(数字的值实例)?((数字)值).intValue():null;
if(integerValue==null){
试一试{
integerValue=Integer.valueOf(value.toString());
}
捕获(可丢弃的t){
}
}
返回integerValue.intValue();
}
公共浮点getFloat(){
Float floatValue=(数值实例)?((数值)值)。floatValue():null;
if(floatValue==null){
试一试{
floatValue=Float.valueOf(value.toString());
}
捕获(可丢弃的t){
}
}
返回floatValue.floatValue();
}
公共静态void saveToFile(字符串路径)引发IOException{
FileWriter fw=新的FileWriter(路径);
属性=新属性();
对于(选项:Options.values()){
if(option.saveValue){
properties.setProperty(option.name(),option.getString());
}
}
if(DEBUG.getBoolean()){
属性列表(系统输出);
}
properties.store(fw,null);
}
公共静态void loadFromFile(字符串路径)引发IOException{
FileReader fr=新的FileReader(路径);
属性=新属性();
荷载(fr);
if(DEBUG.getBoolean()){
属性列表(系统输出);
}
对象值=空;
对于(选项:Options.values()){
if(option.saveValue){
Class clazz=option.value.getClass();
试一试{
if(String.class.equals(clazz)){
value=properties.getProperty(option.name());
}
否则{
value=clazz.getConstructor(String.class).newInstance(properties.getProperty(option.name());
}
}
catch(NoSuchMethodException-ex){
Debug.log(ex);
}
catch(实例化异常){
Debug.log(ex);
}
捕获(非法访问例外){
Debug.log(ex);
}
捕获(IllegalArgumentException ex){
Debug.log(ex);
}
捕获(调用TargetException ex){
Debug.log(ex);
}
if(值!=null){
选项。设置值(值);
}
}
}
}
}

这样,我可以轻松地保存和检索文件中的值。问题是我不想到处重复这段代码。正如我们所知,枚举不能被扩展;所以无论我在哪里使用它,我都必须把所有这些方法放在那里。我只想声明这些值以及它们是否应该被持久化。每次都没有方法定义;有什么想法吗?

使用
enum
保存这样的可配置值看起来是一个完全错误的设计。枚举是单例的,因此在任何给定的时间内,实际上只能有一个配置处于活动状态

EnumMap
听起来更像您需要的。它位于
枚举
的外部,因此您可以根据需要实例化任意多个配置

import java.util.*;
public class EnumMapExample {
    static enum Options {
        DEBUG, ALWAYS_SAVE, THREAD_COUNT;
    }
    public static void main(String[] args) {
        Map<Options,Object> normalConfig = new EnumMap<Options,Object>(Options.class);
        normalConfig.put(Options.DEBUG, false);
        normalConfig.put(Options.THREAD_COUNT, 3);
        System.out.println(normalConfig);
        // prints "{DEBUG=false, THREAD_COUNT=3}"

        Map<Options,Object> debugConfig = new EnumMap<Options,Object>(Options.class);
        debugConfig.put(Options.DEBUG, true);
        debugConfig.put(Options.THREAD_COUNT, 666);
        System.out.println(debugConfig);
        // prints "{DEBUG=true, THREAD_COUNT=666}"  
    }
}
import java.util.*;
公共类EnumMapExample{
静态枚举选项{
调试、始终保存、线程计数;
}
公共静态void main(字符串[]args){
Map normalConfig=新的EnumMap(Options.class);
normalConfig.put(Options.DEBUG,false);
normalConfig.put(Options.THREAD\u COUNT,3);
System.out.println(normalConfig);
//打印“{DEBUG=false,THREAD\u COUNT=3}”
Map debugConfig=new EnumMap(Options.class);
debugConfig.put(Options.DEBUG,true);
debugConfig.put(Options.THREAD_COUNT,666);
System.out.println(debugConfig);
//打印“{DEBUG=true,THREAD_COUNT=666}”
}
}
API链接
  • 用于
    enum
    类型键的专用
    Map
    实现。枚举映射中的所有键必须来自在创建映射时显式或隐式指定的单个枚举类型。枚举映射在内部表示为数组。这种表示非常紧凑和高效


我尝试对枚举映射和属性文件执行类似的操作(请参见下面的代码)。但我的枚举很简单,只有一个值,只有一个嵌入的case除外。我可能有更安全的东西。我会到处找的

package p;

import java.util.*;
import java.io.*;

public class GenericAttributes<T extends Enum<T>> {
    public GenericAttributes(final Class<T> keyType) {
        map = new EnumMap<T, Object>(this.keyType = keyType);
    }

    public GenericAttributes(final Class<T> keyType, final Properties properties) {
        this(keyType);
        addStringProperties(properties);
    }

    public Object get(final T key) {
        // what does a null value mean?
        // depends on P's semantics
        return map.containsKey(key) ? map.get(key) : null;
    }

    public boolean contains(final T key) {
        return map.containsKey(key);
    }

    public void change(final T key, final Object value) {
        remove(key);
        put(key, value);
    }

    public Object put(final T key, final Object value) {
        if (map.containsKey(key))
            throw new RuntimeException("map already contains: " + key);
        else
            return map.put(key, value);
    }

    public Object remove(final T key) {
        if (!map.containsKey(key))
            throw new RuntimeException("map does not contain: " + key);
        return map.remove(key);
    }

    public String toString() {
        return toString(defaultEquals, defaultEndOfLine);
    }

    // maybe we don;t need this stuff
    // we have tests for it though
    // it might be useful
    public String toString(final String equals, final String endOfLine) {
        final StringBuffer sb = new StringBuffer();
        for (Map.Entry<T, Object> entry : map.entrySet())
            sb.append(entry.getKey()).append(equals).append(entry.getValue()).append(endOfLine);
        return sb.toString();
    }

    public Properties toProperties() {
        final Properties p = new Properties();
        for (Map.Entry<T, Object> entry : map.entrySet())
            p.put(entry.getKey().toString(), entry.getValue().toString());
        return p;
    }

    public void addStringProperties(final Properties properties) {
        // keep this for strings, but mostly do work in the enum class
        // i.e. static GenericAttributes<PA> fromProperties();
        // which would use a fromString()
        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
            final String key = (String) entry.getKey();
            final String value = (String) entry.getValue();
            addProperty(key, value);
        }
    }

    public void addProperty(final String key, final Object value) {
        try {
            final T e = Enum.valueOf(keyType, key);
            map.put(e, value);
        } catch (IllegalArgumentException e) {
            System.err.println(key + " is not an enum from: " + keyType);
        }
    }

    public int size() {
        return map.size();
    }

    public static Properties load(final InputStream inputStream,final Properties defaultProperties) {
        final Properties p=defaultProperties!=null?new Properties(defaultProperties):new Properties();
        try {
            p.load(inputStream);
        } catch(IOException e) {
            throw new RuntimeException(e);
        }
        return p;
    }
    public static Properties load(final File file,final Properties defaultProperties) {
        Properties p=null;
        try {
            final InputStream is=new FileInputStream(file);
            p=load(is,defaultProperties);
            is.close();
        } catch(IOException e) {
            throw new RuntimeException(e);
        }
        return p;
    }
    public static void store(final OutputStream outputStream, final Properties properties) {
        try {
            properties.store(outputStream, null);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void store(final File file, final Properties properties) {
        try {
            final OutputStream os = new FileOutputStream(file);
            store(os, properties);
            os.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    final Class<T> keyType;
    static final String defaultEquals = "=", defaultEndOfLine = "\n";
    private final EnumMap<T, Object> map;

    public static void main(String[] args) {
    }
}

package p;
import static org.junit.Assert.*;
import org.junit.*;
import java.io.*;
import java.util.*;
enum A1 {
    foo,bar,baz;
}
enum A2 {
    x,y,z;
}
public class GenericAttributesTestCase {
    @Test public void testGenericAttributes() {
        new GenericAttributes<A1>(A1.class);
    }
    @Test public void testGenericAttributesKeyTypeProperties() {
        final Properties expected=gA1.toProperties();
        final GenericAttributes<A1> gA=new GenericAttributes<A1>(A1.class,expected);
        final Properties actual=gA.toProperties();
        assertEquals(expected,actual);
    }
    @Test public void testGet() {
        final A1 key=A1.foo;
        emptyGA1.put(key,null);
        final Object actual=emptyGA1.get(key);
        assertEquals(null,actual);
    }
    @Test public void testGetInteger() {
    // attributes.add(key,integer);
    // assertEquals(integer,attributes.get("key"));
    }
    @Test public void testContains() {
        for(A1 a:A1.values())
            assertFalse(emptyGA1.contains(a));
    }
    @Test public void testChange() {
        final A1 key=A1.foo;
        final Integer value=42;
        emptyGA1.put(key,value);
        final Integer expected=43;
        emptyGA1.change(key,expected);
        final Object actual=emptyGA1.get(key);
        assertEquals(expected,actual);
    }
    @Test public void testAdd() {
        final A1 key=A1.foo;
        final Integer expected=42;
        emptyGA1.put(key,expected);
        final Object actual=emptyGA1.get(key);
        assertEquals(expected,actual);
    }
    @Test public void testRemove() {
        final A1 key=A1.foo;
        final Integer value=42;
        emptyGA1.put(key,value);
        emptyGA1.remove(key);
        assertFalse(emptyGA1.contains(key));
    }
    @Test public void testToString() {
        final String actual=gA1.toString();
        final String expected="foo=a foo value\nbar=a bar value\n";
        assertEquals(expected,actual);
    }
    @Test public void testToStringEqualsEndOfLine() {
        final String equals=",";
        final String endOFLine=";";
        final String actual=gA1.toString(equals,endOFLine);
        final String expected="foo,a foo value;bar,a bar value;";
        assertEquals(expected,actual);
    }
    @Test public void testEmbedded() {
        final String equals=",";
        final String endOfLine=";";
        //System.out.println("toString(\""+equals+"\",\""+endOFLine+"\"):");
        final String embedded=gA1.toString(equals,endOfLine);
        GenericAttributes<A2> gA2=new GenericAttributes<A2>(A2.class);
        gA2.put(A2.x,embedded);
        //System.out.println("embedded:\n"+gA2);
        // maybe do file={name=a.jpg;dx=1;zoom=.5}??
        // no good, key must be used more than once
        // so file:a.jpg={} and hack
        // maybe file={name=...} will work
        // since we have to treat it specially anyway?
        // maybe this is better done in ss first
        // to see how it grows?
    }
    @Test public void testFromString() {
    // final Attributes a=Attributes.fromString("");
    // final String expected="";
    // assertEquals(expected,a.toString());
    }
    @Test public void testToProperties() {
        final Properties expected=new Properties();
        expected.setProperty("foo","a foo value");
        expected.setProperty("bar","a bar value");
        final Properties actual=gA1.toProperties();
        assertEquals(expected,actual);
    }
    @Test public void testAddProperties() {
        final Properties p=gA1.toProperties();
        final GenericAttributes<A1> ga=new GenericAttributes<A1>(A1.class);
        ga.addStringProperties(p);
        // assertEquals(ga1,ga); // fails since we need to define equals!
        // hack, go backwards
        final Properties p2=ga.toProperties();
        assertEquals(p,p2); // hack until we define equals
    }
    @Test public void testStore() throws Exception {
        final Properties expected=gA1.toProperties();
        final ByteArrayOutputStream baos=new ByteArrayOutputStream();
        GenericAttributes.store(baos,expected);
        baos.close();
        final byte[] bytes=baos.toByteArray();
        final ByteArrayInputStream bais=new ByteArrayInputStream(bytes);
        final Properties actual=GenericAttributes.load(bais,null);
        bais.close();
        assertEquals(expected,actual);
    }
    @Test public void testLoad() throws Exception {
        final Properties expected=gA1.toProperties();
        final ByteArrayOutputStream baos=new ByteArrayOutputStream();
        GenericAttributes.store(baos,expected);
        baos.close();
        final ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());
        final Properties actual=GenericAttributes.load(bais,null);
        bais.close();
        assertEquals(expected,actual);
    }
    @Test public void testMain() {
    // fail("Not yet implemented");
    }
    GenericAttributes<A1> gA1=new GenericAttributes<A1>(A1.class);
    {
        gA1.put(A1.foo,"a foo value");
        gA1.put(A1.bar,"a bar value");
    }
    GenericAttributes<A1> emptyGA1=new GenericAttributes<A1>(A1.class);
}
p包;
导入java.util.*;
导入java.io.*;
公共类通用电气
package p;
import java.util.*;
public enum GA {
    // like properties, seems like this wants to be constructed with a set of default values
    i(Integer.class) {
        Integer fromString(final String s) {
            return new Integer(s);
        }
        Integer fromNull() {
            return zero; // return empty string?
        }
    },
    b(Boolean.class) {
        Boolean fromString(final String s) {
            return s.startsWith("t")?true:false;
        }
        Boolean fromNull() {
            return false;
        }
    },
    d(Double.class) {
        Double fromString(final String s) {
            return new Double(s);
        }
        Double fromNull() {
            return new Double(zero);
        }
    };
    GA() {
        this(String.class);
    }
    GA(final Class clazz) {
        this.clazz=clazz;
    }
    abstract Object fromString(String string);
    abstract Object fromNull();
    static GenericAttributes<GA> fromProperties(final Properties properties) {
        final GenericAttributes<GA> pas=new GenericAttributes<GA>(GA.class);
        for(Map.Entry<Object,Object> entry:properties.entrySet()) {
            final String key=(String)entry.getKey();
            final GA pa=valueOf(key);
            if(pa!=null) {
                final String stringValue=(String)entry.getValue();
                Object value=pa.fromString(stringValue);
                pas.addProperty(key,value);
            } else throw new RuntimeException(key+"is not a member of "+"GA");
        }
        return pas;
    }
    // private final Object defaultValue; // lose type?; require cast?
    /* private */final Class clazz;
    static final Integer zero=new Integer(0);
}