/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2008-2013 Palo Alto Research Center, Inc.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
* This library 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 GNU
* Lesser General Public License for more details. You should have received
* a copy of the GNU Lesser General Public License along with this library;
* if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
* Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.ccnx.ccn.protocol;
import java.io.Serializable;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import org.ccnx.ccn.impl.encoding.CCNProtocolDTags;
import org.ccnx.ccn.impl.encoding.GenericXMLEncodable;
import org.ccnx.ccn.impl.encoding.XMLDecoder;
import org.ccnx.ccn.impl.encoding.XMLEncodable;
import org.ccnx.ccn.impl.encoding.XMLEncoder;
import org.ccnx.ccn.impl.support.DataUtils;
import org.ccnx.ccn.io.content.ContentDecodingException;
import org.ccnx.ccn.io.content.ContentEncodingException;
/**
* ContentNames consist of a sequence of byte[] components which may not
* be assumed to follow any string encoding, or any other particular encoding.
*
* Warning: This class is designed to be immutable. Some methods expose the internal
* byte[] components. You must be careful not to change the contents of these byte[]
* when you receive the values back from this class.
*/
public class ContentName extends GenericXMLEncodable implements XMLEncodable, Comparable<ContentName>, Serializable, ContentNameProvider, Iterable<byte []> {
private static final long serialVersionUID = 2754391169423477552L;
/**
* Official CCN URI scheme.
*/
public static final String SCHEME = "ccnx:";
/**
* This scheme has been deprecated, but we still want to accept it.
*/
public static final String ORIGINAL_SCHEME = "ccn:";
public static final String SEPARATOR = "/";
private static final byte[][] empty = new byte[][]{ };
public static final ContentName ROOT = new ContentName();
// Goal is to make this final in the future.
protected byte[][] _components;
// Constructors
/**
* Will become private in future. Today used together with {@link #decode(XMLDecoder)}
* to decode a ContentName. In the future there will be a XMLDecoder constructor used for decoding.
*/
public ContentName() {
_components = empty;
}
// support for name builder methods
/**
* Allows a class to be included as an argument to a ContentName builder.
* @see builder
*/
public interface ComponentProvider {
/**
* @return Fetches a component. The byte array that is returned must be immutable - i.e. never get changed.
* If there is any risk the byte array you have may be changed you must return a new copy of it.
*/
public byte[] getComponent();
}
@Deprecated
protected ContentName(ArrayList<byte[]> components) {
_components = components.toArray(new byte[components.size()][]);
}
/**
* This Constructor is required to avoid the varargs constructor interpreting
* a single byte[] as a list of separate byte arguments.
* @param component a single component. The data is cloned, so there is no restriction on
* its use after this call.
*/
public ContentName(byte[] component) {
_components = new byte[][] { component.clone() };
}
/**
* Varargs name builder, Strings always represent a single component, interpreted as UTF8.
* Varargs name builder. Convenience method to allow ContentNames to be constructed from multiple parts.
* @param stringParser method to call to parse String arguments
* @param args Any number of null, byte[], String, ContentNameProvider or ComponentProvider arguments
* @return an ArrayList of components built by assembling components from all the arguments passed in.
* @throws MalformedContentNameStringException if a String argument does not parse correctly when passed to stringParser
*/
public ContentName(Object... args) {
int componentCount = 0;
// first make 1 pass through the arguments validating them,
// converting them to either byte[] or byte[][]
// and determining the final component count.
for(int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg instanceof byte[]) {
// incoming byte[] needs to be cloned to ensure ContentName's immutability
byte[] component = (byte[]) arg;
componentCount++;
args[i] = component.clone();
} else if (arg instanceof ContentNameProvider) {
ContentName name = ((ContentNameProvider) arg).getContentName();
componentCount += name._components.length;
args[i] = name._components;
} else if (arg instanceof String) {
String str = (String) arg;
args[i] = str.getBytes();
componentCount ++;
} else if (arg instanceof ContentName.ComponentProvider) {
ContentName.ComponentProvider p = (ContentName.ComponentProvider) arg;
componentCount++;
args[i] = p.getComponent();
} else
throw new IllegalArgumentException("Argument " + i+1 + " is " + (arg==null?"null":("a " + arg.getClass().getSimpleName())));
}
// allocate an array for the components
_components = new byte[componentCount][];
// and collect the components into the array
// now the args must be either byte[], byte[][] or null.
int c = 0;
for(Object arg : args) {
if (arg instanceof byte[]) {
_components[c++] = (byte[]) arg;
} else if (arg != null) {
for(byte[] component : (byte[][]) arg)
_components[c++] = component;
}
}
}
/*
* Varargs calls pay a small speed penalty, since the args have to be wrapped up
* in an Object[], costing a heap allocation. A very common case is taking a
* ContentName and adding a single item to it. For this case explicit non varargs
* methods are provided, to avoid the speed & allocation penalty of the varargs call.
*/
public ContentName(ContentName parent, byte[] component) {
if (parent == null)
parent = ROOT;
if (component == null) {
_components = parent._components;
return;
}
_components = new byte[parent._components.length+1][];
System.arraycopy(parent._components, 0, _components, 0, parent._components.length);
_components[parent._components.length] = component;
}
public ContentName(ContentName parent, ContentNameProvider cnp) {
if (parent == null)
parent = ROOT;
if (cnp == null) {
_components = parent._components;
return;
}
ContentName cn = cnp.getContentName();
_components = new byte[parent._components.length+cn._components.length][];
System.arraycopy(parent._components, 0, _components, 0, parent._components.length);
System.arraycopy(cn._components, 0, _components, parent._components.length, cn._components.length);
}
public ContentName(ContentName parent, String component) {
if (parent == null)
parent = ROOT;
if (component == null) {
_components = parent._components;
return;
}
_components = new byte[parent._components.length+1][];
System.arraycopy(parent._components, 0, _components, 0, parent._components.length);
_components[parent._components.length] = component.getBytes();
}
public ContentName(ContentName parent, ComponentProvider cprov) {
if (parent == null)
parent = ROOT;
if (cprov == null) {
_components = parent._components;
return;
}
_components = new byte[parent._components.length+1][];
System.arraycopy(parent._components, 0, _components, 0, parent._components.length);
_components[parent._components.length] = cprov.getComponent();
}
public final ContentName getContentName() {
return this;
}
/**
* This method is here to avoid a warning when passing a byte[][]
* parameter directly into the varargs constructor.
*/
public ContentName(byte components[][]) {
this((Object[]) components);
}
/**
* Constructor given another ContentName, appends extra components.
* @param parent used for the base of the name.
* @param childComponents components to be appended.
* @deprecated superseded by the {@link ContentName#ContentName(Object...)} varargs constructor.
*/
@Deprecated
public ContentName(ContentName parent, byte [][] childComponents) {
_components = new byte[parent._components.length + childComponents.length][];
System.arraycopy(parent._components, 0, _components, 0, parent._components.length);
System.arraycopy(childComponents, 0, _components, parent._components.length, childComponents.length);
}
/**
* Now that components() returns an ArrayList<byte []>, make a constructor that takes that
* as input.
* @param parent used for the base of the name.
* @param childComponents the additional name components to add at the end of parent
* @deprecated superseded by the {@link ContentName#ContentName(Object...)} varargs constructor.
*/
@Deprecated
public ContentName(ContentName parent, ArrayList<byte []> childComponents) {
_components = new byte[parent._components.length + childComponents.size()][];
System.arraycopy(parent._components, 0, _components, 0, parent._components.length);
for(int i = parent._components.length, j = 0; j < childComponents.size(); i++, j++)
_components[i] = childComponents.get(j);
}
/**
* parent is base name, then add components from childComponets starting
* at index "start".
* @param parent used for the base of the name.
* @param start index in childComponents to begin adding from
* @param childComponents the additional name components to add at the end of parent
* @deprecated use {@link ContentName#right(int)} and the {@link ContentName#ContentName(Object...)}
* varargs constructor instead.
*/
@Deprecated
public ContentName(ContentName parent, int start, ArrayList<byte []> childComponents) {
_components = new byte[parent._components.length + childComponents.size() - start][];
System.arraycopy(parent._components, 0, _components, 0, parent._components.length);
for(int i = parent._components.length, j = start; j < childComponents.size(); i++, j++)
_components[i] = childComponents.get(j);
}
/**
* Constructor for extending or contracting names.
* @param count only this number of name components are taken from components.
* @param components
* @deprecated Use {@link ContentName#cut(int)} with a ContentName instead.
*/
@Deprecated
public ContentName(int count, byte components[][]) {
if (0 >= count) {
_components = new byte[0][];
} else {
int max = (null == components) ? 0 :
((count > components.length) ?
components.length : count);
_components = new byte[count][];
for (int i=0; i < max; ++i) {
_components[i] = components[i].clone();
}
}
}
/**
* Constructor for extending or contracting names.
* Performs a faster shallow copy of the components, as we don't tend to alter name components
* once created.
* @param count Only this number of name components are copied into the new name.
* @param components These are the name components to be copied. Can be null, empty, or longer or shorter than count.
* @deprecated Use {@link ContentName#cut(int)} instead.
*/
@Deprecated
public ContentName(int count, ArrayList<byte []>components) {
if (0 >= count) {
_components = new byte[0][];
} else {
int max = (null == components) ? 0 :
((count > components.size()) ?
components.size() : count);
_components = new byte[count][];
for (int i=0; i < max; ++i) {
_components[i] = components.get(i).clone();
}
}
}
/**
* Subname constructor for extending or contracting names, extracts particular
* subcomponents from an existing set.
* Performs a faster shallow copy of the components, as we don't tend to alter name components
* once created.
* @param start This is index (0-based) of the first component to copy.
* @param count Only this number of name components are copied into the new name. If count-start is
* greater than the last component in the components array, only copies count-start.
* @param components These are the name components to be copied. Can be null, empty, or longer or shorter than count.
* @deprecated Use {@link ContentName#subname(int, int)} instead.
*/
@Deprecated
public ContentName(int start, int count, ArrayList<byte []>components) {
if (0 >= count) {
_components = new byte[0][];
} else {
int max = (null == components) ? 0 :
((count > (components.size()-start)) ?
(components.size()-start) : count);
_components = new byte[max][];
for (int i=start; i < max+start; ++i) {
_components[i] = components.get(i).clone();
}
}
}
/**
* Copy constructor, should not generally be needed since ContentNames are
* immutable. Used by subclasses merely wanting to converta a ContentName into
* a different type of name for encoding/decoding.
*/
public ContentName(ContentName otherName) {
_components = otherName._components;
}
/**
* Return the <code>ContentName</code> represented by the given URI.
* A CCN <code>ContentName</code> consists of a sequence of binary components
* of any length (including 0), which allows such things as encrypted
* name components. It is often convenient to work with string
* representations of names in various forms.
* <p>
* The canonical String representation of a CCN <code>ContentName</code> is a
* URI encoding of the name according to RFC 3986 with the addition
* of special treatment for name components of 0 length or containing
* only one or more of the byte value 0x2E, which is the US-ASCII
* encoding of '.'. The major features of the URI encoding are the
* use of a limited set of characters and the use of percent-encoding
* to encode all other byte values. The combination of percent-encoding
* and special treatment for certain name components allows the
* canonical CCN string representation to encode all possible CCN names.
* <p>
* The legal characters in the URI are limited to the <i>unreserved</i> characters
* "a" through "z", "A" through "Z", "0" through "9", and "-", "_", ".", and "~"
* plus the <i>reserved</i> delimiters "!", "$" "&", "'", "(", ")",
* "*", "+", ",", ";", "=".
* The reserved delimiter "/" is a special case interpreted as component separator and so
* may not be used within a component unescaped.
* Any query (starting '?') or fragment (starting '#') is ignored which means that these
* reserved delimiters must be percent-encoded if they are to be part of the name.
* <p>
* The URI must begin with either the "/" delimiter or the scheme specification "ccnx:"
* plus delimiter to make URI absolute.
* <p>
* The decoding from a URI String to a ContentName translates each legal
* character to its US-ASCII byte encoding, except for the "." which is subject
* to special handling described below. Any other byte value in a component
* (including those corresponding to "/" and ":") must be percent-encoded in
* the URI. Any character sequence starting with "?" or "#" is discarded (to the
* end of the component).
* <p>
* The resolution rules for relative references are applied in this
* decoding:
* <ul>
* <li> "//" in the URI is interpreted as "/"
* <li> "/./" and "/." in the URI are interpreted as "/" and ""
* <li> "/../" and "/.." in the URI are interpreted as removing the preceding component
* </ul>
* <p>
* Any component of 0 length, or containing only one or more of the byte
* value 0x2E ("."), is represented in the URI by one "." per byte plus the
* suffix "..." which provides unambiguous representation of all possible name
* components in conjunction with the use of the resolution rules given above.
* Thus the decoding from URI String to ContentName makes conversions such as:
* <ul>
* <li> "/.../" in the URI is converted to a 0-length name component
* <li> "/..../" in the URI is converted to the name component {0x2E}
* <li> "/...../" in the URI is converted to the name component {0x2E, 0x2E}
* <li> "/....../" in the URI is converted to the name component {0x2E, 0x2E, 0x2E}
* </ul>
* <p>
* Note that this URI encoding is very similar to but not the same as the
* application/x-www-form-urlencoded MIME format that is used by the Java
* java.net.URLDecoder.
*
* TODO: Inconsistent with C lib in that it does not strip authority part
* TODO: Inconsistent with C lib in that it does not fully strip query and fragment parts (within component only)
* @param name
* @return
* @throws MalformedContentNameStringException
*/
public static ContentName fromURI(String name) throws MalformedContentNameStringException {
ContentName result;
if ((name == null) || (name.length() == 0)) {
return ROOT;
}
try {
if (!name.startsWith(SEPARATOR)){
if ((!name.startsWith(SCHEME + SEPARATOR)) && (!name.startsWith(ORIGINAL_SCHEME + SEPARATOR))) {
throw new MalformedContentNameStringException("ContentName strings must begin with " + SEPARATOR + " or " + SCHEME + SEPARATOR);
}
if (name.startsWith(SCHEME)) {
name = name.substring(SCHEME.length());
} else if (name.startsWith(ORIGINAL_SCHEME)) {
name = name.substring(ORIGINAL_SCHEME.length());
}
}
String[] parts = name.split(SEPARATOR);
if (parts.length == 0) {
// We've been asked to parse the root name.
return ROOT;
}
ArrayList<byte []> comps = new ArrayList<byte []>(parts.length -1);
// Leave off initial empty component
for (int i=1; i < parts.length; ++i) {
try {
byte[] component = Component.parseURI(parts[i]);
if (null != component) {
comps.add(component);
}
} catch (Component.DotDot c) {
// Need to strip "parent"
if (comps.isEmpty()) {
throw new MalformedContentNameStringException("ContentName string contains too many .. components: " + name);
} else {
comps.remove(comps.size()-1);
}
}
}
result = new ContentName();
result._components = comps.toArray(new byte[comps.size()][]);
return result;
} catch (URISyntaxException e) {
throw new MalformedContentNameStringException(e.getMessage());
}
}
/**
* Given an array of strings, apply URI decoding and create a ContentName
* @see fromURI(String)
* @throws MalformedContentNameStringException
* @deprecated Use {@link #fromURI(String)} or {@link #fromURI(Object...)} instead.
* This is method creates an inconsistency in the API (Strings are components, not paths, unlike
* other fromURI methods), hence is deprecated and will be removed in future.
*/
@Deprecated
public static ContentName fromURI(String parts[]) throws MalformedContentNameStringException {
if ((parts == null) || (parts.length == 0)) {
return ROOT;
}
try {
ArrayList<byte []> comps = new ArrayList<byte []>(parts.length);
for (String str : parts) {
try {
byte[] component = Component.parseURI(str);
if (component != null) {
comps.add(component);
}
} catch (Component.DotDot c) {
// Need to strip "parent"
if (comps.isEmpty()) {
throw new MalformedContentNameStringException("ContentName parts contains too many .. components");
} else {
comps.remove(comps.size()-1);
}
}
}
ContentName result = new ContentName();
result._components = comps.toArray(new byte[comps.size()][]);
return result;
} catch (URISyntaxException e) {
throw new MalformedContentNameStringException(e.getMessage());
}
}
/**
* Return the <code>ContentName</code> created by appending one component
* to the supplied parent. The new component is converted from URI
* string encoding.
* @see fromURI(String)
* @param parent used for the base of the name.
* @param name sequence of URI encoded name components, appended to the base.
* @throws MalformedContentNameStringException
* @Deprecated Use new {@link ContentName#ContentName(ContentName, String)} or {@link Component#parseURI(String)}
* and new {@link ContentName#ContentName(ContentName, byte[])} for more exact semantic match.
*/
@Deprecated
public static ContentName fromURI(ContentName parent, String name) throws MalformedContentNameStringException {
if (name == null)
return parent;
try {
ContentName result = new ContentName();
try {
byte[] decodedName = Component.parseURI(name);
if (decodedName == null)
return parent;
// add a single component
result._components = new byte[parent._components.length + 1][];
System.arraycopy(parent._components, 0, result._components, 0, parent._components.length);
result._components[parent._components.length] = decodedName;
} catch (Component.DotDot c) {
// Need to strip "parent"
if (result._components.length < 1) {
throw new MalformedContentNameStringException("ContentName parts contains too many .. components");
} else {
result._components = new byte[parent._components.length - 1][];
System.arraycopy(parent._components, 0, result._components, 0, parent._components.length - 1);
}
}
return result;
} catch (URISyntaxException e) {
throw new MalformedContentNameStringException(e.getMessage());
}
}
/**
* Return the <code>ContentName</code> created from a native Java String.
* In native strings only "/" is special, interpreted as component delimiter,
* while all other characters will be encoded as UTF-8 in the output <code>ContentName</code>
* Native String representations do not incorporate a URI scheme, and so must
* begin with the component delimiter "/".
* TODO use Java string escaping rules?
* @param name
* @throws MalformedContentNameStringException if name does not start with "/"
*/
public static ContentName fromNative(String name) throws MalformedContentNameStringException {
if (!name.startsWith(SEPARATOR)){
throw new MalformedContentNameStringException("ContentName native strings must begin with " + SEPARATOR);
}
String[] parts = name.split(SEPARATOR);
if (parts.length == 0) {
// We've been asked to parse the root name.
return ROOT;
}
ContentName result = new ContentName();
result._components = new byte[parts.length-1][];
// Leave off initial empty component
for (int i=1; i < parts.length; ++i) {
result._components[i-1] = Component.parseNative(parts[i]);
}
return result;
}
/**
* Return the <code>ContentName</code> created by appending one component
* to the supplied parent.
* This method intentionally throws no declared exceptions
* so you can be confident in encoding any native Java String.
* @param parent used for the base of the name.
* @param name Native Java String which will be encoded as UTF-8 in the output
* <code>ContentName</code>
* @deprecated Use new {@link ContentName#ContentName(ContentName, String)} instead.
*/
@Deprecated
public static ContentName fromNative(ContentName parent, String name) {
return new ContentName(parent, name);
}
/**
* @deprecated Use new {@link ContentName#ContentName(ContentName, byte[])} instead.
*/
@Deprecated
public static ContentName fromNative(ContentName parent, byte [] name) {
return new ContentName(parent, name);
}
/**
* @deprecated Use new {@link ContentName#ContentName(Object...)} instead.
*/
@Deprecated
public static ContentName fromNative(ContentName parent, String name1, String name2) {
return new ContentName(parent, name1, name2);
}
/**
* @deprecated Use {@link #ContentName(Object...)} instead.
* Note - in this method case each string, including the first one is interpreted as a
* single component, unlike other fromNative() methods.
*/
@Deprecated
public static ContentName fromNative(String [] parts) {
return new ContentName((Object[]) parts);
}
/**
* @deprecated Use {@link #ContentName(Object...)}.
*/
@Deprecated
public static ContentName fromNative(ContentName parent, String [] parts) {
int extra = (null != parts) ? parts.length : 0;
int parentCount = (null != parent) ? parent._components.length : 0;
ContentName result = new ContentName();
result._components = new byte[parentCount + extra][];
if (parent != null)
System.arraycopy(parent._components, 0, result._components, 0, parentCount);
if (parts != null) {
for (int i=0; i < parts.length; ++i)
result._components[i+parentCount] = Component.parseNative(parts[i]);
}
return result;
}
/**
* Returns a new name with the last component removed.
*/
public ContentName parent() {
ContentName result = new ContentName();
result._components = new byte[_components.length - 1][];
System.arraycopy(_components, 0, result._components, 0, _components.length-1);
return result;
}
@Override
public String toString() {
if (null == _components) return "(null)";
// toString of root name is "/"
if (_components.length == 0) return SEPARATOR;
StringBuffer nameBuf = new StringBuffer();
for (byte[] component : _components) {
nameBuf.append(SEPARATOR);
nameBuf.append(Component.printURI(component));
}
return nameBuf.toString();
}
/**
* Print as string with scheme in front. toString already
* prints in URI format with leading /, just add scheme.
*/
public String toURIString() {
return SCHEME + toString();
}
/**
* Print bytes in the URI Generic Syntax of RFC 3986
* including byte sequences that are not legal character
* encodings in any character set and byte sequences that have special
* meaning for URI resolution per RFC 3986. This is designed to match
* the C library URI encoding.
*
* This method must be invertible by parseComponent() so
* for any input sequence of bytes it must be the case
* that parseComponent(printComponent(input)) == input.
*
* All bytes that are unreserved characters per RFC 3986 are left unescaped.
* Other bytes are percent encoded.
*
* Empty path components and path components "." and ".." have special
* meaning for relative URI resolution per RFC 3986. To guarantee
* these component variations are preserved and recovered exactly when
* the URI is parsed by parseComponent() we use a convention that
* components that are empty or consist entirely of '.' characters will
* have "..." appended. This is intended to be consistent with the CCN C
* library handling of URI representation of names.
* @param bs input byte array.
* @return
* @deprecated Use {@link Component#printURI(byte[],int,int)} instead
*/
@Deprecated
public static String componentPrintURI(byte[] bs, int offset, int length) {
return Component.printURI(bs, offset, length);
}
/**
* @deprecated Use {@link Component#printURI(byte[])} instead
*/
@Deprecated
public static String componentPrintURI(byte [] bs) {
return Component.printURI(bs);
}
/**
* @deprecated Use {@link Component#printNative(byte[])} instead
*/
@Deprecated
public static String componentPrintNative(byte[] bs) {
return Component.printNative(bs);
}
/**
* @deprecated Use {@link Component#hexPrint(byte[])} instead
*/
@Deprecated
public static String hexPrint(byte [] bs) {
return Component.hexPrint(bs);
}
/**
* Parse the URI Generic Syntax of RFC 3986.
* Including handling percent encoding of sequences that are not legal character
* encodings in any character set. This method is the inverse of
* printComponent() and for any input sequence of bytes it must be the case
* that parseComponent(printComponent(input)) == input. Note that the inverse
* is NOT true printComponent(parseComponent(input)) != input in general.
*
* @see fromURI(String)
*
* Note in particular that this method interprets sequences of more than
* two dots ('.') as representing an empty component or dot component value
* as encoded by componentPrint. That is, the component value will be
* the value obtained by removing three dots.
* @param name a single component of a name, URI encoded
* @return a name component
* @deprecated Use {@link Component#parseURI(String)} instead
*/
@Deprecated
public static byte[] componentParseURI(String name) throws Component.DotDot, URISyntaxException {
return Component.parseURI(name);
}
/**
* Parse native string component: just UTF-8 encode
* For full names in native strings only "/" is special
* but for an individual component we will even allow that.
* This method intentionally throws no declared exceptions
* so you can be confident in encoding any native Java String
* TODO make this use Java string escaping rules?
* @param name Component as native Java string
* @deprecated Use {@link Component#parseNative(String)} instead
*/
@Deprecated
public static byte[] componentParseNative(String name) {
return Component.parseNative(name);
}
/**
* @return
* Warning: this returns the internal byte arrays used in the ContentName component representation, without copying them.
* You must not modify the contents of these byte arrays, or you will break the immutability
* of ContentNames, which can have many problematic consequences.
* @deprecated Use an iterator instead.
*/
@Deprecated
public ArrayList<byte[]> components() {
ArrayList<byte[]> result = new ArrayList<byte []>(_components.length);
for(byte[] component : _components)
result.add(component);
return result;
}
/**
* @return The number of components in the name.
*/
public int count() {
if (null == _components) return 0;
return _components.length;
}
/**
* Append a name to this name.
*/
public ContentName append(ContentName other) {
ContentName cn = new ContentName();
cn._components = new byte[_components.length + other._components.length][];
System.arraycopy(_components, 0, cn._components, 0, _components.length);
System.arraycopy(other._components, 0, cn._components, _components.length, other._components.length);
return cn;
}
/**
* Append a name to this one, where the child name might have more than one
* path component -- e.g. foo/bar/bash. Will add leading / to postfix for
* parsing, if one not present.
* @throws MalformedContentNameStringException
*/
public ContentName append(String postfix) throws MalformedContentNameStringException {
if (!postfix.startsWith("/")) {
postfix = "/" + postfix;
}
ContentName postfixName = ContentName.fromNative(postfix);
return this.append(postfixName);
}
/**
* Get the i'th component, indexed from 0.
* @param i index of component to fetch, first (leftmost) component = 0.
* @return null if i is out of range.
* Warning: this returns the internal byte array used in the ContentName component representation.
* You must not modify the contents of this byte array, or you will break the immutability
* of ContentNames, which can have many problematic consequences.
*/
public final byte[] component(int i) {
if ((null == _components) || (i >= _components.length) || i < 0) return null;
return _components[i];
}
/**
* @return null if there are no components.
* Warning: this returns the internal byte array used in the ContentName component representation.
* You must not modify the contents of this byte array, or you will break the immutability
* of ContentNames, which can have many problematic consequences.
*/
public final byte [] lastComponent() {
if (null == _components || _components.length == 0)
return null;
return _components[_components.length-1];
}
/**
* @return The i'th component, converted using URI encoding.
*/
public String stringComponent(int i) {
if ((null == _components) || (i >= _components.length)) return null;
return Component.printURI(_components[i]);
}
/**
* Used by NetworkObject to decode the object from a network stream.
* @see org.ccnx.ccn.impl.encoding.XMLEncodable
*/
@Override
public void decode(XMLDecoder decoder) throws ContentDecodingException {
decoder.readStartElement(getElementLabel());
ArrayList<byte []> components = new ArrayList<byte []>(6);
while (decoder.peekStartElement(CCNProtocolDTags.Component))
components.add(decoder.readBinaryElement(CCNProtocolDTags.Component));
decoder.readEndElement();
_components = components.toArray(new byte[components.size()][]);
}
/**
* Test if this name is a prefix of another name - i.e. do all components in this name exist in the
* name being compared with. Note there do not need to be any more components in the name
* being compared with.
* @param name name being compared with.
*/
public boolean isPrefixOf(ContentName name) {
return isPrefixOf(name, _components.length);
}
/**
* Tests if the first n components are a prefix of name
* @param name
* @param count number of components to check
*/
public boolean isPrefixOf(ContentName name, int count) {
if (null == name)
return false;
if (count > name._components.length)
return false;
for (int i=0; i < count; ++i) {
if (!Arrays.equals(name.component(i), component(i)))
return false;
}
return true;
}
/**
* Compare our name to the name of the ContentObject.
* If our name is 1 component longer than the ContentObject
* and no prefix count is set, our name might contain a digest.
* In that case, try matching the content to the last component as
* a digest.
*
* @param other
* @return
*/
public boolean isPrefixOf(ContentObject other) {
return isPrefixOf(other, _components.length);
}
public boolean isPrefixOf(ContentObject other, int count) {
boolean match = isPrefixOf(other.name(), count);
if (match || _components.length != count)
return match;
if (_components.length == other.name()._components.length + 1) {
return Arrays.equals(_components[_components.length-1], other.digest());
}
return false;
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (! (obj instanceof ContentName)) {
if (obj instanceof ContentNameProvider)
obj = ((ContentNameProvider) obj).getContentName();
else
return false;
}
ContentName other = (ContentName) obj;
return Arrays.deepEquals(_components, other._components);
}
@Override
public int hashCode() {
return Arrays.deepHashCode(_components);
}
/**
* Parses the canonical URI representation.
* @param str
* @return
* @throws MalformedContentNameStringException
* @Deprecated Use {@link #fromURI(String)}
*/
@Deprecated
public static ContentName parse(String str) throws MalformedContentNameStringException {
if(str == null) return null;
if(str.length() == 0) return ROOT;
return fromURI(str);
}
/**
* Uses the canonical URI representation
* @param str
* @return
* @deprecated Use {@link #contains(ComponentProvider)} instead.
*/
@Deprecated
public boolean contains(String str) throws URISyntaxException {
try {
byte[] parsed = Component.parseURI(str);
if (null == parsed) {
return false;
} else {
return contains(parsed);
}
} catch (Component.DotDot c) {
return false;
}
}
public boolean contains(byte [] component) {
return (containsWhere(component) >= 0);
}
public boolean contains(ComponentProvider cp) {
return (containsWhere(cp) >= 0);
}
/**
* Looks for a component.
* @param str Component to search for, encoded using URI encoding.
* @return The index of the first component that matched. Starts at 0.
* @throws URISyntaxException
*/
public int containsWhere(String str) throws URISyntaxException {
try {
byte[] parsed = Component.parseURI(str);
if (null == parsed) {
return -1;
} else {
return containsWhere(parsed);
}
} catch (Component.DotDot c) {
return -1;
}
}
public int containsWhere(ComponentProvider cp) {
return containsWhere(cp.getComponent());
}
/**
* Looks for a component, starting from the end..
* @param str Component to search for, encoded using URI encoding.
* @return The index of the first component that matched. Starts at 0.
* @throws URISyntaxException
*/
public int whereLast(String str) throws URISyntaxException {
try {
byte[] parsed = Component.parseURI(str);
if (null == parsed) {
return -1;
} else {
return whereLast(parsed);
}
} catch (Component.DotDot c) {
return -1;
}
}
/**
* Return component index of the first matching component if it exists.
* @param component Component to search for.
* @return -1 on failure, component index otherwise (starts at 0).
*/
public int containsWhere(byte [] component) {
for (int i=0; i < _components.length; ++i)
if (Arrays.equals(_components[i],component))
return i;
return -1;
}
/**
* Return component index of the last matching component if it exists.
* @param component Component to search for.
* @return -1 on failure, component index otherwise (starts at 0).
*/
public int whereLast(byte [] component) {
for (int i=_components.length-1; i >= 0; --i)
if (Arrays.equals(_components[i],component))
return i;
return -1;
}
public int whereLast(ComponentProvider cp) {
return whereLast(cp.getComponent());
}
/**
* Does a component of the ContentName startWith value?
*
* @param value
* @return
* @deprecated Renamed to {@link #componentStartsWith(byte[])}
*/
@Deprecated
public boolean startsWith(byte [] value) {
return componentStartsWith(value);
}
/**
* Return component index of first component that starts with argument value
*
* @param value
* @return
* @deprecated Renamed to {@link #componentStartsWithWhere(byte[])}
*/
@Deprecated
public int startsWithWhere(byte [] value) {
return componentStartsWithWhere(value);
}
/**
* @return Does any component in the ContentName start with value?
*/
public boolean componentStartsWith(byte [] value) {
return (componentStartsWithWhere(value) >= 0);
}
/**
* @return index of first component that starts with argument value
*/
public int componentStartsWithWhere(byte [] value) {
int i=0;
int size = value.length;
for (i=0; i < _components.length; ++i) {
byte [] component = _components[i];
if (size <= component.length) {
boolean result = true;
for (int j = 0; j < size; j++) {
if (component[j] != value[j]) {
result = false;
break;
}
}
if (result)
return i;
}
}
return -1;
}
/**
* Return the first componentCount components of this name as a new name.
* @param componentCount
* @return
*/
public ContentName cut(int componentCount) {
if ((componentCount < 0) || (componentCount > _components.length)) {
throw new IllegalArgumentException("Illegal component count: " + componentCount);
}
if (componentCount == _components.length)
return this;
ContentName result = new ContentName();
result._components = new byte[componentCount][];
System.arraycopy(_components, 0, result._components, 0, componentCount);
return result;
}
/**
* Removes the first components from the name. Includes only components from position on.
* @param position component number to include as first component in the new name.
* 0 includes all components, 1 includes from the second component on.
* @return A new name using the components starting from position.
*/
public ContentName right(int position) {
if ((position < 0) || (position > _components.length)) {
throw new IllegalArgumentException("Illegal component count: " + position);
}
if (position == 0)
return this;
ContentName result = new ContentName();
int length = _components.length - position;
result._components = new byte[length][];
System.arraycopy(_components, position, result._components, 0, length);
return result;
}
/**
* Slice the name off right before the given component.
* @param component
* @return a new name with only the leftmost components before component.
* If component is not found in the name, then returns the whole name.
*/
public ContentName cut(byte [] component) {
int offset = this.containsWhere(component);
if (offset < 0) {
// unfragmented
return this;
}
// else need to cut it
return cut(offset);
}
/**
* Slice the name off right before the given component
* @param component In URI encoded form.
* @deprecated Use {@link #cut(byte[])}
*/
@Deprecated
public ContentName cut(String component) throws URISyntaxException {
try {
byte[] parsed = Component.parseURI(component);
if (null == parsed) {
return this;
} else {
return cut(parsed);
}
} catch (Component.DotDot c) {
return this;
}
}
/**
* Return a subname of this name as a new name. Two indexes are supplied,
* and a new name is made, taking the components between these indexes.
* @param start the starting component index (0-based)
* @param end the number of the last component to include. (1-based)
* @return the new name.
*/
public ContentName subname(int start, int end) {
if (start < 0 || start > _components.length)
throw new IllegalArgumentException("Start out of range");
if (end < start || end > _components.length)
throw new IllegalArgumentException("End out of range");
if (start == end)
return ROOT;
ContentName result = new ContentName();
result._components = new byte[end-start][];
System.arraycopy(_components, start, result._components, 0, end-start);
return result;
}
/**
* Return the number of components that match
*/
public int matchLength(ContentName name) {
int length = 0;
int otherLength = name.count() - 1;
for (int i = 0; i < count(); i++) {
if (otherLength < i)
break;
if (!Arrays.equals(_components[i], name.component(i)))
break;
length++;
}
return length;
}
/**
* Return the remainder of this name after the prefix, if the prefix
* is a prefix of this name. Otherwise return null. If the prefix is
* identical to this name, return the root (empty) name.
*/
public ContentName postfix(ContentName prefix) {
if (!prefix.isPrefixOf(this))
return null;
return subname(prefix._components.length, _components.length);
}
/**
* Used by NetworkObject to encode the object to a network stream.
* @see org.ccnx.ccn.impl.encoding.XMLEncodable
*/
@Override
public void encode(XMLEncoder encoder) throws ContentEncodingException {
if (!validate()) {
throw new ContentEncodingException("Cannot encode " + this.getClass().getName() + ": field values missing.");
}
encoder.writeStartElement(getElementLabel());
for (int i=0; i < _components.length; ++i) {
encoder.writeElement(CCNProtocolDTags.Component, _components[i]);
}
encoder.writeEndElement();
}
@Override
public boolean validate() {
return (null != _components);
}
@Override
public long getElementLabel() {
return CCNProtocolDTags.Name;
}
/**
* @deprecated Use {@link #cut(int)}
*/
@Deprecated
public ContentName copy(int nameComponentCount) {
return cut(nameComponentCount);
}
public int compareTo(ContentName o) {
if (this == o)
return 0;
int thisCount = _components.length;
int oCount = o._components.length;
int len = (thisCount > oCount) ? thisCount : oCount;
int componentResult;
for (int i=0; i < len; ++i) {
componentResult = DataUtils.compare(this.component(i), o.component(i));
if (0 != componentResult)
return componentResult;
}
return 0;
}
/**
* This returns the components of the name.
* Warning - the iterator directly returns the underlying byte arrays used in the name to
* hold the components. These must not be modified (they may be reused in multiple places,
* and are assumed not to change).
* @return iterator over the name components.
*/
public Iterator<byte[]> iterator() {
return Arrays.asList(_components).iterator();
}
}