Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * Copyright (c) 2012, Francis Galiegue <fgaliegue@gmail.com>
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the Lesser GNU General Public License as
   * published by the Free Software Foundation, either version 3 of the
   * License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * Lesser GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 package com.github.fge.jsonschema.ref;
 
 
 import java.util.List;

Implementation of IETF JSON Pointer draft, version 9

JSON Pointer is an IETF draft defining a way to address paths within JSON values (including non container values).

An individual entry of a JSON Pointer is called a reference token. For JSON Objects, a reference token is a member name. For arrays, it is an index. Indices start at 0. Note that array indices written with a leading 0 are considered to be failing (ie, 0 is OK but 00 is not).

The general syntax is /reference/tokens/here. A JSON Pointer may be empty, in which case this refers to the JSON value itself.

The difficulty solved by JSON Pointer is that any JSON String is valid as an object member name. These are all valid object member names, and all of them can be addressed by using JSON Pointer:

  • "" -- the empty string;
  • "/";
  • "0";
  • "-1";
  • ".", "..", "../..".

The latter example is the reason why a JSON Pointer is always absolute.

All instances of this class are immutable (and therefore thread safe).

 
 
 public final class JsonPointer
     extends JsonFragment
 {
    
The empty pointer

This is what will be returned by JsonFragment.fromFragment(java.lang.String) if the submitted fragment is empty.

 
     private static final JsonPointer EMPTY
         = new JsonPointer("", ImmutableList.<String>of());

    
Reference token separator
 
     private static final CharMatcher SLASH = CharMatcher.is('/');

    
Escape character in a "cooked" element
 
     private static final CharMatcher ESCAPE_CHAR = CharMatcher.is('~');

    
"0": for array index reference token needs
 
     private static final CharMatcher ZERO = CharMatcher.is('0');

    
Replacement map for getting a raw reference token out of a cooked one

~0 becomes ~ and ~1 becomes /.

This is a com.google.common.collect.BiMap so that it can also be used in the reverse situation.

    private static final BiMap<CharacterCharacterESCAPE_REPLACEMENT_MAP
        = new ImmutableBiMap.Builder<CharacterCharacter>()
            .put('0''~')
            .put('1''/')
            .build();

    
Character matcher for an escaped reference token

This is built from ESCAPE_REPLACEMENT_MAP's keys.

    private static final CharMatcher ESCAPED;

    
Character matcher for a raw reference token

This is built from ESCAPE_REPLACEMENT_MAP's values.

    private static final CharMatcher SPECIAL;
    static {
        CharMatcher escaped = .special = .;
        for (final Character c1.keySet())
            escaped = escaped.or(CharMatcher.is(c1));
        for (final Character c2.values())
            special = special.or(CharMatcher.is(c2));
         = escaped.precomputed();
         = special.precomputed();
    }

    
The list of individual reference tokens, in order.
    private final List<StringrefTokens;

    
Return an empty pointer

Returns:
a statically created empty JSON Pointer
    public static JsonPointer empty()
    {
        return ;
    }

    
Constructor

Parameters:
input The input string, guaranteed not to be JSON encoded
Throws:
com.github.fge.jsonschema.main.JsonSchemaException illegal JSON Pointer
    public JsonPointer(final String input)
        throws JsonSchemaException
    {
        super(input);
        final ImmutableList.Builder<Stringbuilder = ImmutableList.builder();
        decode(inputbuilder);
         = builder.build();
    }

    
Private constructor for building a pointer with all pointer elements (reference tokens, full string representation) already computed

Parameters:
fullPointer the pointer as a string
refTokens the reference tokens
    private JsonPointer(final String fullPointerfinal List<StringrefTokens)
    {
        super(fullPointer);
        this. = refTokens;
    }

    
Static private constructor to build a pointer out of a list of reference tokens

Parameters:
refTokens the list of reference tokens
Returns:
a newly constructed JSON Pointer
    private static JsonPointer fromElements(final List<StringrefTokens)
    {
        if (refTokens.isEmpty())
            return empty();
        final StringBuilder sb = new StringBuilder();
        for (final String rawrefTokens)
            sb.append('/').append(refTokenEncode(raw));
        return new JsonPointer(sb.toString(), refTokens);
    }

    
Append a pointer to the current pointer

Parameters:
other the other pointer
Returns:
a new instance with the pointer appended
    public JsonPointer append(final JsonPointer other)
    {
        final List<StringnewElements = ImmutableList.<String>builder()
            .addAll().addAll(other.refTokens).build();
        if (newElements.isEmpty())
            return empty();
        final String newPath =  + other.asString;
        return new JsonPointer(newPathnewElements);
    }

    
Append a reference token as a string to this pointer.

Parameters:
element the element to append
Returns:
a new instance with the element appended
    public JsonPointer append(final String element)
    {
        final List<StringnewElements = ImmutableList.<String>builder()
            .addAll().add(element).build();
        return new JsonPointer( + '/' + refTokenEncode(element),
            newElements);
    }

    
Append an array index to this pointer.

Note that the index validity is NOT checked for (ie, you can append -1 if you want to -- don't do that)

Parameters:
index the index to add
Returns:
a new instance with the index appended
    public JsonPointer append(final int index)
    {
        return append(Integer.toString(index));
    }

    
Apply the pointer to a JSON value and return the result

If the pointer fails to look up a value, a com.fasterxml.jackson.databind.node.MissingNode is returned.

Parameters:
node the node to apply the pointer to
Returns:
the resulting node
    @Override
    public JsonNode resolve(final JsonNode node)
    {
        JsonNode ret = node;
        for (final String pathElement : ) {
            if (!ret.isContainerNode())
                return MissingNode.getInstance();
            ret = ret.isObject()
                ? ret.path(pathElement)
                : ret.path(arrayIndexFor(pathElement));
            if (ret.isMissingNode())
                break;
        }
        return ret;
    }
    @Override
    public boolean isEmpty()
    {
        return .isEmpty();
    }
    @Override
    public boolean isPointer()
    {
        return true;
    }

    
Return this pointer as a series of JSON Pointers starting from the beginning

Returns:
an unmodifiable list of JSON Pointers
    public List<JsonPointerasElements()
    {
        final ImmutableList.Builder<JsonPointerbuilder
            = ImmutableList.builder();
        for (final String raw)
            builder.add(fromElements(ImmutableList.of(raw)));
        return builder.build();
    }

    
Return true if this JSON Pointer is "parent" of another one

That is, its number of reference tokens is less than, or equal to, the other pointer's, and its first reference tokens are the same.

This means that this will also return true if the pointers are equal.

Parameters:
other the other pointer
Returns:
true if this pointer is the parent of the other
    public boolean isParentOf(final JsonPointer other)
    {
        return Collections.indexOfSubList(other.refTokens) == 0;
    }

    
Relativize a pointer to the current pointer

If isParentOf(com.github.fge.jsonschema.ref.JsonPointer) returns false, this will return the other pointer.

Otherwise, it will return a pointer containing all reference tokens following this pointer's reference tokens. For instance, relativizing /a/b against /a/b/c gives /c.

If the pointers are the same, it will return an empty pointer.

Parameters:
other the pointer to relativize this pointer to
Returns:
a relativized pointer
    public JsonPointer relativize(final JsonPointer other)
    {
        if (!isParentOf(other))
            return other;
        final List<Stringlist = other.refTokens.subList(.size(),
            other.refTokens.size());
        return fromElements(list);
    }

    
Initialize the object

We read the string sequentially, a slash, then a reference token, then a slash, etc. Bail out if the string is malformed.

Parameters:
input Input string, guaranteed not to be JSON/URI encoded
builder the list builder
Throws:
com.github.fge.jsonschema.main.JsonSchemaException the input is not a valid JSON Pointer
    private static void decode(final String input,
        final ImmutableList.Builder<Stringbuilder)
        throws JsonSchemaException
    {
        String cookedraw;
        String victim = input;
        while (!victim.isEmpty()) {
            /*
             * Skip the /
             */
            if (!victim.startsWith("/")) {
                final Message.Builder msg
                    = newMsg("reference token not preceeded by '/'");
                throw new JsonSchemaException(msg.build());
            }
            victim = victim.substring(1);
            /*
             * Grab the "cooked" reference token
             */
            cooked = getNextRefToken(victim);
            victim = victim.substring(cooked.length());
            /*
             * Decode it, push it in the refTokens list
             */
            raw = refTokenDecode(cooked);
            builder.add(raw);
        }
    }

    
Grab a (cooked) reference token from an input string

This method is only called from decode(java.lang.String,com.google.common.collect.ImmutableList.Builder), after a delimiter (/) has been swallowed up. The input string is therefore guaranteed to start with a reference token, which may be empty.

Parameters:
input the input string
Returns:
the cooked reference token
Throws:
com.github.fge.jsonschema.main.JsonSchemaException the string is malformed
    private static String getNextRefToken(final String input)
        throws JsonSchemaException
    {
        final StringBuilder sb = new StringBuilder();
        final char[] array = input.toCharArray();
        /*
         * If we encounter a /, this is the end of the current token.
         *
         * If we encounter a ~, ensure that what follows is either 0 or 1.
         *
         * If we encounter any other character, append it.
         */
        boolean inEscape = false;
        for (final char carray) {
            if (inEscape) {
                if (!.matches(c)) {
                    final Message.Builder msg = newMsg("bad escape sequence: " +
                        "'~' not followed by a valid token")
                        .addInfo("allowed".keySet())
                        .addInfo("found", Character.valueOf(c));
                    throw new JsonSchemaException(msg.build());
                }
                sb.append(c);
                inEscape = false;
                continue;
            }
            if (.matches(c))
                break;
            if (.matches(c))
                inEscape = true;
            sb.append(c);
        }
        if (inEscape)
            throw new JsonSchemaException(newMsg("bad escape sequence: '~' " +
                "not followed by any token").build());
        return sb.toString();
    }

    
Turn a cooked reference token into a raw reference token

This means we replace all occurrences of ~0 with ~, and all occurrences of ~1 with /.

It is called from decode(java.lang.String,com.google.common.collect.ImmutableList.Builder), in order to push a reference token into refTokens.

Parameters:
cooked the cooked token
Returns:
the raw token
    private static String refTokenDecode(final String cooked)
    {
        final StringBuilder sb = new StringBuilder(cooked.length());
        /*
         * Replace all occurrences of "~0" with "~", and all occurrences of "~1"
         * with "/".
         *
         * The input is guaranteed to be well formed.
         */
        final char[] array = cooked.toCharArray();
        boolean inEscape = false;
        for (final char carray) {
            if (.matches(c)) {
                inEscape = true;
                continue;
            }
            if (inEscape) {
                sb.append(.get(c));
                inEscape = false;
            } else
                sb.append(c);
        }
        return sb.toString();
    }

    
Make a cooked reference token out of a raw reference token

Parameters:
raw the raw token
Returns:
the cooked token
    private static String refTokenEncode(final String raw)
    {
        final StringBuilder sb = new StringBuilder(raw.length());
        /*
         * Replace all occurrences of "~" with "~0" and all occurrences of "/"
         * with "~1".
         */
        final char[] array = raw.toCharArray();
        for (final char carray)
            if (.matches(c))
                sb.append('~').append(.inverse().get(c));
            else
                sb.append(c);
        return sb.toString();
    }
    private static Message.Builder newMsg(final String reason)
    {
        return ..newMessage().setKeyword("$ref")
            .setMessage("illegal JSON Pointer").addInfo("reason"reason);
    }

    
Return an array index corresponding to the given reference token

If no array index can be found, -1 is returned. As the result is used with com.fasterxml.jackson.databind.JsonNode.path(int), we are guaranteed correct results, since this will return a com.fasterxml.jackson.databind.node.MissingNode in this case.

Parameters:
pathElement the path element as a string
Returns:
the index, or -1 if the index is invalid
    private static int arrayIndexFor(final String pathElement)
    {
        /*
         * Empty? No dice.
         */
        if (pathElement.isEmpty())
            return -1;
        /*
         * Leading zeroes are not allowed in number-only refTokens for arrays.
         * But then, 0 followed by anything else than a number is invalid as
         * well. So, if the string starts with '0', return 0 if the token length
         * is 1 or -1 otherwise.
         */
        if (.matches(pathElement.charAt(0)))
            return pathElement.length() == 1 ? 0 : -1;
        /*
         * Otherwise, parse as an int. If we can't, -1.
         */
        try {
            return Integer.parseInt(pathElement);
        } catch (NumberFormatException ignored) {
            return -1;
        }
    }
New to GrepCode? Check out our FAQ X