Merge "Fix checkListKey not checking actual/expected values"
[yangtools.git] / code-generator / binding-data-codec / src / main / java / org / opendaylight / yangtools / binding / data / codec / impl / LazyDataObject.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.binding.data.codec.impl;
9
10 import com.google.common.base.MoreObjects;
11 import com.google.common.base.MoreObjects.ToStringHelper;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.ImmutableMap;
15 import java.lang.reflect.InvocationHandler;
16 import java.lang.reflect.InvocationTargetException;
17 import java.lang.reflect.Method;
18 import java.lang.reflect.Proxy;
19 import java.util.Map;
20 import java.util.Objects;
21 import java.util.concurrent.ConcurrentHashMap;
22 import org.opendaylight.yangtools.binding.data.codec.util.AugmentationReader;
23 import org.opendaylight.yangtools.yang.binding.Augmentable;
24 import org.opendaylight.yangtools.yang.binding.Augmentation;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
26 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 class LazyDataObject implements InvocationHandler, AugmentationReader {
32
33     private static final Logger LOG = LoggerFactory.getLogger(LazyDataObject.class);
34     private static final String GET_IMPLEMENTED_INTERFACE = "getImplementedInterface";
35     private static final String TO_STRING = "toString";
36     private static final String EQUALS = "equals";
37     private static final String GET_AUGMENTATION = "getAugmentation";
38     private static final String HASHCODE = "hashCode";
39     private static final Object NULL_VALUE = new Object();
40
41     private final ConcurrentHashMap<Method, Object> cachedData = new ConcurrentHashMap<>();
42     private final NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> data;
43     private final DataObjectCodecContext<?> context;
44
45     private volatile ImmutableMap<Class<? extends Augmentation<?>>, Augmentation<?>> cachedAugmentations = null;
46     private volatile Integer cachedHashcode = null;
47
48     @SuppressWarnings({ "rawtypes", "unchecked" })
49     LazyDataObject(final DataObjectCodecContext<?> ctx, final NormalizedNodeContainer data) {
50         this.context = Preconditions.checkNotNull(ctx, "Context must not be null");
51         this.data = Preconditions.checkNotNull(data, "Data must not be null");
52     }
53
54     @Override
55     public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
56         if (method.getParameterTypes().length == 0) {
57             final String name = method.getName();
58             if (GET_IMPLEMENTED_INTERFACE.equals(name)) {
59                 return context.bindingClass();
60             } else if (TO_STRING.equals(name)) {
61                 return bindingToString();
62             } else if (HASHCODE.equals(name)) {
63                 return bindingHashCode();
64             }
65             return getBindingData(method);
66         } else if (GET_AUGMENTATION.equals(method.getName())) {
67             return getAugmentationImpl((Class<?>) args[0]);
68         } else if (EQUALS.equals(method.getName())) {
69             return bindingEquals(args[0]);
70         }
71         throw new UnsupportedOperationException("Unsupported method " + method);
72     }
73
74     private boolean bindingEquals(final Object other) {
75         if (other == null) {
76             return false;
77         }
78         if (!context.bindingClass().isAssignableFrom(other.getClass())) {
79             return false;
80         }
81         try {
82             for (final Method m : context.getHashCodeAndEqualsMethods()) {
83                 final Object thisValue = getBindingData(m);
84                 final Object otherValue = m.invoke(other);
85                 if(!Objects.equals(thisValue, otherValue)) {
86                     return false;
87                 }
88             }
89         } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
90             LOG.warn("Can not determine equality of {} and {}", this, other, e);
91             return false;
92         }
93         return true;
94     }
95
96     private Integer bindingHashCode() {
97         final Integer ret = cachedHashcode;
98         if (ret != null) {
99             return ret;
100         }
101
102         final int prime = 31;
103         int result = 1;
104         for (final Method m : context.getHashCodeAndEqualsMethods()) {
105             final Object value = getBindingData(m);
106             result += prime * result + ((value == null) ? 0 : value.hashCode());
107         }
108         if (Augmentation.class.isAssignableFrom(context.bindingClass())) {
109             result += prime * result + (getAugmentations(this).hashCode());
110         }
111         cachedHashcode = result;
112         return result;
113     }
114
115     private Object getBindingData(final Method method) {
116         Object cached = cachedData.get(method);
117         if (cached == null) {
118             final Object readedValue = context.getBindingChildValue(method, data);
119             if (readedValue == null) {
120                 cached = NULL_VALUE;
121             } else {
122                 cached = readedValue;
123             }
124             cachedData.putIfAbsent(method, cached);
125         }
126
127         return cached == NULL_VALUE ? null : cached;
128     }
129
130     private Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentationsImpl() {
131         ImmutableMap<Class<? extends Augmentation<?>>, Augmentation<?>> ret = cachedAugmentations;
132         if (ret == null) {
133             synchronized (this) {
134                 ret = cachedAugmentations;
135                 if (ret == null) {
136                     ret = ImmutableMap.copyOf(context.getAllAugmentationsFrom(data));
137                     cachedAugmentations = ret;
138                 }
139             }
140         }
141
142         return ret;
143     }
144
145     @Override
146     public Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentations(final Object obj) {
147         Preconditions.checkArgument(this == Proxy.getInvocationHandler(obj),
148                 "Supplied object is not associated with this proxy handler");
149
150         return getAugmentationsImpl();
151     }
152
153     private Object getAugmentationImpl(final Class<?> cls) {
154         final ImmutableMap<Class<? extends Augmentation<?>>, Augmentation<?>> aug = cachedAugmentations;
155         if (aug != null) {
156             return aug.get(cls);
157         }
158         Preconditions.checkNotNull(cls,"Supplied augmentation must not be null.");
159         final Optional<DataContainerCodecContext<?>> augCtx= context.getPossibleStreamChild(cls);
160         if(augCtx.isPresent()) {
161             final Optional<NormalizedNode<?, ?>> augData = data.getChild(augCtx.get().getDomPathArgument());
162             if (augData.isPresent()) {
163                 return augCtx.get().dataFromNormalizedNode(augData.get());
164             }
165         }
166         return null;
167     }
168
169     public String bindingToString() {
170         final ToStringHelper helper = MoreObjects.toStringHelper(context.bindingClass()).omitNullValues();
171
172         for (final Method m :context.getHashCodeAndEqualsMethods()) {
173             helper.add(m.getName(), getBindingData(m));
174         }
175         if (Augmentable.class.isAssignableFrom(context.bindingClass())) {
176             helper.add("augmentations", getAugmentationsImpl());
177         }
178         return helper.toString();
179     }
180
181     @Override
182     public int hashCode() {
183         final int prime = 31;
184         int result = 1;
185         result = prime * result + context.hashCode();
186         result = prime * result + data.hashCode();
187         return result;
188     }
189
190     @Override
191     public boolean equals(final Object obj) {
192         if (this == obj) {
193             return true;
194         }
195         if (obj == null) {
196             return false;
197         }
198         if (getClass() != obj.getClass()) {
199             return false;
200         }
201         final LazyDataObject other = (LazyDataObject) obj;
202         if (context == null) {
203             if (other.context != null) {
204                 return false;
205             }
206         } else if (!context.equals(other.context)) {
207             return false;
208         }
209         if (data == null) {
210             if (other.data != null) {
211                 return false;
212             }
213         } else if (!data.equals(other.data)) {
214             return false;
215         }
216         return true;
217     }
218 }