Skip to content

Commit 06eba66

Browse files
committed
using URI instances instead of Strings for representing URIs
1 parent 595e04f commit 06eba66

File tree

5 files changed

+106
-86
lines changed

5 files changed

+106
-86
lines changed

core/src/main/java/org/everit/json/schema/loader/SchemaLoader.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.everit.json.schema.loader;
1717

18+
import java.net.URI;
19+
import java.net.URISyntaxException;
1820
import java.util.ArrayList;
1921
import java.util.Arrays;
2022
import java.util.Collection;
@@ -86,7 +88,7 @@ public static class SchemaLoaderBuilder {
8688

8789
Map<String, ReferenceSchema.Builder> pointerSchemas = new HashMap<>();
8890

89-
String id;
91+
URI id;
9092

9193
Map<String, FormatValidator> formatValidators = new HashMap<>();
9294

@@ -118,7 +120,23 @@ public SchemaLoaderBuilder httpClient(final SchemaClient httpClient) {
118120
return this;
119121
}
120122

121-
SchemaLoaderBuilder id(final String id) {
123+
/**
124+
* Sets the initial resolution scope of the schema. {@code id} and {@code $ref} attributes
125+
* accuring in the schema will be resolved against this value.
126+
*
127+
* @param id
128+
* the initial (absolute) URI, used as the resolution scope.
129+
* @return {@code this}
130+
*/
131+
public SchemaLoaderBuilder resolutionScope(final String id) {
132+
try {
133+
return resolutionScope(new URI(id));
134+
} catch (URISyntaxException e) {
135+
throw new RuntimeException(e);
136+
}
137+
}
138+
139+
public SchemaLoaderBuilder resolutionScope(final URI id) {
122140
this.id = id;
123141
return this;
124142
}
@@ -198,7 +216,7 @@ public static Schema load(final JSONObject schemaJson) {
198216
*/
199217
public static Schema load(final JSONObject schemaJson, final SchemaClient httpClient) {
200218
String schemaId = schemaJson.optString("id");
201-
SchemaLoader loader = builder().id(schemaId)
219+
SchemaLoader loader = builder().resolutionScope(schemaId)
202220
.schemaJson(schemaJson)
203221
.httpClient(httpClient)
204222
.build();
@@ -207,7 +225,7 @@ public static Schema load(final JSONObject schemaJson, final SchemaClient httpCl
207225

208226
private final SchemaClient httpClient;
209227

210-
private String id = null;
228+
private URI id = null;
211229

212230
private final Map<String, ReferenceSchema.Builder> pointerSchemas;
213231

@@ -250,7 +268,7 @@ public SchemaLoader(final SchemaLoaderBuilder builder) {
250268
final SchemaClient httpClient) {
251269
this(builder().schemaJson(schemaJson)
252270
.rootSchemaJson(rootSchemaJson)
253-
.id(id)
271+
.resolutionScope(id)
254272
.httpClient(httpClient)
255273
.pointerSchemas(pointerSchemas));
256274
}
@@ -517,7 +535,7 @@ private Schema.Builder<?> loadForType(final Object type) {
517535
* Returns a schema builder instance after looking up the JSON pointer.
518536
*/
519537
private Schema.Builder<?> lookupReference(final String relPointerString, final JSONObject ctx) {
520-
String absPointerString = ReferenceResolver.resolve(id, relPointerString);
538+
String absPointerString = ReferenceResolver.resolve(id, relPointerString).toString();
521539
if (pointerSchemas.containsKey(absPointerString)) {
522540
return pointerSchemas.get(absPointerString);
523541
}
@@ -551,7 +569,7 @@ private boolean schemaHasAnyOf(final Collection<String> propNames) {
551569
}
552570

553571
private SchemaLoaderBuilder selfBuilder() {
554-
SchemaLoaderBuilder rval = builder().id(id).schemaJson(schemaJson)
572+
SchemaLoaderBuilder rval = builder().resolutionScope(id).schemaJson(schemaJson)
555573
.rootSchemaJson(rootSchemaJson)
556574
.pointerSchemas(pointerSchemas)
557575
.httpClient(httpClient)

core/src/main/java/org/everit/json/schema/loader/internal/ReferenceResolver.java

Lines changed: 20 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
*/
1616
package org.everit.json.schema.loader.internal;
1717

18-
import java.net.MalformedURLException;
19-
import java.net.URL;
20-
import java.util.Objects;
18+
import java.net.URI;
19+
import java.net.URISyntaxException;
2120

2221
/**
2322
* Resolves an {@code id} or {@code ref} against a parent scope.
@@ -37,63 +36,30 @@ public final class ReferenceResolver {
3736
* the new segment (complete URI, path, fragment etc) which must be resolved
3837
* @return the resolved URI
3938
*/
40-
public static String resolve(final String parentScope, final String encounteredSegment) {
41-
return new ReferenceResolver(parentScope, encounteredSegment).resolve();
42-
}
43-
44-
private final String parentScope;
45-
46-
private final String encounteredSegment;
47-
48-
private ReferenceResolver(final String parentScope, final String encounteredSegment) {
49-
this.parentScope = Objects.requireNonNull(parentScope, "parentScope cannot be null");
50-
this.encounteredSegment = Objects.requireNonNull(encounteredSegment,
51-
"encounteredSegment cannot be null");
52-
}
53-
54-
private String concat() {
55-
return parentScope + encounteredSegment;
39+
public static URI resolve(final URI parentScope, final String encounteredSegment) {
40+
return parentScope.resolve(encounteredSegment);
5641
}
5742

58-
private String handlePathIdAttr() {
59-
try {
60-
URL parentScopeURL = new URL(parentScope);
61-
StringBuilder newIdBuilder = new StringBuilder().append(parentScopeURL.getProtocol())
62-
.append("://")
63-
.append(parentScopeURL.getHost());
64-
if (parentScopeURL.getPort() > -1) {
65-
newIdBuilder.append(":").append(parentScopeURL.getPort());
66-
}
67-
newIdBuilder.append(removeTrailingSegmentFrom(parentScopeURL.getPath()))
68-
.append("/")
69-
.append(encounteredSegment);
70-
return newIdBuilder.toString();
71-
} catch (MalformedURLException e1) {
72-
return concat();
73-
}
74-
}
75-
76-
private String nonfragmentIdAttr() {
43+
/**
44+
* Creates an absolute JSON pointer string based on a parent scope and a newly encountered pointer
45+
* segment ({@code id} or {@code ref} value).
46+
*
47+
* @param parentScope
48+
* the most immediate parent scope that the resolution should be performed against
49+
* @param encounteredSegment
50+
* the new segment (complete URI, path, fragment etc) which must be resolved
51+
* @return the resolved URI
52+
*/
53+
public static String resolve(final String parentScope, final String encounteredSegment) {
7754
try {
78-
URL url = new URL(encounteredSegment);
79-
return url.toExternalForm();
80-
} catch (MalformedURLException e) {
81-
return handlePathIdAttr();
82-
}
83-
}
84-
85-
private String removeTrailingSegmentFrom(final String path) {
86-
if (path.isEmpty() || "/".equals(path)) {
87-
return "";
55+
return new URI(parentScope).resolve(encounteredSegment).toString();
56+
} catch (URISyntaxException e) {
57+
throw new RuntimeException(e);
8858
}
89-
return path.substring(0, path.lastIndexOf('/'));
59+
// return new ReferenceResolver(parentScope, encounteredSegment).resolve();
9060
}
9161

92-
private String resolve() {
93-
if (encounteredSegment.startsWith("#")) {
94-
return concat();
95-
}
96-
return nonfragmentIdAttr();
62+
private ReferenceResolver() {
9763
}
9864

9965
}

core/src/main/java/org/everit/json/schema/loader/internal/ResolutionScopeChangeListener.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,20 @@
1515
*/
1616
package org.everit.json.schema.loader.internal;
1717

18+
import java.net.URI;
1819
import java.util.function.Consumer;
1920

2021
/**
2122
* Event handler interface used by {@link TypeBasedMultiplexer} to notify client(s) (which is
2223
* currently a schema loader instance) about resolution scope changes.
2324
*/
2425
@FunctionalInterface
25-
public interface ResolutionScopeChangeListener extends Consumer<String> {
26+
public interface ResolutionScopeChangeListener extends Consumer<URI> {
2627

2728
@Override
28-
default void accept(final String t) {
29+
default void accept(final URI t) {
2930
resolutionScopeChanged(t);
3031
}
3132

32-
void resolutionScopeChanged(String newResolutionScope);
33+
void resolutionScopeChanged(URI newResolutionScope);
3334
}

core/src/main/java/org/everit/json/schema/loader/internal/TypeBasedMultiplexer.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.everit.json.schema.loader.internal;
1717

18+
import java.net.URI;
1819
import java.util.ArrayList;
1920
import java.util.Collection;
2021
import java.util.HashMap;
@@ -68,7 +69,7 @@ private class IdModifyingTypeConsumerImpl extends OnTypeConsumerImpl<JSONObject>
6869
public TypeBasedMultiplexer then(final Consumer<JSONObject> consumer) {
6970
Consumer<JSONObject> wrapperConsumer = obj -> {
7071
if (obj.has("id") && obj.get("id") instanceof String) {
71-
String origId = id;
72+
URI origId = id;
7273
String idAttr = obj.getString("id");
7374
id = ReferenceResolver.resolve(id, idAttr);
7475
triggerResolutionScopeChange();
@@ -135,7 +136,7 @@ public TypeBasedMultiplexer then(final Consumer<E> consumer) {
135136

136137
private final Object obj;
137138

138-
private String id = "";
139+
private URI id;
139140

140141
private final Collection<ResolutionScopeChangeListener> scopeChangeListeners = new ArrayList<>(1);
141142

@@ -176,10 +177,10 @@ public TypeBasedMultiplexer(final String keyOfObj, final Object obj) {
176177
* @param id
177178
* the scope id at the point where the multiplexer is initialized.
178179
*/
179-
public TypeBasedMultiplexer(final String keyOfObj, final Object obj, final String id) {
180+
public TypeBasedMultiplexer(final String keyOfObj, final Object obj, final URI id) {
180181
this.keyOfObj = keyOfObj;
181182
this.obj = Objects.requireNonNull(obj, "obj cannot be null");
182-
this.id = id == null ? "" : id;
183+
this.id = id;
183184
}
184185

185186
public void addResolutionScopeChangeListener(

core/src/test/java/org/everit/json/schema/loader/internal/TypeBasedMultiplexerTest.java

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@
1515
*/
1616
package org.everit.json.schema.loader.internal;
1717

18+
import java.net.URI;
19+
import java.net.URISyntaxException;
20+
1821
import org.everit.json.schema.SchemaException;
22+
import org.hamcrest.Description;
23+
import org.hamcrest.Matcher;
24+
import org.hamcrest.TypeSafeMatcher;
1925
import org.json.JSONArray;
2026
import org.json.JSONObject;
2127
import org.junit.Test;
@@ -29,28 +35,52 @@ public void differentPortNum() {
2935
"http://x.y.z:8080/rootschema.json");
3036
}
3137

38+
private URI uri(final String uri) {
39+
try {
40+
return new URI(uri);
41+
} catch (URISyntaxException e) {
42+
throw new RuntimeException(e);
43+
}
44+
}
45+
3246
@Test
3347
public void dispatchesIdChangeEvent() {
3448
JSONObject scopeChangingObj = objectWithId("changedId");
35-
TypeBasedMultiplexer subject = new TypeBasedMultiplexer(null, scopeChangingObj, "orig");
49+
TypeBasedMultiplexer subject =
50+
new TypeBasedMultiplexer(null, scopeChangingObj, uri("http://orig"));
3651
ResolutionScopeChangeListener mockListener = Mockito.mock(ResolutionScopeChangeListener.class);
3752
subject.addResolutionScopeChangeListener(mockListener);
3853
subject.ifObject().then(o -> {
3954
}).requireAny();
40-
Mockito.verify(mockListener).resolutionScopeChanged("origchangedId");
41-
Mockito.verify(mockListener).resolutionScopeChanged("orig");
55+
Mockito.verify(mockListener).resolutionScopeChanged(
56+
Mockito.argThat(uriAsString("http://origchangedId")));
57+
Mockito.verify(mockListener).resolutionScopeChanged(uri("http://orig"));
4258
}
4359

4460
private void expectScopeChanges(final JSONObject subjectOfMultiplexing, final String newScope,
4561
final String origScope) {
4662
TypeBasedMultiplexer subject = new TypeBasedMultiplexer(null, subjectOfMultiplexing,
47-
origScope);
63+
uri(origScope));
4864
ResolutionScopeChangeListener mockListener = Mockito.mock(ResolutionScopeChangeListener.class);
4965
subject.addResolutionScopeChangeListener(mockListener);
5066
subject.ifObject().then(o -> {
5167
}).requireAny();
52-
Mockito.verify(mockListener).resolutionScopeChanged(newScope);
53-
Mockito.verify(mockListener).resolutionScopeChanged(origScope);
68+
Mockito.verify(mockListener).resolutionScopeChanged(Mockito.argThat(uriAsString(newScope)));
69+
Mockito.verify(mockListener).resolutionScopeChanged(uri(origScope));
70+
}
71+
72+
private Matcher<URI> uriAsString(final String uri) {
73+
return new TypeSafeMatcher<URI>() {
74+
75+
@Override
76+
public void describeTo(final Description arg0) {
77+
}
78+
79+
@Override
80+
protected boolean matchesSafely(final URI actual) {
81+
return actual.toString().equals(uri);
82+
}
83+
};
5484
}
5585

5686
@Test
@@ -84,21 +114,25 @@ public void relpathThenFragment() {
84114
JSONObject innerObj = objectWithId("#bar");
85115
outerObj.put("innerObj", innerObj);
86116
TypeBasedMultiplexer outerMultiplexer = new TypeBasedMultiplexer(null, outerObj,
87-
"http://x.y.z/rootschema.json");
117+
uri("http://x.y.z/rootschema.json"));
88118
ResolutionScopeChangeListener outerListener = Mockito.mock(ResolutionScopeChangeListener.class);
89119
ResolutionScopeChangeListener innerListener = Mockito.mock(ResolutionScopeChangeListener.class);
90120
outerMultiplexer.addResolutionScopeChangeListener(outerListener);
91-
outerMultiplexer.ifObject().then(obj -> {
92-
TypeBasedMultiplexer innerMultiplexer = new TypeBasedMultiplexer(null, obj.get("innerObj"),
93-
"http://x.y.z/otherschema.json");
94-
innerMultiplexer.addResolutionScopeChangeListener(innerListener);
95-
innerMultiplexer.ifObject().then(o -> {
96-
}).requireAny();
97-
}).requireAny();
98-
Mockito.verify(outerListener).resolutionScopeChanged("http://x.y.z/otherschema.json");
99-
Mockito.verify(innerListener).resolutionScopeChanged("http://x.y.z/otherschema.json#bar");
100-
Mockito.verify(innerListener).resolutionScopeChanged("http://x.y.z/otherschema.json");
101-
Mockito.verify(outerListener).resolutionScopeChanged("http://x.y.z/rootschema.json");
121+
outerMultiplexer
122+
.ifObject()
123+
.then(
124+
obj -> {
125+
TypeBasedMultiplexer innerMultiplexer =
126+
new TypeBasedMultiplexer(null, obj.get("innerObj"),
127+
uri("http://x.y.z/otherschema.json"));
128+
innerMultiplexer.addResolutionScopeChangeListener(innerListener);
129+
innerMultiplexer.ifObject().then(o -> {
130+
}).requireAny();
131+
}).requireAny();
132+
Mockito.verify(outerListener).resolutionScopeChanged(uri("http://x.y.z/otherschema.json"));
133+
Mockito.verify(innerListener).resolutionScopeChanged(uri("http://x.y.z/otherschema.json#bar"));
134+
Mockito.verify(innerListener).resolutionScopeChanged(uri("http://x.y.z/otherschema.json"));
135+
Mockito.verify(outerListener).resolutionScopeChanged(uri("http://x.y.z/rootschema.json"));
102136
}
103137

104138
@Test

0 commit comments

Comments
 (0)