枚举中的可配置值

时间:2010-05-25 04:58:15

标签: 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_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);
                }
            }
        }
    }
}

这样,我可以轻松地保存和检索文件中的值。问题是我不想在任何地方重复这个代码。就像我们所知,枚举不能延长;所以无论我在哪里使用它,我都必须把所有这些方法放在那里。我只想声明值,如果它们应该被保留。每次都没有方法定义;任何想法?

3 个答案:

答案 0 :(得分:3)

使用enum来保存这样的可配置值看起来像是一个完全错误的设计。枚举是单例,因此有效地在任何给定时间只能激活一个配置。

EnumMap听起来更像你需要的东西。它位于enum的外部,因此您可以根据需要实例化任意数量的配置。

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}"  
    }
}

API链接

  • java.util.EnumMap
      

    用于Map类型键的专用enum实现。枚举映射中的所有键必须来自创建映射时显式或隐式指定的单个枚举类型。枚举映射在内部表示为数组。这种表现非常紧凑和高效。

答案 1 :(得分:0)

我尝试使用枚举映射和属性文件做类似的事情(请参阅下面的代码)。但我的枚举很简单,除了嵌入式案例外只有一个值。我可能有一些更安全的东西。我会环顾四周。

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);
}

回答你的评论:

似乎我通过使用枚举作为键来获取值。我可能很困惑。

枚举可以实现一个接口,每组枚举可以拥有该基类的实例并委托对它的调用(参见http://java.sun.com/docs/books/effective/toc.html的第34项)

我发现其他代码与我的通用属性一起使用(请参阅下文),但我找不到任何测试,并且我不太确定我在做什么,除了可能添加更强大的输入。

我所有这一切的动机是为像picasa这样的照片浏览器存储一些属性,我想在一个属性文件的单行中存储一堆图片的属性

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);
}

答案 2 :(得分:0)

如果您仍在寻找答案,可以尝试使用MIT许可证开源的Properties库。使用它,您不必指定字符串常量,所有内容都将由您定义的枚举确定。而且,它还有一些其他功能。这个图书馆的亮点是:

  1. 所有属性键都在一个地方定义,即用户定义的enum
  2. 属性值可以包含变量(以$符号开头,例如$PATH)其中PATH是同一文件中的属性键
  3. 属性值可以作为指定的数据类型获取,因此无需将字符串值转换为所需的数据类型
  4. 属性值可以作为指定数据类型列表获取
  5. 属性值可以是多行文本
  6. 可以强制使用属性键或选项
  7. 如果值不可用,可以指定属性键的默认值
  8. 线程安全
  9. 您可以找到示例程序here