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