View Javadoc
1   /*
2    * Prometheus: Application 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.prometheus.data;
18  
19  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
20  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
22  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldDef;
23  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldSetDef;
24  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldVersionedDef;
25  import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionValues;
26  import org.w3c.dom.Document;
27  import org.w3c.dom.Element;
28  import org.w3c.dom.Node;
29  
30  import java.util.ArrayList;
31  import java.util.Iterator;
32  import java.util.LinkedHashMap;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Map.Entry;
36  
37  /**
38   * Arguments class for DataItem.
39   *
40   * @author Tony Washer
41   */
42  public class PrometheusDataValues {
43      /**
44       * Interface for an infoSet item.
45       */
46      @FunctionalInterface
47      public interface PrometheusInfoSetItem {
48          /**
49           * Obtain infoSet.
50           *
51           * @return the infoSet
52           */
53          PrometheusDataInfoSet<?> getInfoSet();
54      }
55  
56      /**
57       * Interface for a grouped item.
58       */
59      public interface PrometheusGroupedItem {
60          /**
61           * Is the item a child.
62           *
63           * @return true/false
64           */
65          boolean isChild();
66  
67          /**
68           * Obtain the child iterator.
69           *
70           * @return the iterator
71           */
72          Iterator<? extends PrometheusDataItem> childIterator();
73      }
74  
75      /**
76       * InfoSet Items tag.
77       */
78      private static final String TAG_INFOSET = PrometheusDataResource.DATAINFOSET_NAME.getValue();
79  
80      /**
81       * Children Items tag.
82       */
83      private static final String TAG_CHILDREN = PrometheusDataResource.DATAVALUES_CHILDREN.getValue();
84  
85      /**
86       * Child Item tag.
87       */
88      private static final String TAG_CHILD = PrometheusDataResource.DATAVALUES_CHILD.getValue();
89  
90      /**
91       * List type attribute.
92       */
93      protected static final String ATTR_TYPE = PrometheusDataResource.DATAVALUES_ATTRTYPE.getValue();
94  
95      /**
96       * List size attribute.
97       */
98      protected static final String ATTR_SIZE = PrometheusDataResource.DATAVALUES_ATTRSIZE.getValue();
99  
100     /**
101      * Data Version attribute.
102      */
103     protected static final String ATTR_VERS = PrometheusDataResource.DATAVALUES_ATTRVER.getValue();
104 
105     /**
106      * The item type.
107      */
108     private final String theItemType;
109 
110     /**
111      * Field Definitions.
112      */
113     private final Map<MetisDataFieldId, Object> theFields;
114 
115     /**
116      * InfoSet values.
117      */
118     private final List<PrometheusInfoItem> theInfoItems;
119 
120     /**
121      * Child values.
122      */
123     private final List<PrometheusDataValues> theChildren;
124 
125     /**
126      * Constructor.
127      *
128      * @param pItem     the Item to obtain values from
129      * @param pItemName the item name
130      */
131     protected PrometheusDataValues(final PrometheusDataItem pItem,
132                                    final String pItemName) {
133         /* Store Item type */
134         theItemType = pItemName;
135 
136         /* Create the map and list */
137         theFields = new LinkedHashMap<>();
138 
139         /* Store the id */
140         theFields.put(MetisDataResource.DATA_ID, pItem.getIndexedId());
141 
142         /* Access values */
143         final MetisFieldVersionValues myValues = pItem.getValues();
144 
145         /* Iterate through the fields */
146         final Iterator<MetisFieldDef> myIterator = pItem.getDataFieldSet().fieldIterator();
147         while (myIterator.hasNext()) {
148             final MetisFieldDef myField = myIterator.next();
149             final MetisDataFieldId myFieldId = myField.getFieldId();
150 
151             /* Ignore field if it is irrelevant */
152             if (!(myField instanceof MetisFieldVersionedDef myVersioned)
153                     || !myVersioned.isEquality()) {
154                 continue;
155             }
156 
157             /* If the field is to be included */
158             if (pItem.includeXmlField(myFieldId)) {
159                 /* Store the value if it is non-null */
160                 theFields.put(myFieldId, myValues.getValue(myField));
161             }
162         }
163 
164         /* If the item is an infoSet item */
165         if (pItem instanceof PrometheusInfoSetItem myInfoItem) {
166             /* Access InfoSet */
167             final PrometheusDataInfoSet<?> myInfoSet = myInfoItem.getInfoSet();
168 
169             /* If the InfoSet is non-empty */
170             if (myInfoSet.isEmpty()) {
171                 /* No infoSet items */
172                 theInfoItems = null;
173             } else {
174                 /* Allocate infoItems list */
175                 theInfoItems = new ArrayList<>();
176 
177                 /* Iterator over the values */
178                 final Iterator<?> myInfoIterator = myInfoSet.iterator();
179                 while (myInfoIterator.hasNext()) {
180                     final Object myCurr = myInfoIterator.next();
181 
182                     /* If this is a DataInfo item */
183                     if (myCurr instanceof PrometheusDataInfoItem myItem) {
184                         /* Add item to the list */
185                         final PrometheusInfoItem myInfo = new PrometheusInfoItem(myItem);
186                         theInfoItems.add(myInfo);
187                     }
188                 }
189             }
190 
191             /* Else not that we have no infoItems */
192         } else {
193             theInfoItems = null;
194         }
195 
196         /* If the item is a grouped item */
197         if (pItem instanceof PrometheusGroupedItem myGrouped) {
198             /* Access child iterator */
199             final Iterator<? extends PrometheusDataItem> myChildIterator = myGrouped.childIterator();
200 
201             /* If there are no children */
202             if (myChildIterator == null) {
203                 theChildren = null;
204             } else {
205                 /* Allocate child list */
206                 theChildren = new ArrayList<>();
207 
208                 /* Iterator over the values */
209                 while (myChildIterator.hasNext()) {
210                     final PrometheusDataItem myCurr = myChildIterator.next();
211 
212                     /* Add child to the list */
213                     final PrometheusDataValues myChild = new PrometheusDataValues(myCurr, TAG_CHILD);
214                     theChildren.add(myChild);
215                 }
216             }
217 
218             /* Else note that we have no children */
219         } else {
220             theChildren = null;
221         }
222     }
223 
224     /**
225      * Constructor.
226      *
227      * @param pOwner the Owner of the DataInfo Item
228      * @param pInfo  the values of the DataInfo Item
229      */
230     private PrometheusDataValues(final PrometheusDataItem pOwner,
231                                  final PrometheusInfoItem pInfo) {
232         /* Store Item type */
233         theItemType = "";
234 
235         /* Create the map and list */
236         theFields = new LinkedHashMap<>();
237 
238         /* Store the id if available */
239         final Integer myId = pInfo.getId();
240         if (myId != null) {
241             theFields.put(MetisDataResource.DATA_ID, myId);
242         }
243 
244         /* Store the Info Type */
245         theFields.put(PrometheusDataResource.DATAINFO_TYPE, pInfo.getName());
246 
247         /* Store the Owner */
248         theFields.put(PrometheusDataResource.DATAINFO_OWNER, pOwner.getIndexedId());
249 
250         /* Store the value */
251         theFields.put(PrometheusDataResource.DATAINFO_VALUE, pInfo.getValue());
252 
253         /* Set other fields to null */
254         theInfoItems = null;
255         theChildren = null;
256     }
257 
258     /**
259      * Constructor.
260      *
261      * @param pElement the Item to obtain values from
262      * @param pFields  the field definitions
263      */
264     public PrometheusDataValues(final Element pElement,
265                                 final MetisFieldSetDef pFields) {
266         this(pElement, pFields, pElement.getNodeName());
267     }
268 
269     /**
270      * Constructor.
271      *
272      * @param pElement  the Item to obtain values from
273      * @param pFields   the field definitions
274      * @param pItemName the item name
275      */
276     protected PrometheusDataValues(final Element pElement,
277                                    final MetisFieldSetDef pFields,
278                                    final String pItemName) {
279         /* Store Item type */
280         theItemType = pItemName;
281 
282         /* Create the map */
283         theFields = new LinkedHashMap<>();
284 
285         /* Declare the id if it exists */
286         final Integer myId = getId(pElement);
287         if (myId != null) {
288             theFields.put(MetisDataResource.DATA_ID, myId);
289         }
290 
291         /* Loop through the children */
292         final Iterator<MetisFieldDef> myIterator = pFields.fieldIterator();
293         while (myIterator.hasNext()) {
294             final MetisFieldDef myField = myIterator.next();
295 
296             /* If the field is an equality valueSet item */
297             if (myField instanceof MetisFieldVersionedDef myVersioned
298                     && myVersioned.isEquality()) {
299                 /* Access element */
300                 final Element myChild = getChild(pElement, myField.getFieldId().getId());
301                 if (myChild != null) {
302                     /* Put value */
303                     theFields.put(myField.getFieldId(), myChild.getTextContent());
304                 }
305             }
306         }
307 
308         /* Look for an InfoSet list */
309         final Element myInfoSet = getChild(pElement, TAG_INFOSET);
310         if (myInfoSet != null) {
311             /* Allocate infoItems list */
312             theInfoItems = new ArrayList<>();
313 
314             /* Loop through the child values */
315             for (Node myCurr = myInfoSet.getFirstChild(); myCurr != null; myCurr = myCurr.getNextSibling()) {
316                 /* If the child is an element */
317                 if (myCurr instanceof Element myChild) {
318                     /* Add item to the list */
319                     final PrometheusInfoItem myInfo = new PrometheusInfoItem(myChild);
320                     theInfoItems.add(myInfo);
321                 }
322             }
323 
324             /* Else not that we have no infoItems */
325         } else {
326             theInfoItems = null;
327         }
328 
329         /* Look for children */
330         final Element myChildren = getChild(pElement, TAG_CHILDREN);
331         if (myChildren != null) {
332             /* Allocate infoItems list */
333             theChildren = new ArrayList<>();
334 
335             /* Loop through the child values */
336             for (Node myCurr = myChildren.getFirstChild(); myCurr != null; myCurr = myCurr.getNextSibling()) {
337                 /* If the child is the correct element */
338                 if (myCurr instanceof Element myChild
339                         && TAG_CHILD.equals(myCurr.getNodeName())) {
340                     /* Add item to the list */
341                     final PrometheusDataValues myValues = new PrometheusDataValues(myChild, pFields, theItemType);
342                     theChildren.add(myValues);
343                 }
344             }
345 
346             /* Else not that we have no children */
347         } else {
348             theChildren = null;
349         }
350     }
351 
352     /**
353      * Constructor.
354      *
355      * @param pName the Item type
356      */
357     public PrometheusDataValues(final String pName) {
358         /* Store Item type */
359         theItemType = pName;
360 
361         /* Create the map */
362         theFields = new LinkedHashMap<>();
363 
364         /* No underlying arrays */
365         theInfoItems = null;
366         theChildren = null;
367     }
368 
369     /**
370      * Constructor.
371      *
372      * @param pItem the Item to obtain values from
373      */
374     public PrometheusDataValues(final PrometheusDataItem pItem) {
375         this(pItem, pItem.getDataFieldSet().getName());
376     }
377 
378     /**
379      * Obtain Item Type.
380      *
381      * @return the Item Type
382      */
383     public final String getItemType() {
384         return theItemType;
385     }
386 
387     /**
388      * Obtain Field iterator.
389      *
390      * @return the Field iterator
391      */
392     public final Iterator<Entry<MetisDataFieldId, Object>> fieldIterator() {
393         return theFields.entrySet().iterator();
394     }
395 
396     /**
397      * Does this item have InfoItems?
398      *
399      * @return true/false
400      */
401     public final boolean hasInfoItems() {
402         return theInfoItems != null;
403     }
404 
405     /**
406      * Obtain InfoItems iterator.
407      *
408      * @return the iterator
409      */
410     public final Iterator<PrometheusInfoItem> infoIterator() {
411         return theInfoItems.iterator();
412     }
413 
414     /**
415      * Does this item have children?
416      *
417      * @return true/false
418      */
419     public final boolean hasChildren() {
420         return theChildren != null;
421     }
422 
423     /**
424      * Obtain Child iterator.
425      *
426      * @return the iterator
427      */
428     public final Iterator<PrometheusDataValues> childIterator() {
429         return theChildren.iterator();
430     }
431 
432     /**
433      * Add value.
434      *
435      * @param pField the Field definition
436      * @param pValue the field value
437      */
438     public void addValue(final MetisDataFieldId pField,
439                          final Object pValue) {
440         /* If the value is non-null */
441         if (pValue != null) {
442             /* Add the field */
443             theFields.put(pField, pValue);
444         }
445     }
446 
447     /**
448      * Obtain value.
449      *
450      * @param pField the Field definition
451      * @return the field value
452      */
453     public Object getValue(final MetisDataFieldId pField) {
454         /* Return the field */
455         return theFields.get(pField);
456     }
457 
458     /**
459      * Obtain value of specified class.
460      *
461      * @param pField the Field definition
462      * @param pClass the class
463      * @param <T>    the item type
464      * @return the field value
465      */
466     public <T> T getValue(final MetisDataFieldId pField,
467                           final Class<T> pClass) {
468         /* Return the properly cast field */
469         return pClass.cast(getValue(pField));
470     }
471 
472     /**
473      * Obtain id from element.
474      *
475      * @param pElement the element.
476      * @return the id
477      */
478     private static Integer getId(final Element pElement) {
479         /* Access the id */
480         final String myId = pElement.getAttribute(MetisDataResource.DATA_ID.getId());
481         return !myId.isEmpty()
482                 ? Integer.parseInt(myId)
483                 : null;
484     }
485 
486     /**
487      * Obtain child element with given name.
488      *
489      * @param pParent the parent element
490      * @param pName   the element name
491      * @return the element
492      */
493     private static Element getChild(final Element pParent,
494                                     final String pName) {
495         /* Loop through the child values */
496         for (Node myCurr = pParent.getFirstChild(); myCurr != null; myCurr = myCurr.getNextSibling()) {
497             /* If the child is the correct element */
498             if (myCurr instanceof Element myElement
499                     && pName.equals(myCurr.getNodeName())) {
500                 /* Return the element */
501                 return myElement;
502             }
503         }
504 
505         /* Not found */
506         return null;
507     }
508 
509     /**
510      * Create XML element for item.
511      *
512      * @param pDocument  the document to hold the item.
513      * @param pFormatter the data formatter
514      * @param pStoreIds  do we include IDs in XML
515      * @return the new element
516      */
517     protected Element createXML(final Document pDocument,
518                                 final OceanusDataFormatter pFormatter,
519                                 final boolean pStoreIds) {
520         /* Create an element for the item */
521         final Element myElement = pDocument.createElement(theItemType);
522 
523         /* Loop through the values */
524         for (Entry<MetisDataFieldId, Object> myEntry : theFields.entrySet()) {
525             /* Access parts */
526             final MetisDataFieldId myFieldId = myEntry.getKey();
527             final Object myValue = myEntry.getValue();
528 
529             /* If this is the Id */
530             if (MetisDataResource.DATA_ID.equals(myFieldId)) {
531                 /* Add as an Attribute if required */
532                 if (pStoreIds) {
533                     myElement.setAttribute(myFieldId.getId(), myValue.toString());
534                 }
535 
536                 /* Skip to next field */
537                 continue;
538             }
539 
540             /* Create the child element */
541             final Element myChild = pDocument.createElement(myFieldId.getId());
542             myElement.appendChild(myChild);
543 
544             /* Store the value */
545             myChild.setTextContent(pFormatter.formatObject(myValue));
546         }
547 
548         /* If we have InfoSet items */
549         if (theInfoItems != null) {
550             /* Add infoSet */
551             final Element myInfoSet = pDocument.createElement(TAG_INFOSET);
552             myElement.appendChild(myInfoSet);
553 
554             /* Loop through the items */
555             for (PrometheusInfoItem myInfo : theInfoItems) {
556                 /* Create the element */
557                 final Element myItem = pDocument.createElement(myInfo.getName());
558                 myInfoSet.appendChild(myItem);
559 
560                 /* Set the id if required */
561                 if (pStoreIds) {
562                     myItem.setAttribute(MetisDataResource.DATA_ID.getValue(), myInfo.getId().toString());
563                 }
564 
565                 /* Set the value */
566                 myItem.setTextContent(pFormatter.formatObject(myInfo.getValue()));
567             }
568         }
569 
570         /* If we have children */
571         if (theChildren != null) {
572             /* Add children */
573             final Element myChildren = pDocument.createElement(TAG_CHILDREN);
574             myElement.appendChild(myChildren);
575 
576             /* Loop through the children */
577             final Iterator<PrometheusDataValues> myIterator = theChildren.iterator();
578             while (myIterator.hasNext()) {
579                 final PrometheusDataValues myValues = myIterator.next();
580 
581                 /* Create the subElement and append */
582                 final Element myChild = myValues.createXML(pDocument, pFormatter, pStoreIds);
583                 myChildren.appendChild(myChild);
584             }
585         }
586 
587         /* Return the element */
588         return myElement;
589     }
590 
591     /**
592      * InfoItem class.
593      */
594     public static final class PrometheusInfoItem {
595         /**
596          * Name of item.
597          */
598         private final String theName;
599 
600         /**
601          * Id of item.
602          */
603         private final Integer theId;
604 
605         /**
606          * Value of item.
607          */
608         private final Object theValue;
609 
610         /**
611          * Constructor.
612          *
613          * @param pInfo the info Item
614          */
615         private PrometheusInfoItem(final PrometheusDataInfoItem pInfo) {
616             /* Access the infoClass */
617             final PrometheusDataInfoClass myClass = pInfo.getInfoClass();
618 
619             /* Store values */
620             theName = myClass.toString();
621             theId = pInfo.getIndexedId();
622             theValue = myClass.isLink()
623                     ? pInfo.getLink()
624                     : pInfo.getValue(Object.class);
625         }
626 
627         /**
628          * Constructor.
629          *
630          * @param pElement the XML element
631          */
632         private PrometheusInfoItem(final Element pElement) {
633             /* Store values */
634             theName = pElement.getNodeName();
635             theId = PrometheusDataValues.getId(pElement);
636             theValue = pElement.getTextContent();
637         }
638 
639         /**
640          * Obtain name of item.
641          *
642          * @return the name
643          */
644         public String getName() {
645             return theName;
646         }
647 
648         /**
649          * Obtain id of item.
650          *
651          * @return the id
652          */
653         public Integer getId() {
654             return theId;
655         }
656 
657         /**
658          * Obtain value of item.
659          *
660          * @return the value
661          */
662         public Object getValue() {
663             return theValue;
664         }
665 
666         @Override
667         public boolean equals(final Object pThat) {
668             /* Handle the trivial cases */
669             if (this == pThat) {
670                 return true;
671             }
672             if (pThat == null) {
673                 return false;
674             }
675 
676             /* Make sure that the object is the same class */
677             if (pThat.getClass() != getClass()) {
678                 return false;
679             }
680 
681             /* Access the object as an InfoItem */
682             final PrometheusInfoItem myItem = (PrometheusInfoItem) pThat;
683 
684             if (!theName.equals(myItem.getName())) {
685                 return false;
686             }
687             if (!theId.equals(myItem.getId())) {
688                 return false;
689             }
690             return theValue.equals(myItem.getValue());
691         }
692 
693         @Override
694         public int hashCode() {
695             return theName.hashCode() + theId + theValue.hashCode();
696         }
697 
698         @Override
699         public String toString() {
700             return theName + "=" + theValue;
701         }
702 
703         /**
704          * Obtain DataValues.
705          *
706          * @param pOwner the owner
707          * @return the dataValues
708          */
709         public PrometheusDataValues getValues(final PrometheusDataItem pOwner) {
710             return new PrometheusDataValues(pOwner, this);
711         }
712     }
713 }