com.github.fge.jackson.jsonpointer.TreePointer Maven / Gradle / Ivy
/*
* Copyright (c) 2014, Francis Galiegue ([email protected])
*
* This software is dual-licensed under:
*
* - the Lesser General Public License (LGPL) version 3.0 or, at your option, any
* later version;
* - the Apache Software License (ASL) version 2.0.
*
* The text of this file and of both licenses is available at the root of this
* project or, if you have the jar distribution, in directory META-INF/, under
* the names LGPL-3.0.txt and ASL-2.0.txt respectively.
*
* Direct link to the sources:
*
* - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt
* - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt
*/
package com.github.fge.jackson.jsonpointer;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.node.MissingNode;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.github.fge.msgsimple.bundle.MessageBundle;
import com.github.fge.msgsimple.load.MessageBundles;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import javax.annotation.concurrent.ThreadSafe;
import java.util.Iterator;
import java.util.List;
/**
* A pointer into a {@link TreeNode}
*
* Note that all pointers are absolute: they start from the root of
* the tree. This is to mirror the behaviour of JSON Pointer proper.
*
* The class does not decode a JSON Pointer representation itself; however
* it provides all the necessary methods for implementations to achieve this.
*
*
* This class has two traversal methods: {@link #get(TreeNode)} and {@link
* #path(TreeNode)}. The difference between both is that {@code path()} may
* return another node than {@code null} if the tree representation has such
* a node. This is the case, for instance, for {@link JsonNode}, which has a
* {@link MissingNode}.
*
* At the core, this class is essentially a(n ordered!) {@link List} of
* {@link TokenResolver}s (which is iterable via the class itself).
*
* Note that this class' {@link #hashCode()}, {@link #equals(Object)} and
* {@link #toString()} are final.
*
* @param the type of the tree
*/
@ThreadSafe
@JsonSerialize(using = ToStringSerializer.class)
public abstract class TreePointer
implements Iterable>
{
protected static final MessageBundle BUNDLE
= MessageBundles.getBundle(JsonPointerMessages.class);
/**
* The reference token separator
*/
private static final char SLASH = '/';
/**
* What this tree can see as a missing node (may be {@code null})
*/
private final T missing;
/**
* The list of token resolvers
*/
protected final List> tokenResolvers;
/**
* Main protected constructor
*
* This constructor makes an immutable copy of the list it receives as
* an argument.
*
* @param missing the representation of a missing node (may be null)
* @param tokenResolvers the list of reference token resolvers
*/
protected TreePointer(final T missing,
final List> tokenResolvers)
{
this.missing = missing;
this.tokenResolvers = ImmutableList.copyOf(tokenResolvers);
}
/**
* Alternate constructor
*
* This is the same as calling {@link #TreePointer(TreeNode, List)} with
* {@code null} as the missing node.
*
* @param tokenResolvers the list of token resolvers
*/
protected TreePointer(final List> tokenResolvers)
{
this(null, tokenResolvers);
}
/**
* Traverse a node and return the result
*
* Note that this method shortcuts: it stops at the first node it cannot
* traverse.
*
* @param node the node to traverse
* @return the resulting node, {@code null} if not found
*/
public final T get(final T node)
{
T ret = node;
for (final TokenResolver tokenResolver: tokenResolvers) {
if (ret == null)
break;
ret = tokenResolver.get(ret);
}
return ret;
}
/**
* Traverse a node and return the result
*
* This is like {@link #get(TreeNode)}, but it will return the missing
* node if traversal fails.
*
* @param node the node to traverse
* @return the result, or the missing node
* @see #TreePointer(TreeNode, List)
*/
public final T path(final T node)
{
final T ret = get(node);
return ret == null ? missing : ret;
}
/**
* Tell whether this pointer is empty
*
* @return true if the reference token list is empty
*/
public final boolean isEmpty()
{
return tokenResolvers.isEmpty();
}
@Override
public final Iterator> iterator()
{
return tokenResolvers.iterator();
}
@Override
public final int hashCode()
{
return tokenResolvers.hashCode();
}
@Override
public final boolean equals(final Object obj)
{
if (obj == null)
return false;
if (this == obj)
return true;
if (getClass() != obj.getClass())
return false;
final TreePointer> other = (TreePointer>) obj;
return tokenResolvers.equals(other.tokenResolvers);
}
@Override
public final String toString()
{
final StringBuilder sb = new StringBuilder();
/*
* This works fine: a TokenResolver's .toString() always returns the
* cooked representation of its underlying ReferenceToken.
*/
for (final TokenResolver tokenResolver: tokenResolvers)
sb.append('/').append(tokenResolver);
return sb.toString();
}
/**
* Decode an input into a list of reference tokens
*
* @param input the input
* @return the list of reference tokens
* @throws JsonPointerException input is not a valid JSON Pointer
* @throws NullPointerException input is null
*/
protected static List tokensFromInput(final String input)
throws JsonPointerException
{
String s = BUNDLE.checkNotNull(input, "nullInput");
final List ret = Lists.newArrayList();
String cooked;
int index;
char c;
// TODO: see how this can be replaced with a CharBuffer -- seek etc
while (!s.isEmpty()) {
c = s.charAt(0);
if (c != SLASH)
throw new JsonPointerException(BUNDLE.getMessage("notSlash"));
s = s.substring(1);
index = s.indexOf(SLASH);
cooked = index == -1 ? s : s.substring(0, index);
ret.add(ReferenceToken.fromCooked(cooked));
if (index == -1)
break;
s = s.substring(index);
}
return ret;
}
}