Fix netconf-connector-config groupId
[netconf.git] / netconf / tools / netconf-cli / src / main / java / org / opendaylight / netconf / cli / reader / custom / ConfigReader.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netconf.cli.reader.custom;
9
10 import static org.opendaylight.netconf.cli.io.IOUtil.isSkipInput;
11
12 import com.google.common.base.Function;
13 import com.google.common.base.Optional;
14 import com.google.common.base.Preconditions;
15 import com.google.common.base.Strings;
16 import com.google.common.collect.Collections2;
17 import com.google.common.collect.Lists;
18 import com.google.common.collect.Maps;
19 import java.io.IOException;
20 import java.net.URI;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.SortedSet;
26 import java.util.TreeSet;
27 import jline.console.completer.Completer;
28 import org.opendaylight.netconf.cli.CommandArgHandlerRegistry;
29 import org.opendaylight.netconf.cli.io.BaseConsoleContext;
30 import org.opendaylight.netconf.cli.io.ConsoleContext;
31 import org.opendaylight.netconf.cli.io.ConsoleIO;
32 import org.opendaylight.netconf.cli.io.IOUtil;
33 import org.opendaylight.netconf.cli.reader.AbstractReader;
34 import org.opendaylight.netconf.cli.reader.ReadingException;
35 import org.opendaylight.yangtools.yang.common.QName;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
39 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
40 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
41 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
42 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
43 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.Module;
45 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
46
47 /**
48  * Custom reader implementation for filter elements in get/get-config rpcs. This
49  * reader overrides the default anyxml reader and reads filter as a schema path.
50  */
51 public class ConfigReader extends AbstractReader<DataSchemaNode> {
52
53     public static final String SEPARATOR = "/";
54
55     private final CommandArgHandlerRegistry commandArgHandlerRegistry;
56     private final Map<String, QName> mappedModules;
57     private final Map<URI, QName> mappedModulesNamespace;
58
59     public ConfigReader(final ConsoleIO console, final SchemaContext remoteSchemaContext,
60             final CommandArgHandlerRegistry commandArgHandlerRegistry) {
61         super(console, remoteSchemaContext);
62         this.commandArgHandlerRegistry = commandArgHandlerRegistry;
63
64         mappedModules = Maps.newHashMap();
65         mappedModulesNamespace = Maps.newHashMap();
66         for (final Module module : remoteSchemaContext.getModules()) {
67             final QName moduleQName = QName.create(module.getNamespace(), module.getRevision(), module.getName());
68             mappedModules.put(moduleQName.getLocalName(), moduleQName);
69             mappedModulesNamespace.put(moduleQName.getNamespace(), moduleQName);
70         }
71     }
72
73     // FIXME refactor + unite common code with FilterReader
74
75     @Override
76     protected List<NormalizedNode<?, ?>> readWithContext(final DataSchemaNode schemaNode) throws IOException, ReadingException {
77         console.writeLn("Config " + schemaNode.getQName().getLocalName());
78         console.writeLn("Submit path of the data to edit. Use TAB for autocomplete");
79
80         final String rawValue = console.read();
81
82         // FIXME isSkip check should be somewhere in abstractReader
83         if (isSkipInput(rawValue) || Strings.isNullOrEmpty(rawValue)) {
84             return Collections.emptyList();
85         }
86
87         final List<QName> filterPartsQNames = Lists.newArrayList();
88
89         for (final String part : rawValue.split(SEPARATOR)) {
90             final QName qName = IOUtil.qNameFromKeyString(part, mappedModules);
91             filterPartsQNames.add(qName);
92         }
93
94         List<? extends NormalizedNode<?, ?>> previous = readInnerNode(rawValue);
95
96         for (final QName qName : Lists.reverse(filterPartsQNames).subList(1, filterPartsQNames.size())) {
97             previous = Collections.<NormalizedNode<?, ?>>singletonList(
98                     ImmutableContainerNodeBuilder.create()
99                             .withNodeIdentifier(new NodeIdentifier(qName))
100                             .withValue(previous == null ? Collections.<DataContainerChild<?, ?>>emptyList() : (Collection) previous).build()
101             );
102         }
103
104         if (previous == null) {
105             return Collections.singletonList(null);
106         }
107
108         final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builder = ImmutableContainerNodeBuilder.create();
109         builder.withNodeIdentifier(new NodeIdentifier(schemaNode.getQName()));
110         builder.withValue((Collection<DataContainerChild<?, ?>>) previous);
111
112         return Collections.<NormalizedNode<?, ?>> singletonList(builder.build());
113     }
114
115     private List<NormalizedNode<?, ?>> readInnerNode(final String pathString) throws ReadingException {
116         final Optional<DataSchemaNode> schema = getCurrentNode(getSchemaContext(), pathString);
117         Preconditions.checkState(schema.isPresent(), "Unable to find schema for %s", pathString);
118         return commandArgHandlerRegistry.getGenericReader(getSchemaContext(), true).read(schema.get());
119     }
120
121     @Override
122     protected ConsoleContext getContext(final DataSchemaNode schemaNode) {
123         return new FilterConsoleContext(schemaNode, getSchemaContext());
124     }
125
126     private final class FilterConsoleContext extends BaseConsoleContext<DataSchemaNode> {
127
128         private final SchemaContext remoteSchemaContext;
129
130         public FilterConsoleContext(final DataSchemaNode schemaNode, final SchemaContext remoteSchemaContext) {
131             super(schemaNode);
132             this.remoteSchemaContext = remoteSchemaContext;
133         }
134
135         @Override
136         protected List<Completer> getAdditionalCompleters() {
137             return Collections.<Completer> singletonList(new FilterCompleter(remoteSchemaContext));
138         }
139     }
140
141     private final class FilterCompleter implements Completer {
142
143         private final SchemaContext remoteSchemaContext;
144
145         public FilterCompleter(final SchemaContext remoteSchemaContext) {
146             this.remoteSchemaContext = remoteSchemaContext;
147         }
148
149         @Override
150         public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
151             final int idx = buffer.lastIndexOf(SEPARATOR);
152
153             final Optional<DataSchemaNode> currentNode = getCurrentNode(remoteSchemaContext, buffer);
154             if (currentNode.isPresent() && currentNode.get() instanceof DataNodeContainer) {
155                 final Collection<DataSchemaNode> childNodes = ((DataNodeContainer) currentNode.get()).getChildNodes();
156                 final Collection<String> transformed = Collections2.transform(childNodes,
157                         new Function<DataSchemaNode, String>() {
158                             @Override
159                             public String apply(final DataSchemaNode input) {
160                                 return IOUtil.qNameToKeyString(input.getQName(),
161                                         mappedModulesNamespace.get(input.getQName().getNamespace()).getLocalName());
162                             }
163                         });
164
165                 fillCandidates(buffer.substring(idx + 1), candidates, transformed);
166             }
167
168             return idx == -1 ? 0 : idx + 1;
169         }
170
171         private void fillCandidates(final String buffer, final List<CharSequence> candidates,
172                 final Collection<String> transformed) {
173             final SortedSet<String> strings = new TreeSet<>(transformed);
174
175             if (buffer == null) {
176                 candidates.addAll(strings);
177             } else {
178                 for (final String match : strings.tailSet(buffer)) {
179                     if (!match.startsWith(buffer)) {
180                         break;
181                     }
182                     candidates.add(match);
183                 }
184             }
185
186             if (candidates.size() == 1) {
187                 candidates.set(0, candidates.get(0) + SEPARATOR);
188             }
189         }
190
191     }
192
193     private Optional<DataSchemaNode> getCurrentNode(DataSchemaNode parent, final String buffer) {
194         for (final String part : buffer.split(SEPARATOR)) {
195             if (IOUtil.isQName(part) == false) {
196                 return Optional.of(parent);
197             }
198
199             final QName qName;
200             try {
201                 qName = IOUtil.qNameFromKeyString(part, mappedModules);
202             } catch (final ReadingException e) {
203                 return Optional.of(parent);
204             }
205             if (parent instanceof DataNodeContainer) {
206                 parent = ((DataNodeContainer) parent).getDataChildByName(qName);
207             } else {
208                 // This should check if we are at the end of buffer ?
209                 return Optional.of(parent);
210             }
211         }
212         return Optional.of(parent);
213     }
214
215 }