Bug 4969: NPE in JSONCodecFactory by attempt to find codec for a leafref
[yangtools.git] / yang / 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 com.google.common.annotations.Beta;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Verify;
13 import com.google.common.cache.CacheBuilder;
14 import com.google.common.cache.CacheLoader;
15 import com.google.common.cache.LoadingCache;
16 import com.google.gson.stream.JsonWriter;
17 import java.io.IOException;
18 import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
19 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
20 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
21 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
22 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
23 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
24 import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
25 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
26 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
27 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
28 import org.opendaylight.yangtools.yang.model.util.DerivedType;
29 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 /**
34  * Factory for creating JSON equivalents of codecs. Each instance of this object is bound to
35  * a particular {@link SchemaContext}, but can be reused by multiple {@link JSONNormalizedNodeStreamWriter}s.
36  */
37 @Beta
38 public final class JSONCodecFactory {
39     private static final Logger LOG = LoggerFactory.getLogger(JSONCodecFactory.class);
40     private static final JSONCodec<Object> NULL_CODEC = new JSONCodec<Object>() {
41         @Override
42         public Object deserialize(final String input) {
43             return null;
44         }
45
46         @Override
47         public String serialize(final Object input) {
48             return null;
49         }
50
51         @Override
52         public boolean needQuotes() {
53             return false;
54         }
55
56         @Override
57         public void serializeToWriter(final JsonWriter writer, final Object value) throws IOException {
58             // NOOP since codec is unkwown.
59             LOG.warn("Call of the serializeToWriter method on JSONCodecFactory.NULL_CODEC object. No operation performed.");
60         }
61     };
62
63     private final LoadingCache<DataSchemaNode, JSONCodec<Object>> codecs =
64             CacheBuilder.newBuilder().softValues().build(new CacheLoader<DataSchemaNode, JSONCodec<Object>>() {
65         @Override
66         public JSONCodec<Object> load(final DataSchemaNode key) throws Exception {
67             final TypeDefinition<?> type;
68             if (key instanceof LeafSchemaNode) {
69                 type = ((LeafSchemaNode) key).getType();
70             } else if (key instanceof LeafListSchemaNode) {
71                 type = ((LeafListSchemaNode) key).getType();
72             } else {
73                 throw new IllegalArgumentException("Not supported node type " + key.getClass().getName());
74             }
75             return createCodec(key,type);
76         }
77     });
78
79     private final SchemaContext schemaContext;
80     private final JSONCodec<?> iidCodec;
81
82     private JSONCodecFactory(final SchemaContext context) {
83         this.schemaContext = Preconditions.checkNotNull(context);
84         iidCodec = new JSONStringInstanceIdentifierCodec(context, this);
85     }
86
87     /**
88      * Instantiate a new codec factory attached to a particular context.
89      *
90      * @param context SchemaContext to which the factory should be bound
91      * @return A codec factory instance.
92      */
93     public static JSONCodecFactory create(final SchemaContext context) {
94         return new JSONCodecFactory(context);
95     }
96
97     @SuppressWarnings("unchecked")
98     private JSONCodec<Object> createCodec(final DataSchemaNode key, final TypeDefinition<?> type) {
99         final TypeDefinition<?> normalizedType = DerivedType.from(type);
100         if (normalizedType instanceof LeafrefTypeDefinition) {
101             return createReferencedTypeCodec(key, (LeafrefTypeDefinition) normalizedType);
102         } else if (normalizedType instanceof IdentityrefTypeDefinition) {
103             final JSONCodec<?> jsonStringIdentityrefCodec =
104                     new JSONStringIdentityrefCodec(schemaContext, key.getQName().getModule());
105             return (JSONCodec<Object>) jsonStringIdentityrefCodec;
106         }
107         return createFromSimpleType(normalizedType);
108     }
109
110     private JSONCodec<Object> createReferencedTypeCodec(final DataSchemaNode schema,
111             final LeafrefTypeDefinition type) {
112         // FIXME: Verify if this does indeed support leafref of leafref
113         final TypeDefinition<?> referencedType =
114                 SchemaContextUtil.getBaseTypeForLeafRef(type, getSchemaContext(), schema);
115         Verify.verifyNotNull(referencedType, "Unable to find base type for leafref node '%s'.", schema.getPath());
116         return createCodec(schema, referencedType);
117     }
118
119     @SuppressWarnings("unchecked")
120     private JSONCodec<Object> createFromSimpleType(final TypeDefinition<?> type) {
121         if (type instanceof InstanceIdentifierTypeDefinition) {
122             return (JSONCodec<Object>) iidCodec;
123         }
124         if (type instanceof EmptyTypeDefinition) {
125             return JSONEmptyCodec.INSTANCE;
126         }
127
128         final TypeDefinitionAwareCodec<Object, ?> codec = TypeDefinitionAwareCodec.from(type);
129         if (codec == null) {
130             LOG.debug("Codec for type \"{}\" is not implemented yet.", type.getQName()
131                     .getLocalName());
132             return NULL_CODEC;
133         }
134         return (JSONCodec<Object>) AbstractJSONCodec.create(codec);
135     }
136
137     SchemaContext getSchemaContext() {
138         return schemaContext;
139     }
140
141     JSONCodec<Object> codecFor(final DataSchemaNode schema) {
142         return codecs.getUnchecked(schema);
143     }
144
145 }