first cut of typed settings

This commit is contained in:
Rafael Chaves 2007-12-29 08:58:14 +00:00
parent 68f112c4ac
commit 34a34d6d27
12 changed files with 239 additions and 624 deletions

View File

@ -5,3 +5,4 @@ Bundle-SymbolicName: org.umlgraph.engine.tests
Bundle-Version: 1.0.0
Require-Bundle: org.junit,
org.umlgraph.engine
Export-Package: org.umlgraph.engine.tests

View File

@ -4,7 +4,7 @@ import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.umlgraph.engine.options.SettingKey;
import org.umlgraph.settings.SettingKey;
public class SettingKeyTests extends TestCase {
public SettingKeyTests(String name) {

View File

@ -1,96 +1,47 @@
package org.umlgraph.engine.tests;
import java.util.Collection;
import org.umlgraph.engine.options.Settings;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.umlgraph.settings.SettingDefinitions;
import org.umlgraph.settings.Settings;
public class SettingTests extends TestCase {
public void testBasic() {
Settings root = new Settings();
root.set("@option2", "foo");
root.set("@option3", Boolean.TRUE);
assertNull(root.get("@option1"));
assertEquals("foo", root.get("@option2"));
assertTrue(root.<Boolean> get("@option3"));
public static class TestBasicDefinitions implements SettingDefinitions {
public static String option1;
public static String option2;
public static Boolean option3;
}
public void testDefault() {
Settings root = new Settings();
root.set("@option1", "foo");
root.setDefault("@option2", Boolean.TRUE);
root.set("@option3", 42);
root.setDefault("@option3", 52);
assertEquals("foo", root.get("@option1"));
assertEquals(Boolean.TRUE, root.get("@option2"));
assertEquals(42, root.get("@option3"));
public static class TestDefault implements SettingDefinitions {
public static String option1 = "bar";
public static Boolean option2 = true;
public static Integer option3 = 52;
}
public void testRemoval() {
Settings root = new Settings();
root.set("@option1", "foo");
root.setDefault("@option1", "bar");
assertEquals("foo", root.get("@option1"));
root.set("@option1", null);
assertEquals("bar", root.get("@option1"));
root.setDefault("@option1", null);
assertNull(root.get("@option1"));
public static class TestFallbackDefinitions implements SettingDefinitions {
public static String option1;
public static String option2;
public static Boolean option3;
public static Integer option4;
}
public void testDefaultFirstLevel() {
Settings root = new Settings();
root.set("context1@option1", "foo");
root.setDefault("context1@option2", Boolean.TRUE);
root.set("context1@option3", 42);
root.setDefault("context1@option3", 52);
assertEquals("foo", root.get("context1@option1"));
assertEquals(Boolean.TRUE, root.get("context1@option2"));
assertEquals(42, root.get("context1@option3"));
public static class TestRemovalDefinitions implements SettingDefinitions {
public static String option1 = "bar";
public static String option2;
}
public void testDefaultSecondLevel() {
Settings root = new Settings();
root.set("context1/context2@option1", "foo");
root.setDefault("context1/context2@option2", Boolean.TRUE);
root.set("context1/context2@option3", 42);
root.setDefault("context1/context2@option3", 52);
assertEquals("foo", root.get("context1/context2@option1"));
assertEquals(Boolean.TRUE, root.get("context1/context2@option2"));
assertEquals(42, root.get("context1/context2@option3"));
public static Test suite() {
return new TestSuite(SettingTests.class);
}
public void testFirstLevel() {
Settings root = new Settings();
root.set("context1@option2", "foo");
root.set("context1@option3", Boolean.TRUE);
assertNull(root.get("context1@option1"));
assertEquals("foo", root.get("context1@option2"));
assertTrue(root.<Boolean> get("context1@option3"));
public SettingTests(String name) {
super(name);
}
public void testSecondLevel() {
Settings root = new Settings();
root.set("context1/context2/@option2", "foo");
root.set("context1/context2/@option3", Boolean.TRUE);
assertNull(root.get("context1/context2/@option1"));
assertEquals("foo", root.get("context1/context2/@option2"));
assertTrue(root.<Boolean> get("context1/context2/@option3"));
}
public void testBasicFallback() {
Settings root = new Settings();
Settings root = new Settings(TestFallbackDefinitions.class);
root.set("@option2", "foo");
root.set("@option3", Boolean.TRUE);
root.set("context1/@option3", Boolean.FALSE);
@ -116,87 +67,8 @@ public class SettingTests extends TestCase {
assertEquals(42, root.get("context1/context2@option4"));
}
public void testFallbackWithDefaults() {
Settings root = new Settings();
root.set("@option1", "foo");
root.setDefault("@option2", Boolean.TRUE);
root.setDefault("context1/@option1", "zoo");
root.setDefault("context1/context2/@option1", null);
assertEquals("zoo", root.get("context1@option1"));
assertEquals(Boolean.TRUE, root.get("context1@option2"));
assertNull(root.get("context1@option1", false));
assertEquals("zoo", root.get("context1/context2@option1"));
assertEquals(Boolean.TRUE, root.get("context1/context@option2"));
}
public void testSetToDefault() {
Settings root = new Settings();
root.set("@option1", "foo");
root.setDefault("@option1", "bar");
root.set("context1/@option1", "zoo");
root.set("context1/@option2", Boolean.TRUE);
assertEquals("zoo", root.get("context1/context2@option1"));
root.setToDefault("context1/context2@option1");
assertEquals("bar", root.get("context1/context2@option1"));
assertEquals(true, root.get("context1/context2@option2"));
try {
root.setToDefault("context1/context2@option2");
fail("should have failed");
} catch (IllegalStateException ise) {
// expected
}
}
public void testDefaultCancellation() {
Settings root = new Settings();
root.set("@option2", "foo");
root.setDefault("@option3", Boolean.TRUE);
root.set("context1/@option3", Boolean.FALSE);
root.set("context1/@option4", 42);
root.set("context1/context2/@option1", "zoo");
root.set("context1/context2/@option2", "bar");
root.set("context1/context2/@option3", Boolean.TRUE);
assertNull(root.get("@option1"));
assertNull(root.get("context1/@option1"));
assertEquals("zoo", root.get("context1/context2/@option1"));
assertEquals("foo", root.get("@option2"));
assertEquals("foo", root.get("context1/@option2"));
assertEquals("bar", root.get("context1/context2@option2"));
assertEquals(Boolean.TRUE, root.get("@option3"));
assertEquals(Boolean.FALSE, root.get("context1/@option3"));
assertEquals(Boolean.TRUE, root.get("context1/context2@option3"));
assertEquals(null, root.get("@option4"));
assertEquals(42, root.get("context1/@option4"));
assertEquals(42, root.get("context1/context2@option4"));
}
public void testOptionKeys() {
Settings root = new Settings();
root.set("@option2", "foo");
root.set("@option3", Boolean.TRUE);
root.setDefault("@option4", 42);
Collection<String> keys = root.getOptionKeys();
assertEquals(2, keys.size());
assertFalse(keys.contains("option1"));
assertTrue(keys.contains("option2"));
assertTrue(keys.contains("option3"));
assertFalse(keys.contains("option4"));
}
public void testContextCreation() {
Settings root = new Settings();
Settings root = new Settings(TestBasicDefinitions.class);
Settings context1 = root.node("context1");
assertNotNull(context1);
Settings context2 = root.node("context1/context2");
@ -212,20 +84,8 @@ public class SettingTests extends TestCase {
assertTrue(root.nodeExists("context1/context3"));
}
public void testParenting() {
Settings root = new Settings();
Settings context1 = root.node("context1");
Settings context2 = root.node("context1/context2");
Settings context3 = root.node("context1/context3");
assertNull(root.getParent());
assertSame(root, context1.getParent());
assertSame(context1, context2.getParent());
assertSame(context1, context3.getParent());
}
public void testContextCreationByOption() {
Settings root = new Settings();
Settings root = new Settings(TestBasicDefinitions.class);
root.set("@option1", "foo");
root.set("context1@option2", "bar");
root.set("context1/context2@option3", "zoo");
@ -237,11 +97,147 @@ public class SettingTests extends TestCase {
assertFalse(root.nodeExists("context2"));
}
public SettingTests(String name) {
super(name);
public void testDefault() {
Settings root = new Settings(TestDefault.class);
root.set("@option1", "foo");
root.set("@option3", 42);
assertEquals("foo", root.get("@option1"));
assertEquals(Boolean.TRUE, root.get("@option2"));
assertEquals(42, root.get("@option3"));
root.set("@option3", null);
assertEquals(52, root.get("@option3"));
}
public static Test suite() {
return new TestSuite(SettingTests.class);
public void testDefaultCancellation() {
Settings root = new Settings(TestBasicDefinitions.class);
root.set("@option2", "foo");
root.set("context1/@option3", Boolean.FALSE);
root.set("context1/@option4", 42);
root.set("context1/context2/@option1", "zoo");
root.set("context1/context2/@option2", "bar");
root.set("context1/context2/@option3", Boolean.TRUE);
assertNull(root.get("@option1"));
assertNull(root.get("context1/@option1"));
assertEquals("zoo", root.get("context1/context2/@option1"));
assertEquals("foo", root.get("@option2"));
assertEquals("foo", root.get("context1/@option2"));
assertEquals("bar", root.get("context1/context2@option2"));
assertEquals(null, root.get("@option3"));
assertEquals(Boolean.FALSE, root.get("context1/@option3"));
assertEquals(Boolean.TRUE, root.get("context1/context2@option3"));
}
public void testDefaultFirstLevel() {
Settings root = new Settings(TestDefault.class);
root.set("context1@option1", "foo");
root.set("context1@option3", 42);
assertEquals("foo", root.get("context1@option1"));
assertEquals(Boolean.TRUE, root.get("context1@option2"));
assertEquals(42, root.get("context1@option3"));
}
public void testDefaultSecondLevel() {
Settings root = new Settings(TestDefault.class);
root.set("context1/context2@option1", "foo");
root.set("context1/context2@option3", 42);
assertEquals("foo", root.get("context1/context2@option1"));
assertEquals(Boolean.TRUE, root.get("context1/context2@option2"));
assertEquals(42, root.get("context1/context2@option3"));
}
public void testFallbackWithDefaults() {
Settings root = new Settings(TestDefault.class);
root.set("@option1", "zoo");
assertEquals("zoo", root.get("context1@option1"));
assertEquals(Boolean.TRUE, root.get("context1@option2"));
assertNull(root.get("context1@option1", false));
root.set("@option1", null);
assertEquals("bar", root.get("context1/context2@option1"));
assertEquals(Boolean.TRUE, root.get("context1/context@option2"));
}
public void testFirstLevel() {
Settings root = new Settings(TestBasicDefinitions.class);
root.set("context1@option2", "foo");
root.set("context1@option3", Boolean.TRUE);
assertNull(root.get("context1@option1"));
assertEquals("foo", root.get("context1@option2"));
assertTrue(root.<Boolean> get("context1@option3"));
}
public void testParenting() {
Settings root = new Settings(TestBasicDefinitions.class);
Settings context1 = root.node("context1");
Settings context2 = root.node("context1/context2");
Settings context3 = root.node("context1/context3");
assertNull(root.getParent());
assertSame(root, context1.getParent());
assertSame(context1, context2.getParent());
assertSame(context1, context3.getParent());
}
public void testRemoval() {
Settings root = new Settings(TestRemovalDefinitions.class);
root.set("@option1", "foo");
root.set("@option2", "zoo");
assertEquals("foo", root.get("@option1"));
assertEquals("zoo", root.get("@option2"));
root.set("@option1", null);
root.set("@option2", null);
assertEquals("bar", root.get("@option1"));
assertNull(root.get("@option2"));
}
public void testRoot() {
Settings root = new Settings(TestBasicDefinitions.class);
root.set("@option2", "foo");
root.set("@option3", Boolean.TRUE);
assertNull(root.get("@option1"));
assertEquals("foo", root.get("@option2"));
assertTrue(root.<Boolean> get("@option3"));
}
public void testSecondLevel() {
Settings root = new Settings(TestBasicDefinitions.class);
root.set("context1/context2/@option2", "foo");
root.set("context1/context2/@option3", Boolean.TRUE);
assertNull(root.get("context1/context2/@option1"));
assertEquals("foo", root.get("context1/context2/@option2"));
assertTrue(root.<Boolean> get("context1/context2/@option3"));
}
public void testSetToDefault() {
Settings root = new Settings(TestDefault.class);
root.set("@option1", "foo");
root.set("context1/@option1", "zoo");
root.set("context1/@option2", Boolean.TRUE);
assertEquals("zoo", root.get("context1/context2@option1"));
root.setToDefault("context1/context2@option1");
assertEquals("bar", root.get("context1/context2@option1"));
assertEquals(true, root.get("context1/context2@option2"));
// clear default value
TestDefault.option2 = null;
try {
root.setToDefault("context1/context2@option2");
fail("should have failed");
} catch (IllegalStateException ise) {
// expected
}
}
}

View File

@ -4,5 +4,5 @@ Bundle-Name: UMLGraph
Bundle-SymbolicName: org.umlgraph.engine
Bundle-Version: 5.0.0.qualifier
Bundle-Vendor: umlgraph.org
Require-Bundle: org.eclipse.equinox.preferences
Export-Package: org.umlgraph.engine.options
Require-Bundle: org.umlgraph.settings;visibility:=reexport
Export-Package: org.umlgraph.engine

View File

@ -15,39 +15,41 @@
* $Id$
*
*/
package org.umlgraph.engine.options;
package org.umlgraph.engine;
import org.umlgraph.settings.SettingDefinitions;
/**
* Constants for class diagram options.
*/
public interface ClassDiagramOptions {
public class ClassDiagramOptions implements SettingDefinitions {
/**
* Produce fully-qualified class names (boolean).
* Produce fully-qualified class names.
*/
String QUALIFIED_NAMES = "classDiagramQualifiedNames";
public static boolean classDiagramQualifiedNames;
/**
* When using qualified class names, put the package name in the line after the class name, in order to reduce the width of class nodes.
*/
String POSTFIX_QUALIFIED_NAMES = "classDiagramPostfixQualifiedNames";
public static boolean classDiagramPostfixQualifiedNames;
/**
* Try to automatically infer dependencies between classes by inspecting operation and attribute types. See the class diagram inference chapter for more details. Disabled by default. (boolean)
* Try to automatically infer dependencies between classes by inspecting operation and attribute types. See the class diagram inference chapter for more details. Disabled by default.
*/
String INFER_DEPENDENCY = "classDiagramInferDependency";
public static boolean classDiagramInferDependency;
/**
* Try to automatically infer association relationships between classes by inspecting attribute types. See the class diagram inference chapter for further details. Disabled by default. (boolean)
* Try to automatically infer association relationships between classes by inspecting attribute types. See the class diagram inference chapter for further details. Disabled by default.
*/
String INFER_ASSOCIATIONS = "classDiagramInferUsage";
public static boolean classDiagramInferUsage;
/**
* The type of relationship inferred when -inferrel is activated. Defaults to "navassoc" (see the class modelling chapter for a list of relationship types). (String)
* The type of relationship inferred when -inferrel is activated. Defaults to "navassoc" (see the class modelling chapter for a list of relationship types).
*/
String INFER_ASSOCIATION_TYPE = "classDiagramInferAssociationType";
public static String classDiagramInferAssociationType;
/**
* Specifies the lowest visibility level of elements used to infer dependencies among classes. Possible values are private, package, protected, public, in this order. The default value is private. Use higher levels to limit the number of inferred dependencies. (String)
* Specifies the lowest visibility level of elements used to infer dependencies among classes. Possible values are private, package, protected, public, in this order. The default value is private. Use higher levels to limit the number of inferred dependencies.
*/
String INFER_DEPENDENCY_VISIBILITY = "classDiagramInferDependencyVisibility";
public static String classDiagramInferDependencyVisibility;
}

View File

@ -16,55 +16,57 @@
*
*/
package org.umlgraph.engine.options;
package org.umlgraph.engine;
import org.umlgraph.settings.SettingDefinitions;
/**
* Constants for class options.
*/
public interface ClassOptions {
public class ClassOptions implements SettingDefinitions {
/**
* Specify the font name to use inside abstract class nodes (String).
*/
String FONT_NAME_ABSTRACT_CLASS = "classAbstractFontName";
public static String classAbstractFontName;
/**
* Specify the font name to use for the class names (String).
*/
String FONT_NAME_CLASS_NAME = "classNameFontName";
public static String classNameFontName;
/**
* Specify the font name use for the class name of abstract classes
* (String).
*/
String FONT_NAME_ABSTRACT_CLASS_NAME = "classAbstractNameFontName";
public static String classAbstractNameFontName;
/**
* Specify the font size to use for the class names (int).
*/
String FONT_SIZE_CLASS_NAME = "classNameFontSize";
public static int classNameFontSize;
/**
* Show class attributes (boolean).
*/
String SHOW_ATTRIBUTES = "classShowAttributes";
public static boolean classShowAttributes;
/**
* Show class operations (Java methods)
*/
String SHOW_OPERATIONS = "classShowOperations";
public static boolean classShowOperations;
/**
* Adorn class elements according to their visibility (private, public, protected, package) (boolean).
*/
String SHOW_VISIBILITY = "classShowVisibility";
public static boolean classShowVisibility;
/**
* Add type information to attributes and operations (boolean)
*/
String SHOW_TYPES = "classFeatureTypes";
public static boolean classFeatureTypes;
/**
* For enumerations, also show the values they can take. (boolean)
*/
String SHOW_ENUM_VALUES = "classShowEnumValues";
public static boolean classShowEnumValues;
}

View File

@ -16,24 +16,32 @@
*
*/
package org.umlgraph.engine.options;
package org.umlgraph.engine;
import org.umlgraph.settings.SettingDefinitions;
/**
* Generic constants for options applicable to all types of UML diagrams.
*/
public interface DiagramOptions {
public class DiagramOptions implements SettingDefinitions {
/**
* Layout the graph in the horizontal direction (boolean).
*/
String HORIZONTAL = "diagramHorizontal";
public static boolean diagramHorizontal;
/**
* Specify the graph's background color.
*/
String BACKGROUND_COLOR = "diagramBackgroundColor";
public static String diagramBackgroundColor;
/**
* Specify entities to hide from the graph. Matching is done using a non-anchored regular match. For instance, "-hide (Big|\.)Widget" would hide "com.foo.widgets.Widget" and "com.foo.widgets.BigWidget". Can also be used without arguments, in this case it will hide everything (useful in the context of views to selectively unhide some portions of the graph, see the view chapter for further details). */
String hide();
* Specify entities to hide from the graph. Matching is done using a
* non-anchored regular match. For instance, "-hide (Big|\.)Widget" would
* hide "com.foo.widgets.Widget" and "com.foo.widgets.BigWidget". Can also
* be used without arguments, in this case it will hide everything (useful
* in the context of views to selectively unhide some portions of the graph,
* see the view chapter for further details).
*/
// TODO String hide();
}

View File

@ -16,26 +16,28 @@
*
*/
package org.umlgraph.engine.options;
package org.umlgraph.engine;
import org.umlgraph.settings.SettingDefinitions;
/**
* Constants for edge options.
*/
public interface EdgeOptions {
public class EdgeOptions implements SettingDefinitions {
/**
* Specify the font name to use for edge labels (String).
*/
public String FONT_NAME = "edgeFontName";
public static String edgeFontName;
/**
* Specify the font size to use for edge labels (int).
*/
public String FONT_SIZE = "edgeFontSize";
public static int edgeFontSize;
/**
* Specify the font color to use for edge labels (String).
*/
public String FONT_COLOR = "edgeFontColor";
public static String edgeFontColor;
/**
* Specify the color for drawing edges.
*/
public String COLOR = "edgeColor";
public static String edgeColor;
}

View File

@ -16,39 +16,40 @@
*
*/
package org.umlgraph.engine.options;
package org.umlgraph.engine;
import org.umlgraph.settings.SettingDefinitions;
/**
* Constants for node options.
* Node options.
*/
public interface NodeOptions {
public class NodeOptions implements SettingDefinitions {
/**
* Specify the color to use to fill the shapes (String).
*/
public String FILL_COLOR = "nodeFillColor";
public static String nodeFillColor;
/**
* Specify the font name to use inside nodes (String).
*/
public String FONT_NAME = "nodeFontName";
public static String nodeFontName;
/**
* Specify the font size to use inside nodes (int).
*/
public String FONT_SIZE = "nodeFontSize";
public static int nodeFontSize;
/**
* Specify the font name to use for the tags (String).
*/
public String FONT_NAME_TAG = "nodeTagFontName";
public static String nodeTagFontName;
/**
* Specify the font size to use for the tags (int).
*/
public String FONT_SIZE_TAG = "nodeTagFontSize";
public static int nodeTagFontSize;
/**
* Specify the font color to use inside nodes (String).
*/
public String FONT_COLOR = "nodeFontColor";
public static String nodeFontColor;
}

View File

@ -16,22 +16,24 @@
*
*/
package org.umlgraph.engine.options;
package org.umlgraph.engine;
import org.umlgraph.settings.SettingDefinitions;
/**
* Constants for package options.
*/
public interface PackageOptions {
public class PackageOptions implements SettingDefinitions {
/**
* Specify the font name to use for the package names (used only when the
* package name is postfixed, see -postfixpackage).
*/
String FONT_NAME_PACKAGE_NAME = "packageNameFontName";
public static String packageNameFontName;
/**
* Specify the font size to use for the package names (used only when it
* package name is postfixed, see -postfixpackage).
*/
String FONT_SIZE_PACKAGE_NAME = "packageNameFontSize";
public static int packageNameFontSize;
}

View File

@ -1,117 +0,0 @@
/*
* (C) Copyright 2007-2008 Abstratt Technologies
*
* Permission to use, copy, and distribute this software and its
* documentation for any purpose and without fee is hereby granted,
* provided that the above copyright notice appear in all copies and that
* both that copyright notice and this permission notice appear in
* supporting documentation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* $Id$
*
*/
package org.umlgraph.engine.options;
import java.util.Arrays;
import java.util.StringTokenizer;
/**
* A key for accessing a specific setting.
*/
public class SettingKey {
private static final String PATH_DELIMITER = "/";
private static final String OPTION_DELIMITER = "@";
private static final String[] EMPTY_CONTEXT_PATH = {};
private String[] nodePath;
private String key;
public SettingKey(String[] nodePath, String key) {
this.nodePath = nodePath;
this.key = key;
}
public SettingKey(String stringKey) {
int atIndex = stringKey.indexOf(OPTION_DELIMITER);
if (atIndex >= 0) {
if (atIndex == stringKey.length() - 1)
throw new IllegalArgumentException("argument: " + stringKey);
key = stringKey.substring(atIndex + 1);
if (atIndex == 0) {
nodePath = EMPTY_CONTEXT_PATH;
return;
}
}
String nodePathStr = atIndex < 0 ? stringKey : stringKey.substring(0,
atIndex);
StringTokenizer pathSegmenter = new StringTokenizer(nodePathStr,
PATH_DELIMITER);
nodePath = new String[pathSegmenter.countTokens()];
int i = 0;
while (pathSegmenter.hasMoreTokens())
nodePath[i++] = pathSegmenter.nextToken();
}
public String[] getNodePath() {
return nodePath;
}
public boolean isRoot() {
return nodePath.length == 0;
}
public boolean hasKey() {
return key != null;
}
public String getKey() {
return key;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
result = prime * result + Arrays.hashCode(nodePath);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final SettingKey other = (SettingKey) obj;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
if (!Arrays.equals(nodePath, other.nodePath))
return false;
return true;
}
@Override
public String toString() {
StringBuffer result = new StringBuffer(PATH_DELIMITER);
for (int i = 0; i < nodePath.length; i++) {
result.append(nodePath[i]);
result.append(PATH_DELIMITER);
}
if (hasKey()) {
result.append(OPTION_DELIMITER);
result.append(key);
}
return result.toString();
}
}

View File

@ -1,282 +0,0 @@
/*
* (C) Copyright 2007-2008 Abstratt Technologies
*
* Permission to use, copy, and distribute this software and its
* documentation for any purpose and without fee is hereby granted,
* provided that the above copyright notice appear in all copies and that
* both that copyright notice and this permission notice appear in
* supporting documentation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* $Id$
*
*/
package org.umlgraph.engine.options;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* A typed hierarchic preferences mechanism that takes advantage of
* generics for providing a simpler protocol.
*
* Features:
* <ul>
* <li>default values</li>
* <li>contexts are hierarchic, option value lookup falls back to parent
* if option is not found at the current level</li>
* <li>
* options are typed, so an attempt at setting them with the wrong value
* type causes an exception
* </li>
* <li>
* option contexts are statically typed, meaning that set of
* available options for a context is defined at compile time
* and trying to set an unexpected option value causes an
* exception.
* </li>
* </ul>
* Non-features:
* <ul>
* <li>
* persistence
* </li>
* </ul>
*/
public class Settings {
/*
* The child setting nodes.
*/
private Map<String, Settings> children = new HashMap<String, Settings>();
/*
* The setting values defined for this setting node.
*/
private Map<String, Object> values = new HashMap<String, Object>();
/*
* The default values defined for this setting node.
*/
private Map<String, Object> defaultValues = new HashMap<String, Object>();
private Settings parent;
public Settings() {
}
private Settings(Settings parent) {
this.parent = parent;
}
/**
* Returns the setting node corresponding to the given path. If the node
* does not exist, returns a new empty node.
*
* @param path
* the setting node path
* @return the setting node corresponding to the given path
*/
public Settings node(String[] path) {
return createNode(path, 0);
}
public Settings node(String path) {
return node(new SettingKey(path).getNodePath());
}
/**
* Creates a setting node under the given path, or returns the existing one.
*/
private Settings createNode(String[] path, int start) {
if (path.length == start)
return this;
Settings found = children.get(path[start]);
if (found == null)
children.put(path[start], found = new Settings(this));
if (start == path.length - 1)
return found;
return found.createNode(path, start + 1);
}
/**
* Returns whether the given node exists.
*/
public boolean nodeExists(String[] path) {
return existingNode(path) != null;
}
public boolean nodeExists(String path) {
return nodeExists(new SettingKey(path).getNodePath());
}
/**
* Returns an existing setting node corresponding to the given path.
*
* @param path
* the setting path
* @return the settings object, or <code>null</code>
*/
public Settings existingNode(String[] segments) {
Settings current = this;
for (String segment : segments) {
current = current.children.get(segment);
if (current == null)
return null;
}
return current;
}
public Settings existingNode(String path) {
return existingNode(new SettingKey(path).getNodePath());
}
/**
* Returns the value of the option corresponding to the given key. If
* acceptDefault is <code>true</code> and a value for the option does not
* exist, returns the default value. Otherwise, returns <code>null</code>.
*
* @param key
* the option key
* @param acceptDefault
* whether a default value should be returned
* @return the value for the given key, or <code>null</code>
*/
public <T> T get(SettingKey key, boolean acceptDefault) {
if (!acceptDefault) {
Settings node = existingNode(key.getNodePath());
return node == null ? null : (T) node.values.get(key.getKey());
}
Settings node = lastExistingNode(key.getNodePath(), 0);
return node.searchValue(key.getKey());
}
private <T> T searchValue(String key) {
T ownValue = (T) values.get(key);
if (ownValue != null)
return ownValue;
T ownDefaultValue = (T) defaultValues.get(key);
if (ownDefaultValue != null)
return ownDefaultValue;
return (T) (parent == null ? null : parent.searchValue(key));
}
private <T> T searchDefaultValue(String key) {
T ownDefaultValue = (T) defaultValues.get(key);
if (ownDefaultValue != null)
return ownDefaultValue;
return (T) (parent == null ? null : parent.searchDefaultValue(key));
}
private Settings lastExistingNode(String[] nodePath, int start) {
if (start == nodePath.length)
return this;
Settings immediateChild = children.get(nodePath[start]);
return immediateChild == null ? this : immediateChild.lastExistingNode(nodePath, start+1);
}
/**
* Returns the value of the option corresponding to the given path. If
* acceptDefault is <code>true</code> and a value for the option does not
* exist, returns the default value. Otherwise, returns <code>null</code>.
* This is just a convenience method for {@link #get(SettingKey, boolean)}.
*
* @param path
* the option path
* @param acceptDefault
* whether a default value should be returned
* @return the value for the given path, or <code>null</code>
*/
public <T> T get(String path, boolean acceptDefault) {
return get(new SettingKey(path), acceptDefault);
}
public <T> T get(String path) {
return get(new SettingKey(path), true);
}
/**
* Sets the value for the given option.
*
* @param key
* the option key
* @param value
* the new value, or <code>null</code>
*/
public <T> void set(SettingKey key, T value) {
if (value == null) {
Settings node = existingNode(key.getNodePath());
if (node != null)
node.values.remove(key.getKey());
} else {
Settings node = createNode(key.getNodePath(), 0);
node.values.put(key.getKey(), value);
}
}
public <T> void set(String path, T value) {
set(new SettingKey(path), value);
}
public <T> void setToDefault(String path) {
setToDefault(new SettingKey(path));
}
/**
* Sets the given option to its current default value.
*
* @param settingKey the key to set to default
*/
public <T> void setToDefault(SettingKey settingKey) {
T defaultValue = node(settingKey.getNodePath()).searchDefaultValue(settingKey.getKey());
if (defaultValue == null)
throw new IllegalStateException();
set(settingKey, defaultValue);
}
/**
* Sets the default value for the given option.
*
* @param key
* the option key
* @param value
* the new value, or <code>null</code>
*/
public <T> void setDefault(SettingKey key, T defaultValue) {
if (defaultValue == null) {
Settings node = existingNode(key.getNodePath());
if (node != null)
node.defaultValues.remove(key.getKey());
} else {
Settings node = createNode(key.getNodePath(), 0);
node.defaultValues.put(key.getKey(), defaultValue);
}
}
public <T> void setDefault(String path, T defaultValue) {
setDefault(new SettingKey(path), defaultValue);
}
/**
* Returns all options set for this context.
*
* @return
*/
public Collection<String> getOptionKeys() {
return this.values.keySet();
}
/**
* Returns the parent settings, or <code>null</code> if this is a root setting.
*
* @return the parent settings, or <code>null</code>
*/
public Settings getParent() {
return parent;
}
}