2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.netconf.cli.reader.custom;
10 import static org.opendaylight.controller.netconf.cli.io.IOUtil.isSkipInput;
11 import com.google.common.base.Function;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Strings;
15 import com.google.common.collect.Collections2;
16 import com.google.common.collect.Lists;
17 import com.google.common.collect.Maps;
18 import java.io.IOException;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.List;
24 import java.util.SortedSet;
25 import java.util.TreeSet;
26 import jline.console.completer.Completer;
27 import org.opendaylight.controller.netconf.cli.CommandArgHandlerRegistry;
28 import org.opendaylight.controller.netconf.cli.io.BaseConsoleContext;
29 import org.opendaylight.controller.netconf.cli.io.ConsoleContext;
30 import org.opendaylight.controller.netconf.cli.io.ConsoleIO;
31 import org.opendaylight.controller.netconf.cli.io.IOUtil;
32 import org.opendaylight.controller.netconf.cli.reader.AbstractReader;
33 import org.opendaylight.controller.netconf.cli.reader.ReadingException;
34 import org.opendaylight.yangtools.yang.common.QName;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
36 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
38 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
39 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
40 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
41 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
42 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.Module;
44 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
47 * Custom reader implementation for filter elements in get/get-config rpcs. This
48 * reader overrides the default anyxml reader and reads filter as a schema path.
50 public class ConfigReader extends AbstractReader<DataSchemaNode> {
52 public static final String SEPARATOR = "/";
54 private final CommandArgHandlerRegistry commandArgHandlerRegistry;
55 private final Map<String, QName> mappedModules;
56 private final Map<URI, QName> mappedModulesNamespace;
58 public ConfigReader(final ConsoleIO console, final SchemaContext remoteSchemaContext,
59 final CommandArgHandlerRegistry commandArgHandlerRegistry) {
60 super(console, remoteSchemaContext);
61 this.commandArgHandlerRegistry = commandArgHandlerRegistry;
63 mappedModules = Maps.newHashMap();
64 mappedModulesNamespace = Maps.newHashMap();
65 for (final Module module : remoteSchemaContext.getModules()) {
66 final QName moduleQName = QName.create(module.getNamespace(), module.getRevision(), module.getName());
67 mappedModules.put(moduleQName.getLocalName(), moduleQName);
68 mappedModulesNamespace.put(moduleQName.getNamespace(), moduleQName);
72 // FIXME refactor + unite common code with FilterReader
75 protected List<NormalizedNode<?, ?>> readWithContext(final DataSchemaNode schemaNode) throws IOException, ReadingException {
76 console.writeLn("Config " + schemaNode.getQName().getLocalName());
77 console.writeLn("Submit path of the data to edit. Use TAB for autocomplete");
79 final String rawValue = console.read();
81 // FIXME isSkip check should be somewhere in abstractReader
82 if (isSkipInput(rawValue) || Strings.isNullOrEmpty(rawValue)) {
83 return Collections.emptyList();
86 final List<QName> filterPartsQNames = Lists.newArrayList();
88 for (final String part : rawValue.split(SEPARATOR)) {
89 final QName qName = IOUtil.qNameFromKeyString(part, mappedModules);
90 filterPartsQNames.add(qName);
93 List<? extends NormalizedNode<?, ?>> previous = readInnerNode(rawValue);
95 for (final QName qName : Lists.reverse(filterPartsQNames).subList(1, filterPartsQNames.size())) {
96 previous = Collections.<NormalizedNode<?, ?>>singletonList(
97 ImmutableContainerNodeBuilder.create()
98 .withNodeIdentifier(new NodeIdentifier(qName))
99 .withValue(previous == null ? Collections.<DataContainerChild<?, ?>>emptyList() : (Collection) previous).build()
103 if (previous == null) {
104 return Collections.singletonList(null);
107 final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builder = ImmutableContainerNodeBuilder.create();
108 builder.withNodeIdentifier(new NodeIdentifier(schemaNode.getQName()));
109 builder.withValue((Collection<DataContainerChild<?, ?>>) previous);
111 return Collections.<NormalizedNode<?, ?>> singletonList(builder.build());
114 private List<NormalizedNode<?, ?>> readInnerNode(final String pathString) throws ReadingException {
115 final Optional<DataSchemaNode> schema = getCurrentNode(getSchemaContext(), pathString);
116 Preconditions.checkState(schema.isPresent(), "Unable to find schema for %s", pathString);
117 return commandArgHandlerRegistry.getGenericReader(getSchemaContext(), true).read(schema.get());
121 protected ConsoleContext getContext(final DataSchemaNode schemaNode) {
122 return new FilterConsoleContext(schemaNode, getSchemaContext());
125 private final class FilterConsoleContext extends BaseConsoleContext<DataSchemaNode> {
127 private final SchemaContext remoteSchemaContext;
129 public FilterConsoleContext(final DataSchemaNode schemaNode, final SchemaContext remoteSchemaContext) {
131 this.remoteSchemaContext = remoteSchemaContext;
135 protected List<Completer> getAdditionalCompleters() {
136 return Collections.<Completer> singletonList(new FilterCompleter(remoteSchemaContext));
140 private final class FilterCompleter implements Completer {
142 private final SchemaContext remoteSchemaContext;
144 public FilterCompleter(final SchemaContext remoteSchemaContext) {
145 this.remoteSchemaContext = remoteSchemaContext;
149 public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
150 final int idx = buffer.lastIndexOf(SEPARATOR);
152 final Optional<DataSchemaNode> currentNode = getCurrentNode(remoteSchemaContext, buffer);
153 if (currentNode.isPresent() && currentNode.get() instanceof DataNodeContainer) {
154 final Collection<DataSchemaNode> childNodes = ((DataNodeContainer) currentNode.get()).getChildNodes();
155 final Collection<String> transformed = Collections2.transform(childNodes,
156 new Function<DataSchemaNode, String>() {
158 public String apply(final DataSchemaNode input) {
159 return IOUtil.qNameToKeyString(input.getQName(),
160 mappedModulesNamespace.get(input.getQName().getNamespace()).getLocalName());
164 fillCandidates(buffer.substring(idx + 1), candidates, transformed);
167 return idx == -1 ? 0 : idx + 1;
170 private void fillCandidates(final String buffer, final List<CharSequence> candidates,
171 final Collection<String> transformed) {
172 final SortedSet<String> strings = new TreeSet<>(transformed);
174 if (buffer == null) {
175 candidates.addAll(strings);
177 for (final String match : strings.tailSet(buffer)) {
178 if (!match.startsWith(buffer)) {
181 candidates.add(match);
185 if (candidates.size() == 1) {
186 candidates.set(0, candidates.get(0) + SEPARATOR);
192 private Optional<DataSchemaNode> getCurrentNode(DataSchemaNode parent, final String buffer) {
193 for (final String part : buffer.split(SEPARATOR)) {
194 if (IOUtil.isQName(part) == false) {
195 return Optional.of(parent);
200 qName = IOUtil.qNameFromKeyString(part, mappedModules);
201 } catch (final ReadingException e) {
202 return Optional.of(parent);
204 if (parent instanceof DataNodeContainer) {
205 parent = ((DataNodeContainer) parent).getDataChildByName(qName);
207 // This should check if we are at the end of buffer ?
208 return Optional.of(parent);
211 return Optional.of(parent);