BUG 2889 : migration of netconf-cli to NormalizedNode api's
[controller.git] / opendaylight / netconf / netconf-cli / src / main / java / org / opendaylight / controller / netconf / cli / reader / impl / BasicDataHolderReader.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.impl;
9
10 import static org.opendaylight.controller.netconf.cli.io.IOUtil.isSkipInput;
11 import static org.opendaylight.controller.netconf.cli.io.IOUtil.listType;
12
13 import com.google.common.base.Function;
14 import com.google.common.base.Optional;
15 import com.google.common.collect.BiMap;
16 import com.google.common.collect.Collections2;
17 import com.google.common.collect.HashBiMap;
18 import java.io.IOException;
19 import java.util.Collections;
20 import java.util.List;
21 import jline.console.completer.Completer;
22 import jline.console.completer.StringsCompleter;
23 import org.opendaylight.controller.netconf.cli.io.ConsoleIO;
24 import org.opendaylight.controller.netconf.cli.io.IOUtil;
25 import org.opendaylight.controller.netconf.cli.reader.AbstractReader;
26 import org.opendaylight.controller.netconf.cli.reader.ReadingException;
27 import org.opendaylight.yangtools.yang.common.QName;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
30 import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
31 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder;
32 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.Module;
35 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
36 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
37 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
38 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
39 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
40 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 public abstract class BasicDataHolderReader<T extends DataSchemaNode> extends AbstractReader<T> {
45
46     private static final Logger LOG = LoggerFactory.getLogger(BasicDataHolderReader.class);
47     private DataHolderCompleter currentCompleter;
48
49     public BasicDataHolderReader(final ConsoleIO console, final SchemaContext schemaContext,
50             final boolean readConfigNode) {
51         super(console, schemaContext, readConfigNode);
52     }
53
54     public BasicDataHolderReader(final ConsoleIO console, final SchemaContext schemaContext) {
55         super(console, schemaContext);
56     }
57
58     @Override
59     public List<NormalizedNode<?, ?>> readWithContext(final T schemaNode) throws IOException, ReadingException {
60         TypeDefinition<?> type = getType(schemaNode);
61         console.formatLn("Submit %s %s(%s)", listType(schemaNode), schemaNode.getQName().getLocalName(), type.getQName().getLocalName());
62
63         while (baseTypeFor(type) instanceof UnionTypeDefinition) {
64             final Optional<TypeDefinition<?>> optionalTypeDef = new UnionTypeReader(console).read(type);
65             if (!optionalTypeDef.isPresent()) {
66                 return postSkipOperations(schemaNode);
67             }
68             type = optionalTypeDef.get();
69         }
70
71         if (currentCompleter == null) {
72             currentCompleter = getBaseCompleter(schemaNode);
73         }
74
75         // TODO what if type is leafref, instance-identifier?
76
77         // Handle empty type leaf by question
78         if (isEmptyType(type)) {
79             final Optional<Boolean> shouldAddEmpty = new DecisionReader().read(console, "Add empty type leaf %s ?",
80                     schemaNode.getQName().getLocalName());
81             if (shouldAddEmpty.isPresent()) {
82                 if (shouldAddEmpty.get()) {
83                     return wrapValue(schemaNode, "");
84                 } else {
85                     return Collections.emptyList();
86                 }
87             } else {
88                 return postSkipOperations(schemaNode);
89             }
90         }
91
92         final String rawValue = readValue();
93         if (isSkipInput(rawValue)) {
94             return postSkipOperations(schemaNode);
95         }
96
97         final Object resolvedValue = currentCompleter.resolveValue(rawValue);
98
99         // Reset state TODO should be in finally
100         currentCompleter = null;
101         return wrapValue(schemaNode, resolvedValue);
102     }
103
104     private List<NormalizedNode<?, ?>> postSkipOperations(final DataSchemaNode schemaNode) throws IOException {
105         console.formatLn("Skipping %s", schemaNode.getQName());
106         return Collections.emptyList();
107     }
108
109     private TypeDefinition<?> baseTypeFor(final TypeDefinition<?> type) {
110         if (type.getBaseType() != null) {
111             return baseTypeFor(type.getBaseType());
112         }
113         return type;
114     }
115
116     protected String readValue() throws IOException {
117         return console.read();
118     }
119
120     private List<NormalizedNode<?, ?>> wrapValue(final T schemaNode, final Object value) {
121         final NormalizedNode<?, ?> newNode = ImmutableLeafNodeBuilder.create()
122                 .withNodeIdentifier(new NodeIdentifier(schemaNode.getQName()))
123                 .withValue(value).build();
124         return Collections.<NormalizedNode<?, ?>>singletonList(newNode);
125     }
126
127     protected abstract TypeDefinition<?> getType(final T schemaNode);
128
129     protected final DataHolderCompleter getBaseCompleter(final T schemaNode) {
130         final TypeDefinition<?> type = getType(schemaNode);
131         final DataHolderCompleter currentCompleter;
132
133         // Add enum completer
134         if (type instanceof EnumTypeDefinition) {
135             currentCompleter = new EnumDataHolderCompleter(type);
136         } else if (type instanceof IdentityrefTypeDefinition) {
137             currentCompleter = new IdentityRefDataHolderCompleter(type, getSchemaContext());
138         } else {
139             currentCompleter = new GeneralDataHolderCompleter(type);
140         }
141         this.currentCompleter = currentCompleter;
142         return currentCompleter;
143     }
144
145     private static interface DataHolderCompleter extends Completer {
146
147         Object resolveValue(String rawValue) throws ReadingException;
148     }
149
150     private static class GeneralDataHolderCompleter implements DataHolderCompleter {
151
152         private final Optional<TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>>> codec;
153         private final TypeDefinition<?> type;
154
155         public GeneralDataHolderCompleter(final TypeDefinition<?> type) {
156             this.type = type;
157             codec = getCodecForType(type);
158         }
159
160         protected TypeDefinition<?> getType() {
161             return type;
162         }
163
164         private Optional<TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>>> getCodecForType(
165                 final TypeDefinition<?> type) {
166             if (type != null) {
167                 return Optional
168                         .<TypeDefinitionAwareCodec<Object, ? extends TypeDefinition<?>>> fromNullable(TypeDefinitionAwareCodec
169                                 .from(type));
170             }
171             return Optional.absent();
172         }
173
174         @Override
175         public Object resolveValue(final String rawValue) throws ReadingException {
176             try {
177                 return codec.isPresent() ? codec.get().deserialize(rawValue) : rawValue;
178             } catch (final RuntimeException e) {
179                 final String message = "It wasn't possible deserialize value " + rawValue + ".";
180                 LOG.error(message, e);
181                 throw new ReadingException(message, e);
182             }
183         }
184
185         @Override
186         public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
187             return 0;
188         }
189     }
190
191     private static final class EnumDataHolderCompleter extends GeneralDataHolderCompleter {
192
193         public EnumDataHolderCompleter(final TypeDefinition<?> type) {
194             super(type);
195         }
196
197         @Override
198         public Object resolveValue(final String rawValue) throws ReadingException {
199             return super.resolveValue(rawValue);
200         }
201
202         @Override
203         public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
204             return new StringsCompleter(Collections2.transform(((EnumTypeDefinition) getType()).getValues(),
205                     new Function<EnumPair, String>() {
206                         @Override
207                         public String apply(final EnumPair input) {
208                             return input.getName();
209                         }
210                     })).complete(buffer, cursor, candidates);
211         }
212     }
213
214     private static final class IdentityRefDataHolderCompleter extends GeneralDataHolderCompleter {
215
216         private final BiMap<String, QName> identityMap;
217
218         public IdentityRefDataHolderCompleter(final TypeDefinition<?> type, final SchemaContext schemaContext) {
219             super(type);
220             this.identityMap = getIdentityMap(schemaContext);
221         }
222
223         private static BiMap<String, QName> getIdentityMap(final SchemaContext schemaContext) {
224             final BiMap<String, QName> identityMap = HashBiMap.create();
225             for (final Module module : schemaContext.getModules()) {
226                 for (final IdentitySchemaNode identity : module.getIdentities()) {
227                     identityMap.put(getIdentityName(identity, module), identity.getQName());
228                 }
229             }
230             return identityMap;
231         }
232
233         private static String getIdentityName(final IdentitySchemaNode rpcDefinition, final Module module) {
234             return IOUtil.qNameToKeyString(rpcDefinition.getQName(), module.getName());
235         }
236
237         @Override
238         public Object resolveValue(final String rawValue) throws ReadingException {
239             final QName qName = identityMap.get(rawValue);
240             if (qName == null) {
241                 throw new ReadingException("No identity found for " + rawValue + " available " + identityMap.keySet());
242             }
243             return qName;
244         }
245
246         @Override
247         public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
248
249             return new StringsCompleter(Collections2.transform(((IdentityrefTypeDefinition) getType()).getIdentity()
250                     .getDerivedIdentities(), new Function<IdentitySchemaNode, String>() {
251                         @Override
252                         public String apply(final IdentitySchemaNode input) {
253                             return identityMap.inverse().get(input.getQName());
254                         }
255                     })).complete(buffer, cursor, candidates);
256         }
257     }
258 }