Add InputStreamNormalizer
[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.Builders;
39 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
40 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
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(getEffectiveModelContext(), 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     protected final JSONCodec<?> unknownCodec(final UnknownTypeDefinition type) {
224         return NullJSONCodec.INSTANCE;
225     }
226
227     // Returns a one-off factory for the purposes of normalizing an anydata tree.
228     //
229     // 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
230     //               whether our cache is thread-safe -- in which case we should just return this.
231     //               The supplier/cache/factory layout needs to be reworked so that this call ends up being equivalent
232     //               to JSONCodecFactorySupplier.getShared() in case this factory is not thread safe.
233     //
234     //               The above is not currently possible, as we cannot reference JSONCodecFactorySupplier from the
235     //               factory due to that potentially creating a circular reference.
236     final JSONCodecFactory rebaseTo(final EffectiveModelContext newSchemaContext) {
237         return rebaseTo(newSchemaContext, new LazyCodecCache<>());
238     }
239
240     abstract JSONCodecFactory rebaseTo(EffectiveModelContext newSchemaContext, CodecCache<JSONCodec<?>> newCache);
241
242     abstract JSONCodec<?> wrapDecimalCodec(DecimalStringCodec decimalCodec);
243
244     abstract JSONCodec<?> wrapIntegerCodec(AbstractIntegerStringCodec<?, ?> integerCodec);
245
246     @Override
247     protected final NormalizationResult<ContainerNode> parseDatastore(final InputStream stream,
248             final NodeIdentifier containerName, final Unqualified moduleName)
249                 throws IOException, NormalizationException {
250         // This is bit more involved: given this example document:
251         //
252         //          {
253         //            "ietf-restconf:data" : {
254         //              "foo:foo" : {
255         //                "str" : "str"
256         //              }
257         //            }
258         //          }
259         //
260         // we need to first peel this part:
261         //
262         //          {
263         //            "ietf-restconf:data" :
264         //
265         // validating it really the name matches rootName and that it is followed by '{', i.e. it really is an object.
266         //
267         // We then need to essentially do the equivalent of parseStream() on the EffectiveModelContext, but the receiver
268         // should be the builder for our resulting node -- we cannot and do not want to use a holder, as can legally
269         // more than one child.
270         //
271         // Then we need to take care of the last closing brace, raising an error if there is any other content -- i.e.
272         // we need to reach the end of JsonReader.
273         //
274         // And then it's just a matter of returning the built container.
275         try (var reader = new JsonReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
276             reader.beginObject();
277             final var name = reader.nextName();
278             final var expected = moduleName.getLocalName() + ':' + containerName.getNodeType().getLocalName();
279             if (!expected.equals(name)) {
280                 throw NormalizationException.ofMessage("Expected name '" + expected + "', got '" + name + "'");
281             }
282
283             final var builder = Builders.containerBuilder().withNodeIdentifier(containerName);
284
285             if (reader.peek() == JsonToken.BEGIN_OBJECT) {
286                 try (var writer = ImmutableNormalizedNodeStreamWriter.from(builder)) {
287                     try (var parser = JsonParserStream.create(writer, this)) {
288                         parser.parse(reader);
289                     } catch (JsonParseException e) {
290                         throw NormalizationException.ofCause(e);
291                     }
292                 }
293             }
294
295             reader.endObject();
296             final var nextToken = reader.peek();
297             if (nextToken != JsonToken.END_DOCUMENT) {
298                 throw NormalizationException.ofMessage("Expected end of JSON document, got " + nextToken);
299             }
300             return new NormalizationResult<>(builder.build());
301         } catch (IllegalStateException e) {
302             throw NormalizationException.ofCause(e);
303         }
304     }
305
306     @Override
307     protected final NormalizationResult<?> parseData(final SchemaInferenceStack stack, final InputStream stream)
308             throws IOException, NormalizationException {
309         // Point to parent node
310         stack.exit();
311         return parseStream(stack.toInference(), stream);
312     }
313
314     @Override
315     protected final NormalizationResult<?> parseChildData(final InputStream stream,
316             final EffectiveStatementInference inference) throws IOException, NormalizationException {
317         return parseStream(inference, stream);
318     }
319
320     @Override
321     protected final NormalizationResult<?> parseInputOutput(final SchemaInferenceStack stack, final QName expected,
322             final InputStream stream) throws IOException, NormalizationException {
323         return checkNodeName(parseStream(stack.toInference(), stream), expected);
324     }
325
326     private @NonNull NormalizationResult<?> parseStream(final @NonNull EffectiveStatementInference inference,
327             final @NonNull InputStream stream) throws IOException, NormalizationException {
328         try (var reader = new JsonReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
329             final var holder = new NormalizationResultHolder();
330             try (var writer = ImmutableNormalizedNodeStreamWriter.from(holder)) {
331                 try (var parser = JsonParserStream.create(writer, this, inference)) {
332                     parser.parse(reader);
333                 } catch (JsonParseException e) {
334                     throw NormalizationException.ofCause(e);
335                 }
336             }
337             return holder.getResult();
338         }
339     }
340 }