Skip to content

Commit 77fa991

Browse files
committed
Fork JsonRef for improved performance.
Pull request pending: java-json-tools/json-schema-core#24
1 parent 66ffce0 commit 77fa991

1 file changed

Lines changed: 346 additions & 0 deletions

File tree

  • src/main/scala/com/github/fge/jsonschema/core/ref
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
/*
2+
* Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com)
3+
*
4+
* This software is dual-licensed under:
5+
*
6+
* - the Lesser General Public License (LGPL) version 3.0 or, at your option, any
7+
* later version;
8+
* - the Apache Software License (ASL) version 2.0.
9+
*
10+
* The text of this file and of both licenses is available at the root of this
11+
* project or, if you have the jar distribution, in directory META-INF/, under
12+
* the names LGPL-3.0.txt and ASL-2.0.txt respectively.
13+
*
14+
* Direct link to the sources:
15+
*
16+
* - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt
17+
* - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt
18+
*/
19+
20+
package com.github.fge.jsonschema.core.ref;
21+
22+
import com.fasterxml.jackson.databind.JsonNode;
23+
import com.github.fge.jackson.jsonpointer.JsonPointer;
24+
import com.github.fge.jackson.jsonpointer.JsonPointerException;
25+
import com.github.fge.jsonschema.core.exceptions.JsonReferenceException;
26+
import com.github.fge.jsonschema.core.exceptions.ProcessingException;
27+
import com.github.fge.jsonschema.core.messages.JsonSchemaCoreMessageBundle;
28+
import com.github.fge.jsonschema.core.report.ProcessingMessage;
29+
import com.github.fge.jsonschema.core.util.URIUtils;
30+
import com.github.fge.msgsimple.bundle.MessageBundle;
31+
import com.github.fge.msgsimple.load.MessageBundles;
32+
import com.google.common.base.Optional;
33+
import com.google.common.cache.CacheBuilder;
34+
import com.google.common.cache.CacheBuilderSpec;
35+
import com.google.common.cache.CacheLoader;
36+
import com.google.common.cache.LoadingCache;
37+
38+
import javax.annotation.Nonnull;
39+
import javax.annotation.concurrent.Immutable;
40+
import java.net.URI;
41+
import java.net.URISyntaxException;
42+
import java.util.Collections;
43+
import java.util.HashMap;
44+
import java.util.Map;
45+
46+
/**
47+
* Representation of a JSON Reference
48+
*
49+
* <p><a href="http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03">JSON
50+
* Reference</a>, currently a draft, is a way to define a path within a JSON
51+
* document.</p>
52+
*
53+
* <p>To quote the draft, "A JSON Reference is a JSON object, which contains
54+
* a member named "$ref", which has a JSON string value." This string value
55+
* must be a URI. Example:</p>
56+
*
57+
* <pre>
58+
* {
59+
* "$ref": "http://example.com/example.json#/foo/bar"
60+
* }
61+
* </pre>
62+
*
63+
* <p>This class differs from the JSON Reference draft in that it accepts to
64+
* process illegal references, in the sense that they are URIs, but their
65+
* fragment parts are not JSON Pointers (in which case {@link #isLegal()}
66+
* returns {@code false}.</p>
67+
*
68+
* <p>The implementation is a wrapper over Java's {@link URI}, with the
69+
* following characteristics:</p>
70+
*
71+
* <ul>
72+
* <li>all URIs are normalized from the get go;</li>
73+
* <li>an empty fragment is equivalent to no fragment at all, and stands for
74+
* a root JSON Pointer, as required by the draft;</li>
75+
* <li>a reference is taken to be absolute if the underlying URI is absolute
76+
* <i>and</i> it has no fragment, or an empty fragment.</li>
77+
* </ul>
78+
*
79+
* <p>It also special cases the following:</p>
80+
*
81+
* <ul>
82+
* <li>an empty reference (for instance, used in anonymouns schemas);</li>
83+
* <li>URIs with the {@code jar} scheme (the resolving algorithm differs --
84+
* please note that this breaks URI resolution rules).</li>
85+
* </ul>
86+
*
87+
*/
88+
@Immutable
89+
public abstract class JsonRef
90+
{
91+
private static final MessageBundle BUNDLE
92+
= MessageBundles.getBundle(JsonSchemaCoreMessageBundle.class);
93+
94+
/**
95+
* The empty URI
96+
*/
97+
private static final URI EMPTY_URI = URI.create("");
98+
99+
/**
100+
* A "hash only" URI -- used by {@link EmptyJsonRef}
101+
*/
102+
protected static final URI HASHONLY_URI = URI.create("#");
103+
104+
/**
105+
* Whether this JSON Reference is legal
106+
*/
107+
protected final boolean legal;
108+
109+
/**
110+
* The URI, as provided by the input, with an appended empty fragment if
111+
* no fragment was provided
112+
*/
113+
protected final URI uri;
114+
115+
/**
116+
* The locator of this reference. This is the URI with an empty fragment
117+
* part.
118+
*/
119+
protected final URI locator;
120+
121+
/**
122+
* The pointer of this reference, if any
123+
*
124+
* <p>Initialized to null if the fragment part is not a JSON Pointer.</p>
125+
*
126+
* @see #isLegal()
127+
*/
128+
protected final JsonPointer pointer;
129+
130+
/**
131+
* String representation
132+
*/
133+
private final String asString;
134+
135+
/**
136+
* Hashcode
137+
*/
138+
private final int hashCode;
139+
140+
/**
141+
* Main constructor, {@code protected} by design
142+
*
143+
* @param uri the URI to build that reference
144+
*/
145+
protected JsonRef(final URI uri)
146+
{
147+
final String scheme = uri.getScheme();
148+
final String ssp = uri.getSchemeSpecificPart();
149+
/*
150+
* Account for URIs with no fragment: substitute an empty one
151+
*/
152+
final String fragment = Optional.fromNullable(uri.getFragment()).or("");
153+
154+
/*
155+
* Compute the fragment
156+
*/
157+
boolean isLegal = true;
158+
JsonPointer ptr;
159+
try {
160+
ptr = fragment.isEmpty() ? JsonPointer.empty()
161+
: new JsonPointer(fragment);
162+
} catch (JsonPointerException ignored) {
163+
ptr = null;
164+
isLegal = false;
165+
}
166+
legal = isLegal;
167+
pointer = ptr;
168+
169+
try {
170+
this.uri = new URI(scheme, ssp, fragment);
171+
locator = new URI(scheme, ssp, "");
172+
asString = this.uri.toString();
173+
hashCode = asString.hashCode();
174+
} catch (URISyntaxException e) {
175+
/*
176+
* Can't happen: we did have a legal URI to start with
177+
*/
178+
throw new RuntimeException("WTF??", e);
179+
}
180+
}
181+
182+
private static LoadingCache<URI, JsonRef> uriToJsonRef = CacheBuilder.newBuilder().maximumSize(1000).build(new CacheLoader<URI, JsonRef>() {
183+
public JsonRef load(@Nonnull final URI uri) {
184+
final URI normalized = URIUtils.normalizeURI(uri);
185+
if (HASHONLY_URI.equals(normalized) || EMPTY_URI.equals(normalized)) return EmptyJsonRef.getInstance();
186+
return "jar".equals(normalized.getScheme()) ? new JarJsonRef(normalized) : new HierarchicalJsonRef(normalized);
187+
}
188+
});
189+
190+
/**
191+
* Build a JSON Reference from a URI
192+
*
193+
* @param uri the provided URI
194+
* @return the JSON Reference
195+
* @throws NullPointerException the provided URI is null
196+
*/
197+
public static JsonRef fromURI(final URI uri)
198+
{
199+
BUNDLE.checkNotNull(uri, "jsonRef.nullURI");
200+
return uriToJsonRef.getUnchecked(uri);
201+
}
202+
203+
/**
204+
* Build a JSON Reference from a string input
205+
*
206+
* @param s the string
207+
* @return the reference
208+
* @throws JsonReferenceException string is not a valid URI
209+
* @throws NullPointerException provided string is null
210+
*/
211+
public static JsonRef fromString(final String s)
212+
throws JsonReferenceException
213+
{
214+
BUNDLE.checkNotNull(s, "jsonRef.nullInput");
215+
try {
216+
return fromURI(new URI(s));
217+
} catch (URISyntaxException e) {
218+
throw new JsonReferenceException(new ProcessingMessage()
219+
.setMessage(BUNDLE.getMessage("jsonRef.invalidURI"))
220+
.putArgument("input", s), e);
221+
}
222+
223+
}
224+
225+
/**
226+
* Return an empty reference
227+
*
228+
* <p>An empty reference is a reference which only has an empty fragment.
229+
* </p>
230+
*
231+
* @return a statically allocated empty reference
232+
*/
233+
public static JsonRef emptyRef()
234+
{
235+
return EmptyJsonRef.getInstance();
236+
}
237+
238+
/**
239+
* Return the underlying URI for this JSON Reference
240+
*
241+
* @return the URI
242+
*/
243+
public final URI toURI()
244+
{
245+
return uri;
246+
}
247+
248+
/**
249+
* Tell whether this reference is an absolute reference
250+
*
251+
* <p>See description.</p>
252+
*
253+
* @return {@code true} if the JSON Reference is absolute
254+
*/
255+
public abstract boolean isAbsolute();
256+
257+
/**
258+
* Resolve this reference against another reference
259+
*
260+
* @param other the reference to resolve
261+
* @return the resolved reference
262+
*/
263+
public abstract JsonRef resolve(final JsonRef other);
264+
265+
/**
266+
* Return this JSON Reference's locator
267+
*
268+
* <p>This returns the reference with an empty fragment, ie the URI of the
269+
* document itself.</p>
270+
*
271+
* @return an URI
272+
*/
273+
public final URI getLocator()
274+
{
275+
return locator;
276+
}
277+
278+
/**
279+
* Tell whether this JSON Reference is legal
280+
*
281+
* <p>Recall: it is legal if and only if its fragment part is a JSON
282+
* pointer.</p>
283+
*
284+
* @return {@code true} if legal
285+
* @see JsonPointer
286+
*/
287+
public final boolean isLegal()
288+
{
289+
return legal;
290+
}
291+
292+
/**
293+
* Return the fragment part of this JSON Reference as a JSON Pointer
294+
*
295+
* <p>If the reference is not legal, this returns {@code null} <b>without
296+
* further notice</b>, so beware!</p>
297+
*
298+
* @return a JSON Pointer
299+
* @see JsonPointer
300+
*/
301+
public final JsonPointer getPointer()
302+
{
303+
return pointer;
304+
}
305+
306+
/**
307+
* Tell whether the current JSON Reference "contains" another
308+
*
309+
* <p>This is considered true iif both references have the same locator,
310+
* in other words, if they differ only by their fragment part.</p>
311+
*
312+
* @param other the other reference
313+
* @return see above
314+
*/
315+
public final boolean contains(final JsonRef other)
316+
{
317+
return locator.equals(other.locator);
318+
}
319+
320+
@Override
321+
public final int hashCode()
322+
{
323+
return hashCode;
324+
}
325+
326+
@Override
327+
public final boolean equals(final Object obj)
328+
{
329+
if (obj == null)
330+
return false;
331+
if (this == obj)
332+
return true;
333+
334+
if (!(obj instanceof JsonRef))
335+
return false;
336+
337+
final JsonRef that = (JsonRef) obj;
338+
return asString.equals(that.asString);
339+
}
340+
341+
@Override
342+
public final String toString()
343+
{
344+
return asString;
345+
}
346+
}

0 commit comments

Comments
 (0)