org.kiji.schema.KijiURI.java Source code

Java tutorial

Introduction

Here is the source code for org.kiji.schema.KijiURI.java

Source

/**
 * (c) Copyright 2012 WibiData, Inc.
 *
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.kiji.schema;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.regex.Pattern;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;

import org.kiji.annotations.ApiAudience;
import org.kiji.annotations.ApiStability;
import org.kiji.schema.hbase.HBaseKijiURI.HBaseKijiURIBuilder;
import org.kiji.schema.impl.KijiURIParser;
import org.kiji.schema.impl.KijiURIParser.ZooKeeperAuthorityParser;
import org.kiji.schema.util.KijiNameValidator;
import org.kiji.schema.zookeeper.ZooKeeperFactory;

/**
 * a {@code URI} that uniquely identifies a Kiji instance, table, and column-set.
 * Use {@value org.kiji.schema.KConstants#DEFAULT_HBASE_URI} for the default Kiji instance URI.
 *
 * <p>
 *
 * {@code KijiURI} objects can be constructed directly from parsing a URI string:
 * <pre><code>
 * final KijiURI uri = KijiURI.newBuilder("kiji://.env/default/mytable/col").build();
 * </code></pre>
 *
 * <p>
 *
 * Alternatively, {@code KijiURI} objects can be constructed from components by using a builder:
 * <pre><code>
 * final KijiURI uri = KijiURI.newBuilder()
 *   .withInstanceName("default")
 *   .withTableName("mytable")
 *   .addColumnName(KijiColumnName.create(col))
 *   .build();
 * </code></pre>
 *
 * <H2>Syntax</H2>
 *
 * A KijiURI is composed of multiple components: a {@code scheme}, a {@code cluster-identifier},
 * and optionally, an {@code instance-name}, a {@code table-name}, and {@code column-names}.
 * The text format of a {@code KijiURI} must be of the form:
 * <pre><code>
 * scheme://cluster-identifier[/instance-name[/table-name[/column-names]]]
 * </code></pre>
 * where square brackets indicate optional components.
 *
 * <H3>Scheme</H3>
 *
 * The scheme of all {@code KijiURI}s is a identifier prefixed with the string "{@code kiji}", and
 * followed by any combination of letters, digits, plus ("+"), period ("."), or hyphen ("-").
 * <p>
 * The scheme is specific to the type of cluster identified, and determines how the remaining
 * components will be parsed.
 * <p>
 * The default {@code KijiURI} scheme is "{@value #KIJI_SCHEME}". When this scheme is parsed an
 * {@link org.kiji.schema.hbase.HBaseKijiURI} will be created.
 *
 * <H3>Cluster Identifier</H3>
 *
 * The cluster identifier contains the information necessary for Kiji to identify the host cluster.
 * The exact form of the cluster identifier is specific to the cluster type (which is identified
 * by the scheme).
 *
 * At a minimum, the cluster identifier includes ZooKeeper ensemble information for connecting to
 * the ZooKeeper service hosting Kiji. The ZooKeeper Ensemble address is located in the 'authority'
 * position as defined in RFC3986, and may take one of the following host/port forms, depending on
 * whether one or more hosts is specified, and whether a port is specified:
 *
 * <li> {@code .env}
 * <li> {@code host}
 * <li> {@code host:port}
 * <li> {@code host1,host2}
 * <li> {@code (host1,host2):port}
 *
 * The {@value #ENV_URI_STRING} value will resolve at runtime to a ZooKeeper ensemble address
 * taken from the environment. Specifics of how the address is resolved is scheme-specific.
 * <p>
 * Note that, depending on the scheme, the cluster identifier may contain path segments, and thus
 * is potentially larger than just the URI authority.
 *
 * <H3>Instance Name</H3>
 *
 * The instance name component is optional. Identifies a Kiji instance hosted on the cluster.
 * Only valid Kiji instance names may be used.
 *
 * <H3>Table Name</H3>
 *
 * The table name component is optional, and may only be used if the instance name is defined.
 * The table name identifies a Kiji table in the identified instance. Only a valid Kiji table name
 * may be used.
 *
 * <H3>Column Names</H3>
 *
 * The column names component is optional, and may only be used if the table name is defined.
 * The column names identify a set of Kiji column names in the specified table. The column names
 * are comma separated with no spaces, and may contain only valid Kiji column names.
 *
 * <H2>Examples</H2>
 *
 * The following are valid {@code KijiURI}s:
 *
 * <li> {@code kiji://zkHost}
 * <li> {@code kiji://zkHost/instance}
 * <li> {@code kiji://zkHost/instance/table}
 * <li> {@code kiji://zkHost:zkPort/instance/table}
 * <li> {@code kiji://zkHost1,zkHost2/instance/table}
 * <li> {@code kiji://(zkHost1,zkHost2):zkPort/instance/table}
 * <li> {@code kiji://zkHost/instance/table/col}
 * <li> {@code kiji://zkHost/instance/table/col1,col2}
 * <li> {@code kiji://.env/instance/table}
 *
 * <H2>Usage</H2>
 *
 * The {@link KijiURI} class is not directly instantiable (it is effectively {@code abstract}).
 * The builder will instead return a concrete subclass based on the scheme of the provided URI or
 * URI string. If no URI or URI string is provided to create the builder, then the default
 * {@link org.kiji.schema.hbase.HBaseKijiURI} will be assumed.
 *
 * All {@link KijiURI} implementations are immutable and thread-safe.
 */
@ApiAudience.Public
@ApiStability.Stable
public class KijiURI {

    /**
     * Default {@code KijiURI} scheme. When a {@code KijiURI} with this scheme is parsed, the default
     * {@code KijiURI} type ({@link org.kiji.schema.hbase.HBaseKijiURI}) is used.
     */
    public static final String KIJI_SCHEME = "kiji";

    /** String to specify an unset KijiURI field. */
    public static final String UNSET_URI_STRING = ".unset";

    /** String to specify a value through the local environment. */
    public static final String ENV_URI_STRING = ".env";

    /** Default Zookeeper port. */
    public static final int DEFAULT_ZOOKEEPER_CLIENT_PORT = 2181;

    /** Pattern matching "(host1,host2,host3):port". */
    public static final Pattern RE_AUTHORITY_GROUP = Pattern.compile("\\(([^)]+)\\):(\\d+)");

    /** Pattern matching valid Kiji schemes. */
    private static final Pattern RE_SCHEME = Pattern.compile("(?i)kiji[a-z+.-0-9]*://.*");

    /**
     * The scheme of this KijiURI.
     */
    private final String mScheme;

    /**
     * Ordered list of Zookeeper quorum host names or IP addresses.
     * Preserves user ordering. Never null.
     */
    private final ImmutableList<String> mZookeeperQuorum;

    /** Normalized (sorted) version of mZookeeperQuorum. Never null. */
    private final ImmutableList<String> mZookeeperQuorumNormalized;

    /** Zookeeper client port number. */
    private final int mZookeeperClientPort;

    /** Kiji instance name. Null means unset. */
    private final String mInstanceName;

    /** Kiji table name. Null means unset. */
    private final String mTableName;

    /** Kiji column names. Never null. Empty means unset. Preserves user ordering. */
    private final ImmutableList<KijiColumnName> mColumnNames;

    /** Normalized version of mColumnNames. Never null. */
    private final ImmutableList<KijiColumnName> mColumnNamesNormalized;

    /**
     * Constructs a new KijiURI with the given parameters.
     *
     * @param scheme of the KijiURI.
     * @param zookeeperQuorum Zookeeper quorum.
     * @param zookeeperClientPort Zookeeper client port.
     * @param instanceName Instance name.
     * @param tableName Table name.
     * @param columnNames Column names.
     * @throws KijiURIException If the parameters are invalid.
     */
    protected KijiURI(final String scheme, final Iterable<String> zookeeperQuorum, final int zookeeperClientPort,
            final String instanceName, final String tableName, final Iterable<KijiColumnName> columnNames) {
        mScheme = scheme;
        mZookeeperQuorum = ImmutableList.copyOf(zookeeperQuorum);
        mZookeeperQuorumNormalized = ImmutableSortedSet.copyOf(mZookeeperQuorum).asList();
        mZookeeperClientPort = zookeeperClientPort;
        mInstanceName = ((null == instanceName) || !instanceName.equals(UNSET_URI_STRING)) ? instanceName : null;
        mTableName = ((null == tableName) || !tableName.equals(UNSET_URI_STRING)) ? tableName : null;
        mColumnNames = ImmutableList.copyOf(columnNames);
        mColumnNamesNormalized = ImmutableSortedSet.copyOf(mColumnNames).asList();
        validateNames();
    }

    /**
     * Builder class for constructing KijiURIs.
     */
    public static class KijiURIBuilder {

        /**
         * The scheme of the KijiURI being built.
         */
        protected String mScheme;

        /**
         * Zookeeper quorum: comma-separated list of Zookeeper host names or IP addresses.
         * Preserves user ordering.
         */
        protected ImmutableList<String> mZookeeperQuorum;

        /** Zookeeper client port number. */
        protected int mZookeeperClientPort;

        /** Kiji instance name. Null means unset. */
        protected String mInstanceName;

        /** Kiji table name. Null means unset. */
        protected String mTableName;

        /** Kiji column names. Never null. Empty means unset. Preserves user ordering. */
        protected ImmutableList<KijiColumnName> mColumnNames;

        /**
         * Constructs a new builder for KijiURIs.
         *
         * @param scheme of the URI.
         * @param zookeeperQuorum The initial zookeeper quorum.
         * @param zookeeperClientPort The initial zookeeper client port.
         * @param instanceName The initial instance name.
         * @param tableName The initial table name.
         * @param columnNames The initial column names.
         */
        protected KijiURIBuilder(final String scheme, final Iterable<String> zookeeperQuorum,
                final int zookeeperClientPort, final String instanceName, final String tableName,
                final Iterable<KijiColumnName> columnNames) {
            mScheme = scheme;
            mZookeeperQuorum = ImmutableList.copyOf(zookeeperQuorum);
            mZookeeperClientPort = zookeeperClientPort;
            mInstanceName = ((null == instanceName) || !instanceName.equals(UNSET_URI_STRING)) ? instanceName
                    : null;
            mTableName = ((null == tableName) || !tableName.equals(UNSET_URI_STRING)) ? tableName : null;
            mColumnNames = ImmutableList.copyOf(columnNames);
        }

        /**
         * Constructs a new builder for KijiURIs with default values.
         * See {@link KijiURI#newBuilder()} for specific values.
         */
        protected KijiURIBuilder() {
            mScheme = KIJI_SCHEME;
            mZookeeperQuorum = ZooKeeperAuthorityParser.ENV_ZOOKEEPER_QUORUM;
            mZookeeperClientPort = ZooKeeperAuthorityParser.ENV_ZOOKEEPER_CLIENT_PORT;
            mInstanceName = KConstants.DEFAULT_INSTANCE_NAME;
            mTableName = UNSET_URI_STRING;
            mColumnNames = ImmutableList.of();
        }

        /**
         * Configures the KijiURI with Zookeeper Quorum.
         *
         * @param zookeeperQuorum The zookeeper quorum.
         * @return This builder instance so you may chain configuration method calls.
         */
        public KijiURIBuilder withZookeeperQuorum(String[] zookeeperQuorum) {
            mZookeeperQuorum = ImmutableList.copyOf(zookeeperQuorum);
            return this;
        }

        /**
         * Configures the KijiURI with Zookeeper Quorum.
         *
         * @param zookeeperQuorum The zookeeper quorum.
         * @return This builder instance so you may chain configuration method calls.
         */
        public KijiURIBuilder withZookeeperQuorum(Iterable<String> zookeeperQuorum) {
            mZookeeperQuorum = ImmutableList.copyOf(zookeeperQuorum);
            return this;
        }

        /**
         * Configures the KijiURI with the Zookeeper client port.
         *
         * @param zookeeperClientPort The port.
         * @return This builder instance so you may chain configuration method calls.
         */
        public KijiURIBuilder withZookeeperClientPort(int zookeeperClientPort) {
            mZookeeperClientPort = zookeeperClientPort;
            return this;
        }

        /**
         * Configures the KijiURI with the Kiji instance name.
         *
         * @param instanceName The Kiji instance name.
         * @return This builder instance so you may chain configuration method calls.
         */
        public KijiURIBuilder withInstanceName(String instanceName) {
            mInstanceName = instanceName;
            return this;
        }

        /**
         * Configures the KijiURI with the Kiji table name.
         *
         * @param tableName The Kiji table name.
         * @return This builder instance so you may chain configuration method calls.
         */
        public KijiURIBuilder withTableName(String tableName) {
            mTableName = tableName;
            return this;
        }

        /**
         * Configures the KijiURI with the Kiji column names.
         *
         * @param columnNames The Kiji column names to configure.
         * @return This builder instance so you may chain configuration method calls.
         */
        public KijiURIBuilder withColumnNames(Collection<String> columnNames) {
            ImmutableList.Builder<KijiColumnName> builder = ImmutableList.builder();
            for (String column : columnNames) {
                builder.add(KijiColumnName.create(column));
            }
            mColumnNames = builder.build();
            return this;
        }

        /**
         * Adds the column names to the Kiji URI column names.
         *
         * @param columnNames The Kiji column names to add.
         * @return This builder instance so you may chain configuration method calls.
         */
        public KijiURIBuilder addColumnNames(Collection<KijiColumnName> columnNames) {
            ImmutableList.Builder<KijiColumnName> builder = ImmutableList.builder();
            builder.addAll(mColumnNames).addAll(columnNames);
            mColumnNames = builder.build();
            return this;
        }

        /**
         * Adds the column name to the Kiji URI column names.
         *
         * @param columnName The Kiji column name to add.
         * @return This builder instance so you may chain configuration method calls.
         */
        public KijiURIBuilder addColumnName(KijiColumnName columnName) {
            ImmutableList.Builder<KijiColumnName> builder = ImmutableList.builder();
            builder.addAll(mColumnNames).add(columnName);
            mColumnNames = builder.build();
            return this;
        }

        /**
         * Configures the KijiURI with the Kiji column names.
         *
         * @param columnNames The Kiji column names.
         * @return This builder instance so you may chain configuration method calls.
         */
        public KijiURIBuilder withColumnNames(Iterable<KijiColumnName> columnNames) {
            mColumnNames = ImmutableList.copyOf(columnNames);
            return this;
        }

        /**
         * Builds the configured KijiURI.
         *
         * @return A KijiURI.
         * @throws KijiURIException If the KijiURI was configured improperly.
         */
        public KijiURI build() {
            throw new UnsupportedOperationException("Abstract method.");
        }
    }

    /**
     * Gets a builder configured with default Kiji URI fields for an HBase cluster.
     *
     * More precisely, the following defaults are initialized:
     * <ul>
     *   <li>The Zookeeper quorum and client port is taken from the Hadoop <tt>Configuration</tt></li>
     *   <li>The Kiji instance name is set to <tt>KConstants.DEFAULT_INSTANCE_NAME</tt>
     *       (<tt>"default"</tt>).</li>
     *   <li>The table name and column names are explicitly left unset.</li>
     * </ul>
     *
     * @return A builder configured with defaults for an HBase cluster.
     */
    public static KijiURIBuilder newBuilder() {
        return new HBaseKijiURIBuilder();
    }

    /**
     * Gets a builder configured with a Kiji URI.
     *
     * @param uri The Kiji URI to configure the builder from.
     * @return A builder configured with uri.
     */
    public static KijiURIBuilder newBuilder(KijiURI uri) {
        return uri.getBuilder();
    }

    /**
     * Gets a builder configured with the Kiji URI.
     *
     * <p> The String parameter can be a relative URI (with a specified instance), in which
     *     case it is automatically normalized relative to DEFAULT_HBASE_URI.
     *
     * @param uri String specification of a Kiji URI.
     * @return A builder configured with uri.
     * @throws KijiURIException If the uri is invalid.
     */
    public static KijiURIBuilder newBuilder(final String uri) {
        final String uriWithScheme;
        if (RE_SCHEME.matcher(uri).matches()) {
            uriWithScheme = uri;
        } else {
            uriWithScheme = String.format("%s/%s/", KConstants.DEFAULT_HBASE_URI, uri);
        }
        try {
            return KijiURIParser.Factory.get(new URI(uriWithScheme));
        } catch (URISyntaxException exn) {
            throw new KijiURIException(uri, exn.getMessage());
        }
    }

    /**
     * Resolve the path relative to this KijiURI. Returns a new instance.
     *
     * @param path The path to resolve.
     * @return The resolved KijiURI.
     * @throws KijiURIException If this KijiURI is malformed.
     */
    public KijiURI resolve(String path) {
        try {
            // Without the "./", URI will assume a path containing a colon
            // is a new URI, for example "family:column".
            URI uri = new URI(toString()).resolve(String.format("./%s", path));
            return KijiURIParser.Factory.get(uri).build();
        } catch (URISyntaxException e) {
            throw new RuntimeException(String
                    .format("KijiURI was incorrectly constructed (should never happen): %s", this.toString()));
        } catch (IllegalArgumentException e) {
            throw new KijiURIException(this.toString(), String.format("Path can not be resolved: %s", path));
        }
    }

    /**
     * Returns the address of each member of the ZooKeeper ensemble associated with this KijiURI
     * in comma-separated host:port  (standard ZooKeeper) format. This method will always return the
     * correct addresses of the ZooKeeper ensemble which hosts the metadata for this KijiURI's
     * instance.
     *
     * @return the addresses of the ZooKeeper ensemble members of the Kiji cluster.
     */
    public String getZooKeeperEnsemble() {
        return ZooKeeperFactory.Provider.get().getZooKeeperEnsemble(this);
    }

    /**
     * Returns the set of Zookeeper quorum hosts (names or IPs).
     *
     * <p> This method is not always guaranteed to return valid ZooKeeper hostnames, instead use
     *    {@link org.kiji.schema.KijiURI#getZooKeeperEnsemble()}. </p>
     *
     * <p> Host names or IP addresses are de-duplicated and sorted. </p>
     *
     * @return the set of Zookeeper quorum hosts (names or IPs).
     *     Never null.
     */
    public ImmutableList<String> getZookeeperQuorum() {
        return mZookeeperQuorumNormalized;
    }

    /**
     * Returns the original user-specified list of Zookeeper quorum hosts.
     *
     * <p> This method is not always guaranteed to return valid ZooKeeper hostnames, instead use
     *    {@link org.kiji.schema.KijiURI#getZooKeeperEnsemble()}. </p>
     *
     * <p> Host names are exactly as specified by the user. </p>
     *
     * @return the original user-specified list of Zookeeper quorum hosts.
     *     Never null.
     */
    public ImmutableList<String> getZookeeperQuorumOrdered() {
        return mZookeeperQuorum;
    }

    /** @return Zookeeper client port. */
    public int getZookeeperClientPort() {
        return mZookeeperClientPort;
    }

    /**
     * Returns the scheme of this KijiURI.
     *
     * @return the scheme of this KijiURI.
     */
    public String getScheme() {
        return mScheme;
    }

    /**
     * Returns the name of the Kiji instance specified by this URI, if any.
     *
     * @return the name of the Kiji instance specified by this URI.
     *     Null means unspecified (ie. this URI does not target a Kiji instance).
     */
    public String getInstance() {
        return mInstanceName;
    }

    /**
     * Returns the name of the Kiji table specified by this URI, if any.
     *
     * @return the name of the Kiji table specified by this URI.
     *     Null means unspecified (ie. this URI does not target a Kiji table).
     */
    public String getTable() {
        return mTableName;
    }

    /** @return Kiji columns (comma-separated list of Kiji column names), normalized. Never null. */
    public ImmutableList<KijiColumnName> getColumns() {
        return mColumnNamesNormalized;
    }

    /** @return Kiji columns (comma-separated list of Kiji column names), ordered. Never null. */
    public Collection<KijiColumnName> getColumnsOrdered() {
        return mColumnNames;
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
        return toString(false);
    }

    /**
     * Returns a string representation of this URI that preserves ordering of lists in fields,
     * such as the Zookeeper quorum and Kiji columns.
     *
     * @return An order-preserving string representation of this URI.
     */
    public String toOrderedString() {
        return toString(true);
    }

    /**
     * Returns a string representation of this URI.
     *
     * @param preserveOrdering Whether to preserve ordering of lsits in fields.
     * @return A string representation of this URI.
     */
    private String toString(boolean preserveOrdering) {
        // Remove trailing unset fields.
        if (!mColumnNames.isEmpty()) {
            return toStringCol(preserveOrdering);
        } else if (mTableName != null) {
            return toStringTable(preserveOrdering);
        } else if (mInstanceName != null) {
            return toStringInstance(preserveOrdering);
        } else {
            return toStringAuthority(preserveOrdering);
        }
    }

    /** {@inheritDoc} */
    @Override
    public int hashCode() {
        return toString().hashCode();
    }

    /**
     * {@inheritDoc}
     *
     * *note* {@code KijiURI}'s are compared based on the string representation of the {@code URI}.
     * If two {@code KijiURI}s point to the same cluster with a different scheme, then they will
     * compare differently, for example {@code kiji://localhost} and {@code kiji-hbase://localhost}.
     */
    @Override
    public boolean equals(Object object) {
        if (object == null) {
            return false;
        }
        return object.getClass() == this.getClass() && object.toString().equals(this.toString());
    }

    /**
     * Validates the names used in the URI.
     *
     * @throws KijiURIException if there is an invalid name in this URI.
     */
    private void validateNames() {
        if ((mInstanceName != null) && !KijiNameValidator.isValidKijiName(mInstanceName)) {
            throw new KijiURIException(
                    String.format("Invalid Kiji URI: '%s' is not a valid Kiji instance name.", mInstanceName));
        }
        if ((mTableName != null) && !KijiNameValidator.isValidLayoutName(mTableName)) {
            throw new KijiURIException(
                    String.format("Invalid Kiji URI: '%s' is not a valid Kiji table name.", mTableName));
        }
    }

    /**
     * Appends the cluster identifier for this {@code KijiURI} to the passed in {@link StringBuilder}.
     *
     * This is a default implementation which will append the ZooKeeper ensemble to the string
     * builder.  May be overridden by subclasses if more than the ZooKeeper ensemble is necessary for
     * the cluster identifier.
     *
     * @param sb a StringBuilder to append the cluster identifier to.
     * @param preserveOrdering whether to preserve ordering in the cluster identifier components.
     * @return the StringBuilder with the cluster identifier appended.
     */
    protected StringBuilder appendClusterIdentifier(final StringBuilder sb, final boolean preserveOrdering) {
        ImmutableList<String> zookeeperQuorum = preserveOrdering ? mZookeeperQuorum : mZookeeperQuorumNormalized;
        if (zookeeperQuorum == null) {
            sb.append(UNSET_URI_STRING);
        } else if (zookeeperQuorum.size() == 1) {
            sb.append(zookeeperQuorum.get(0));
        } else {
            sb.append('(');
            Joiner.on(',').appendTo(sb, zookeeperQuorum);
            sb.append(')');
        }
        sb.append(':').append(mZookeeperClientPort).append('/');

        return sb;
    }

    /**
     * Formats the full KijiURI up to the authority, preserving order.
     *
     * @param preserveOrdering Whether to preserve ordering.
     * @return Representation of this KijiURI up to the authority.
     */
    private String toStringAuthority(boolean preserveOrdering) {
        StringBuilder sb = new StringBuilder();
        sb.append(mScheme).append("://");

        appendClusterIdentifier(sb, preserveOrdering);
        return sb.toString();
    }

    /**
     * Formats the full KijiURI up to the instance.
     *
     * @param preserveOrdering Whether to preserve ordering.
     * @return Representation of this KijiURI up to the instance.
     */
    private String toStringInstance(boolean preserveOrdering) {
        return String.format("%s%s/", toStringAuthority(preserveOrdering),
                (null == mInstanceName) ? UNSET_URI_STRING : mInstanceName);
    }

    /**
     * Formats the full KijiURI up to the table.
     *
     * @param preserveOrdering Whether to preserve ordering.
     * @return Representation of this KijiURI up to the table.
     */
    private String toStringTable(boolean preserveOrdering) {
        return String.format("%s%s/", toStringInstance(preserveOrdering),
                (null == mTableName) ? UNSET_URI_STRING : mTableName);
    }

    /**
     * Formats the full KijiURI up to the column.
     *
     * @param preserveOrdering Whether to preserve ordering.
     * @return Representation of this KijiURI up to the table.
     */
    private String toStringCol(boolean preserveOrdering) {
        String columnField;
        ImmutableList<KijiColumnName> columns = preserveOrdering ? mColumnNames : mColumnNamesNormalized;
        if (columns.isEmpty()) {
            columnField = UNSET_URI_STRING;
        } else {
            ImmutableList.Builder<String> builder = ImmutableList.builder();
            for (KijiColumnName column : columns) {
                builder.add(column.getName());
            }
            ImmutableList<String> strColumns = builder.build();
            if (strColumns.size() == 1) {
                columnField = strColumns.get(0);
            } else {
                columnField = Joiner.on(",").join(strColumns);
            }
        }

        try {
            // SCHEMA-6. URI Encode column names using RFC-2396.
            final URI columnsEncoded = new URI(KIJI_SCHEME, columnField, null);
            return String.format("%s%s/", toStringTable(preserveOrdering),
                    columnsEncoded.getRawSchemeSpecificPart());
        } catch (URISyntaxException e) {
            throw new KijiURIException(e.getMessage());
        }
    }

    /**
     * Creates a builder with fields from this {@code KijiURI}.
     *
     * @return a builder with fields from this {@code KijiURI}.
     */
    protected KijiURIBuilder getBuilder() {
        throw new UnsupportedOperationException("Abstract method.");
    }

    /**
     * Returns a {@link KijiFactory} suitable for installing an instance of this {@code KijiURI}.
     *
     * {@code KijiURI} implementations are required to override this method to return a concrete
     * implementation of {@code KijiFactory} appropriate for the cluster type.
     *
     * @return a {@code KijiFactory} for this {@code KijiURI}.
     */
    protected KijiFactory getKijiFactory() {
        throw new UnsupportedOperationException("Abstract method.");
    }

    /**
     * Returns a {@link KijiFactory} suitable for installing an instance of this {@code KijiURI}.
     *
     * {@code KijiURI} implementations are required to override this method. Furthermore, the
     * overridden implementation is required to return a subclass of {@code KijiInstaller} which
     * has overridden implementations of
     * {@link KijiInstaller#install(KijiURI, org.kiji.schema.hbase.HBaseFactory, java.util.Map,
     * org.apache.hadoop.conf.Configuration)} and
     * {@link KijiInstaller#uninstall(KijiURI, org.kiji.schema.hbase.HBaseFactory,
     * org.apache.hadoop.conf.Configuration)}.
     *
     * @return a {@code KijiFactory} for this {@code KijiURI}.
     */
    protected KijiInstaller getKijiInstaller() {
        throw new UnsupportedOperationException("Abstract method.");
    }
}