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.MetisDataFieldValue;
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  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldDef;
25  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldSetDef;
26  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldVersionedDef;
27  import io.github.tonywasher.joceanus.metis.field.MetisFieldValidation;
28  import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionHistory;
29  import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedItem;
30  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
31  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusDecimal;
32  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
33  import io.github.tonywasher.joceanus.oceanus.profile.OceanusProfile;
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         /* Switch on object */
139         switch (myObject) {
140             case MetisFieldItem myField -> formatHTMLEosFieldItem(myField);
141             case StackTraceElement[] myStack -> formatHTMLStackTrace(myStack);
142             case OceanusProfile myProfile -> formatHTMLEosFieldItem(new MetisViewerProfileWrapper(myProfile));
143             case Throwable myThrow -> formatHTMLEosFieldItem(new MetisViewerExceptionWrapper(myThrow));
144             default -> formatHTMLUnknown(myObject);
145         }
146     }
147 
148     /**
149      * Build HTML collection.
150      *
151      * @param pObject the object
152      * @param pStart  the start index for the section
153      */
154     private void formatHTMLCollection(final Object pObject,
155                                       final int pStart) {
156         /* handle DataDifference */
157         Object myObject = pObject instanceof MetisDataDelta myDelta
158                 ? myDelta.getObject()
159                 : pObject;
160 
161         /* handle embedded objects */
162         if (myObject instanceof MetisDataList<?> myList) {
163             myObject = myList.getUnderlyingList();
164         } else if (myObject instanceof MetisDataMap<?, ?> myMap) {
165             myObject = myMap.getUnderlyingMap();
166         }
167 
168         /* If we are List */
169         if (myObject instanceof List<?> myList) {
170             formatHTMLListSection(myList, pStart);
171 
172             /* If we are Map */
173         } else if (myObject instanceof Map<?, ?> myMap) {
174             formatHTMLMapSection(myMap, pStart);
175         }
176     }
177 
178     /**
179      * Build HTML item.
180      *
181      * @param pObject the object
182      * @param pIndex  index of the item
183      */
184     private void formatHTMLItem(final Object pObject,
185                                 final int pIndex) {
186         /* handle DataDifference */
187         Object myObject = pObject instanceof MetisDataDelta myDelta
188                 ? myDelta.getObject()
189                 : pObject;
190 
191         /* handle embedded objects */
192         if (myObject instanceof MetisDataList<?> myList) {
193             myObject = myList.getUnderlyingList();
194         }
195 
196         /* If we are List */
197         if (myObject instanceof List<?> myList) {
198             formatHTMLListItem(myList, pIndex);
199         }
200     }
201 
202     /**
203      * Build HTML table describing DataEosFieldItem.
204      *
205      * @param pItem the item
206      */
207     private void formatHTMLEosFieldItem(final MetisFieldItem pItem) {
208         /* Access details */
209         final MetisFieldSetDef myFields = pItem.getDataFieldSet();
210         final boolean isVersioned = pItem instanceof MetisFieldVersionedItem;
211         final MetisFieldVersionedItem myItem = isVersioned
212                 ? (MetisFieldVersionedItem) pItem
213                 : null;
214 
215         /* Initialise the document */
216         theBuilder.newTitle(myFields.getName());
217         theBuilder.newTable();
218         theBuilder.newTitleCell(COLUMN_FIELD);
219         theBuilder.newTitleCell(COLUMN_VALUE);
220 
221         /* Loop through the fields */
222         final Iterator<MetisFieldDef> myIterator = myFields.fieldIterator();
223         while (myIterator.hasNext()) {
224             /* Access Field */
225             final MetisFieldDef myField = myIterator.next();
226 
227             /* Access the value */
228             final Object myValue = myField.isCalculated()
229                     ? MetisDataFieldValue.SKIP
230                     : myField.getFieldValue(pItem);
231 
232             /* Skip value if required */
233             if (skipValue(myValue)) {
234                 continue;
235             }
236 
237             /* Start the field */
238             theBuilder.newTableRow();
239             theBuilder.newDataCell(myField.getFieldId().getId());
240             if (myItem != null
241                     && myField instanceof MetisFieldVersionedDef
242                     && myItem.fieldChanged(myField).isDifferent()) {
243                 theBuilder.newDataCell(myValue, true);
244             } else {
245                 theBuilder.newDataCell(myValue);
246             }
247         }
248     }
249 
250     /**
251      * Should we skip the value.
252      *
253      * @param pValue the value
254      * @return true/false
255      */
256     private static boolean skipValue(final Object pValue) {
257         /* Access the value */
258         Object myValue = pValue;
259 
260         /* Skip empty lists */
261         if (myValue instanceof MetisDataList<?> myList) {
262             myValue = myList.getUnderlyingList();
263         }
264         if (myValue instanceof List<?> myList) {
265             return myList.isEmpty();
266         }
267 
268         /* Skip empty maps */
269         if (myValue instanceof MetisDataMap<?, ?> myMap) {
270             myValue = myMap.getUnderlyingMap();
271         }
272         if (myValue instanceof Map<?, ?> myMap) {
273             return myMap.isEmpty();
274         }
275 
276         /* Skip empty history/errors */
277         if (myValue instanceof MetisFieldVersionHistory myHistory) {
278             return !myHistory.hasHistory();
279         }
280         if (myValue instanceof MetisFieldValidation myValid) {
281             return !myValid.hasErrors();
282         }
283 
284         /* Skip zero decimals */
285         if (myValue instanceof OceanusDecimal myDecimal) {
286             return myDecimal.isZero();
287         }
288         if (myValue instanceof Number myNumber) {
289             return myNumber.longValue() == 0;
290         }
291 
292         /* Skip false */
293         if (myValue instanceof Boolean myBool) {
294             return !myBool;
295         }
296 
297         /* Skip value if required */
298         return myValue == null
299                 || MetisDataFieldValue.SKIP.equals(myValue);
300     }
301 
302     /**
303      * Build HTML table describing list item.
304      *
305      * @param pList  the list
306      * @param pIndex the index of the item
307      */
308     private void formatHTMLListItem(final List<?> pList,
309                                     final int pIndex) {
310         /* Obtain the object */
311         final Object myObject = pList.get(pIndex - 1);
312 
313         /* Format the object */
314         formatHTMLObject(myObject);
315     }
316 
317     /**
318      * Build HTML table describing list section.
319      *
320      * @param pList  the list
321      * @param pStart the start index for the section
322      */
323     private void formatHTMLListSection(final List<?> pList,
324                                        final int pStart) {
325         /* Initialise the document */
326         theBuilder.newTitle(TABLE_LIST);
327         theBuilder.newTable();
328         theBuilder.newTitleCell(COLUMN_INDEX);
329         theBuilder.newTitleCell(COLUMN_VALUE);
330 
331         /* If there are items in the list */
332         if (!pList.isEmpty()) {
333             /* Calculate start point */
334             final int myStart = (pStart - 1) * ITEMS_PER_PAGE;
335 
336             /* Create iterator at start */
337             final Iterator<?> myIterator = pList.listIterator(myStart);
338 
339             /* Loop up to the limit */
340             int myCount = ITEMS_PER_PAGE;
341             int myIndex = myStart + 1;
342             while (myIterator.hasNext()
343                     && myCount-- > 0) {
344                 /* Access the key and value */
345                 final Object myObject = myIterator.next();
346 
347                 /* Format the row */
348                 theBuilder.newTableRow();
349                 theBuilder.newDataCell(myIndex++);
350                 theBuilder.newDataCell(myObject);
351             }
352         }
353     }
354 
355     /**
356      * Build HTML table describing map section.
357      *
358      * @param pMap   the map
359      * @param pStart the start index for the section
360      */
361     private void formatHTMLMapSection(final Map<?, ?> pMap,
362                                       final int pStart) {
363 
364         /* Initialise the document */
365         theBuilder.newTitle(TABLE_MAP);
366         theBuilder.newTable();
367         theBuilder.newTitleCell(COLUMN_INDEX);
368         theBuilder.newTitleCell(COLUMN_KEY);
369         theBuilder.newTitleCell(COLUMN_VALUE);
370 
371         /* If there are items in the list */
372         if (!pMap.isEmpty()) {
373             /* Calculate start point */
374             int myCount = (pStart - 1) * ITEMS_PER_PAGE;
375             int myIndex = myCount + 1;
376 
377             /* Create iterator and shift to start */
378             final Iterator<?> myIterator = pMap.entrySet().iterator();
379             if (myCount > 0) {
380                 /* Skip leading entries */
381                 while (myIterator.hasNext()
382                         && myCount-- > 0) {
383                     myIterator.next();
384                 }
385             }
386 
387             /* Loop up to the limit */
388             myCount = ITEMS_PER_PAGE;
389             while (myIterator.hasNext()
390                     && (myCount-- > 0)) {
391                 /* Access the key and value */
392                 final Entry<?, ?> myEntry = (Entry<?, ?>) myIterator.next();
393 
394                 /* Format the row */
395                 theBuilder.newTableRow();
396                 theBuilder.newDataCell(myIndex++);
397                 theBuilder.newDataCell(myEntry.getKey());
398                 theBuilder.newDataCell(myEntry.getValue());
399             }
400         }
401     }
402 
403     /**
404      * Build HTML table describing stack.
405      *
406      * @param pStack the stack to describe
407      */
408     private void formatHTMLStackTrace(final StackTraceElement[] pStack) {
409         /* Initialise the document */
410         theBuilder.newTitle(TABLE_STACKTRACE);
411         theBuilder.newTable();
412         theBuilder.newTitleCell(TABLE_STACKTRACE);
413 
414         /* Loop through the elements */
415         for (StackTraceElement st : pStack) {
416             /* Format the row */
417             theBuilder.newTableRow();
418             theBuilder.newDataCell(st.toString());
419         }
420     }
421 
422     /**
423      * Build HTML table describing unknown object.
424      *
425      * @param pObject the object to describe
426      */
427     private void formatHTMLUnknown(final Object pObject) {
428         /* Initialise the document */
429         theBuilder.newTitle("Unknown");
430         theBuilder.newTable();
431         theBuilder.newTitleCell(COLUMN_FIELD);
432         theBuilder.newTitleCell(COLUMN_VALUE);
433 
434         /* Describe the class */
435         theBuilder.newTableRow();
436         theBuilder.newDataCell("Class");
437         theBuilder.newDataCell(pObject != null
438                 ? pObject.getClass()
439                 : null);
440 
441         /* Describe the object */
442         theBuilder.newTableRow();
443         theBuilder.newDataCell("Value");
444         theBuilder.newDataCell(pObject);
445     }
446 }