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