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.base.OceanusException;
20  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusDecimal;
21  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
22  import io.github.tonywasher.joceanus.oceanus.profile.OceanusProfile;
23  import io.github.tonywasher.joceanus.metis.data.MetisDataDelta;
24  import io.github.tonywasher.joceanus.metis.data.MetisDataFieldValue;
25  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataList;
26  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataMap;
27  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
28  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldDef;
29  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldSetDef;
30  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldVersionedDef;
31  import io.github.tonywasher.joceanus.metis.field.MetisFieldValidation;
32  import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionHistory;
33  import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedItem;
34  
35  import java.util.Iterator;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Map.Entry;
39  
40  /**
41   * Data Viewer Formatter.
42   */
43  public class MetisViewerFormatter {
44      /**
45       * Items per page.
46       */
47      protected static final int ITEMS_PER_PAGE = 50;
48  
49      /**
50       * The index column.
51       */
52      private static final String COLUMN_INDEX = "Index";
53  
54      /**
55       * The field column.
56       */
57      private static final String COLUMN_FIELD = MetisViewerResource.VIEWER_COLUMN_FIELD.getValue();
58  
59      /**
60       * The key column.
61       */
62      private static final String COLUMN_KEY = MetisViewerResource.VIEWER_COLUMN_KEY.getValue();
63  
64      /**
65       * The value column.
66       */
67      private static final String COLUMN_VALUE = MetisViewerResource.VIEWER_COLUMN_VALUE.getValue();
68  
69      /**
70       * List Table.
71       */
72      private static final String TABLE_LIST = "List";
73  
74      /**
75       * Map Table.
76       */
77      private static final String TABLE_MAP = MetisViewerResource.VIEWER_TABLE_MAP.getValue();
78  
79      /**
80       * Stack Trace table.
81       */
82      private static final String TABLE_STACKTRACE = MetisViewerResource.VIEWER_TABLE_STACKTRACE.getValue();
83  
84      /**
85       * The HTML Builder.
86       */
87      private final MetisViewerBuilder theBuilder;
88  
89      /**
90       * Constructor.
91       *
92       * @param pFormatter the data formatter
93       * @throws OceanusException on error
94       */
95      protected MetisViewerFormatter(final OceanusDataFormatter pFormatter) throws OceanusException {
96          theBuilder = new MetisViewerBuilder(pFormatter);
97      }
98  
99      /**
100      * Build HTML table describing Object.
101      *
102      * @param pPage the viewer page
103      */
104     protected void formatPage(final MetisViewerPage pPage) {
105         /* Reset the document */
106         theBuilder.resetDocument(pPage);
107 
108         /* Switch on the Mode */
109         switch (pPage.getMode()) {
110             case CONTENTS:
111                 formatHTMLObject(pPage.getObject());
112                 break;
113             case SUMMARY:
114                 formatHTMLCollection(pPage.getObject(), pPage.getItemNo());
115                 break;
116             case ITEMS:
117                 formatHTMLItem(pPage.getObject(), pPage.getItemNo());
118                 break;
119             default:
120                 break;
121         }
122 
123         /* Format the document */
124         theBuilder.formatDocument();
125     }
126 
127     /**
128      * Build HTML table.
129      *
130      * @param pObject the object
131      */
132     private void formatHTMLObject(final Object pObject) {
133         /* handle DataDifference */
134         final Object myObject = pObject instanceof MetisDataDelta myDelta
135                 ? myDelta.getObject()
136                 : pObject;
137 
138         /* If we are FieldItem */
139         if (myObject instanceof MetisFieldItem myField) {
140             formatHTMLEosFieldItem(myField);
141 
142             /* If we are Stack Trace */
143         } else if (myObject instanceof StackTraceElement[] myStack) {
144             formatHTMLStackTrace(myStack);
145 
146             /* If we are TethysProfile */
147         } else if (myObject instanceof OceanusProfile myProfile) {
148             formatHTMLEosFieldItem(new MetisViewerProfileWrapper(myProfile));
149 
150             /* If we are Throwable */
151         } else if (myObject instanceof Throwable myThrow) {
152             formatHTMLEosFieldItem(new MetisViewerExceptionWrapper(myThrow));
153 
154             /* else handle unsupported list item */
155         } else {
156             formatHTMLUnknown(myObject);
157         }
158     }
159 
160     /**
161      * Build HTML collection.
162      *
163      * @param pObject the object
164      * @param pStart  the start index for the section
165      */
166     private void formatHTMLCollection(final Object pObject,
167                                       final int pStart) {
168         /* handle DataDifference */
169         Object myObject = pObject instanceof MetisDataDelta myDelta
170                 ? myDelta.getObject()
171                 : pObject;
172 
173         /* handle embedded objects */
174         if (myObject instanceof MetisDataList<?> myList) {
175             myObject = myList.getUnderlyingList();
176         } else if (myObject instanceof MetisDataMap<?, ?> myMap) {
177             myObject = myMap.getUnderlyingMap();
178         }
179 
180         /* If we are List */
181         if (myObject instanceof List<?> myList) {
182             formatHTMLListSection(myList, pStart);
183 
184             /* If we are Map */
185         } else if (myObject instanceof Map<?, ?> myMap) {
186             formatHTMLMapSection(myMap, pStart);
187         }
188     }
189 
190     /**
191      * Build HTML item.
192      *
193      * @param pObject the object
194      * @param pIndex  index of the item
195      */
196     private void formatHTMLItem(final Object pObject,
197                                 final int pIndex) {
198         /* handle DataDifference */
199         Object myObject = pObject instanceof MetisDataDelta myDelta
200                 ? myDelta.getObject()
201                 : pObject;
202 
203         /* handle embedded objects */
204         if (myObject instanceof MetisDataList<?> myList) {
205             myObject = myList.getUnderlyingList();
206         }
207 
208         /* If we are List */
209         if (myObject instanceof List<?> myList) {
210             formatHTMLListItem(myList, pIndex);
211         }
212     }
213 
214     /**
215      * Build HTML table describing DataEosFieldItem.
216      *
217      * @param pItem the item
218      */
219     private void formatHTMLEosFieldItem(final MetisFieldItem pItem) {
220         /* Access details */
221         final MetisFieldSetDef myFields = pItem.getDataFieldSet();
222         final boolean isVersioned = pItem instanceof MetisFieldVersionedItem;
223         final MetisFieldVersionedItem myItem = isVersioned
224                 ? (MetisFieldVersionedItem) pItem
225                 : null;
226 
227         /* Initialise the document */
228         theBuilder.newTitle(myFields.getName());
229         theBuilder.newTable();
230         theBuilder.newTitleCell(COLUMN_FIELD);
231         theBuilder.newTitleCell(COLUMN_VALUE);
232 
233         /* Loop through the fields */
234         final Iterator<MetisFieldDef> myIterator = myFields.fieldIterator();
235         while (myIterator.hasNext()) {
236             /* Access Field */
237             final MetisFieldDef myField = myIterator.next();
238 
239             /* Access the value */
240             final Object myValue = myField.isCalculated()
241                     ? MetisDataFieldValue.SKIP
242                     : myField.getFieldValue(pItem);
243 
244             /* Skip value if required */
245             if (skipValue(myValue)) {
246                 continue;
247             }
248 
249             /* Start the field */
250             theBuilder.newTableRow();
251             theBuilder.newDataCell(myField.getFieldId().getId());
252             if (myItem != null
253                     && myField instanceof MetisFieldVersionedDef
254                     && myItem.fieldChanged(myField).isDifferent()) {
255                 theBuilder.newDataCell(myValue, true);
256             } else {
257                 theBuilder.newDataCell(myValue);
258             }
259         }
260     }
261 
262     /**
263      * Should we skip the value.
264      *
265      * @param pValue the value
266      * @return true/false
267      */
268     private static boolean skipValue(final Object pValue) {
269         /* Access the value */
270         Object myValue = pValue;
271 
272         /* Skip empty lists */
273         if (myValue instanceof MetisDataList<?> myList) {
274             myValue = myList.getUnderlyingList();
275         }
276         if (myValue instanceof List<?> myList) {
277             return myList.isEmpty();
278         }
279 
280         /* Skip empty maps */
281         if (myValue instanceof MetisDataMap<?, ?> myMap) {
282             myValue = myMap.getUnderlyingMap();
283         }
284         if (myValue instanceof Map<?, ?> myMap) {
285             return myMap.isEmpty();
286         }
287 
288         /* Skip empty history/errors */
289         if (myValue instanceof MetisFieldVersionHistory myHistory) {
290             return !myHistory.hasHistory();
291         }
292         if (myValue instanceof MetisFieldValidation myValid) {
293             return !myValid.hasErrors();
294         }
295 
296         /* Skip zero decimals */
297         if (myValue instanceof OceanusDecimal myDecimal) {
298             return myDecimal.isZero();
299         }
300         if (myValue instanceof Number myNumber) {
301             return myNumber.longValue() == 0;
302         }
303 
304         /* Skip false */
305         if (myValue instanceof Boolean myBool) {
306             return !myBool;
307         }
308 
309         /* Skip value if required */
310         return myValue == null
311                 || MetisDataFieldValue.SKIP.equals(myValue);
312     }
313 
314     /**
315      * Build HTML table describing list item.
316      *
317      * @param pList  the list
318      * @param pIndex the index of the item
319      */
320     private void formatHTMLListItem(final List<?> pList,
321                                     final int pIndex) {
322         /* Obtain the object */
323         final Object myObject = pList.get(pIndex - 1);
324 
325         /* Format the object */
326         formatHTMLObject(myObject);
327     }
328 
329     /**
330      * Build HTML table describing list section.
331      *
332      * @param pList  the list
333      * @param pStart the start index for the section
334      */
335     private void formatHTMLListSection(final List<?> pList,
336                                        final int pStart) {
337         /* Initialise the document */
338         theBuilder.newTitle(TABLE_LIST);
339         theBuilder.newTable();
340         theBuilder.newTitleCell(COLUMN_INDEX);
341         theBuilder.newTitleCell(COLUMN_VALUE);
342 
343         /* If there are items in the list */
344         if (!pList.isEmpty()) {
345             /* Calculate start point */
346             final int myStart = (pStart - 1) * ITEMS_PER_PAGE;
347 
348             /* Create iterator at start */
349             final Iterator<?> myIterator = pList.listIterator(myStart);
350 
351             /* Loop up to the limit */
352             int myCount = ITEMS_PER_PAGE;
353             int myIndex = myStart + 1;
354             while (myIterator.hasNext()
355                     && myCount-- > 0) {
356                 /* Access the key and value */
357                 final Object myObject = myIterator.next();
358 
359                 /* Format the row */
360                 theBuilder.newTableRow();
361                 theBuilder.newDataCell(myIndex++);
362                 theBuilder.newDataCell(myObject);
363             }
364         }
365     }
366 
367     /**
368      * Build HTML table describing map section.
369      *
370      * @param pMap   the map
371      * @param pStart the start index for the section
372      */
373     private void formatHTMLMapSection(final Map<?, ?> pMap,
374                                       final int pStart) {
375 
376         /* Initialise the document */
377         theBuilder.newTitle(TABLE_MAP);
378         theBuilder.newTable();
379         theBuilder.newTitleCell(COLUMN_INDEX);
380         theBuilder.newTitleCell(COLUMN_KEY);
381         theBuilder.newTitleCell(COLUMN_VALUE);
382 
383         /* If there are items in the list */
384         if (!pMap.isEmpty()) {
385             /* Calculate start point */
386             int myCount = (pStart - 1) * ITEMS_PER_PAGE;
387             int myIndex = myCount + 1;
388 
389             /* Create iterator and shift to start */
390             final Iterator<?> myIterator = pMap.entrySet().iterator();
391             if (myCount > 0) {
392                 /* Skip leading entries */
393                 while (myIterator.hasNext()
394                         && myCount-- > 0) {
395                     myIterator.next();
396                 }
397             }
398 
399             /* Loop up to the limit */
400             myCount = ITEMS_PER_PAGE;
401             while (myIterator.hasNext()
402                     && (myCount-- > 0)) {
403                 /* Access the key and value */
404                 final Entry<?, ?> myEntry = (Entry<?, ?>) myIterator.next();
405 
406                 /* Format the row */
407                 theBuilder.newTableRow();
408                 theBuilder.newDataCell(myIndex++);
409                 theBuilder.newDataCell(myEntry.getKey());
410                 theBuilder.newDataCell(myEntry.getValue());
411             }
412         }
413     }
414 
415     /**
416      * Build HTML table describing stack.
417      *
418      * @param pStack the stack to describe
419      */
420     private void formatHTMLStackTrace(final StackTraceElement[] pStack) {
421         /* Initialise the document */
422         theBuilder.newTitle(TABLE_STACKTRACE);
423         theBuilder.newTable();
424         theBuilder.newTitleCell(TABLE_STACKTRACE);
425 
426         /* Loop through the elements */
427         for (StackTraceElement st : pStack) {
428             /* Format the row */
429             theBuilder.newTableRow();
430             theBuilder.newDataCell(st.toString());
431         }
432     }
433 
434     /**
435      * Build HTML table describing unknown object.
436      *
437      * @param pObject the object to describe
438      */
439     private void formatHTMLUnknown(final Object pObject) {
440         /* Initialise the document */
441         theBuilder.newTitle("Unknown");
442         theBuilder.newTable();
443         theBuilder.newTitleCell(COLUMN_FIELD);
444         theBuilder.newTitleCell(COLUMN_VALUE);
445 
446         /* Describe the class */
447         theBuilder.newTableRow();
448         theBuilder.newDataCell("Class");
449         theBuilder.newDataCell(pObject != null
450                 ? pObject.getClass()
451                 : null);
452 
453         /* Describe the object */
454         theBuilder.newTableRow();
455         theBuilder.newDataCell("Value");
456         theBuilder.newDataCell(pObject);
457     }
458 }