Настраиваемые значения в Enum - PullRequest
1 голос
/ 25 мая 2010

Я часто использую этот дизайн в своем коде для поддержки настраиваемых значений. Рассмотрим этот код:

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 ]

3 голосов
/ 25 мая 2010

Использование 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. Все ключи в карте перечисления должны происходить из единственного типа перечисления, который указывается, явно или неявно, при создании карты. Enum карты представлены внутри как массивы. Это представление чрезвычайно компактно и эффективно.

0 голосов
/ 09 сентября 2017

Если вы все еще ищете ответы, вы можете попробовать Свойства библиотека с открытым исходным кодом с лицензией MIT. Используя это, вам не нужно указывать строковые константы, и все будет определяться перечисленным вами перечислением И у него есть и другие функции. Основные характеристики этой библиотеки:

  1. Все ключи свойств определены в одном месте, то есть определены пользователем enum
  2. Значения свойств могут содержать переменные (начиная со знака $, например, $PATH), где PATH - ключ свойства в том же файле
  3. Значение свойства может быть получено как указанный тип данных, поэтому нет необходимости преобразовывать строковое значение в требуемый тип данных
  4. Значение свойства можно получить в виде списка указанных типов данных
  5. Значение свойства может быть многострочным текстом
  6. Может сделать ключи свойств обязательными или необязательными
  7. Можно указать значение по умолчанию для ключа свойства, если значение недоступно
  8. Поток безопасен

Примеры программ здесь

0 голосов
/ 31 мая 2010

Я попытался сделать что-то похожее с enum-картами и файлами свойств (см. Код ниже). но мои перечисления были простыми и имели только одно значение, за исключением встроенного регистра. у меня может быть что-то более безопасное. я поищу вокруг.

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

отвечая на ваш комментарий:

похоже, что я получаю значения, используя enum в качестве ключа. Я, вероятно, смущен.

перечисление может реализовать интерфейс, и каждый набор перечислений может иметь экземпляр этого базового класса и делегировать вызовы к нему (см. Пункт 34 http://java.sun.com/docs/books/effective/toc.html)

Я нашел другой код, который шел с моими общими атрибутами (см. Ниже), но я не могу найти никаких тестов для него, и я не совсем уверен, что я делал, кроме, возможно, добавления более сильной типизации. *

Моей мотивацией для всего этого было сохранение некоторых атрибутов для средства просмотра фотографий, таких как 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...