com.ebuddy.cassandra.structure.DefaultPath.java Source code

Java tutorial

Introduction

Here is the source code for com.ebuddy.cassandra.structure.DefaultPath.java

Source

/*
 * Copyright 2013 eBuddy B.V.
 *
 *    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 com.ebuddy.cassandra.structure;

import static com.google.common.collect.Iterables.elementsEqual;
import static com.google.common.collect.Iterables.getFirst;
import static com.google.common.collect.Iterables.limit;
import static com.google.common.collect.Iterables.skip;
import static com.google.common.collect.Iterables.unmodifiableIterable;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.List;

import javax.annotation.Nullable;

import com.ebuddy.cassandra.Path;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

/**
 * Implementation of Path used as column names in Cassandra for encoding structures and for querying elements
 * of a structured object.
 *
 * @author Eric Zoerner <a href="mailto:ezoerner@ebuddy.com">ezoerner@ebuddy.com</a>
 */
public class DefaultPath implements Path {
    private static final char PATH_DELIMITER_CHAR = '/';
    private static final String LIST_INDEX_PREFIX = "@";

    private static final Function<String, String> urlEncodeFunction = new UrlEncode();

    private final Iterable<String> pathElements;

    /** Create a DefaultPath from an iterable of encoded path element strings. */
    private DefaultPath(Iterable<String> encodedPathElements) {
        pathElements = encodedPathElements;
    }

    /** Create a DefaultPath from un-encoded string elements. */
    public static DefaultPath fromStrings(String... elements) {
        return new DefaultPath(Lists.transform(Arrays.asList(elements), urlEncodeFunction));
    }

    @Override
    public Path concat(Path other) {
        Iterable<String> newPathElements = Iterables.concat(pathElements, other.getElements());
        return new DefaultPath(newPathElements);
    }

    /**
     * Paths always end with the delimiter character in order to facilitate
     * start/finish slice queries in Cassandra.
     * @return the (encoded) String representation of a path.
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        for (String pathElement : pathElements) {
            builder.append(pathElement);
            builder.append(PATH_DELIMITER_CHAR);
        }
        return builder.toString();
    }

    public static DefaultPath fromIndex(int i) {
        return new DefaultPath(Arrays.asList(LIST_INDEX_PREFIX + i));
    }

    @Override
    public String head() {
        return getFirst(pathElements, null);
    }

    @Override
    public DefaultPath tail() {
        return tail(1);
    }

    @Override
    public DefaultPath tail(int startIndex) {
        return new DefaultPath(skip(pathElements, startIndex));
    }

    @Override
    public boolean isEmpty() {
        return Iterables.isEmpty(pathElements);
    }

    @Override
    public boolean startsWith(Path path) {
        return elementsEqual(limit(pathElements, path.size()), path.getElements());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        return elementsEqual(pathElements, ((DefaultPath) o).pathElements);
    }

    @Override
    public int hashCode() {
        return Lists.newArrayList(pathElements).hashCode();
    }

    @Override
    public int size() {
        return Iterables.size(pathElements);
    }

    @Override
    public Iterable<String> getElements() {
        return unmodifiableIterable(pathElements);
    }

    @Override
    public Path withIndices(int... indices) {
        List<String> newPathElements = Lists.newArrayList(pathElements);
        for (int index : indices) {
            newPathElements.add(LIST_INDEX_PREFIX + index);
        }
        return new DefaultPath(newPathElements);
    }

    @Override
    public Path withElements(String... elements) {
        List<String> newPathElements = Lists.newArrayList(pathElements);
        newPathElements.addAll(Lists.transform(Arrays.asList(elements), urlEncodeFunction));
        return new DefaultPath(newPathElements);
    }

    /** Create a Path object from a String produced by the Path#toString method. */

    public static Path fromEncodedPathString(String pathString) {
        Iterable<String> parts = Splitter.on(PATH_DELIMITER_CHAR).omitEmptyStrings().split(pathString);
        return new DefaultPath(parts);
    }

    public static int getListIndex(String pathElement) {
        if (pathElement.isEmpty()) {
            throw new IllegalStateException("empty path");
        }

        if (!pathElement.startsWith(LIST_INDEX_PREFIX)) {
            throw new IllegalStateException("not a list index");
        }

        String rest = pathElement.substring(LIST_INDEX_PREFIX.length());
        try {
            return Integer.parseInt(rest);
        } catch (NumberFormatException ignored) {
            throw new IllegalStateException("bad format for list index");
        }
    }

    /**
     * Return true if all the given encoded path elements are list indexes.
     * @throws IllegalArgumentException if an empty path is found
     */
    public static boolean isList(Iterable<String> encodedElements) {
        for (String element : encodedElements) {
            if (!isListIndex(element)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Return true if the first element in this path is a list index.
     */
    private static boolean isListIndex(String pathElement) {
        if (!pathElement.startsWith(LIST_INDEX_PREFIX)) {
            return false;
        }
        String rest = pathElement.substring(LIST_INDEX_PREFIX.length());
        int index;
        try {
            index = Integer.parseInt(rest);
        } catch (NumberFormatException ignored) {
            return false;
        }
        return index >= 0;
    }

    private static class UrlEncode implements Function<String, String> {
        @Nullable
        @Override
        public String apply(@Nullable String s) {
            if (s == null) {
                return null;
            }
            String encodedString;
            try {
                encodedString = URLEncoder.encode(s, "UTF-8");
            } catch (UnsupportedEncodingException ignored) {
                throw new AssertionError("UTF-8 is unknown?");
            }
            return encodedString;
        }
    }
}