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