/*
 * Copyright 2018, TeamDev. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package io.spine.base;

import com.google.common.base.Splitter;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Message;

import java.util.Iterator;
import java.util.List;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Utilities for working with {@link FieldPath} instances.
 *
 * @author Dmytro Dashenkov
 */
public final class FieldPaths {

    private static final Splitter dotSplitter = Splitter.on('.').trimResults();

    /**
     * Prevents the utility class instantiation.
     */
    private FieldPaths() {
    }

    public static FieldPath parse(String stringPath) {
        checkNotNull(stringPath);
        checkArgument(!stringPath.isEmpty(), "Path must not be empty.");

        List<String> pathElements = dotSplitter.splitToList(stringPath);
        FieldPath result = FieldPath
                .newBuilder()
                .addAllFieldName(pathElements)
                .build();
        return result;
    }

    public static Object fieldAt(Message holder, FieldPath path) {
        checkNotNull(holder);
        checkNotNull(path);
        checkArgument(path.getFieldNameCount() > 0, "Field path must not be empty.");

        Message message = holder;
        Object currentValue = message;
        for (Iterator<String> iterator = path.getFieldNameList().iterator(); iterator.hasNext(); ) {
            String fieldName = iterator.next();
            FieldDescriptor field = message.getDescriptorForType()
                                           .findFieldByName(fieldName);
            checkArgument(field != null, "Field `%s` is not found.", fieldName);
            currentValue = message.getField(field);
            if (currentValue instanceof Message) {
                message = (Message) currentValue;
            } else {
                checkArgument(!iterator.hasNext(), "%s is not a message.", currentValue);
            }
        }
        return currentValue;
    }
}

@@ -1649,32 +1649,210 @@
 aths() %7B%0A    %7D%0A%0A
+    /**%0A     * Parses the given field path into a %7B@link FieldPath%7D.%0A     *%0A     * @param stringPath%0A     *         non-empty field path%0A     * @return parsed field path%0A     */%0A
     public stati
@@ -2237,24 +2237,557 @@
 ult;%0A    %7D%0A%0A
+    /**%0A     * Obtains the value of the field at the given field path from the given value holder.%0A     *%0A     * %3Cp%3EFor example, if the given path is %7B@code protocol.name%7D and the given value holder is of%0A     * type %7B@link io.spine.net.Uri io.spine.net.Uri%7D, the method invocation is equivalent to%0A     * %7B@code uri.getSchema().getName()%7D.%0A     *%0A     * @param holder%0A     *         message to obtain the (nested) field value from%0A     * @param path%0A     *         non-empty field path%0A     * @return the value of the field%0A     */%0A
     public s

Document `FieldPaths`.