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