Remove netconf from commons/opendaylight pom
[controller.git] / opendaylight / netconf / tools / 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 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;
19 import java.net.URI;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.Map;
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;
45
46 /**
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.
49  */
50 public class ConfigReader extends AbstractReader<DataSchemaNode> {
51
52     public static final String SEPARATOR = "/";
53
54     private final CommandArgHandlerRegistry commandArgHandlerRegistry;
55     private final Map<String, QName> mappedModules;
56     private final Map<URI, QName> mappedModulesNamespace;
57
58     public ConfigReader(final ConsoleIO console, final SchemaContext remoteSchemaContext,
59             final CommandArgHandlerRegistry commandArgHandlerRegistry) {
60         super(console, remoteSchemaContext);
61         this.commandArgHandlerRegistry = commandArgHandlerRegistry;
62
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);
69         }
70     }
71
72     // FIXME refactor + unite common code with FilterReader
73
74     @Override
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");
78
79         final String rawValue = console.read();
80
81         // FIXME isSkip check should be somewhere in abstractReader
82         if (isSkipInput(rawValue) || Strings.isNullOrEmpty(rawValue)) {
83             return Collections.emptyList();
84         }
85
86         final List<QName> filterPartsQNames = Lists.newArrayList();
87
88         for (final String part : rawValue.split(SEPARATOR)) {
89             final QName qName = IOUtil.qNameFromKeyString(part, mappedModules);
90             filterPartsQNames.add(qName);
91         }
92
93         List<? extends NormalizedNode<?, ?>> previous = readInnerNode(rawValue);
94
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()
100             );
101         }
102
103         if (previous == null) {
104             return Collections.singletonList(null);
105         }
106
107         final DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> builder = ImmutableContainerNodeBuilder.create();
108         builder.withNodeIdentifier(new NodeIdentifier(schemaNode.getQName()));
109         builder.withValue((Collection<DataContainerChild<?, ?>>) previous);
110
111         return Collections.<NormalizedNode<?, ?>> singletonList(builder.build());
112     }
113
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());
118     }
119
120     @Override
121     protected ConsoleContext getContext(final DataSchemaNode schemaNode) {
122         return new FilterConsoleContext(schemaNode, getSchemaContext());
123     }
124
125     private final class FilterConsoleContext extends BaseConsoleContext<DataSchemaNode> {
126
127         private final SchemaContext remoteSchemaContext;
128
129         public FilterConsoleContext(final DataSchemaNode schemaNode, final SchemaContext remoteSchemaContext) {
130             super(schemaNode);
131             this.remoteSchemaContext = remoteSchemaContext;
132         }
133
134         @Override
135         protected List<Completer> getAdditionalCompleters() {
136             return Collections.<Completer> singletonList(new FilterCompleter(remoteSchemaContext));
137         }
138     }
139
140     private final class FilterCompleter implements Completer {
141
142         private final SchemaContext remoteSchemaContext;
143
144         public FilterCompleter(final SchemaContext remoteSchemaContext) {
145             this.remoteSchemaContext = remoteSchemaContext;
146         }
147
148         @Override
149         public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
150             final int idx = buffer.lastIndexOf(SEPARATOR);
151
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>() {
157                             @Override
158                             public String apply(final DataSchemaNode input) {
159                                 return IOUtil.qNameToKeyString(input.getQName(),
160                                         mappedModulesNamespace.get(input.getQName().getNamespace()).getLocalName());
161                             }
162                         });
163
164                 fillCandidates(buffer.substring(idx + 1), candidates, transformed);
165             }
166
167             return idx == -1 ? 0 : idx + 1;
168         }
169
170         private void fillCandidates(final String buffer, final List<CharSequence> candidates,
171                 final Collection<String> transformed) {
172             final SortedSet<String> strings = new TreeSet<>(transformed);
173
174             if (buffer == null) {
175                 candidates.addAll(strings);
176             } else {
177                 for (final String match : strings.tailSet(buffer)) {
178                     if (!match.startsWith(buffer)) {
179                         break;
180                     }
181                     candidates.add(match);
182                 }
183             }
184
185             if (candidates.size() == 1) {
186                 candidates.set(0, candidates.get(0) + SEPARATOR);
187             }
188         }
189
190     }
191
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);
196             }
197
198             final QName qName;
199             try {
200                 qName = IOUtil.qNameFromKeyString(part, mappedModules);
201             } catch (final ReadingException e) {
202                 return Optional.of(parent);
203             }
204             if (parent instanceof DataNodeContainer) {
205                 parent = ((DataNodeContainer) parent).getDataChildByName(qName);
206             } else {
207                 // This should check if we are at the end of buffer ?
208                 return Optional.of(parent);
209             }
210         }
211         return Optional.of(parent);
212     }
213
214 }