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