0011bc666b2286d75149c44e0e1b387430b9af01
[mdsal.git] / binding / yang-binding / src / main / java / org / opendaylight / yangtools / yang / binding / util / DataObjectReadingUtil.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.binding.util;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12
13 import com.google.common.base.Optional;
14 import com.google.common.collect.ImmutableMap;
15 import com.google.common.collect.ImmutableMap.Builder;
16 import java.lang.reflect.InvocationTargetException;
17 import java.lang.reflect.Method;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Map.Entry;
24 import org.opendaylight.yangtools.yang.binding.Augmentable;
25 import org.opendaylight.yangtools.yang.binding.Augmentation;
26 import org.opendaylight.yangtools.yang.binding.DataContainer;
27 import org.opendaylight.yangtools.yang.binding.DataObject;
28 import org.opendaylight.yangtools.yang.binding.Identifiable;
29 import org.opendaylight.yangtools.yang.binding.Identifier;
30 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
31 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
32 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
33 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
34
35 public final class DataObjectReadingUtil {
36
37     private static final DataObjectReadingStrategy REAUSABLE_AUGMENTATION_READING_STRATEGY =
38             new AugmentationReadingStrategy();
39
40     private DataObjectReadingUtil() {
41         throw new UnsupportedOperationException("Utility class. Instantion is not allowed.");
42     }
43
44     /**
45      * Read data from parent at specified path.
46      *
47      * @param parent
48      *            Parent object on which read operation will be performed
49      * @param parentPath
50      *            Path, to parent object.
51      * @param childPath
52      *            Path, which is nested to parent, and should be read.
53      * @return Value of object.
54      */
55     public static <T extends DataObject, P extends DataObject> Map<InstanceIdentifier<T>, T> readData(
56             final P parent, final InstanceIdentifier<P> parentPath, final InstanceIdentifier<T> childPath) {
57         checkArgument(parent != null, "Parent must not be null.");
58         checkArgument(parentPath != null, "Parent path must not be null");
59         checkArgument(childPath != null, "Child path must not be null");
60         checkArgument(parentPath.containsWildcarded(childPath), "Parent object must be parent of child.");
61
62         List<PathArgument> pathArgs = subList(parentPath.getPathArguments(), childPath.getPathArguments());
63         @SuppressWarnings("rawtypes")
64         Map<InstanceIdentifier, DataContainer> lastFound = Collections.singletonMap(parentPath, parent);
65         for (PathArgument pathArgument : pathArgs) {
66             @SuppressWarnings("rawtypes")
67             final ImmutableMap.Builder<InstanceIdentifier, DataContainer> potentialBuilder = ImmutableMap.builder();
68             for (@SuppressWarnings("rawtypes") Entry<InstanceIdentifier, DataContainer> entry : lastFound.entrySet()) {
69                 potentialBuilder.putAll(readData(entry, pathArgument));
70             }
71             lastFound = potentialBuilder.build();
72             if (lastFound.isEmpty()) {
73                 return Collections.emptyMap();
74             }
75         }
76         @SuppressWarnings({ "unchecked", "rawtypes" })
77         final Map<InstanceIdentifier<T>, T> result = (Map) lastFound;
78         return result;
79     }
80
81     @SuppressWarnings("rawtypes")
82     private static Map<InstanceIdentifier, DataContainer> readData(final Entry<InstanceIdentifier, DataContainer> entry,
83             final PathArgument pathArgument) {
84         return readData(entry.getValue(), entry.getKey(), pathArgument);
85     }
86
87     public static <T extends DataObject> Optional<T> readData(final DataObject source, final Class<T> child) {
88         checkArgument(source != null, "Object should not be null.");
89         checkArgument(child != null, "Child type should not be null");
90         Class<? extends DataContainer> parentClass = source.getImplementedInterface();
91
92         @SuppressWarnings("unchecked")
93         T potential = (T) resolveReadStrategy(parentClass, child).read(source, child);
94         return Optional.fromNullable(potential);
95     }
96
97     @SuppressWarnings("rawtypes")
98     private static Map<InstanceIdentifier, DataContainer> readData(final DataContainer parent,
99             final InstanceIdentifier parentPath, final PathArgument child) {
100         checkArgument(parent != null, "Object should not be null.");
101         checkArgument(child != null, "Child argument should not be null");
102         Class<? extends DataContainer> parentClass = parent.getImplementedInterface();
103         return resolveReadStrategy(parentClass, child.getType()).readUsingPathArgument(parent, child, parentPath);
104     }
105
106     private static DataObjectReadingStrategy resolveReadStrategy(final Class<? extends DataContainer> parentClass,
107             final Class<? extends DataContainer> type) {
108
109         // FIXME: Add caching of strategies
110         return createReadStrategy(parentClass, type);
111     }
112
113     private static DataObjectReadingStrategy createReadStrategy(final Class<? extends DataContainer> parent,
114             final Class<? extends DataContainer> child) {
115
116         if (Augmentable.class.isAssignableFrom(parent) && Augmentation.class.isAssignableFrom(child)) {
117             return REAUSABLE_AUGMENTATION_READING_STRATEGY;
118         }
119
120         /*
121          * FIXME Ensure that this strategies also works for children of cases.
122          * Possible edge-case is : Parent container uses grouping foo case is
123          * added by augmentation also uses foo.
124          */
125         if (Identifiable.class.isAssignableFrom(child)) {
126             @SuppressWarnings("unchecked")
127             final Class<? extends Identifiable<?>> identifiableClass = (Class<? extends Identifiable<?>>) child;
128             return new ListItemReadingStrategy(parent, identifiableClass);
129         }
130         return new ContainerReadingStrategy(parent, child);
131     }
132
133     @SuppressWarnings("rawtypes")
134     private abstract static class DataObjectReadingStrategy {
135
136         private final Class<? extends DataContainer> parentType;
137         private final Class<? extends DataContainer> childType;
138         private final Method getterMethod;
139
140         @SuppressWarnings("unchecked")
141         DataObjectReadingStrategy(final Class parentType, final Class childType) {
142             checkArgument(DataContainer.class.isAssignableFrom(parentType));
143             checkArgument(DataContainer.class.isAssignableFrom(childType));
144             this.parentType = parentType;
145             this.childType = childType;
146             this.getterMethod = resolveGetterMethod(parentType, childType);
147         }
148
149         @SuppressWarnings("unchecked")
150         DataObjectReadingStrategy(final Class parentType, final Class childType, final Method getter) {
151             this.parentType = parentType;
152             this.childType = childType;
153             this.getterMethod = getter;
154         }
155
156         @SuppressWarnings("unused")
157         protected Class<? extends DataContainer> getParentType() {
158             return parentType;
159         }
160
161         protected Class<? extends DataContainer> getChildType() {
162             return childType;
163         }
164
165         protected Method getGetterMethod() {
166             return getterMethod;
167         }
168
169         public abstract Map<InstanceIdentifier, DataContainer> readUsingPathArgument(DataContainer parent,
170                 PathArgument childArgument, InstanceIdentifier targetBuilder);
171
172         public abstract DataContainer read(DataContainer parent, Class<?> child);
173
174         private static Method resolveGetterMethod(final Class<? extends DataContainer> parent, final Class<?> child) {
175             String methodName = "get" + child.getSimpleName();
176             try {
177                 return parent.getMethod(methodName);
178             } catch (NoSuchMethodException e) {
179                 throw new IllegalArgumentException(e);
180             } catch (SecurityException e) {
181                 throw new IllegalStateException(e);
182             }
183         }
184
185     }
186
187     @SuppressWarnings("rawtypes")
188     private static final class ContainerReadingStrategy extends DataObjectReadingStrategy {
189         ContainerReadingStrategy(final Class<? extends DataContainer> parent,
190                 final Class<? extends DataContainer> child) {
191             super(parent, child);
192             checkArgument(child.isAssignableFrom(getGetterMethod().getReturnType()));
193         }
194
195         @Override
196         public Map<InstanceIdentifier, DataContainer> readUsingPathArgument(final DataContainer parent,
197                 final PathArgument childArgument, final InstanceIdentifier parentPath) {
198             final DataContainer result = read(parent, childArgument.getType());
199             if (result != null) {
200                 @SuppressWarnings("unchecked")
201                 InstanceIdentifier childPath = parentPath.child(childArgument.getType());
202                 return Collections.singletonMap(childPath, result);
203             }
204             return Collections.emptyMap();
205         }
206
207         @Override
208         public DataContainer read(final DataContainer parent, final Class<?> child) {
209             try {
210                 Object potentialData = getGetterMethod().invoke(parent);
211                 checkState(potentialData instanceof DataContainer);
212                 return (DataContainer) potentialData;
213
214             } catch (IllegalAccessException | InvocationTargetException e) {
215                 throw new IllegalArgumentException(e);
216             }
217         }
218     }
219
220     @SuppressWarnings("rawtypes")
221     private static final class ListItemReadingStrategy extends DataObjectReadingStrategy {
222         ListItemReadingStrategy(final Class<? extends DataContainer> parent, final Class child) {
223             super(parent, child);
224             checkArgument(Iterable.class.isAssignableFrom(getGetterMethod().getReturnType()));
225         }
226
227         @Override
228         public DataContainer read(final DataContainer parent, final Class<?> child) {
229             // This will always fail since we do not have key.
230             return null;
231         }
232
233         @SuppressWarnings("unchecked")
234         @Override
235         public Map<InstanceIdentifier, DataContainer> readUsingPathArgument(final DataContainer parent,
236                 final PathArgument childArgument, final InstanceIdentifier builder) {
237             try {
238                 Object potentialList = getGetterMethod().invoke(parent);
239                 if (potentialList instanceof Iterable) {
240                     final Iterable<Identifiable> dataList = (Iterable<Identifiable>) potentialList;
241                     return childArgument instanceof IdentifiableItem
242                             ? readUsingIdentifiableItem(dataList, (IdentifiableItem) childArgument, builder)
243                                     : readAll(dataList, builder);
244                 }
245             } catch (InvocationTargetException | IllegalArgumentException | IllegalAccessException e) {
246                 throw new IllegalStateException(e);
247             }
248             return Collections.emptyMap();
249         }
250
251         private Map<InstanceIdentifier, DataContainer> readAll(final Iterable<Identifiable> dataList,
252                 final InstanceIdentifier parentPath) {
253             Builder<InstanceIdentifier, DataContainer> result = ImmutableMap.builder();
254             for (Identifiable item : dataList) {
255                 @SuppressWarnings("unchecked")
256                 InstanceIdentifier childPath = parentPath.child(getChildType(), item.key());
257                 result.put(childPath, (DataContainer) item);
258             }
259             return result.build();
260         }
261
262         @SuppressWarnings("unchecked")
263         private static Map<InstanceIdentifier, DataContainer> readUsingIdentifiableItem(
264                 final Iterable<Identifiable> dataList, final IdentifiableItem childArgument,
265                 final InstanceIdentifier parentPath) {
266             final Identifier<?> key = childArgument.getKey();
267             for (Identifiable item : dataList) {
268                 if (key.equals(item.key()) && item instanceof DataContainer) {
269                     checkState(childArgument.getType().isInstance(item),
270                             "Found child is not instance of requested type");
271                     InstanceIdentifier childPath = parentPath
272                             .child(childArgument.getType(), item.key());
273                     return Collections.singletonMap(childPath, (DataContainer) item);
274                 }
275             }
276             return Collections.emptyMap();
277         }
278
279     }
280
281     private static final class AugmentationReadingStrategy extends DataObjectReadingStrategy {
282         AugmentationReadingStrategy() {
283             super(Augmentable.class, Augmentation.class, null);
284         }
285
286         @SuppressWarnings("rawtypes")
287         @Override
288         public Map<InstanceIdentifier, DataContainer> readUsingPathArgument(final DataContainer parent,
289                 final PathArgument childArgument, final InstanceIdentifier builder) {
290             checkArgument(childArgument instanceof Item, "Path Argument must be Item without keys");
291             DataContainer aug = read(parent, childArgument.getType());
292             if (aug == null) {
293                 return Collections.emptyMap();
294             }
295
296             @SuppressWarnings("unchecked")
297             final InstanceIdentifier childPath = builder.child(childArgument.getType());
298             return Collections.singletonMap(childPath, aug);
299         }
300
301         @Override
302         public DataContainer read(final DataContainer parent, final Class<?> child) {
303             checkArgument(Augmentation.class.isAssignableFrom(child), "Child must be Augmentation.");
304             checkArgument(parent instanceof Augmentable<?>, "Parent must be Augmentable.");
305
306             @SuppressWarnings({ "rawtypes", "unchecked" })
307             Augmentation potential = ((Augmentable) parent).augmentation(child);
308             checkState(potential instanceof DataContainer, "Readed augmention must be data object");
309             return (DataContainer) potential;
310         }
311     }
312
313     /**
314      * Create sublist view of child from element on [size-of-parent] position to last element.
315      *
316      * @return sublist view of child argument
317      * @throws IllegalArgumentException
318      *             if parent argument is bigger than child
319      */
320     private static <P, C> List<C> subList(final Iterable<P> parent, final Iterable<C> child) {
321         Iterator<P> parentIt = parent.iterator();
322         List<C> result = new ArrayList<>();
323         for (C arg : child) {
324             if (parentIt.hasNext()) {
325                 parentIt.next();
326             } else {
327                 result.add(arg);
328             }
329         }
330         if (parentIt.hasNext()) {
331             throw new IllegalArgumentException("Parent argument is bigger than child.");
332         }
333         return result;
334     }
335 }