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