2
\$\begingroup\$

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<String, Setting> options;

    public DefaultConfiguration() {
        this.options = new HashMap<String, Setting>();
    }

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

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

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

    class Setting<W> {
        private final Class<? super W> type;
        private final W setting;

        public Setting(W setting, Class<? super W> type) {
            this.type = type;
            this.setting = setting;
        }

        public W getSetting(Class<? super W> 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<String, Object>
{
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    @Override
    public <T> T get(String option, Class<T> 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);
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ A "Type-safe configuration class" is called a Java Bean. \$\endgroup\$ Jan 15, 2014 at 7:30
  • \$\begingroup\$ A Java Bean wouldn't allow the passing of arbitrary values. I want users of a framework to be able to pass any value they want to be used inside their "plugin". \$\endgroup\$
    – cldfzn
    Jan 15, 2014 at 16:00
  • \$\begingroup\$ "wouldn't allow the passing of arbitrary values" is pretty much the definition of type-safe. \$\endgroup\$ Jan 16, 2014 at 9:40

1 Answer 1

2
\$\begingroup\$

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<String, Object> options;

public DefaultConfiguration() {
    this.options = new HashMap<String, Object>();
}

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

@Override
public <T> void set(String option, T setting, Class<T> 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<T> 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<String,Object>
          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 <T> T get(String option, Class<T> 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.

\$\endgroup\$
4
  • \$\begingroup\$ Yeah after looking posting this I came to the same conclusion of it being worthless to have all of that generic junk in there. Went off the edge there. How does the design work out for as a method for implementing a configuration. Is this a lackluster design as well? \$\endgroup\$
    – cldfzn
    Jan 14, 2014 at 19:58
  • \$\begingroup\$ Also, is there a better way to prevent people from using my code wrong? I want developers to be able to pass arbitrary details to their plugin implementations. \$\endgroup\$
    – cldfzn
    Jan 14, 2014 at 20:07
  • \$\begingroup\$ ` I want developers to be able to pass arbitrary details to their plugin implementations.` I don't beleieve Generics will help you tbe that flexible. \$\endgroup\$
    – rolfl
    Jan 14, 2014 at 20:39
  • \$\begingroup\$ Yeah I've removed the generics and just have a convenience method for the configuration implementations to specify their type, attempt to cast, and throw an API exception around the ClassCastException if it is thrown. I'm now mostly interested in whether using a map to provide this functionality is the most prudent design. \$\endgroup\$
    – cldfzn
    Jan 14, 2014 at 21:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.