org.opendaylight.netconf.cli.reader.custom.ConfigReader.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.netconf.cli.reader.custom.ConfigReader.java

Source

/*
 * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.netconf.cli.reader.custom;

import static org.opendaylight.netconf.cli.io.IOUtil.isSkipInput;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.io.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import jline.console.completer.Completer;
import org.opendaylight.netconf.cli.CommandArgHandlerRegistry;
import org.opendaylight.netconf.cli.io.BaseConsoleContext;
import org.opendaylight.netconf.cli.io.ConsoleContext;
import org.opendaylight.netconf.cli.io.ConsoleIO;
import org.opendaylight.netconf.cli.io.IOUtil;
import org.opendaylight.netconf.cli.reader.AbstractReader;
import org.opendaylight.netconf.cli.reader.ReadingException;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;

/**
 * Custom reader implementation for filter elements in get/get-config rpcs. This
 * reader overrides the default anyxml reader and reads filter as a schema path.
 */
public class ConfigReader extends AbstractReader<DataSchemaNode> {

    public static final String SEPARATOR = "/";

    private final CommandArgHandlerRegistry commandArgHandlerRegistry;
    private final Map<String, QName> mappedModules;
    private final Map<URI, QName> mappedModulesNamespace;

    public ConfigReader(final ConsoleIO console, final SchemaContext remoteSchemaContext,
            final CommandArgHandlerRegistry commandArgHandlerRegistry) {
        super(console, remoteSchemaContext);
        this.commandArgHandlerRegistry = commandArgHandlerRegistry;

        mappedModules = Maps.newHashMap();
        mappedModulesNamespace = Maps.newHashMap();
        for (final Module module : remoteSchemaContext.getModules()) {
            final QName moduleQName = QName.create(module.getNamespace(), module.getRevision(), module.getName());
            mappedModules.put(moduleQName.getLocalName(), moduleQName);
            mappedModulesNamespace.put(moduleQName.getNamespace(), moduleQName);
        }
    }

    // FIXME refactor + unite common code with FilterReader

    @Override
    protected List<NormalizedNode<?, ?>> readWithContext(final DataSchemaNode schemaNode)
            throws IOException, ReadingException {
        console.writeLn("Config " + schemaNode.getQName().getLocalName());
        console.writeLn("Submit path of the data to edit. Use TAB for autocomplete");

        final String rawValue = console.read();

        // FIXME isSkip check should be somewhere in abstractReader
        if (isSkipInput(rawValue) || Strings.isNullOrEmpty(rawValue)) {
            return Collections.emptyList();
        }

        final List<QName> filterPartsQNames = Lists.newArrayList();

        for (final String part : rawValue.split(SEPARATOR)) {
            final QName qName = IOUtil.qNameFromKeyString(part, mappedModules);
            filterPartsQNames.add(qName);
        }

        List<? extends NormalizedNode<?, ?>> previous = readInnerNode(rawValue);

        for (final QName qName : Lists.reverse(filterPartsQNames).subList(1, filterPartsQNames.size())) {
            previous = Collections.<NormalizedNode<?, ?>>singletonList(
                    ImmutableContainerNodeBuilder.create().withNodeIdentifier(new NodeIdentifier(qName))
                            .withValue(previous == null ? Collections.<DataContainerChild<?, ?>>emptyList()
                                    : (Collection) previous)
                            .build());
        }

        if (previous == null) {
            return Collections.singletonList(null);
        }

        final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builder = ImmutableContainerNodeBuilder
                .create();
        builder.withNodeIdentifier(new NodeIdentifier(schemaNode.getQName()));
        builder.withValue((Collection<DataContainerChild<?, ?>>) previous);

        return Collections.<NormalizedNode<?, ?>>singletonList(builder.build());
    }

    private List<NormalizedNode<?, ?>> readInnerNode(final String pathString) throws ReadingException {
        final Optional<DataSchemaNode> schema = getCurrentNode(getSchemaContext(), pathString);
        Preconditions.checkState(schema.isPresent(), "Unable to find schema for %s", pathString);
        return commandArgHandlerRegistry.getGenericReader(getSchemaContext(), true).read(schema.get());
    }

    @Override
    protected ConsoleContext getContext(final DataSchemaNode schemaNode) {
        return new FilterConsoleContext(schemaNode, getSchemaContext());
    }

    private final class FilterConsoleContext extends BaseConsoleContext<DataSchemaNode> {

        private final SchemaContext remoteSchemaContext;

        public FilterConsoleContext(final DataSchemaNode schemaNode, final SchemaContext remoteSchemaContext) {
            super(schemaNode);
            this.remoteSchemaContext = remoteSchemaContext;
        }

        @Override
        protected List<Completer> getAdditionalCompleters() {
            return Collections.<Completer>singletonList(new FilterCompleter(remoteSchemaContext));
        }
    }

    private final class FilterCompleter implements Completer {

        private final SchemaContext remoteSchemaContext;

        public FilterCompleter(final SchemaContext remoteSchemaContext) {
            this.remoteSchemaContext = remoteSchemaContext;
        }

        @Override
        public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
            final int idx = buffer.lastIndexOf(SEPARATOR);

            final Optional<DataSchemaNode> currentNode = getCurrentNode(remoteSchemaContext, buffer);
            if (currentNode.isPresent() && currentNode.get() instanceof DataNodeContainer) {
                final Collection<DataSchemaNode> childNodes = ((DataNodeContainer) currentNode.get())
                        .getChildNodes();
                final Collection<String> transformed = Collections2.transform(childNodes,
                        new Function<DataSchemaNode, String>() {
                            @Override
                            public String apply(final DataSchemaNode input) {
                                return IOUtil.qNameToKeyString(input.getQName(),
                                        mappedModulesNamespace.get(input.getQName().getNamespace()).getLocalName());
                            }
                        });

                fillCandidates(buffer.substring(idx + 1), candidates, transformed);
            }

            return idx == -1 ? 0 : idx + 1;
        }

        private void fillCandidates(final String buffer, final List<CharSequence> candidates,
                final Collection<String> transformed) {
            final SortedSet<String> strings = new TreeSet<>(transformed);

            if (buffer == null) {
                candidates.addAll(strings);
            } else {
                for (final String match : strings.tailSet(buffer)) {
                    if (!match.startsWith(buffer)) {
                        break;
                    }
                    candidates.add(match);
                }
            }

            if (candidates.size() == 1) {
                candidates.set(0, candidates.get(0) + SEPARATOR);
            }
        }

    }

    private Optional<DataSchemaNode> getCurrentNode(DataSchemaNode parent, final String buffer) {
        for (final String part : buffer.split(SEPARATOR)) {
            if (IOUtil.isQName(part) == false) {
                return Optional.of(parent);
            }

            final QName qName;
            try {
                qName = IOUtil.qNameFromKeyString(part, mappedModules);
            } catch (final ReadingException e) {
                return Optional.of(parent);
            }
            if (parent instanceof DataNodeContainer) {
                parent = ((DataNodeContainer) parent).getDataChildByName(qName);
            } else {
                // This should check if we are at the end of buffer ?
                return Optional.of(parent);
            }
        }
        return Optional.of(parent);
    }

}