Map identities to interfaces, not abstract classes
[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
241                     final Iterable<Identifiable> dataList = (Iterable<Identifiable>) potentialList;
242                     if (childArgument instanceof IdentifiableItem<?, ?>) {
243                         return readUsingIdentifiableItem(dataList, (IdentifiableItem) childArgument, builder);
244                     } else {
245                         return readAll(dataList, builder);
246                     }
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.getKey());
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.getKey()) && 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.getKey());
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).getAugmentation(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 }