View Javadoc
1   /*
2    * Metis: Java Data Framework
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.metis.viewer;
18  
19  import io.github.tonywasher.joceanus.metis.data.MetisDataDelta;
20  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataList;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataMap;
22  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
23  import io.github.tonywasher.joceanus.oceanus.profile.OceanusProfile;
24  
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  
29  /**
30   * Data Viewer Page.
31   */
32  public class MetisViewerPage {
33      /**
34       * Items per page.
35       */
36      static final int ITEMS_PER_PAGE = 50;
37  
38      /**
39       * The Master entry.
40       */
41      private final MetisViewerEntry theEntry;
42  
43      /**
44       * The Parent page.
45       */
46      private final MetisViewerPage theParent;
47  
48      /**
49       * The Links.
50       */
51      private final Map<String, Object> theLinkMap;
52  
53      /**
54       * The StringBuilder.
55       */
56      private final StringBuilder theBuilder;
57  
58      /**
59       * The Object.
60       */
61      private final Object theObject;
62  
63      /**
64       * The Size.
65       */
66      private final int theSize;
67  
68      /**
69       * The Pages.
70       */
71      private final int thePages;
72  
73      /**
74       * The mode.
75       */
76      private MetisViewerMode theMode;
77  
78      /**
79       * The Index.
80       */
81      private int theItemNo;
82  
83      /**
84       * The Page No.
85       */
86      private int thePageNo;
87  
88      /**
89       * The NextId.
90       */
91      private int theNextId;
92  
93      /**
94       * The HTML.
95       */
96      private String theHtml;
97  
98      /**
99       * Constructor for initial page.
100      *
101      * @param pEntry the master entry
102      */
103     protected MetisViewerPage(final MetisViewerEntry pEntry) {
104         this(pEntry, null, pEntry.getObject());
105     }
106 
107     /**
108      * Constructor.
109      *
110      * @param pEntry  the master entry
111      * @param pParent the parent page
112      * @param pData   the data
113      */
114     private MetisViewerPage(final MetisViewerEntry pEntry,
115                             final MetisViewerPage pParent,
116                             final Object pData) {
117         /* Record parameters */
118         theEntry = pEntry;
119         theParent = pParent;
120         theObject = pData;
121 
122         /* Create the map and the builder */
123         theLinkMap = new HashMap<>();
124         theBuilder = new StringBuilder();
125 
126         /* Determine the size and pages */
127         theSize = determineSize();
128         thePages = determinePages();
129 
130         /* Determine the initial mode */
131         theMode = determineInitialMode();
132     }
133 
134     /**
135      * Obtain the master entry.
136      *
137      * @return the master entry
138      */
139     protected MetisViewerEntry getMasterEntry() {
140         return theEntry;
141     }
142 
143     /**
144      * Obtain the parent page.
145      *
146      * @return the parent page
147      */
148     protected MetisViewerPage getParent() {
149         return theParent;
150     }
151 
152     /**
153      * Do we have a parent?
154      *
155      * @return true/false
156      */
157     protected boolean hasParent() {
158         return theParent != null;
159     }
160 
161     /**
162      * Obtain the object.
163      *
164      * @return the object
165      */
166     protected Object getObject() {
167         return theObject;
168     }
169 
170     /**
171      * Obtain the mode.
172      *
173      * @return the mode
174      */
175     protected MetisViewerMode getMode() {
176         return theMode;
177     }
178 
179     /**
180      * Obtain the HTML.
181      *
182      * @return the HTML
183      */
184     protected String getHtml() {
185         return theHtml;
186     }
187 
188     /**
189      * Set the HTML.
190      *
191      * @param pHtml the HTML
192      */
193     protected void setHtml(final String pHtml) {
194         theHtml = pHtml;
195     }
196 
197     /**
198      * Determine whether the mode is valid.
199      *
200      * @param pMode the mode
201      * @return true/false
202      */
203     protected boolean validMode(final MetisViewerMode pMode) {
204         return switch (pMode) {
205             case CONTENTS -> hasContents(theObject);
206             case SUMMARY -> isCollection(theObject);
207             case ITEMS -> isNonEmptyList(theObject);
208             default -> false;
209         };
210     }
211 
212     /**
213      * Obtain the itemNo.
214      *
215      * @return the index
216      */
217     protected int getItemNo() {
218         return switch (theMode) {
219             case SUMMARY -> thePageNo + 1;
220             case ITEMS -> theItemNo + 1;
221             default -> -1;
222         };
223     }
224 
225     /**
226      * Obtain the size.
227      *
228      * @return the size
229      */
230     protected int getSize() {
231         return switch (theMode) {
232             case SUMMARY -> thePages;
233             case ITEMS -> theSize;
234             default -> -1;
235         };
236     }
237 
238     /**
239      * Have we got a previous item.
240      *
241      * @return true/false
242      */
243     protected boolean hasPrevious() {
244         return getItemNo() > 1;
245     }
246 
247     /**
248      * Have we got a next item.
249      *
250      * @return true/false
251      */
252     protected boolean hasNext() {
253         return getItemNo() < getSize();
254     }
255 
256     /**
257      * Move to previous page.
258      */
259     protected void previous() {
260         if (hasPrevious()) {
261             if (MetisViewerMode.ITEMS.equals(theMode)) {
262                 theItemNo--;
263             } else {
264                 thePageNo--;
265             }
266         }
267     }
268 
269     /**
270      * Move to next page.
271      */
272     protected void next() {
273         if (hasNext()) {
274             if (MetisViewerMode.ITEMS.equals(theMode)) {
275                 theItemNo++;
276             } else {
277                 thePageNo++;
278             }
279         }
280     }
281 
282     /**
283      * Set the page.
284      *
285      * @param pPage the page #
286      */
287     protected void setPageNo(final int pPage) {
288         if ((pPage > 0)
289                 && (pPage <= getSize())) {
290             if (MetisViewerMode.ITEMS.equals(theMode)) {
291                 theItemNo = pPage - 1;
292             } else {
293                 thePageNo = pPage - 1;
294             }
295         }
296     }
297 
298     /**
299      * Set the mode.
300      *
301      * @param pMode the mode
302      */
303     protected void setMode(final MetisViewerMode pMode) {
304         if (validMode(pMode)) {
305             theMode = pMode;
306         }
307     }
308 
309     /**
310      * Reset the page.
311      */
312     protected void resetPage() {
313         theLinkMap.clear();
314         theNextId = 0;
315     }
316 
317     /**
318      * Generate a new link for the page.
319      *
320      * @param pData the object to link to
321      * @return the link name
322      */
323     protected String newLink(final Object pData) {
324         /* Generate the new id */
325         theBuilder.setLength(0);
326         theBuilder.append("Object");
327         theBuilder.append(theNextId++);
328         final String myId = theBuilder.toString();
329 
330         /* Record the id and return it */
331         theLinkMap.put(myId, pData);
332         return myId;
333     }
334 
335     /**
336      * Obtain the new page for the link.
337      *
338      * @param pLink the link id
339      * @return the new page
340      */
341     protected MetisViewerPage newPage(final String pLink) {
342         /* Lookup the data */
343         final Object myData = theLinkMap.get(pLink);
344         return myData == null
345                 ? this
346                 : new MetisViewerPage(theEntry, this, myData);
347     }
348 
349     /**
350      * Determine the size of a collection.
351      *
352      * @return the size
353      */
354     private int determineSize() {
355         /* handle DataDifference */
356         Object myObject = theObject instanceof MetisDataDelta myDelta
357                 ? myDelta.getObject()
358                 : theObject;
359 
360         /* handle embedded objects */
361         if (myObject instanceof MetisDataList<?> myList) {
362             myObject = myList.getUnderlyingList();
363         }
364         if (myObject instanceof MetisDataMap<?, ?> myMap) {
365             myObject = myMap.getUnderlyingMap();
366         }
367 
368         /* Handle multi-page objects */
369         if (myObject instanceof List<?> myList) {
370             return myList.size();
371         } else if (myObject instanceof Map<?, ?> myMap) {
372             return myMap.size();
373         }
374         return -1;
375     }
376 
377     /**
378      * Determine the pages of a collection.
379      *
380      * @return the pages
381      */
382     private int determinePages() {
383         return theSize == -1
384                 ? -1
385                 : ((theSize - 1) / ITEMS_PER_PAGE) + 1;
386     }
387 
388     /**
389      * Determine the initial Mode.
390      *
391      * @return the initial mode
392      */
393     private MetisViewerMode determineInitialMode() {
394         /* Try Contents */
395         if (validMode(MetisViewerMode.CONTENTS)) {
396             return MetisViewerMode.CONTENTS;
397         }
398 
399         /* Try Summary */
400         if (validMode(MetisViewerMode.SUMMARY)) {
401             return MetisViewerMode.SUMMARY;
402         }
403 
404         /* Handle null */
405         if (theObject == null) {
406             return MetisViewerMode.NULL;
407         }
408 
409         /* Reject the mode */
410         throw new IllegalArgumentException("Invalid object: " + theObject);
411     }
412 
413     /**
414      * Determine whether an object is a collection.
415      *
416      * @param pObject the object
417      * @return true/false
418      */
419     private static boolean isCollection(final Object pObject) {
420         /* handle DataDifference */
421         final Object myObject = pObject instanceof MetisDataDelta myDelta
422                 ? myDelta.getObject()
423                 : pObject;
424 
425         /* Handle extended Lists/Maps */
426         if (myObject instanceof MetisDataList
427                 || myObject instanceof MetisDataMap) {
428             return true;
429         }
430 
431         /* Handle multi-page objects */
432         return myObject instanceof List
433                 || myObject instanceof Map;
434     }
435 
436     /**
437      * Determine whether an object is a non-empty list.
438      *
439      * @param pObject the object
440      * @return true/false
441      */
442     private static boolean isNonEmptyList(final Object pObject) {
443         /* handle DataDifference */
444         Object myObject = pObject instanceof MetisDataDelta myDelta
445                 ? myDelta.getObject()
446                 : pObject;
447 
448         /* handle embedded objects */
449         if (myObject instanceof MetisDataList<?> myList) {
450             myObject = myList.getUnderlyingList();
451         }
452 
453         /* Handle non-empty lists */
454         return myObject instanceof List<?> myList
455                 && !myList.isEmpty();
456     }
457 
458     /**
459      * Determine whether an object has contents.
460      *
461      * @param pObject the object
462      * @return true/false
463      */
464     private static boolean hasContents(final Object pObject) {
465         /* Handle null */
466         if (pObject == null) {
467             return false;
468         }
469 
470         /* handle DataDifference */
471         final Object myObject = pObject instanceof MetisDataDelta myDelta
472                 ? myDelta.getObject()
473                 : pObject;
474 
475         /* Handle structured object */
476         if (myObject instanceof MetisFieldItem) {
477             return true;
478         }
479 
480         /* Handle tethysProfile */
481         if (myObject instanceof OceanusProfile) {
482             return true;
483         }
484 
485         /* Handle simple objects */
486         return myObject instanceof Throwable
487                 || myObject instanceof StackTraceElement[];
488     }
489 
490     /**
491      * Does the object have multiple modes?
492      *
493      * @return true/false
494      */
495     protected boolean hasMultiModes() {
496         return hasContents(theObject) && isCollection(theObject);
497     }
498 
499     /**
500      * Determine whether an object is link-able.
501      *
502      * @param pObject the object
503      * @return true/false
504      */
505     protected static boolean isLinkable(final Object pObject) {
506         return hasContents(pObject) || isCollection(pObject);
507     }
508 }