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