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 8

JSON Pointer is an IETF draft defining a way to address paths within JSON documents. Paths apply to containers, ie arrays or nodes. For objects, path elements are member names. For arrays, they are indices in the array (starting from 0).

The general syntax is #/path/elements/here. A path element is referred to as a "reference token" in the specification.

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 thread safe and immutable.

 
 
 public final class JsonPointer
     extends JsonFragment
 {
     private static final CharMatcher SLASH = CharMatcher.is('/');
     private static final CharMatcher ESCAPE_CHAR = CharMatcher.is('~');
 
     private static final BiMap<CharacterCharacterESCAPE_REPLACEMENT_MAP
         = new ImmutableBiMap.Builder<CharacterCharacter>()
             .put('0''~')
             .put('1''/')
             .build();
 
     private static final CharMatcher ESCAPED = CharMatcher.anyOf("01");
     private static final CharMatcher SPECIAL = CharMatcher.anyOf("~/");
     private static final CharMatcher ZERO = CharMatcher.is('0');

    
The list of individual elements in the pointer.
 
     private final List<Stringelements;

    
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 JsonPointer(final String fullPointerfinal List<Stringelements)
    {
        super(fullPointer);
        this. = elements;
    }

    
Append a path element to this pointer. Returns a new instance.

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

    
Append an array index to this pointer. Returns a new instance.

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));
    }
    @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;
    }

    
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 elements 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 token into elements.

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 element 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 path element

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 elements 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