da267f8da3af91520a11113c51aaba18c0564172
[yangtools.git] / codec / yang-data-codec-gson / src / main / java / org / opendaylight / yangtools / yang / data / codec / gson / JSONCodecFactory.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.yangtools.yang.data.codec.gson;
9
10 import static com.google.common.base.Verify.verifyNotNull;
11
12 import com.google.gson.JsonParseException;
13 import com.google.gson.stream.JsonReader;
14 import com.google.gson.stream.JsonToken;
15 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.io.InputStreamReader;
19 import java.nio.charset.StandardCharsets;
20 import java.util.List;
21 import java.util.function.BiFunction;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.yangtools.yang.common.QName;
24 import org.opendaylight.yangtools.yang.common.QNameModule;
25 import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizationException;
30 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizationResult;
31 import org.opendaylight.yangtools.yang.data.impl.codec.AbstractIntegerStringCodec;
32 import org.opendaylight.yangtools.yang.data.impl.codec.BinaryStringCodec;
33 import org.opendaylight.yangtools.yang.data.impl.codec.BitsStringCodec;
34 import org.opendaylight.yangtools.yang.data.impl.codec.BooleanStringCodec;
35 import org.opendaylight.yangtools.yang.data.impl.codec.DecimalStringCodec;
36 import org.opendaylight.yangtools.yang.data.impl.codec.EnumStringCodec;
37 import org.opendaylight.yangtools.yang.data.impl.codec.StringStringCodec;
38 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
39 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
40 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
41 import org.opendaylight.yangtools.yang.data.util.codec.AbstractInputStreamNormalizer;
42 import org.opendaylight.yangtools.yang.data.util.codec.CodecCache;
43 import org.opendaylight.yangtools.yang.data.util.codec.LazyCodecCache;
44 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
45 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
46 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
47 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
48 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
49 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
50 import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
51 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
52 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
53 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
54 import org.opendaylight.yangtools.yang.model.api.type.Int16TypeDefinition;
55 import org.opendaylight.yangtools.yang.model.api.type.Int32TypeDefinition;
56 import org.opendaylight.yangtools.yang.model.api.type.Int64TypeDefinition;
57 import org.opendaylight.yangtools.yang.model.api.type.Int8TypeDefinition;
58 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
59 import org.opendaylight.yangtools.yang.model.api.type.Uint16TypeDefinition;
60 import org.opendaylight.yangtools.yang.model.api.type.Uint32TypeDefinition;
61 import org.opendaylight.yangtools.yang.model.api.type.Uint64TypeDefinition;
62 import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition;
63 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
64 import org.opendaylight.yangtools.yang.model.api.type.UnknownTypeDefinition;
65 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
66
67 /**
68  * Factory for creating JSON equivalents of codecs. Each instance of this object is bound to
69  * a particular {@link EffectiveModelContext}, but can be reused by multiple {@link JSONNormalizedNodeStreamWriter}s.
70  */
71 public abstract sealed class JSONCodecFactory extends AbstractInputStreamNormalizer<JSONCodec<?>> {
72     @Deprecated(since = "12.0.0", forRemoval = true)
73     static final class Lhotka02 extends JSONCodecFactory {
74         Lhotka02(final @NonNull EffectiveModelContext context, final @NonNull CodecCache<JSONCodec<?>> cache) {
75             super(context, cache, JSONInstanceIdentifierCodec.Lhotka02::new);
76         }
77
78         @Override
79         Lhotka02 rebaseTo(final EffectiveModelContext newSchemaContext, final CodecCache<JSONCodec<?>> newCache) {
80             return new Lhotka02(newSchemaContext, newCache);
81         }
82
83         @Override
84         JSONCodec<?> wrapDecimalCodec(final DecimalStringCodec decimalCodec) {
85             return new NumberJSONCodec<>(decimalCodec);
86         }
87
88         @Override
89         JSONCodec<?> wrapIntegerCodec(final AbstractIntegerStringCodec<?, ?> integerCodec) {
90             return new NumberJSONCodec<>(integerCodec);
91         }
92     }
93
94     static final class RFC7951 extends JSONCodecFactory {
95         RFC7951(final @NonNull  EffectiveModelContext context, final @NonNull CodecCache<JSONCodec<?>> cache) {
96             super(context, cache, JSONInstanceIdentifierCodec.RFC7951::new);
97         }
98
99         @Override
100         RFC7951 rebaseTo(final EffectiveModelContext newSchemaContext, final CodecCache<JSONCodec<?>> newCache) {
101             return new RFC7951(newSchemaContext, newCache);
102         }
103
104         @Override
105         JSONCodec<?> wrapDecimalCodec(final DecimalStringCodec decimalCodec) {
106             return new QuotedJSONCodec<>(decimalCodec);
107         }
108
109         @Override
110         JSONCodec<?> wrapIntegerCodec(final AbstractIntegerStringCodec<?, ?> integerCodec) {
111             return new QuotedJSONCodec<>(integerCodec);
112         }
113     }
114
115     private final @NonNull JSONInstanceIdentifierCodec iidCodec;
116
117     @SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR",
118         justification = "https://github.com/spotbugs/spotbugs/issues/1867")
119     private JSONCodecFactory(final @NonNull EffectiveModelContext context,
120             final @NonNull CodecCache<JSONCodec<?>> cache,
121             final BiFunction<EffectiveModelContext, JSONCodecFactory, @NonNull JSONInstanceIdentifierCodec> iidCodec) {
122         super(context, cache);
123         this.iidCodec = verifyNotNull(iidCodec.apply(context, this));
124     }
125
126     @Override
127     protected final JSONCodec<?> binaryCodec(final BinaryTypeDefinition type) {
128         return new QuotedJSONCodec<>(BinaryStringCodec.from(type));
129     }
130
131     @Override
132     protected final JSONCodec<?> booleanCodec(final BooleanTypeDefinition type) {
133         return new BooleanJSONCodec(BooleanStringCodec.from(type));
134     }
135
136     @Override
137     protected final JSONCodec<?> bitsCodec(final BitsTypeDefinition type) {
138         return new QuotedJSONCodec<>(BitsStringCodec.from(type));
139     }
140
141     @Override
142     protected final JSONCodec<?> decimalCodec(final DecimalTypeDefinition type) {
143         return wrapDecimalCodec(DecimalStringCodec.from(type));
144     }
145
146     @Override
147     protected final JSONCodec<?> emptyCodec(final EmptyTypeDefinition type) {
148         return EmptyJSONCodec.INSTANCE;
149     }
150
151     @Override
152     protected final JSONCodec<?> enumCodec(final EnumTypeDefinition type) {
153         return new QuotedJSONCodec<>(EnumStringCodec.from(type));
154     }
155
156     @Override
157     protected final JSONCodec<QName> identityRefCodec(final IdentityrefTypeDefinition type, final QNameModule module) {
158         return new IdentityrefJSONCodec(modelContext(), module);
159     }
160
161     @Override
162     protected final JSONCodec<YangInstanceIdentifier> instanceIdentifierCodec(
163             final InstanceIdentifierTypeDefinition type) {
164         return iidCodec;
165     }
166
167     @Override
168     public JSONCodec<YangInstanceIdentifier> instanceIdentifierCodec() {
169         return iidCodec;
170     }
171
172     @Override
173     protected final JSONCodec<?> int8Codec(final Int8TypeDefinition type) {
174         return new NumberJSONCodec<>(AbstractIntegerStringCodec.from(type));
175     }
176
177     @Override
178     protected final JSONCodec<?> int16Codec(final Int16TypeDefinition type) {
179         return new NumberJSONCodec<>(AbstractIntegerStringCodec.from(type));
180     }
181
182     @Override
183     protected final JSONCodec<?> int32Codec(final Int32TypeDefinition type) {
184         return new NumberJSONCodec<>(AbstractIntegerStringCodec.from(type));
185     }
186
187     @Override
188     protected final JSONCodec<?> int64Codec(final Int64TypeDefinition type) {
189         return wrapIntegerCodec(AbstractIntegerStringCodec.from(type));
190     }
191
192     @Override
193     protected final JSONCodec<?> stringCodec(final StringTypeDefinition type) {
194         return new QuotedJSONCodec<>(StringStringCodec.from(type));
195     }
196
197     @Override
198     protected final JSONCodec<?> uint8Codec(final Uint8TypeDefinition type) {
199         return new NumberJSONCodec<>(AbstractIntegerStringCodec.from(type));
200     }
201
202     @Override
203     protected final JSONCodec<?> uint16Codec(final Uint16TypeDefinition type) {
204         return new NumberJSONCodec<>(AbstractIntegerStringCodec.from(type));
205     }
206
207     @Override
208     protected final JSONCodec<?> uint32Codec(final Uint32TypeDefinition type) {
209         return new NumberJSONCodec<>(AbstractIntegerStringCodec.from(type));
210     }
211
212     @Override
213     protected final JSONCodec<?> uint64Codec(final Uint64TypeDefinition type) {
214         return wrapIntegerCodec(AbstractIntegerStringCodec.from(type));
215     }
216
217     @Override
218     protected final JSONCodec<?> unionCodec(final UnionTypeDefinition type, final List<JSONCodec<?>> codecs) {
219         return UnionJSONCodec.create(type, codecs);
220     }
221
222     @Override
223     @Deprecated(since = "13.0.3", forRemoval = true)
224     protected final JSONCodec<?> unknownCodec(final UnknownTypeDefinition type) {
225         return NullJSONCodec.INSTANCE;
226     }
227
228     // Returns a one-off factory for the purposes of normalizing an anydata tree.
229     //
230     // FIXME: 7.0.0: this is really ugly, as we should be able to tell if the new context is the same as ours and
231     //               whether our cache is thread-safe -- in which case we should just return this.
232     //               The supplier/cache/factory layout needs to be reworked so that this call ends up being equivalent
233     //               to JSONCodecFactorySupplier.getShared() in case this factory is not thread safe.
234     //
235     //               The above is not currently possible, as we cannot reference JSONCodecFactorySupplier from the
236     //               factory due to that potentially creating a circular reference.
237     final JSONCodecFactory rebaseTo(final EffectiveModelContext newSchemaContext) {
238         return rebaseTo(newSchemaContext, new LazyCodecCache<>());
239     }
240
241     abstract JSONCodecFactory rebaseTo(EffectiveModelContext newSchemaContext, CodecCache<JSONCodec<?>> newCache);
242
243     abstract JSONCodec<?> wrapDecimalCodec(DecimalStringCodec decimalCodec);
244
245     abstract JSONCodec<?> wrapIntegerCodec(AbstractIntegerStringCodec<?, ?> integerCodec);
246
247     @Override
248     protected final NormalizationResult<ContainerNode> parseDatastore(final InputStream stream,
249             final NodeIdentifier containerName, final Unqualified moduleName)
250                 throws IOException, NormalizationException {
251         // This is bit more involved: given this example document:
252         //
253         //          {
254         //            "ietf-restconf:data" : {
255         //              "foo:foo" : {
256         //                "str" : "str"
257         //              }
258         //            }
259         //          }
260         //
261         // we need to first peel this part:
262         //
263         //          {
264         //            "ietf-restconf:data" :
265         //
266         // validating it really the name matches rootName and that it is followed by '{', i.e. it really is an object.
267         //
268         // We then need to essentially do the equivalent of parseStream() on the EffectiveModelContext, but the receiver
269         // should be the builder for our resulting node -- we cannot and do not want to use a holder, as can legally
270         // more than one child.
271         //
272         // Then we need to take care of the last closing brace, raising an error if there is any other content -- i.e.
273         // we need to reach the end of JsonReader.
274         //
275         // And then it's just a matter of returning the built container.
276         try (var reader = new JsonReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
277             reader.beginObject();
278             final var name = reader.nextName();
279             final var expected = moduleName.getLocalName() + ':' + containerName.getNodeType().getLocalName();
280             if (!expected.equals(name)) {
281                 throw NormalizationException.ofMessage("Expected name '" + expected + "', got '" + name + "'");
282             }
283
284             final var builder = ImmutableNodes.newContainerBuilder().withNodeIdentifier(containerName);
285
286             if (reader.peek() == JsonToken.BEGIN_OBJECT) {
287                 try (var writer = ImmutableNormalizedNodeStreamWriter.from(builder)) {
288                     try (var parser = JsonParserStream.create(writer, this)) {
289                         parser.parse(reader);
290                     } catch (JsonParseException e) {
291                         throw NormalizationException.ofCause(e);
292                     }
293                 }
294             }
295
296             reader.endObject();
297             final var nextToken = reader.peek();
298             if (nextToken != JsonToken.END_DOCUMENT) {
299                 throw NormalizationException.ofMessage("Expected end of JSON document, got " + nextToken);
300             }
301             return new NormalizationResult<>(builder.build());
302         } catch (IllegalStateException e) {
303             throw NormalizationException.ofCause(e);
304         }
305     }
306
307     @Override
308     protected final NormalizationResult<?> parseData(final SchemaInferenceStack stack, final InputStream stream)
309             throws IOException, NormalizationException {
310         // Point to parent node
311         stack.exit();
312         return parseStream(stack.toInference(), stream);
313     }
314
315     @Override
316     protected final NormalizationResult<?> parseChildData(final InputStream stream,
317             final EffectiveStatementInference inference) throws IOException, NormalizationException {
318         return parseStream(inference, stream);
319     }
320
321     @Override
322     protected final NormalizationResult<?> parseInputOutput(final SchemaInferenceStack stack, final QName expected,
323             final InputStream stream) throws IOException, NormalizationException {
324         return checkNodeName(parseStream(stack.toInference(), stream), expected);
325     }
326
327     private @NonNull NormalizationResult<?> parseStream(final @NonNull EffectiveStatementInference inference,
328             final @NonNull InputStream stream) throws IOException, NormalizationException {
329         try (var reader = new JsonReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
330             final var holder = new NormalizationResultHolder();
331             try (var writer = ImmutableNormalizedNodeStreamWriter.from(holder)) {
332                 try (var parser = JsonParserStream.create(writer, this, inference)) {
333                     parser.parse(reader);
334                 } catch (JsonParseException e) {
335                     throw NormalizationException.ofCause(e);
336                 }
337             }
338             return holder.getResult();
339         }
340     }
341 }