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