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