All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.named_data.jndn.Name Maven / Gradle / Ivy

Go to download

jNDN is a new implementation of a Named Data Networking client library written in Java. It is wire format compatible with the new NDN-TLV encoding, with NDNx and PARC's CCNx.

There is a newer version: 0.25
Show newest version
/**
 * Copyright (C) 2013-2017 Regents of the University of California.
 * @author: Jeff Thompson 
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

package net.named_data.jndn;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import net.named_data.jndn.encoding.EncodingException;
import net.named_data.jndn.encoding.WireFormat;
import net.named_data.jndn.util.Blob;
import net.named_data.jndn.util.ChangeCountable;
import net.named_data.jndn.encoding.tlv.TlvEncoder;

/**
 * A Name holds an array of Name.Component and represents an NDN name.
 */
public class Name implements ChangeCountable, Comparable {
  /**
   * A Name.Component holds a read-only name component value.
   */
  public static class Component implements Comparable {
    /**
     * Create a new GENERIC Name.Component with a zero-length value.
     */
    public
    Component()
    {
      value_ = new Blob(ByteBuffer.allocate(0), false);
      type_ = ComponentType.GENERIC;
    }

    /**
     * Create a new GENERIC Name.Component, using the existing the Blob value.
     * (To create an ImplicitSha256Digest component, use fromImplicitSha256Digest.)
     * @param value The component value.  value may not be null, but
     * value.buf() may be null.
     */
    public
    Component(Blob value)
    {
      if (value == null)
        throw new NullPointerException("Component: Blob value may not be null");
      value_ = value;
      type_ = ComponentType.GENERIC;
    }

    /**
     * Create a new Name.Component, taking another pointer to the component's
     * read-only value.
     * @param component The component to copy.
     */
    public
    Component(Component component)
    {
      value_ = component.value_;
      type_ = component.type_;
    }

    /**
     * Create a new GENERIC Name.Component, copying the given value.
     * (To create an ImplicitSha256Digest component, use fromImplicitSha256Digest.)
     * @param value The value byte array.
     */
    public
    Component(byte[] value)
    {
      value_ = new Blob(value, true);
      type_ = ComponentType.GENERIC;
    }

    /**
     * Create a new GENERIC Name.Component, converting the value to UTF8 bytes.
     * Note, this does not escape %XX values.  If you need to escape, use
     * Name.fromEscapedString.
     * @param value The string to convert to UTF8.
     */
    public
    Component(String value)
    {
      value_ = new Blob(value);
      type_ = ComponentType.GENERIC;
    }

    /**
     * Get the component value.
     * @return The component value.
     */
    public final Blob
    getValue() { return value_; }

    /**
     * Write this component value to result, escaping characters according to
     * the NDN URI Scheme. This also adds "..." to a value with zero or more ".".
     * This adds a type code prefix as needed, such as "sha256digest=".
     * @param result The StringBuffer to write to.
     */
    public final void
    toEscapedString(StringBuffer result)
    {
      if (type_ == ComponentType.IMPLICIT_SHA256_DIGEST) {
        result.append("sha256digest=");
        Blob.toHex(value_.buf(), result);
      }
      else
        Name.toEscapedString(value_.buf(), result);
    }

    /**
     * Convert this component value by escaping characters according to the
     * NDN URI Scheme. This also adds "..." to a value with zero or more ".".
     * This adds a type code prefix as needed, such as "sha256digest=".
     * @return The escaped string.
     */
    public final String
    toEscapedString()
    {
      StringBuffer result = new StringBuffer(value_.buf().remaining());
      toEscapedString(result);
      return result.toString();
    }

    /**
     * Check if this component is a segment number according to NDN naming
     * conventions for "Segment number" (marker 0x00).
     * http://named-data.net/doc/tech-memos/naming-conventions.pdf
     * @return True if this is a segment number.
     */
    public final boolean
    isSegment()
    {
      return value_.size() >= 1 && value_.buf().get(0) == (byte)0x00 && isGeneric();
    }

    /**
     * Check if this component is a segment byte offset according to NDN
     * naming conventions for segment "Byte offset" (marker 0xFB).
     * http://named-data.net/doc/tech-memos/naming-conventions.pdf
     * @return True if this is a segment byte offset.
     */
    public final boolean
    isSegmentOffset()
    {
      return value_.size() >= 1 && value_.buf().get(0) == (byte)0xFB && isGeneric();
    }

    /**
     * Check if this component is a version number according to NDN naming
     * conventions for "Versioning" (marker 0xFD).
     * @return True if this is a version number.
     */
    public final boolean
    isVersion()
    {
      return value_.size() >= 1 && value_.buf().get(0) == (byte)0xFD && isGeneric();
    }

    /**
     * Check if this component is a timestamp according to NDN naming
     * conventions for "Timestamp" (marker 0xFC).
     * http://named-data.net/doc/tech-memos/naming-conventions.pdf
     * @return True if this is a timestamp.
     */
    public final boolean
    isTimestamp()
    {
      return value_.size() >= 1 && value_.buf().get(0) == (byte)0xFC && isGeneric();
    }

    /**
     * Check if this component is a sequence number according to NDN naming
     * conventions for "Sequencing" (marker 0xFE).
     * http://named-data.net/doc/tech-memos/naming-conventions.pdf
     * @return True if this is a sequence number.
     */
    public final boolean
    isSequenceNumber()
    {
      return value_.size() >= 1 && value_.buf().get(0) == (byte)0xFE && isGeneric();
    }

    /**
     * Check if this component is a generic component.
     * @return True if this is an generic component.
     */
    public final boolean
    isGeneric()
    {
      return type_ == ComponentType.GENERIC;
    }

    /**
     * Check if this component is an ImplicitSha256Digest component.
     * @return True if this is an ImplicitSha256Digest component.
     */
    public final boolean
    isImplicitSha256Digest()
    {
      return type_ == ComponentType.IMPLICIT_SHA256_DIGEST;
    }

    /**
     * Interpret this name component as a network-ordered number and return an
     * integer.
     * @return The integer number.
     */
    public final long
    toNumber()
    {
      ByteBuffer buffer = value_.buf();
      if (buffer == null)
        return 0;

      long result = 0;
      for (int i = buffer.position(); i < buffer.limit(); ++i) {
        result *= 256;
        result += (long)((int)buffer.get(i) & 0xff);
      }

      return result;
    }

    /**
     * Interpret this name component as a network-ordered number with a marker
     * and return an integer.
     * @param marker The required first byte of the component.
     * @return The integer number.
     * @throws EncodingException If the first byte of the component does not
     * equal the marker.
     */
    public final long
    toNumberWithMarker(int marker) throws EncodingException
    {
      ByteBuffer buffer = value_.buf();
      if (buffer == null || buffer.remaining() <= 0 || buffer.get(0) != (byte)marker)
        throw new EncodingException
          ("Name component does not begin with the expected marker.");

      long result = 0;
      for (int i = buffer.position() + 1; i < buffer.limit(); ++i) {
        result *= 256;
        result += (long)((int)buffer.get(i) & 0xff);
      }

      return result;
    }

    /**
     * Interpret this name component as a segment number according to NDN naming
     * conventions for "Segment number" (marker 0x00).
     * http://named-data.net/doc/tech-memos/naming-conventions.pdf
     * @return The integer segment number.
     * @throws EncodingException If the first byte of the component is not the
     * expected marker.
     */
    public final long
    toSegment() throws EncodingException
    {
      return toNumberWithMarker(0x00);
    }

    /**
     * Interpret this name component as a segment byte offset according to NDN
     * naming conventions for segment "Byte offset" (marker 0xFB).
     * http://named-data.net/doc/tech-memos/naming-conventions.pdf
     * @return The integer segment byte offset.
     * @throws EncodingException If the first byte of the component is not the
     * expected marker.
     */
    public final long
    toSegmentOffset() throws EncodingException
    {
      return toNumberWithMarker(0xFB);
    }

    /**
     * Interpret this name component as a version number  according to NDN naming
     * conventions for "Versioning" (marker 0xFD). Note that this returns
     * the exact number from the component without converting it to a time
     * representation.
     * @return The integer version number.
     * @throws EncodingException If the first byte of the component is not the
     * expected marker.
     */
    public final long
    toVersion() throws EncodingException
    {
      return toNumberWithMarker(0xFD);
    }

    /**
     * Interpret this name component as a timestamp  according to NDN naming
     * conventions for "Timestamp" (marker 0xFC).
     * http://named-data.net/doc/tech-memos/naming-conventions.pdf
     * @return The number of microseconds since the UNIX epoch (Thursday,
     * 1 January 1970) not counting leap seconds.
     * @throws EncodingException If the first byte of the component is not the
     * expected marker.
     */
    public final long
    toTimestamp() throws EncodingException
    {
      return toNumberWithMarker(0xFC);
    }

    /**
     * Interpret this name component as a sequence number according to NDN naming
     * conventions for "Sequencing" (marker 0xFE).
     * http://named-data.net/doc/tech-memos/naming-conventions.pdf
     * @return The integer sequence number.
     * @throws EncodingException If the first byte of the component is not the
     * expected marker.
     */
    public final long
    toSequenceNumber() throws EncodingException
    {
      return toNumberWithMarker(0xFE);
    }

    /**
     * Create a component whose value is the nonNegativeInteger encoding of the
     * number.
     * @param number The number to be encoded.
     * @return The component value.
     */
    public static Component
    fromNumber(long number)
    {
      if (number < 0)
        number = 0;

      TlvEncoder encoder = new TlvEncoder(8);
      encoder.writeNonNegativeInteger(number);
      return new Component(new Blob(encoder.getOutput(), false));
    }

    /**
     * Create a component whose value is the marker appended with the
     * nonNegativeInteger encoding of the number.
     * @param number The number to be encoded.
     * @param marker The marker to use as the first byte of the component.
     * @return The component value.
     */
    public static Component
    fromNumberWithMarker(long number, int marker)
    {
      if (number < 0)
        number = 0;

      TlvEncoder encoder = new TlvEncoder(9);
      // Encode backwards.
      encoder.writeNonNegativeInteger(number);
      encoder.writeNonNegativeInteger((long)marker);
      return new Component(new Blob(encoder.getOutput(), false));
    }

    /**
     * Create a component with the encoded segment number according to NDN
     * naming conventions for "Segment number" (marker 0x00).
     * http://named-data.net/doc/tech-memos/naming-conventions.pdf
     * @param segment The segment number.
     * @return The new Component.
     */
    public static Component
    fromSegment(long segment)
    {
      return fromNumberWithMarker(segment, 0x00);
    }

    /**
     * Create a component with the encoded segment byte offset according to NDN
     * naming conventions for segment "Byte offset" (marker 0xFB).
     * http://named-data.net/doc/tech-memos/naming-conventions.pdf
     * @param segmentOffset The segment byte offset.
     * @return The new Component.
     */
    public static Component
    fromSegmentOffset(long segmentOffset)
    {
      return fromNumberWithMarker(segmentOffset, 0xFB);
    }

    /**
     * Create a component with the encoded version number according to NDN
     * naming conventions for "Versioning" (marker 0xFD).
     * http://named-data.net/doc/tech-memos/naming-conventions.pdf
     * Note that this encodes the exact value of version without converting from a
     * time representation.
     * @param version The version number.
     * @return The new Component.
     */
    public static Component
    fromVersion(long version)
    {
      return fromNumberWithMarker(version, 0xFD);
    }

    /**
     * Create a component with the encoded timestamp according to NDN naming
     * conventions for "Timestamp" (marker 0xFC).
     * http://named-data.net/doc/tech-memos/naming-conventions.pdf
     * @param timestamp The number of microseconds since the UNIX epoch (Thursday,
     * 1 January 1970) not counting leap seconds.
     * @return The new Component.
     */
    public static Component
    fromTimestamp(long timestamp)
    {
      return fromNumberWithMarker(timestamp, 0xFC);
    }

    /**
     * Create a component with the encoded sequence number according to NDN naming
     * conventions for "Sequencing" (marker 0xFE).
     * http://named-data.net/doc/tech-memos/naming-conventions.pdf
     * @param sequenceNumber The sequence number.
     * @return The new Component.
     */
    public static Component
    fromSequenceNumber(long sequenceNumber)
    {
      return fromNumberWithMarker(sequenceNumber, 0xFE);
    }

    /**
     * Create a component of type ImplicitSha256DigestComponent, so that
     * isImplicitSha256Digest() is true.
     * @param digest The SHA-256 digest value.
     * @return The new Component.
     * @throws EncodingException If the digest length is not 32 bytes.
     */
    public static Component
    fromImplicitSha256Digest(Blob digest) throws EncodingException
    {
      if (digest.size() != 32)
        throw new EncodingException
          ("Name.Component.fromImplicitSha256Digest: The digest length must be 32 bytes");

      Component result = new Component(digest);
      result.type_ = ComponentType.IMPLICIT_SHA256_DIGEST;
      return result;
    }

    /**
     * Create a component of type ImplicitSha256DigestComponent, so that
     * isImplicitSha256Digest() is true.
     * @param digest The SHA-256 digest value.
     * @return The new Component.
     * @throws EncodingException If the digest length is not 32 bytes.
     */
    public static Component
    fromImplicitSha256Digest(byte[] digest) throws EncodingException
    {
      return fromImplicitSha256Digest(new Blob(digest));
    }

    /**
     * Get the successor of this component, as described in Name.getSuccessor.
     * @return A new Name.Component which is the successor of this.
     */
    public final Component
    getSuccessor()
    {
      // Allocate an extra byte in case the result is larger.
      ByteBuffer result = ByteBuffer.allocate(value_.size() + 1);

      boolean carry = true;
      for (int i = value_.size() - 1; i >= 0; --i) {
        if (carry) {
          // b & 0xff makes the byte unsigned and returns an int.
          int x = value_.buf().get(value_.buf().position() + i) & 0xff;
          x = (x + 1) & 0xff;
          result.put(i, (byte)x);
          carry = (x == 0);
        }
        else
          result.put(i, value_.buf().get(value_.buf().position() + i));
      }

      if (carry)
        // Assume all the bytes were set to zero (or the component was empty).
        // In NDN ordering, carry does not mean to prepend a 1, but to make a
        // component one byte longer of all zeros.
        result.put(result.limit() - 1, (byte)0);
      else
        // We didn't need the extra byte.
        result.limit(value_.size());

      return new Component(new Blob(result, false));
    }

    /**
     * Check if this is the same component as other.
     * @param other The other Component to compare with.
     * @return True if the components are equal, otherwise false.
     */
    public final boolean
    equals(Component other)
    {
      return value_.equals(other.value_) && type_ == other.type_;
    }

    public boolean
    equals(Object other)
    {
      if (!(other instanceof Component))
        return false;

      return equals((Component)other);
    }

    public int hashCode()
    {
      return 37 * type_.getNumericType() + value_.hashCode();
    }

    /**
     * Compare this to the other Component using NDN canonical ordering.
     * @param other The other Component to compare with.
     * @return 0 If they compare equal, -1 if this comes before other in the
     * canonical ordering, or 1 if this comes after other in the canonical
     * ordering.
     */
    public final int
    compare(Component other)
    {
      if (type_.getNumericType() < other.type_.getNumericType())
        return -1;
      if (type_.getNumericType() > other.type_.getNumericType())
        return 1;

      if (value_.size() < other.value_.size())
        return -1;
      if (value_.size() > other.value_.size())
        return 1;

      // The components are equal length. Just do a byte compare.
      return value_.compare(other.value_);
    }

    public final int
    compareTo(Object o) { return this.compare((Component)o); }

    // Also include this version for portability.
    public final int
    CompareTo(Object o) { return this.compare((Component)o); }

    /**
     * Reverse the bytes in buffer starting at position, up to but not including
     * limit.
     * @param buffer
     * @param position
     * @param limit
     */
    private static void reverse(ByteBuffer buffer, int position, int limit)
    {
      int from = position;
      int to = limit - 1;
      while (from < to) {
        // swap
        byte temp = buffer.get(from);
        buffer.put(from, buffer.get(to));
        buffer.put(to, temp);

        --to;
        ++from;
      }
    }

    /**
     * A ComponentType specifies the recognized types of a name component.
     */
    private enum ComponentType {
      IMPLICIT_SHA256_DIGEST(1),
      GENERIC(8);

      ComponentType(int type)
      {
        type_ = type;
      }

      public final int
      getNumericType() { return type_; }

      private final int type_;
    }

    // Note: We keep the type_ internal because it is only used to distinguish
    // from ImplicitSha256Digest. If we support general typed components then
    // we can provide public access.
    private ComponentType type_;
    private final Blob value_;
  }

  /**
   * Create a new Name with no components.
   */
  public
  Name()
  {
    components_ = new ArrayList();
  }

  /**
   * Create a new Name with the components in the given name.
   * @param name The name with components to copy from.
   */
  public
  Name(Name name)
  {
    components_ = new ArrayList(name.components_);
  }

  /**
   * Create a new Name, copying the components.
   * @param components The components to copy.
   */
  public
  Name(ArrayList components)
  {
    // Don't need to deep-copy Component elements because they are read-only.
    components_ = new ArrayList(components);
  }

  /**
   * Create a new Name, copying the components.
   * @param components The components to copy.
   */
  public
  Name(Component[] components)
  {
    components_ = new ArrayList();
    for (int i = 0; i < components.length; ++i)
      components_.add(components[i]);
  }

  /**
   * Parse the uri according to the NDN URI Scheme and create the name with the
   * components.
   * @param uri The URI string.
   */
  public
  Name(String uri)
  {
    components_ = new ArrayList();
    set(uri);
  }

  /**
   * Get the number of components.
   * @return The number of components.
   */
  public final int
  size() { return components_.size(); }

  /**
   * Get the component at the given index.
   * @param i The index of the component, starting from 0. However, if i is
   * negative, return the component at size() - (-i).
   * @return The name component at the index.
   */
  public final Component
  get(int i)
  {
    if (i >= 0)
      return components_.get(i);
    else
      return components_.get(components_.size() - (-i));
  }

  public final void
  set(String uri)
  {
    clear();

    uri = uri.trim();
    if (uri.length() == 0)
      return;

    int iColon = uri.indexOf(':');
    if (iColon >= 0) {
      // Make sure the colon came before a '/'.
      int iFirstSlash = uri.indexOf('/');
      if (iFirstSlash < 0 || iColon < iFirstSlash)
        // Omit the leading protocol such as ndn:
        uri = uri.substring(iColon + 1).trim();
    }

    // Trim the leading slash and possibly the authority.
    if (uri.charAt(0) == '/') {
      if (uri.length() >= 2 && uri.charAt(1) == '/') {
        // Strip the authority following "//".
        int iAfterAuthority = uri.indexOf('/', 2);
        if (iAfterAuthority < 0)
          // Unusual case: there was only an authority.
          return;
        else
          uri = uri.substring(iAfterAuthority + 1).trim();
      }
      else
        uri = uri.substring(1).trim();
    }

    int iComponentStart = 0;

    // Unescape the components.
    String sha256digestPrefix = "sha256digest=";
    while (iComponentStart < uri.length()) {
      int iComponentEnd = uri.indexOf("/", iComponentStart);
      if (iComponentEnd < 0)
        iComponentEnd = uri.length();

      Component component;
      if (sha256digestPrefix.regionMatches
          (0, uri, iComponentStart, sha256digestPrefix.length())) {
        try {
          component = Component.fromImplicitSha256Digest
            (fromHex(uri, iComponentStart + sha256digestPrefix.length(),
                     iComponentEnd));
        } catch (EncodingException ex) {
          throw new Error(ex.getMessage());
        }
      }
      else
        component = new Component
          (fromEscapedString(uri, iComponentStart, iComponentEnd));

      // Ignore illegal components.  This also gets rid of a trailing '/'.
      if (!component.getValue().isNull())
        append(component);

      iComponentStart = iComponentEnd + 1;
    }
  }

  /**
   * Clear all the components.
   */
  public final void
  clear()
  {
    components_.clear();
    ++changeCount_;
  }

  /**
   * Append a new GENERIC component, copying from value.
   * (To append an ImplicitSha256Digest component, use appendImplicitSha256Digest.)
   * @param value The component value.
   * @return This name so that you can chain calls to append.
   */
  public final Name
  append(byte[] value)
  {
    return append(new Component(value));
  }

  /**
   * Append a new GENERIC component, using the existing Blob value.
   * (To append an ImplicitSha256Digest component, use appendImplicitSha256Digest.)
   * @param value The component value.
   * @return This name so that you can chain calls to append.
   */
  public final Name
  append(Blob value)
  {
    return append(new Component(value));
  }

  /**
   * Append the component to this name.
   * @param component The component to append.
   * @return This name so that you can chain calls to append.
   */
  public final Name
  append(Component component)
  {
    components_.add(component);
    ++changeCount_;
    return this;
  }

  public final Name
  append(Name name)
  {
    if (name == this)
      // Copying from this name, so need to make a copy first.
      return append(new Name(name));

    for (int i = 0; i < name.components_.size(); ++i)
      append(name.get(i));

    return this;
  }

  /**
   * Convert the value to UTF8 bytes and append a Name.Component.
   * Note, this does not escape %XX values.  If you need to escape, use
   * Name.fromEscapedString.  Also, if the string has "/", this does not split
   * into separate components.  If you need that then use
   * append(new Name(value)).
   * @param value The string to convert to UTF8.
   * @return This name so that you can chain calls to append.
   */
  public final Name
  append(String value)
  {
    return append(new Component(value));
  }

  /**
   * Get a new name, constructed as a subset of components.
   * @param iStartComponent The index if the first component to get. If
   * iStartComponent is -N then return return components starting from
   * name.size() - N.
   * @param nComponents The number of components starting at iStartComponent.
   * If greater than the size of this name, get until the end of the name.
   * @return A new name.
   */
  public final Name
  getSubName(int iStartComponent, int nComponents)
  {
    if (iStartComponent < 0)
      iStartComponent = components_.size() - (-iStartComponent);

    Name result = new Name();

    int iEnd = iStartComponent + nComponents;
    for (int i = iStartComponent; i < iEnd && i < components_.size(); ++i)
      result.components_.add(components_.get(i));

    return result;
  }

  /**
   * Get a new name, constructed as a subset of components starting at
   * iStartComponent until the end of the name.
   * @param iStartComponent The index if the first component to get. If
   * iStartComponent is -N then return return components starting from
   * name.size() - N.
   * @return A new name.
   */
  public final Name
  getSubName(int iStartComponent)
  {
    return getSubName(iStartComponent, components_.size());
  }

  /**
   * Return a new Name with the first nComponents components of this Name.
   * @param nComponents The number of prefix components.  If nComponents is -N
   * then return the prefix up to name.size() - N. For example getPrefix(-1)
   * returns the name without the final component.
   * @return A new Name.
   */
  public final Name
  getPrefix(int nComponents)
  {
    if (nComponents < 0)
      return getSubName(0, components_.size() + nComponents);
    else
      return getSubName(0, nComponents);
  }

  /**
   * Encode this name as a URI according to the NDN URI Scheme.
   * @param includeScheme If true, include the "ndn:" scheme in the URI, e.g.
   * "ndn:/example/name". If false, just return the path, e.g. "/example/name",
   * which is normally the case where toUri() is used for display.
   * @return The URI string.
   */
  public final String
  toUri(boolean includeScheme)
  {
    if (components_.isEmpty())
      return includeScheme ? "ndn:/" : "/";

    StringBuffer result = new StringBuffer();
    if (includeScheme)
      result.append("ndn:");
    for (int i = 0; i < components_.size(); ++i) {
      result.append("/");
      get(i).toEscapedString(result);
    }

    return result.toString();
  }

  /**
   * Encode this name as a URI according to the NDN URI Scheme. Just return the
   * path, e.g. "/example/name" which is the default case where toUri() is used
   * for display.
   * @return The URI string.
   */
  public final String
  toUri()
  {
    return toUri(false);
  }

  public String toString() { return toUri(); }

  /**
   * Append a component with the encoded segment number according to NDN
   * naming conventions for "Segment number" (marker 0x00).
   * http://named-data.net/doc/tech-memos/naming-conventions.pdf
   * @param segment The segment number.
   * @return This name so that you can chain calls to append.
   */
  public final Name
  appendSegment(long segment)
  {
    return append(Component.fromSegment(segment));
  }

  /**
   * Append a component with the encoded segment byte offset according to NDN
   * naming conventions for segment "Byte offset" (marker 0xFB).
   * http://named-data.net/doc/tech-memos/naming-conventions.pdf
   * @param segmentOffset The segment byte offset.
   * @return This name so that you can chain calls to append.
   */
  public final Name
  appendSegmentOffset(long segmentOffset)
  {
    return append(Component.fromSegmentOffset(segmentOffset));
  }

  /**
   * Append a component with the encoded version number according to NDN
   * naming conventions for "Versioning" (marker 0xFD).
   * http://named-data.net/doc/tech-memos/naming-conventions.pdf
   * Note that this encodes the exact value of version without converting from a
   * time representation.
   * @param version The version number.
   * @return This name so that you can chain calls to append.
   */
  public final Name
  appendVersion(long version)
  {
    return append(Component.fromVersion(version));
  }

  /**
   * Append a component with the encoded timestamp according to NDN naming
   * conventions for "Timestamp" (marker 0xFC).
   * http://named-data.net/doc/tech-memos/naming-conventions.pdf
   * @param timestamp The number of microseconds since the UNIX epoch (Thursday,
   * 1 January 1970) not counting leap seconds.
   * @return This name so that you can chain calls to append.
   */
  public final Name
  appendTimestamp(long timestamp)
  {
    return append(Component.fromTimestamp(timestamp));
  }

  /**
   * Append a component with the encoded sequence number according to NDN naming
   * conventions for "Sequencing" (marker 0xFE).
   * http://named-data.net/doc/tech-memos/naming-conventions.pdf
   * @param sequenceNumber The sequence number.
   * @return This name so that you can chain calls to append.
   */
  public final Name
  appendSequenceNumber(long sequenceNumber)
  {
    return append(Component.fromSequenceNumber(sequenceNumber));
  }

  /**
   * Append a component of type ImplicitSha256DigestComponent, so that
   * isImplicitSha256Digest() is true.
   * @param digest The SHA-256 digest value.
   * @return This name so that you can chain calls to append.
   * @throws EncodingException If the digest length is not 32 bytes.
   */
  public final Name
  appendImplicitSha256Digest(Blob digest) throws EncodingException
  {
    return append(Component.fromImplicitSha256Digest(digest));
  }

  /**
   * Append a component of type ImplicitSha256DigestComponent, so that
   * isImplicitSha256Digest() is true.
   * @param digest The SHA-256 digest value.
   * @return This name so that you can chain calls to append.
   * @throws EncodingException If the digest length is not 32 bytes.
   */
  public final Name
  appendImplicitSha256Digest(byte[] digest) throws EncodingException
  {
    return append(Component.fromImplicitSha256Digest(digest));
  }

  /**
   * Check if this name has the same component count and components as the given
   * name.
   * @param name The Name to check.
   * @return true if the names are equal, otherwise false.
   */
  public boolean
  equals(Name name)
  {
    if (components_.size() != name.components_.size())
      return false;

    // Check from last to first since the last components are more likely to differ.
    for (int i = components_.size() - 1; i >= 0; --i) {
      if (!get(i).getValue().equals(name.get(i).getValue()))
        return false;
    }

    return true;
  }

  public boolean
  equals(Object other)
  {
    if (!(other instanceof Name))
      return false;

    return equals((Name)other);
  }

  public int hashCode()
  {
    if (hashCodeChangeCount_ != getChangeCount()) {
      // The values have changed, so the previous hash code is invalidated.
      haveHashCode_ = false;
      hashCodeChangeCount_ = getChangeCount();
    }

    if (!haveHashCode_) {
      int hashCode = 0;
      // Use a similar hash code algorithm as String.
      for (int i = 0; i < components_.size(); ++i)
        hashCode = 37 * hashCode + components_.get(i).hashCode();

      hashCode_ = hashCode;
      haveHashCode_ = true;
    }

    return hashCode_;
  }

  /**
   * Get the successor of this name which is defined as follows.
   *
   *     N represents the set of NDN Names, and X,Y ∈ N.
   *     Operator < is defined by the NDN canonical order on N.
   *     Y is the successor of X, if (a) X < Y, and (b) ∄ Z ∈ N s.t. X < Z < Y.
   *
   * In plain words, the successor of a name is the same name, but with its last
   * component advanced to a next possible value.
   *
   * Examples:
   *
   * - The successor of / is /%00
   * - The successor of /%00%01/%01%02 is /%00%01/%01%03
   * - The successor of /%00%01/%01%FF is /%00%01/%02%00
   * - The successor of /%00%01/%FF%FF is /%00%01/%00%00%00
   *
   * @return A new name which is the successor of this.
   */
  public final Name
  getSuccessor()
  {
    if (size() == 0) {
      // Return "/%00".
      Name result = new Name();
      result.append(new byte[1]);
      return result;
    }
    else
      return getPrefix(-1).append(get(-1).getSuccessor());
  }

  /**
   * Check if the N components of this name are the same as the first N
   * components of the given name.
   * @param name The Name to check.
   * @return true if this matches the given name, otherwise false.  This always
   * returns true if this name is empty.
   */
  public final boolean
  match(Name name)
  {
    // This name is longer than the name we are checking it against.
    if (components_.size() > name.components_.size())
      return false;

    // Check if at least one of given components doesn't match. Check from last
    // to first since the last components are more likely to differ.
    for (int i = components_.size() - 1; i >= 0; --i) {
      if (!get(i).getValue().equals(name.get(i).getValue()))
        return false;
    }

    return true;
  }

  /**
   * Check if the N components of this name are the same as the first N
   * components of the given name.
   * @param name The Name to check.
   * @return true if this matches the given name, otherwise false.  This always
   * returns true if this name is empty.
   */
  public final boolean
  isPrefixOf(Name name) { return match(name); }

  /**
   * Encode this Name for a particular wire format.
   * @param wireFormat A WireFormat object used to encode this Name.
   * @return The encoded buffer.
   */
  public final Blob
  wireEncode(WireFormat wireFormat)
  {
    return wireFormat.encodeName(this);
  }

  /**
   * Encode this Name for the default wire format
   * WireFormat.getDefaultWireFormat().
   * @return The encoded buffer.
   */
  public final Blob
  wireEncode()
  {
    return wireEncode(WireFormat.getDefaultWireFormat());
  }

  /**
   * Decode the input using a particular wire format and update this Name.
   * @param input The input buffer to decode.  This reads from position() to
   * limit(), but does not change the position.
   * @param wireFormat A WireFormat object used to decode the input.
   * @throws EncodingException For invalid encoding.
   */
  public final void
  wireDecode(ByteBuffer input, WireFormat wireFormat) throws EncodingException
  {
    wireFormat.decodeName(this, input, true);
  }

  /**
   * Decode the input using the default wire format
   * WireFormat.getDefaultWireFormat() and update this Name.
   * @param input The input buffer to decode.  This reads from position() to
   * limit(), but does not change the position.
   * @throws EncodingException For invalid encoding.
   */
  public final void
  wireDecode(ByteBuffer input) throws EncodingException
  {
    wireDecode(input, WireFormat.getDefaultWireFormat());
  }

  /**
   * Decode the input using a particular wire format and update this Name.
   * @param input The input blob to decode.
   * @param wireFormat A WireFormat object used to decode the input.
   * @throws EncodingException For invalid encoding.
   */
  public final void
  wireDecode(Blob input, WireFormat wireFormat) throws EncodingException
  {
    wireFormat.decodeName(this, input.buf(), false);
  }

  /**
   * Decode the input using the default wire format
   * WireFormat.getDefaultWireFormat() and update this Name.
   * @param input The input blob to decode.
   * @throws EncodingException For invalid encoding.
   */
  public final void
  wireDecode(Blob input) throws EncodingException
  {
    wireDecode(input, WireFormat.getDefaultWireFormat());
  }

  /**
   * Compare this to the other Name using NDN canonical ordering.  If the first
   * components of each name are not equal, this returns -1 if the first comes
   * before the second using the NDN canonical ordering for name components, or
   * 1 if it comes after. If they are equal, this compares the second components
   * of each name, etc.  If both names are the same up to the size of the
   * shorter name, this returns -1 if the first name is shorter than the second
   * or 1 if it is longer.  For example, sorted gives:
   * /a/b/d /a/b/cc /c /c/a /bb .  This is intuitive because all names with the
   * prefix /a are next to each other.  But it may be also be counter-intuitive
   * because /c comes before /bb according to NDN canonical ordering since it is
   * shorter.
   * @param other The other Name to compare with.
   * @return 0 If they compare equal, -1 if this Name comes before other in the
   * canonical ordering, or 1 if this Name comes after other in the canonical
   * ordering.
   *
   * See http://named-data.net/doc/0.2/technical/CanonicalOrder.html
   */
  public final int
  compare(Name other)
  {
    return compare(0, components_.size(), other);
  }

  /**
   * Compare a subset of this name to a subset of the other name, equivalent to
   * this.getSubName(iStartComponent, nComponents).compare
   * (other.getSubName(iOtherStartComponent, nOtherComponents)).
   * @param iStartComponent The index if the first component of this name to
   * compare. If iStartComponent is -N then compare components starting from
   * name.size() - N.
   * @param nComponents The number of components starting at iStartComponent.
   * If greater than the size of this name, compare until the end of the name.
   * @param other The other Name to compare with.
   * @param iOtherStartComponent The index if the first component of the other
   * name to compare. If iOtherStartComponent is -N then compare components
   * starting from other.size() - N.
   * @param nOtherComponents The number of components starting at
   * iOtherStartComponent. If greater than the size of the other name, compare
   * until the end of the name.
   * @return 0 If the sub names compare equal, -1 if this sub name comes before
   * the other sub name in the canonical ordering, or 1 if after.
   */
  public final int
  compare
    (int iStartComponent, int nComponents, Name other,
     int iOtherStartComponent, int nOtherComponents)
  {
    if (iStartComponent < 0)
      iStartComponent = size() - (-iStartComponent);
    if (iOtherStartComponent < 0)
      iOtherStartComponent = other.size() - (-iOtherStartComponent);

    nComponents = Math.min(nComponents, size() - iStartComponent);
    nOtherComponents = Math.min(nOtherComponents, other.size() - iOtherStartComponent);

    int count = Math.min(nComponents, nOtherComponents);
    for (int i = 0; i < count; ++i) {
      int comparison = components_.get(iStartComponent + i).compare
        (other.components_.get(iOtherStartComponent + i));
      if (comparison == 0)
        // The components at this index are equal, so check the next components.
        continue;

      // Otherwise, the result is based on the components at this index.
      return comparison;
    }

    // The components up to min(this.size(), other.size()) are equal, so the
    //   shorter name is less.
    if (nComponents < nOtherComponents)
      return -1;
    else if (nComponents > nOtherComponents)
      return 1;
    else
      return 0;
  }

  /**
   * Compare a subset of this name to a subset of the other name, equivalent to
   * this.getSubName(iStartComponent, nComponents).compare
   * (other.getSubName(iOtherStartComponent)), getting all components of other
   * from iOtherStartComponent to the end of the name.
   * @param iStartComponent The index if the first component of this name to
   * compare. If iStartComponent is -N then compare components starting from
   * name.size() - N.
   * @param nComponents The number of components starting at iStartComponent.
   * If greater than the size of this name, compare until the end of the name.
   * @param other The other Name to compare with.
   * @param iOtherStartComponent The index if the first component of the other
   * name to compare. If iOtherStartComponent is -N then compare components
   * starting from other.size() - N.
   * @return 0 If the sub names compare equal, -1 if this sub name comes before
   * the other sub name in the canonical ordering, or 1 if after.
   */
  public final int
  compare
    (int iStartComponent, int nComponents, Name other,
     int iOtherStartComponent)
  {
    return compare
      (iStartComponent, nComponents, other, iOtherStartComponent,
       other.components_.size());
  }

  /**
   * Compare a subset of this name to all of the other name, equivalent to
   * this.getSubName(iStartComponent, nComponents).compare(other).
   * @param iStartComponent The index if the first component of this name to
   * compare. If iStartComponent is -N then compare components starting from
   * name.size() - N.
   * @param nComponents The number of components starting at iStartComponent.
   * If greater than the size of this name, compare until the end of the name.
   * @param other The other Name to compare with.
   * @return 0 If the sub names compare equal, -1 if this sub name comes before
   * the other name in the canonical ordering, or 1 if after.
   */
  public final int
  compare
    (int iStartComponent, int nComponents, Name other)
  {
    return compare
      (iStartComponent, nComponents, other, 0, other.components_.size());
  }

  public final int
  compareTo(Object o) { return this.compare((Name)o); }

  // Also include this version for portability.
  public final int
  CompareTo(Object o) { return this.compare((Name)o); }

  /**
   * Get the change count, which is incremented each time this object is changed.
   * @return The change count.
   */
  public final long
  getChangeCount() { return changeCount_; }

  /**
   * Make a Blob value by decoding the escapedString between beginOffset and
   * endOffset according to the NDN URI Scheme. If the escaped string is
   * "", "." or ".." then return a Blob with a null pointer, which means the
   * component should be skipped in a URI name.
   * This does not check for a type code prefix such as "sha256digest=".
   * @param escapedString The escaped string
   * @param beginOffset The offset in escapedString of the beginning of the
   * portion to decode.
   * @param endOffset The offset in escapedString of the end of the portion to
   * decode.
   * @return The Blob value. If the escapedString is not a valid escaped
   * component, then the Blob has a null pointer.
   */
  public static Blob
  fromEscapedString(String escapedString, int beginOffset, int endOffset)
  {
    String trimmedString =
      escapedString.substring(beginOffset, endOffset).trim();
    ByteBuffer value = unescape(trimmedString);

    // Check for all dots.
    boolean gotNonDot = false;
    for (int i = value.position(); i < value.limit(); ++i) {
      if (value.get(i) != '.') {
        gotNonDot = true;
        break;
      }
    }

    if (!gotNonDot) {
      // Special case for component of only periods.
      if (value.remaining() <= 2)
        // Zero, one or two periods is illegal.  Ignore this component.
        return new Blob();
      else {
        // Remove 3 periods.
        value.position(value.position() + 3);
        return new Blob(value, false);
      }
    }
    else
      return new Blob(value, false);
  }

  /**
   * Make a Blob value by decoding the escapedString according to the NDN URI
   * Scheme.
   * If the escaped string is "", "." or ".." then return a Blob with a null
   * pointer, which means the component should be skipped in a URI name.
   * This does not check for a type code prefix such as "sha256digest=".
   * @param escapedString The escaped string.
   * @return The Blob value. If the escapedString is not a valid escaped
   * component, then the Blob has a null pointer.
   */
  public static Blob
  fromEscapedString(String escapedString)
  {
    return fromEscapedString(escapedString, 0, escapedString.length());
  }

  /**
   * Write the value to result, escaping characters according to the NDN URI
   * Scheme.
   * This also adds "..." to a value with zero or more ".".
   * This does not add a type code prefix such as "sha256digest=".
   * @param value The ByteBuffer with the value.  This reads from position() to
   * limit().
   * @param result The StringBuffer to write to.
   */
  public static void
  toEscapedString(ByteBuffer value, StringBuffer result)
  {
    boolean gotNonDot = false;
    for (int i = value.position(); i < value.limit(); ++i) {
      if (value.get(i) != 0x2e) {
        gotNonDot = true;
        break;
      }
    }
    if (!gotNonDot) {
      // Special case for component of zero or more periods.  Add 3 periods.
      result.append("...");
      for (int i = value.position(); i < value.limit(); ++i)
        result.append('.');
    }
    else {
      for (int i = value.position(); i < value.limit(); ++i) {
        int x = ((int)value.get(i) & 0xff);
        // Check for 0-9, A-Z, a-z, (+), (-), (.), (_)
        if (x >= 0x30 && x <= 0x39 || x >= 0x41 && x <= 0x5a ||
          x >= 0x61 && x <= 0x7a || x == 0x2b || x == 0x2d ||
          x == 0x2e || x == 0x5f)
          result.append((char)x);
        else {
          result.append('%');
          if (x < 16)
            result.append('0');
          result.append(Integer.toHexString(x).toUpperCase());
        }
      }
    }
  }

  /**
   * Convert the value by escaping characters according to the NDN URI Scheme.
   * This also adds "..." to a value with zero or more ".".
   * This does not add a type code prefix such as "sha256digest=".
   * @param value The ByteBuffer with the value.  This reads from position() to
   * limit().
   * @return The escaped string.
   */
  public static String
  toEscapedString(ByteBuffer value)
  {
    StringBuffer result = new StringBuffer(value.remaining());
    toEscapedString(value, result);
    return result.toString();
  }

  /**
   * Make a Blob value by decoding the hexString between beginOffset and
   * endOffset.
   * @param hexString The hex string.
   * @param beginOffset The offset in hexString of the beginning of the
   * portion to decode.
   * @param endOffset The offset in hexString of the end of the portion to
   * decode.
   * @return The Blob value. If the hexString is not a valid hex string, then
   * the Blob has a null pointer.
   */
  public static Blob
  fromHex(String hexString, int beginOffset, int endOffset)
  {
    ByteBuffer result = ByteBuffer.allocate((endOffset - beginOffset) / 2);

    for (int i = beginOffset; i < endOffset; ++i) {
      if (hexString.charAt(i) == ' ')
        // Skip whitespace.
        continue;
      if (i + 1 >= endOffset)
        // Only one hex digit. Ignore.
        break;

      int hi = fromHexChar(hexString.charAt(i));
      int lo = fromHexChar(hexString.charAt(i + 1));

      if (hi < 0 || lo < 0)
        // Invalid hex characters.
        return new Blob();

      result.put((byte)(16 * hi + lo));

      // Skip past the second digit.
      i += 1;
    }

    result.flip();
    return new Blob(result, false);
  }

  /**
   * Convert the hex character to an integer from 0 to 15.
   * @param c The hex character.
   * @return The hex value, or -1 if not a hex character.
   */
  private static int
  fromHexChar(char c)
  {
    if (c >= '0' && c <= '9')
      return (int)c - (int)'0';
    else if (c >= 'A' && c <= 'F')
      return (int)c - (int)'A' + 10;
    else if (c >= 'a' && c <= 'f')
      return (int)c - (int)'a' + 10;
    else
      return -1;
  }

  /**
   * Return a copy of str, converting each escaped "%XX" to the char value.
   * @param str The escaped string.
   * @return The unescaped string as a ByteBuffer with position and limit set.
   */
  private static ByteBuffer
  unescape(String str)
  {
    // We know the result will be shorter than the input str.
    ByteBuffer result = ByteBuffer.allocate(str.length());

    for (int i = 0; i < str.length(); ++i) {
      if (str.charAt(i) == '%' && i + 2 < str.length()) {
        int hi = fromHexChar(str.charAt(i + 1));
        int lo = fromHexChar(str.charAt(i + 2));

        if (hi < 0 || lo < 0)
          // Invalid hex characters, so just keep the escaped string.
          result.put((byte)str.charAt(i)).put((byte)str.charAt(i + 1)).put
            ((byte)str.charAt(i + 2));
        else
          result.put((byte)(16 * hi + lo));

        // Skip ahead past the escaped value.
        i += 2;
      }
      else
        // Just copy through.
        result.put((byte)str.charAt(i));
    }

    result.flip();
    return result;
  }

  private final ArrayList components_;
  private long changeCount_ = 0;
  private boolean haveHashCode_ = false;
  private int hashCode_;
  private long hashCodeChangeCount_ = 0;
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy