1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.testsupport.mocking;
7   
8   import static org.mockito.ArgumentMatchers.eq;
9   import static org.mockito.Mockito.doReturn;
10  
11  import java.net.URI;
12  import java.util.Collections;
13  import java.util.LinkedHashMap;
14  import java.util.LinkedList;
15  import java.util.List;
16  import java.util.Map;
17  import java.util.concurrent.atomic.AtomicInteger;
18  
19  import dev.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
20  import dev.metaschema.core.metapath.item.node.IAssemblyNodeItem;
21  import dev.metaschema.core.metapath.item.node.IDocumentNodeItem;
22  import dev.metaschema.core.metapath.item.node.IFieldNodeItem;
23  import dev.metaschema.core.metapath.item.node.IFlagNodeItem;
24  import dev.metaschema.core.metapath.item.node.IModelNodeItem;
25  import dev.metaschema.core.metapath.item.node.INodeItem;
26  import dev.metaschema.core.metapath.item.node.IRootAssemblyNodeItem;
27  import dev.metaschema.core.metapath.item.node.NodeItemKind;
28  import dev.metaschema.core.model.IAssemblyDefinition;
29  import dev.metaschema.core.model.IFieldDefinition;
30  import dev.metaschema.core.model.IFlagDefinition;
31  import dev.metaschema.core.qname.IEnhancedQName;
32  import dev.metaschema.core.util.CollectionUtil;
33  import dev.metaschema.core.util.ObjectUtils;
34  import edu.umd.cs.findbugs.annotations.NonNull;
35  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
36  
37  /**
38   * Generates mock node item objects.
39   */
40  // FIXME: Integrate with classes in dev.metaschema.core.testsupport
41  @SuppressWarnings("checkstyle:MissingJavadocMethodCheck")
42  @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT")
43  public class MockNodeItemFactory
44      extends AbstractMockitoFactory {
45    /**
46     * Construct a new mocked document node item.
47     *
48     * @param documentURI
49     *          the URI representing document's resource location.
50     * @param rootName
51     *          the qualified name of the document's root node item
52     * @param flags
53     *          the root node item's child flag node items
54     * @param modelItems
55     *          the root node item's child model node items
56     * @return the mocked document node item
57     */
58    @NonNull
59    public IDocumentNodeItem document(
60        URI documentURI,
61        IEnhancedQName rootName,
62        List<IFlagNodeItem> flags,
63        List<IModelNodeItem<?, ?>> modelItems) {
64      String qname = ObjectUtils.requireNonNull(rootName.toString());
65      IDocumentNodeItem document = mock(IDocumentNodeItem.class, qname);
66      IRootAssemblyNodeItem root = mock(IRootAssemblyNodeItem.class, qname);
67      IAssemblyDefinition definition = mock(IAssemblyDefinition.class, qname);
68  
69      // doAnswer(invocation -> Stream.of(root)).when(document).modelItems();
70      doReturn(root).when(document).getRootAssemblyNodeItem();
71      doReturn(Collections.singletonList(root)).when(document).getModelItems();
72      doReturn(documentURI).when(document).getDocumentUri();
73      doReturn(documentURI).when(document).getBaseUri();
74      doReturn(NodeItemKind.DOCUMENT).when(document).getNodeItemKind();
75  
76      doReturn(rootName).when(root).getQName();
77      doReturn(document).when(root).getDocumentNodeItem();
78      doReturn(rootName.toString()).when(root).toString();
79  
80      doReturn(definition).when(root).getDefinition();
81      doReturn(rootName).when(definition).getDefinitionQName();
82  
83      handleModelChildren(document, CollectionUtil.emptyList(), CollectionUtil.singletonList(root));
84      handleModelChildren(root, flags, modelItems);
85  
86      return document;
87    }
88  
89    /**
90     * Generate mock calls for methods related to the node item's children nodes.
91     *
92     * @param <T>
93     *          the Java type of the node item
94     * @param item
95     *          the node item to mock
96     * @param flags
97     *          the node item's child flag node items
98     * @param modelItems
99     *          the node item's child model node items
100    */
101   @SuppressWarnings("null")
102   protected <T extends INodeItem> void handleModelChildren(
103       @NonNull T item,
104       List<IFlagNodeItem> flags,
105       List<IModelNodeItem<?, ?>> modelItems) {
106 
107     doReturn(flags).when(item).getFlags();
108 
109     ObjectUtils.requireNonNull(flags).forEach(flag -> {
110       assert flag != null;
111 
112       // handle each flag child
113       IEnhancedQName qname = flag.getQName();
114       doReturn(flag).when(item).getFlagByName(qname);
115       // link parent
116       doReturn(item).when(flag).getParentNodeItem();
117       doReturn(item).when(flag).getParentContentNodeItem();
118     });
119 
120     Map<IEnhancedQName, List<IModelNodeItem<?, ?>>> modelItemsMap = toModelItemsMap(modelItems);
121 
122     doReturn(modelItemsMap.values()).when(item).getModelItems();
123 
124     ObjectUtils.requireNonNull(modelItemsMap).entrySet().forEach(entry -> {
125       assert entry != null;
126 
127       doReturn(entry.getValue()).when(item).getModelItemsByName(eq(entry.getKey()));
128 
129       AtomicInteger position = new AtomicInteger(1);
130       entry.getValue().forEach(modelItem -> {
131         // handle each model item child
132         // link parent
133         doReturn(item).when(modelItem).getParentNodeItem();
134         doReturn(item instanceof IDocumentNodeItem ? null : item).when(modelItem).getParentContentNodeItem();
135 
136         // establish position
137         doReturn(position.getAndIncrement()).when(modelItem).getPosition();
138       });
139     });
140   }
141 
142   @SuppressWarnings("static-method")
143   @NonNull
144   private Map<IEnhancedQName, List<IModelNodeItem<?, ?>>> toModelItemsMap(List<IModelNodeItem<?, ?>> modelItems) {
145 
146     Map<IEnhancedQName, List<IModelNodeItem<?, ?>>> retval = new LinkedHashMap<>(); // NOPMD - intentional
147     for (IModelNodeItem<?, ?> item : ObjectUtils.requireNonNull(modelItems)) {
148       IEnhancedQName name = item.getQName();
149       List<IModelNodeItem<?, ?>> namedItems = retval.get(name);
150       if (namedItems == null) {
151         namedItems = new LinkedList<>(); // NOPMD - intentional
152         retval.put(name, namedItems);
153       }
154       namedItems.add(item);
155     }
156     return CollectionUtil.unmodifiableMap(retval);
157   }
158 
159   /**
160    * Construct a new mocked flag node item.
161    *
162    * @param name
163    *          the qualified name of the flag node item
164    * @param value
165    *          the flag's value
166    * @return the mocked flag node item
167    */
168   @NonNull
169   public IFlagNodeItem flag(@NonNull IEnhancedQName name, @NonNull IAnyAtomicItem value) {
170     IFlagNodeItem flag = mock(IFlagNodeItem.class, ObjectUtils.notNull(name.toString()));
171     IFlagDefinition definition = mock(IFlagDefinition.class, ObjectUtils.notNull(name.toString()));
172 
173     doReturn(name).when(flag).getQName();
174     doReturn(true).when(flag).hasValue();
175     doReturn(value).when(flag).toAtomicItem();
176     doReturn(name.toString()).when(flag).toString();
177 
178     doReturn(definition).when(flag).getDefinition();
179     doReturn(name).when(definition).getDefinitionQName();
180     doReturn(value.getJavaTypeAdapter()).when(definition).getJavaTypeAdapter();
181 
182     handleModelChildren(flag, CollectionUtil.emptyList(), CollectionUtil.emptyList());
183 
184     return flag;
185   }
186 
187   /**
188    * Construct a new mocked field node item with no child flags.
189    *
190    * @param name
191    *          the qualified name of the field node item
192    * @param value
193    *          the field's value
194    * @return the mocked field node item
195    */
196   @NonNull
197   public IFieldNodeItem field(@NonNull IEnhancedQName name, @NonNull IAnyAtomicItem value) {
198     return field(name, value, CollectionUtil.emptyList());
199   }
200 
201   /**
202    * Construct a new mocked field node item with no child flags.
203    *
204    * @param name
205    *          the qualified name of the field node item
206    * @param value
207    *          the field's value
208    * @param flags
209    *          the node item's child flag node items
210    * @return the mocked field node item
211    */
212   @NonNull
213   public IFieldNodeItem field(
214       @NonNull IEnhancedQName name,
215       @NonNull IAnyAtomicItem value,
216       List<IFlagNodeItem> flags) {
217     IFieldNodeItem field = mock(IFieldNodeItem.class, ObjectUtils.notNull(name.toString()));
218     IFieldDefinition definition = mock(IFieldDefinition.class, ObjectUtils.notNull(name.toString()));
219 
220     doReturn(name).when(field).getQName();
221     doReturn(true).when(field).hasValue();
222     doReturn(value).when(field).toAtomicItem();
223     doReturn(name.toString()).when(field).toString();
224 
225     doReturn(definition).when(field).getDefinition();
226     doReturn(name).when(definition).getDefinitionQName();
227     doReturn(value.getJavaTypeAdapter()).when(definition).getJavaTypeAdapter();
228 
229     handleModelChildren(field, ObjectUtils.requireNonNull(flags), CollectionUtil.emptyList());
230     return field;
231   }
232 
233   /**
234    * Construct a new mocked assembly node item with the provided child flags and
235    * fields.
236    *
237    * @param name
238    *          the qualified name of the assembly node item
239    * @param flags
240    *          the node item's child flag node items
241    * @param modelItems
242    *          the node item's child model node items
243    * @return the mocked assembly node item
244    */
245   @NonNull
246   public IAssemblyNodeItem assembly(
247       @NonNull IEnhancedQName name,
248       List<IFlagNodeItem> flags,
249       List<IModelNodeItem<?, ?>> modelItems) {
250     IAssemblyNodeItem assembly = mock(IAssemblyNodeItem.class, ObjectUtils.notNull(name.toString()));
251     IAssemblyDefinition definition = mock(IAssemblyDefinition.class, ObjectUtils.notNull(name.toString()));
252 
253     doReturn(name).when(assembly).getQName();
254     doReturn(false).when(assembly).hasValue();
255     doReturn(name.toString()).when(assembly).toString();
256 
257     doReturn(definition).when(assembly).getDefinition();
258     doReturn(name).when(definition).getDefinitionQName();
259 
260     handleModelChildren(assembly, ObjectUtils.requireNonNull(flags), ObjectUtils.requireNonNull(modelItems));
261 
262     return assembly;
263   }
264 
265 }