diff --git a/.travis.yml b/.travis.yml index f4a9a7b..81c8133 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ jdk: - openjdk7 - openjdk6 after_success: - - mvn clean test jacoco:report coveralls:jacoco + - mvn clean test jacoco:report coveralls:report diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c5c65b..ddc865b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,12 @@ ### Changed * __BREAKING:__ Toml#getList(String), Toml#getTable(String) and Toml#getTables(String) return null when key is not found +* Removed trailing newline from error messages ### Added * Support for underscores in numbers (the feature branch had accidentally not been merged! :( ) +* Set Toml#entrySet() cf. Reflection section in README * Overloaded getters that take a default value. Thanks to __[udiabon](https://github.com/udiabon)__. ## 0.4.0 / 2015-02-16 diff --git a/README.md b/README.md index 5f42ed0..54bc415 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,35 @@ Long tableD = toml.getLong("table.d"); // returns null, not 5, because of shallo Long arrayD = toml.getLong("array[0].d"); // returns 3 ``` +### Reflection + +`Toml#entrySet()` returns a Set of [Map.Entry](http://docs.oracle.com/javase/6/docs/api/java/util/Map.Entry.html) instances. Modifications to the returned Set are not reflected in the Toml instance. Note that Map.Entry#setValue() will throw an UnsupportedOperationException. + +```java +for (Map.Entry entry : myToml.entrySet()) { + System.out.println(entry.getKey() + " " + entry.getValue()); +} +``` + +You can also convert a Toml instance to a `Map`: + +```java +Toml toml = new Toml().parse("a = 1"); +Map map = toml.to(Map.class); +``` + +`Toml#contains(String)` verifies that the instance contains a key of any type (primitive, table or array of tables) of the given name. `Toml#containsKey(String)`, `Toml#containsTable(String)` and `Toml#containsTableArray(String)` return true only if a key exists and is a primitive, table or array of tables, respectively. Compound keys can be used to check existence at any depth. + + +```java +Toml toml = new Toml().parse("a = 1"); + +toml.contains("a"); // true +toml.conatinsKey("a"); // true +toml.containsTable("a"); // false +toml.containsTableArray("a"); // false +``` + ### Serialization You can serialize a `Toml` or any arbitrary object to a TOML string. diff --git a/pom.xml b/pom.xml index 9531f30..55e3742 100644 --- a/pom.xml +++ b/pom.xml @@ -56,12 +56,6 @@ gson 2.3.1 - - org.easytesting - fest-reflect - 1.4.1 - test - org.hamcrest hamcrest-library @@ -74,7 +68,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.2 + 3.3 1.6 1.6 @@ -83,7 +77,7 @@ org.jacoco jacoco-maven-plugin - 0.7.2.201409121644 + 0.7.5.201505241946 prepare-agent @@ -96,7 +90,7 @@ org.eluder.coveralls coveralls-maven-plugin - 3.0.1 + 3.1.0 @@ -114,7 +108,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.4 + 1.6 sign-artifacts diff --git a/src/main/java/com/moandjiezana/toml/Results.java b/src/main/java/com/moandjiezana/toml/Results.java index cc5ae75..a3ea318 100644 --- a/src/main/java/com/moandjiezana/toml/Results.java +++ b/src/main/java/com/moandjiezana/toml/Results.java @@ -19,7 +19,7 @@ class Results { .append(line) .append(": [") .append(table) - .append("]\n"); + .append("]"); } public void tableDuplicatesKey(String table, AtomicInteger line) { @@ -27,21 +27,19 @@ class Results { .append(line.get()) .append(": [") .append(table) - .append("]\n"); + .append("]"); } public void keyDuplicatesTable(String key, AtomicInteger line) { sb.append("Table already exists for key defined on line ") .append(line.get()) .append(": ") - .append(key) - .append('\n'); + .append(key); } void emptyImplicitTable(String table, int line) { sb.append("Invalid table definition due to empty implicit table name: ") - .append(table) - .append("\n"); + .append(table); } void invalidTable(String table, int line) { @@ -49,7 +47,7 @@ class Results { .append(line) .append(": ") .append(table) - .append("]\n"); + .append("]"); } void duplicateKey(String key, int line) { @@ -59,8 +57,7 @@ class Results { .append(line); } sb.append(": ") - .append(key) - .append('\n'); + .append(key); } void invalidTextAfterIdentifier(Identifier identifier, char text, int line) { @@ -68,24 +65,21 @@ class Results { .append(identifier.getName()) .append(" on line ") .append(line) - .append(". Make sure to terminate the value or add a comment (#).") - .append('\n'); + .append(". Make sure to terminate the value or add a comment (#)."); } void invalidKey(String key, int line) { sb.append("Invalid key on line ") .append(line) .append(": ") - .append(key) - .append('\n'); + .append(key); } void invalidTableArray(String tableArray, int line) { sb.append("Invalid table array definition on line ") .append(line) .append(": ") - .append(tableArray) - .append('\n'); + .append(tableArray); } void invalidValue(String key, String value, int line) { @@ -94,16 +88,14 @@ class Results { .append(": ") .append(key) .append(" = ") - .append(value) - .append('\n'); + .append(value); } void unterminatedKey(String key, int line) { sb.append("Key is not followed by an equals sign on line ") .append(line) .append(": ") - .append(key) - .append('\n'); + .append(key); } void unterminated(String key, String value, int line) { @@ -112,15 +104,13 @@ class Results { .append(": ") .append(key) .append(" = ") - .append(value.trim()) - .append('\n'); + .append(value.trim()); } public void heterogenous(String key, int line) { sb.append(key) .append(" becomes a heterogeneous array on line ") - .append(line) - .append('\n'); + .append(line); } boolean hasErrors() { diff --git a/src/main/java/com/moandjiezana/toml/Toml.java b/src/main/java/com/moandjiezana/toml/Toml.java index 659ad87..d5efe3f 100644 --- a/src/main/java/com/moandjiezana/toml/Toml.java +++ b/src/main/java/com/moandjiezana/toml/Toml.java @@ -1,11 +1,24 @@ package com.moandjiezana.toml; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + import com.google.gson.Gson; import com.google.gson.JsonElement; -import java.io.*; -import java.util.*; - /** *

Provides access to the keys and tables in a TOML data source.

* @@ -26,7 +39,7 @@ import java.util.*; * */ public class Toml { - + private static final Gson DEFAULT_GSON = new Gson(); private Map values = new HashMap(); @@ -220,6 +233,44 @@ public class Toml { return tables; } + /** + * @param key a key name, can be compound (eg. a.b.c) + * @return true if key is present + */ + public boolean contains(String key) { + return get(key) != null; + } + + /** + * @param key a key name, can be compound (eg. a.b.c) + * @return true if key is present and is a primitive + */ + public boolean containsKey(String key) { + Object object = get(key); + + return object != null && !(object instanceof Map) && !(object instanceof List); + } + + /** + * @param key a key name, can be compound (eg. a.b.c) + * @return true if key is present and is a table + */ + public boolean containsTable(String key) { + Object object = get(key); + + return object != null && (object instanceof Map); + } + + /** + * @param key a key name, can be compound (eg. a.b.c) + * @return true if key is present and is a table array + */ + public boolean containsTableArray(String key) { + Object object = get(key); + + return object != null && (object instanceof List); + } + public boolean isEmpty() { return values.isEmpty(); } @@ -268,6 +319,58 @@ public class Toml { return DEFAULT_GSON.fromJson(json, targetClass); } + + /** + * @return a {@link Set} of Map.Entry instances. Modifications to the {@link Set} are not reflected in this Toml instance. Entries are immutable, so {@link Map.Entry#setValue(Object)} throws an UnsupportedOperationException. + */ + public Set> entrySet() { + Set> entries = new LinkedHashSet>(); + + for (Map.Entry entry : values.entrySet()) { + Class entryClass = entry.getValue().getClass(); + + if (Map.class.isAssignableFrom(entryClass)) { + entries.add(new Toml.Entry(entry.getKey(), getTable(entry.getKey()))); + } else if (List.class.isAssignableFrom(entryClass)) { + List value = (List) entry.getValue(); + if (!value.isEmpty() && value.get(0) instanceof Map) { + entries.add(new Toml.Entry(entry.getKey(), getTables(entry.getKey()))); + } else { + entries.add(new Toml.Entry(entry.getKey(), value)); + } + } else { + entries.add(new Toml.Entry(entry.getKey(), entry.getValue())); + } + } + + return entries; + } + + private class Entry implements Map.Entry { + + private final String key; + private final Object value; + + @Override + public String getKey() { + return key; + } + + @Override + public Object getValue() { + return value; + } + + @Override + public Object setValue(Object value) { + throw new UnsupportedOperationException("TOML entry values cannot be changed."); + } + + private Entry(String key, Object value) { + this.key = key; + this.value = value; + } + } /** * Serializes the values of this Toml instance into TOML. @@ -324,9 +427,9 @@ public class Toml { return current; } - + private Toml(Toml defaults, Map values) { - this.values = values != null ? values : Collections.emptyMap(); + this.values = values; this.defaults = defaults; } } diff --git a/src/test/java/com/moandjiezana/toml/ContainsTest.java b/src/test/java/com/moandjiezana/toml/ContainsTest.java new file mode 100644 index 0000000..5e25f6a --- /dev/null +++ b/src/test/java/com/moandjiezana/toml/ContainsTest.java @@ -0,0 +1,79 @@ +package com.moandjiezana.toml; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class ContainsTest { + + @Test + public void should_contain_top_level_of_any_type() throws Exception { + Toml toml = new Toml().parse("a = 1 \n [b] \n b1 = 1 \n [[c]] \n c1 = 1"); + + assertTrue(toml.contains("a")); + assertTrue(toml.contains("b")); + assertTrue(toml.contains("c")); + assertFalse(toml.contains("d")); + } + + @Test + public void should_contain_nested_of_any_type() throws Exception { + Toml toml = new Toml().parse("[a] \n a1 = 1 \n [[b]] \n b1 = 1 \n [[b]] \n b1 = 2 \n b2 = 3"); + + assertTrue(toml.contains("a.a1")); + assertTrue(toml.contains("b[0].b1")); + assertTrue(toml.contains("b[1].b1")); + assertFalse(toml.contains("b[2].b1")); + assertFalse(toml.contains("c.d")); + } + + @Test + public void should_contain_key() throws Exception { + Toml toml = new Toml().parse("a = 1 \n [b] \n b1 = 1 \n [[c]] \n c1 = 1"); + + assertTrue(toml.containsKey("a")); + assertTrue(toml.containsKey("b.b1")); + assertTrue(toml.containsKey("c[0].c1")); + assertFalse(toml.containsKey("b")); + assertFalse(toml.containsKey("c")); + assertFalse(toml.containsKey("d")); + } + + @Test + public void should_contain_table() throws Exception { + Toml toml = new Toml().parse("a = 1 \n [b] \n b1 = 1 \n [b.b2] \n [[c]] \n c1 = 1 \n [c.c2]"); + + assertTrue(toml.containsTable("b")); + assertTrue(toml.containsTable("b.b2")); + assertTrue(toml.containsTable("c[0].c2")); + assertFalse(toml.containsTable("a")); + assertFalse(toml.containsTable("b.b1")); + assertFalse(toml.containsTable("c")); + assertFalse(toml.containsTable("c[0].c1")); + assertFalse(toml.containsTable("d")); + } + + @Test + public void should_contain_table_array() throws Exception { + Toml toml = new Toml().parse("a = 1 \n [b] \n b1 = 1 \n [[c]] \n c1 = 1 \n [c.c2] \n [[c]] \n [[c.c3]] \n c4 = 4"); + + assertTrue(toml.containsTableArray("c")); + assertTrue(toml.containsTableArray("c[1].c3")); + assertFalse(toml.containsTableArray("a")); + assertFalse(toml.containsTableArray("b")); + assertFalse(toml.containsTableArray("b.b1")); + assertFalse(toml.containsTableArray("c[1].c3[0].c4")); + assertFalse(toml.containsTableArray("d")); + } + + @Test + public void should_not_contain_when_parent_table_is_missing() throws Exception { + Toml toml = new Toml().parse("a = \"1\""); + + assertFalse(toml.contains("b.b1")); + assertFalse(toml.containsKey("b.b1")); + assertFalse(toml.containsTable("b.b1")); + assertFalse(toml.containsTableArray("b.b1")); + } +} diff --git a/src/test/java/com/moandjiezana/toml/IterationTest.java b/src/test/java/com/moandjiezana/toml/IterationTest.java new file mode 100644 index 0000000..6258d13 --- /dev/null +++ b/src/test/java/com/moandjiezana/toml/IterationTest.java @@ -0,0 +1,115 @@ +package com.moandjiezana.toml; + +import static java.util.Arrays.asList; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import com.moandjiezana.toml.testutils.Utils; + +public class IterationTest { + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + + @Test + public void should_iterate_over_primitive() throws Exception { + Toml toml = new Toml().parse(file("long")); + Map.Entry entry = toml.entrySet().iterator().next(); + + assertEquals("long", entry.getKey()); + assertEquals(2L, entry.getValue()); + } + + @Test + @SuppressWarnings("unchecked") + public void should_iterate_over_list() throws Exception { + Toml toml = new Toml().parse(file("list")); + Map.Entry entry = toml.entrySet().iterator().next(); + + assertEquals("list", entry.getKey()); + assertThat((List) entry.getValue(), contains("a", "b", "c")); + } + + @Test + @SuppressWarnings("unchecked") + public void should_iterate_over_empty_list() throws Exception { + Toml toml = new Toml().parse("list = []"); + Map.Entry entry = toml.entrySet().iterator().next(); + + assertEquals("list", entry.getKey()); + assertThat((List) entry.getValue(), empty()); + } + + @Test + public void should_iterate_over_table() throws Exception { + Toml toml = new Toml().parse(file("table")); + Map.Entry entry = toml.entrySet().iterator().next(); + + assertEquals("table", entry.getKey()); + assertEquals("a", ((Toml) entry.getValue()).getString("a")); + } + + @Test + @SuppressWarnings("unchecked") + public void should_iterate_over_table_array() throws Exception { + Toml toml = new Toml().parse(file("table_array")); + + Map.Entry entry = toml.entrySet().iterator().next(); + List tableArray = (List) entry.getValue(); + + assertEquals("table_array", entry.getKey()); + assertThat(tableArray, contains(instanceOf(Toml.class), instanceOf(Toml.class))); + } + + @Test + @SuppressWarnings("unchecked") + public void should_iterate_over_multiple_entries() throws Exception { + Toml toml = new Toml().parse(file("multiple")); + + Map entries = new HashMap(); + for (Map.Entry entry : toml.entrySet()) { + entries.put(entry.getKey(), entry.getValue()); + } + + assertThat(entries.keySet(), containsInAnyOrder("a", "b", "c", "e")); + assertThat(entries, hasEntry("a", (Object) "a")); + assertThat(entries, hasEntry("b", (Object) asList(1L, 2L, 3L))); + assertTrue(((Toml) entries.get("c")).getBoolean("d")); + assertThat(((List) entries.get("e")), hasSize(1)); + } + + @Test + public void should_not_support_setValue_method() throws Exception { + Map.Entry entry = new Toml().parse("a = 1").entrySet().iterator().next(); + + expectedException.expect(UnsupportedOperationException.class); + entry.setValue(2L); + } + + @Test + public void should_not_iterate_over_empty_toml() throws Exception { + Toml toml = new Toml().parse(""); + + assertThat(toml.entrySet(), empty()); + } + + private File file(String name) { + return Utils.file(getClass(), "/IteratorTest/" + name); + } +} diff --git a/src/test/resources/IteratorTest/list.toml b/src/test/resources/IteratorTest/list.toml new file mode 100644 index 0000000..fdcf4ec --- /dev/null +++ b/src/test/resources/IteratorTest/list.toml @@ -0,0 +1 @@ +list = ["a", "b", "c"] diff --git a/src/test/resources/IteratorTest/long.toml b/src/test/resources/IteratorTest/long.toml new file mode 100644 index 0000000..3641e48 --- /dev/null +++ b/src/test/resources/IteratorTest/long.toml @@ -0,0 +1 @@ +long = 2 diff --git a/src/test/resources/IteratorTest/multiple.toml b/src/test/resources/IteratorTest/multiple.toml new file mode 100644 index 0000000..38af207 --- /dev/null +++ b/src/test/resources/IteratorTest/multiple.toml @@ -0,0 +1,7 @@ +a = "a" +b = [1, 2, 3] + +[c] + d = true + +[[e]] diff --git a/src/test/resources/IteratorTest/table.toml b/src/test/resources/IteratorTest/table.toml new file mode 100644 index 0000000..541ee71 --- /dev/null +++ b/src/test/resources/IteratorTest/table.toml @@ -0,0 +1 @@ +table = { a = "a" } diff --git a/src/test/resources/IteratorTest/table_array.toml b/src/test/resources/IteratorTest/table_array.toml new file mode 100644 index 0000000..b14906e --- /dev/null +++ b/src/test/resources/IteratorTest/table_array.toml @@ -0,0 +1,3 @@ +[[table_array]] + +[[table_array]]