let subclass of ConfigOption be able to override parent options (#24)

also implement ConfigOption.toString()

Change-Id: I1f1fd5eeaa80252fc093e7cba5af525a32a23c99
diff --git a/src/main/java/com/baidu/hugegraph/config/ConfigOption.java b/src/main/java/com/baidu/hugegraph/config/ConfigOption.java
index 2bc993f..06fb43a 100644
--- a/src/main/java/com/baidu/hugegraph/config/ConfigOption.java
+++ b/src/main/java/com/baidu/hugegraph/config/ConfigOption.java
@@ -160,4 +160,10 @@
                             this.name, value);
         }
     }
+
+    @Override
+    public String toString() {
+        return String.format("[%s]%s=%s", this.dataType.getSimpleName(),
+                             this.name, this.defaultValue);
+    }
 }
diff --git a/src/main/java/com/baidu/hugegraph/config/OptionHolder.java b/src/main/java/com/baidu/hugegraph/config/OptionHolder.java
index 10aa153..f306741 100644
--- a/src/main/java/com/baidu/hugegraph/config/OptionHolder.java
+++ b/src/main/java/com/baidu/hugegraph/config/OptionHolder.java
@@ -42,7 +42,8 @@
         for (Field field : this.getClass().getFields()) {
             try {
                 ConfigOption<?> option = (ConfigOption<?>) field.get(this);
-                this.options.put(option.name(), option);
+                // Fields of subclass first, don't overwrite by superclass
+                this.options.putIfAbsent(option.name(), option);
             } catch (Exception e) {
                 LOG.error("Failed to register option: {}", field, e);
                 throw new ConfigException(
diff --git a/src/test/java/com/baidu/hugegraph/unit/config/HugeConfigTest.java b/src/test/java/com/baidu/hugegraph/unit/config/HugeConfigTest.java
index 6d5066a..5e23e50 100644
--- a/src/test/java/com/baidu/hugegraph/unit/config/HugeConfigTest.java
+++ b/src/test/java/com/baidu/hugegraph/unit/config/HugeConfigTest.java
@@ -54,6 +54,33 @@
     }
 
     @Test
+    public void testOptionsToString() {
+        Assert.assertEquals("[String]group1.text1=text1-value",
+                            TestOptions.text1.toString());
+        Assert.assertEquals("[Integer]group1.int1=1",
+                            TestOptions.int1.toString());
+        Assert.assertEquals("[Long]group1.long1=100",
+                            TestOptions.long1.toString());
+        Assert.assertEquals("[Float]group1.float1=100.0",
+                            TestOptions.float1.toString());
+        Assert.assertEquals("[Double]group1.double1=100.0",
+                            TestOptions.double1.toString());
+        Assert.assertEquals("[Boolean]group1.bool=true",
+                            TestOptions.bool.toString());
+        Assert.assertEquals("[List]group1.list=[list-value1, list-value2]",
+                            TestOptions.list.toString());
+        Assert.assertEquals("[List]group1.map=[key1:value1, key2:value2]",
+                            TestOptions.map.toString());
+
+        Assert.assertEquals("[String]group1.text1=text1-value",
+                            TestSubOptions.text1.toString());
+        Assert.assertEquals("[String]group1.text2=text2-value-override",
+                            TestSubOptions.text2.toString());
+        Assert.assertEquals("[String]group1.textsub=textsub-value",
+                            TestSubOptions.textsub.toString());
+    }
+
+    @Test
     public void testHugeConfig() throws Exception {
         Configuration conf = new PropertiesConfiguration();
         Whitebox.setInternalState(conf, "delimiterParsingDisabled", true);
@@ -117,7 +144,22 @@
         Assert.assertEquals("CHOICE-3", config.get(TestOptions.text3));
     }
 
-    public static final class TestOptions extends OptionHolder {
+    @Test
+    public void testHugeConfigWithOverride() throws Exception {
+        Configuration conf = new PropertiesConfiguration();
+        Whitebox.setInternalState(conf, "delimiterParsingDisabled", true);
+
+        HugeConfig config = new HugeConfig(conf);
+
+        Assert.assertEquals("text1-value", config.get(TestSubOptions.text1));
+
+        Assert.assertEquals("text2-value-override",
+                            config.get(TestSubOptions.text2));
+        Assert.assertEquals("textsub-value",
+                            config.get(TestSubOptions.textsub));
+    }
+
+    public static class TestOptions extends OptionHolder {
 
         private static volatile TestOptions instance;
 
@@ -229,4 +271,23 @@
                         "key1:value1", "key2:value2"
                 );
     }
+
+    public static class TestSubOptions extends TestOptions {
+
+        public static final ConfigOption<String> text2 =
+                new ConfigOption<>(
+                        "group1.text2",
+                        "description of group1.text2",
+                        disallowEmpty(),
+                        "text2-value-override"
+                );
+
+        public static final ConfigOption<String> textsub =
+                new ConfigOption<>(
+                        "group1.textsub",
+                        "description of group1.textsub",
+                        disallowEmpty(),
+                        "textsub-value"
+                );
+    }
 }