Generic java configuration class with type safety In the first code snippet I am creating a generic class to hold configuration details.  I am currently using it as a way to pass run time configuration options to plugins in a generic manner.  The second snippet is a helper class that utilizes the class to provide the public methods for use which helps guarantee type safety.

Questions


How is my use of generics?  I feel like there is a better way to handle this.
Is there a better design pattern to be used here?


DefaultConfiguration

import java.util.HashMap;
import java.util.Map;

public class DefaultConfiguration implements Configuration {
    private final Map&lt;String, Setting&gt; options;

    public DefaultConfiguration() {
        this.options = new HashMap&lt;String, Setting&gt;();
    }

    @Override
    public &lt;T&gt; T get(String option, Class&lt;T&gt; type) {
        Setting setting = options.get(option);
        if (setting != null) {
            return type.cast(setting.getSetting(type));
        }
        return null;
    }

    @Override
    public &lt;T&gt; void set(String option, T setting, Class&lt;T&gt; type) {
        options.put(option, new Setting&lt;T&gt;(setting, type));
    }

    @Override
    public void set(String option, Object setting) {
        options.put(option, new Setting(setting, setting.getClass()));
    }

    class Setting&lt;W&gt; {
        private final Class&lt;? super W&gt; type;
        private final W setting;

        public Setting(W setting, Class&lt;? super W&gt; type) {
            this.type = type;
            this.setting = setting;
        }

        public W getSetting(Class&lt;? super W&gt; type) {
            if (type.isAssignableFrom(this.type)) {
                return setting;
            }
            return null;
        }
    }
}


MyConfiguration

public class MyConfiguration extends DefaultConfiguration {
    public String getSettingA() {
        return super.get("settingA", String.class);
    }

    public void setSettingA(String settingA) {
        super.set("settingA", settingA, String.class);
    }

    public Integer getSettingB() {
        return super.get("settingB", Integer.class);
    }

    public void setSettingB() {
        super.set("settingB", settingB, Integer.class);
    }
}


Updated Classes

import java.util.HashMap;

public class Configuration
    extends HashMap&lt;String, Object&gt;
{
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    @Override
    public &lt;T&gt; T get(String option, Class&lt;T&gt; type)
    {
        Object obj = get(option);
        if (obj == null) {
            return null;
        }
        try {
            return type.cast(obj);
        } catch (ClassCastException ex) {
            throw new ConfigurationException(ex);
        }
    }
}




public class MyConfiguration
    extends Configuration
{
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    private static final String SETTING_A = "settingA";
    private static final String SETTING_B = "settingB";

    public String getSettingA()
    {
        return get(SETTING_A, String.class);
    }

    public void setSettingA(String settingA)
    {
        put(SETTING_A, settingA);
    }

    public Integer getSettingB()
    {
        return get(SETTING_B, Integer.class);
    }

    public void setSettingB()
    {
        put(SETTING_B, settingB);
    }
}
Generics can only go so far to help you, and, this is one of those times where it just does not help all that much.

In essence, you have the following:


a configuration system where you can get values based on names (a Map).
when you set a value it is for a given type (which is either specified when you set the value, or it is not, in which case it is inferred).
when you get a value, it is matched against the type the getter expects, and, if it matches, it is returned.


Since the getter/setter is the only place where you actually know the type of the value, there is actually zero benefit for your entire Setting subclass... it is redundant. You can get the same behaviour with:

private final Map&lt;String, Object&gt; options;

public DefaultConfiguration() {
    this.options = new HashMap&lt;String, Object&gt;();
}

@Override
public &lt;T&gt; T get(String option, Class&lt;T&gt; type) {
    Object value = options.get(option);
    if (value != null) {
        return type.cast(value);
    }
    return null;
}

@Override
public &lt;T&gt; void set(String option, T setting, Class&lt;T&gt; type) {
    // no point in even checking the type, the generic method will enforce that.
    // this method is essentially redundant, and could be
    //   public void set(String option, Object value);
    options.put(option, setting);
}

@Override
public void set(String option, Object setting) {
    // this essentially a duplicate option.
    options.put(option, setting);
}


here you have the exact same functionality as you did before.... if the user wants to, they can compile-time type-check their values when they set them (by ensusuring the Class&lt;T&gt; parameter matches the T setting value

When they retrieve the value, they can cast the result to the specified type, or return null if it is not castable.

The functionality in MyConfiguration is unchanged, but, I recommend you use constants for the keys:

private static final String SETTINGA = "settingA";
....


Finally, you may want to consider your DefaultConfiguration extending something like HashMap directly.... like:

public class DefaultConfiguration
          extends HashMap&lt;String,Object&gt;
          implements Configuration {


then, remove the options Map variable, and simply have methods like (note the direct get(option) instead of options.get(option):

@Override
public &lt;T&gt; T get(String option, Class&lt;T&gt; type) {
    Setting setting = get(option);
    if (setting != null) {
        return type.cast(setting.getSetting(type));
    }
    return null;
}


Now, when people use your class, they have access directly to the configuration too (with, and without your 'helper' methods).

The bottom line though, is that your implementation does not enforce type-safety between the set and the get. The only thing it does is return null instead of throwing a ClassCastException when people use your code wrong.