Skip to content

Commit b3660f8

Browse files
committed
Merge tag '0.5.0'
* tag '0.5.0': * prepare on releases/0.5.0 push possible print API change to 1.0.0 version shown in README is 0.5.0 travis icon in README references master branch build as 0.5.0 * issue 51 (travis should also build against jdk8) show travis build status of develop branch 51: add oraclejdk8 to travis configuration * issue 49, 50 (dupicate keys in maps) check for duplicates in SimpleParserConfigTest Note that CollectionBuilder.add() should check for duplicates throw EdnSyntaxException upon duplicate set element throw EdnSyntaxException upon duplicate map key DefaultMapFactory should throw EdnSyntaxException * issue 47 (read namespaced maps) 47 Test that supporting namespaced maps doens't mess up normal maps 47 refactor 47 First pass at implementing namespaced maps per CLJ-1910
2 parents 1cf977f + 6397f62 commit b3660f8

File tree

11 files changed

+182
-11
lines changed

11 files changed

+182
-11
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
language: java
22
jdk:
3+
- oraclejdk8
34
- oraclejdk7
45
- openjdk7
56
- openjdk6

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
*edn-java* is a library to parse (read) and print (write) [edn](https://github.com/edn-format/edn).
44

5-
This is very early days. There are still rough edges in the design which may see me moving things around some before I'm happy with the result.
5+
[![Build Status](https://travis-ci.org/bpsm/edn-java.svg?branch=master)](https://travis-ci.org/bpsm/edn-java)
66

77
## Installation
88

@@ -12,7 +12,7 @@ This is a Maven project with the following coordinates:
1212
<dependency>
1313
<groupId>us.bpsm</groupId>
1414
<artifactId>edn-java</artifactId>
15-
<version>0.4.7</version>
15+
<version>0.5.0</version>
1616
</dependency>
1717
```
1818

@@ -98,7 +98,13 @@ public class SimpleParserConfigTest {
9898
public CollectionBuilder builder() {
9999
return new CollectionBuilder() {
100100
SortedSet<Object> s = new TreeSet<Object>();
101-
public void add(Object o) { s.add(o); }
101+
public void add(Object o) {
102+
if (!s.add(o)) {
103+
throw new EdnSyntaxException(
104+
"Set contains duplicate element '" + o + "'."
105+
);
106+
}
107+
}
102108
public Object build() { return s; }
103109
};
104110
}
@@ -350,5 +356,5 @@ public class CustomTagPrinter {
350356
### Limitations
351357

352358
- Edn values must be *acyclic*. Any attempt to print a data structure containing cycles will surely end in a stack overflow.
353-
- The current Printing support stikes me a as a bit of a hack. The API may change with 0.5.0.
359+
- The current Printing support stikes me a as a bit of a hack. The API may change with 1.0.0.
354360
- Edn-Java does not provide much by way of "convenience" methods. As a library it's still to young to really know what would be convenient, though I'm open to suggestions.

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
</parent>
99
<groupId>us.bpsm</groupId>
1010
<artifactId>edn-java</artifactId>
11-
<version>0.4.7</version>
11+
<version>0.5.0</version>
1212
<packaging>jar</packaging>
1313
<name>EDN Java</name>
1414
<description>

src/main/java/us/bpsm/edn/parser/CollectionBuilder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public interface CollectionBuilder {
1414
* pairs of the map have been added.
1515
* <p>
1616
* For other collections is can be called any number of times.
17+
* <p>
18+
* Implementations which construct Maps or Sets an should throw an
19+
* EdnSyntaxException if they detect a duplicate key (in the case of
20+
* a map) or a duplicate element (in the case of a set).
21+
* </p>
1722
*
1823
* <p>{@code add()} may not be called after {@code build()}.
1924
* @param o an object to add to the collection under construction. o may

src/main/java/us/bpsm/edn/parser/DefaultMapFactory.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// (c) 2012 B Smith-Mannschott -- Distributed under the Eclipse Public License
22
package us.bpsm.edn.parser;
33

4+
import us.bpsm.edn.EdnSyntaxException;
5+
46
import java.util.Collections;
57
import java.util.HashMap;
68
import java.util.Map;
@@ -14,14 +16,18 @@ public CollectionBuilder builder() {
1416
public void add(Object o) {
1517
if (key == none) {
1618
key = o;
19+
if (map.containsKey(key)) {
20+
throw new EdnSyntaxException(
21+
"Map contains duplicate key '" + key + "'.");
22+
}
1723
} else {
1824
map.put(key, o);
1925
key = none;
2026
}
2127
}
2228
public Object build() {
2329
if (key != none) {
24-
throw new IllegalStateException(
30+
throw new EdnSyntaxException(
2531
"Every map must have an equal number of keys and values.");
2632
}
2733
return Collections.unmodifiableMap(map);

src/main/java/us/bpsm/edn/parser/DefaultSetFactory.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// (c) 2012 B Smith-Mannschott -- Distributed under the Eclipse Public License
22
package us.bpsm.edn.parser;
33

4+
import us.bpsm.edn.EdnSyntaxException;
5+
46
import java.util.Collections;
57
import java.util.HashSet;
68
import java.util.Set;
@@ -10,7 +12,10 @@ public CollectionBuilder builder() {
1012
return new CollectionBuilder() {
1113
Set<Object> set = new HashSet<Object>();
1214
public void add(Object o) {
13-
set.add(o);
15+
if (!set.add(o)) {
16+
throw new EdnSyntaxException(
17+
"Set contains duplicate element '" + o + "'.");
18+
}
1419
}
1520
public Object build() {
1621
return Collections.unmodifiableSet(set);

src/main/java/us/bpsm/edn/parser/ParserImpl.java

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import static us.bpsm.edn.parser.Token.END_LIST;
66
import static us.bpsm.edn.parser.Token.END_MAP_OR_SET;
77
import static us.bpsm.edn.parser.Token.END_VECTOR;
8-
import us.bpsm.edn.EdnSyntaxException;
9-
import us.bpsm.edn.Tag;
8+
9+
import us.bpsm.edn.*;
1010

1111

1212
class ParserImpl implements Parser {
@@ -48,6 +48,16 @@ private Object nextValue(Parseable pbr, boolean discard) {
4848
case BEGIN_MAP:
4949
return parseIntoCollection(cfg.getMapFactory(),
5050
END_MAP_OR_SET, pbr, discard);
51+
case DEFAULT_NAMESPACE_FOLLOWS: {
52+
final String ns = parseNamespaceName(pbr, discard);
53+
Object t = scanner.nextToken(pbr);
54+
if (t != Token.BEGIN_MAP) {
55+
throw new EdnSyntaxException(
56+
"Expected #:" + ns + " to be followed by a map.");
57+
}
58+
return parseIntoCollection(new NamespacedMapFactory(ns),
59+
END_MAP_OR_SET, pbr, discard);
60+
}
5161
case DISCARD:
5262
nextValue(pbr, true);
5363
return nextValue(pbr, discard);
@@ -68,6 +78,21 @@ private Object nextValue(Parseable pbr, boolean discard) {
6878
}
6979
}
7080

81+
private String parseNamespaceName(Parseable pbr, boolean discard) {
82+
final Object nsObj = nextValue(pbr, discard);
83+
if (!(nsObj instanceof Symbol)) {
84+
throw new EdnSyntaxException(
85+
"Expected symbol following #:, but found: " + nsObj);
86+
}
87+
final Symbol nsSym = (Symbol) nsObj;
88+
if (nsSym.getPrefix().length() > 0) {
89+
throw new EdnSyntaxException(
90+
"Expected symbol following #: to be namespaceless, " +
91+
"but found: " + nsSym);
92+
}
93+
return nsSym.getName();
94+
}
95+
7196
private Object nextValue(Tag t, Parseable pbr, boolean discard) {
7297
Object v = nextValue(pbr, discard);
7398
if (discard) {
@@ -95,4 +120,62 @@ private Object parseIntoCollection(CollectionBuilder.Factory f, Token end,
95120
return !discard ? b.build() : null;
96121
}
97122

123+
private class NamespacedMapFactory implements CollectionBuilder.Factory {
124+
private final String defaultNs;
125+
126+
public NamespacedMapFactory(String defaultNs) {
127+
this.defaultNs = defaultNs;
128+
}
129+
130+
@Override
131+
public CollectionBuilder builder() {
132+
return new NamespacedMapBuilder();
133+
}
134+
135+
private class NamespacedMapBuilder implements CollectionBuilder {
136+
private final CollectionBuilder cfgBuilder =
137+
cfg.getMapFactory().builder();
138+
boolean key = true;
139+
140+
@Override
141+
public void add(Object o) {
142+
if (key) {
143+
o = maybeApplyDefaultNamespace(o);
144+
}
145+
key = !key;
146+
cfgBuilder.add(o);
147+
}
148+
149+
@Override
150+
public Object build() {
151+
return cfgBuilder.build();
152+
}
153+
154+
private Object maybeApplyDefaultNamespace(final Object o) {
155+
if (!(o instanceof Symbol || o instanceof Keyword)) {
156+
return o;
157+
}
158+
159+
final Named named = (Named) o;
160+
161+
final String prefix = named.getPrefix();
162+
final String ns;
163+
if ("".equals(prefix)) {
164+
ns = defaultNs;
165+
} else if ("_".equals(prefix)) {
166+
ns = "";
167+
} else {
168+
return o;
169+
}
170+
171+
final String name = named.getName();
172+
if (o instanceof Symbol) {
173+
return Symbol.newSymbol(ns, name);
174+
} else {
175+
assert o instanceof Keyword;
176+
return Keyword.newKeyword(ns, name);
177+
}
178+
}
179+
}
180+
}
98181
}

src/main/java/us/bpsm/edn/parser/ScannerImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ private Object readHashDispatched(Parseable pbr) throws IOException {
191191
return Token.BEGIN_SET;
192192
case '_':
193193
return Token.DISCARD;
194+
case ':':
195+
return Token.DEFAULT_NAMESPACE_FOLLOWS;
194196
default:
195197
return newTag(readSymbol(peek, pbr));
196198
}

src/main/java/us/bpsm/edn/parser/Token.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,10 @@ public enum Token {
3838
* A '#_', which instructs the parser to ignore the next value
3939
* parsed from the input.
4040
*/
41-
DISCARD;
41+
DISCARD,
42+
43+
/**
44+
* A '#:', which introduces a namespaced map as per CLJ-1910.
45+
*/
46+
DEFAULT_NAMESPACE_FOLLOWS;
4247
}

src/test/java/us/bpsm/edn/examples/SimpleParserConfigTest.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import org.junit.Test;
1414

15+
import us.bpsm.edn.EdnSyntaxException;
1516
import us.bpsm.edn.parser.CollectionBuilder;
1617
import us.bpsm.edn.parser.Parseable;
1718
import us.bpsm.edn.parser.Parser;
@@ -27,7 +28,13 @@ public void test() throws IOException {
2728
public CollectionBuilder builder() {
2829
return new CollectionBuilder() {
2930
SortedSet<Object> s = new TreeSet<Object>();
30-
public void add(Object o) { s.add(o); }
31+
public void add(Object o) {
32+
if (!s.add(o)) {
33+
throw new EdnSyntaxException(
34+
"Set contains duplicate element '" + o + "'."
35+
);
36+
}
37+
}
3138
public Object build() { return s; }
3239
};
3340
}

src/test/java/us/bpsm/edn/parser/ParserTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030

3131
import org.junit.Test;
3232

33+
import us.bpsm.edn.EdnSyntaxException;
34+
import us.bpsm.edn.Keyword;
35+
import us.bpsm.edn.Symbol;
3336
import us.bpsm.edn.Tag;
3437

3538

@@ -88,6 +91,54 @@ public void discardedTaggedValuesDoNotCallTransformer() {
8891
assertEquals(123L, parse("#_ " + INVALID_UUID + " 123"));
8992
}
9093

94+
/**
95+
* <p>
96+
* This tests parsing of Namespaced maps as per
97+
* <a href="http://dev.clojure.org/jira/browse/CLJ-1910">CLJ-1910</a>.
98+
* </p>
99+
* <p>
100+
* A map may be optionally preceded by #:SYM, where SYM will be taken to be the
101+
* namespace off all unnamespaced symbol or keyword keys in the map so introduced.
102+
* Furthermore, symbol and keyword keys in the map with the namespace "_" will
103+
* emerge unnamespaced from the parsing.
104+
* </p>
105+
*/
106+
@Test
107+
public void parserUnderstandsNamespacedMaps() {
108+
assertEquals(
109+
parse("#:foo{ :a 1, b 2, _/c 3, :_/d 4, bar/e 5, :bar/f 6}"),
110+
parse("{:foo/a 1, foo/b 2, c 3, :d 4, bar/e 5, :bar/f 6}")
111+
);
112+
}
113+
114+
/**
115+
* This is just a sanity check to make sure that the fact that we add
116+
* support of namespaced maps (which assign "_" a special meaning as a
117+
* namespace prefix on keys) does not interfere with the use of "_" as
118+
* a namespace on keys in non-namespaced maps.
119+
*/
120+
@Test
121+
public void parserShouldNotBeConfusedByUnderscoreInNonNamespacedMaps() {
122+
Map<?,?> m = (Map<?, ?>) parse("{:_/foo 1, _/bar 2}");
123+
assertEquals(1L, m.get(Keyword.newKeyword("_", "foo")));
124+
assertEquals(2L, m.get(Symbol.newSymbol("_", "bar")));
125+
}
126+
127+
@Test(expected=EdnSyntaxException.class)
128+
public void parserShouldDetectDuplicateMapKeys() {
129+
parse("{:a 1, :a 2}");
130+
}
131+
132+
@Test(expected=EdnSyntaxException.class)
133+
public void parserShouldDetectDuplicateMapKeysInNamespacedMaps() {
134+
parse("#:foo{:foo/a 1, :a 2}");
135+
}
136+
137+
@Test(expected=EdnSyntaxException.class)
138+
public void parserShouldDetectDuplicateSetElements() {
139+
parse("#{1 1}");
140+
}
141+
91142
@Test(expected=UnsupportedOperationException.class)
92143
public void parserShouldReturnUnmodifiableListByDefault() {
93144
((List<?>)parse("(1)")).remove(0);

0 commit comments

Comments
 (0)