View Javadoc
1   package org.djutils.metadata;
2   
3   import java.util.Arrays;
4   
5   import org.djutils.exceptions.Throw;
6   
7   /**
8    * MetaDataInterface; documenting Object arrays. <br>
9    * Copyright (c) 2020-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
10   * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
11   * distributed under a three-clause BSD-style license, which can be found at
12   * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>. <br>
13   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
14   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
15   */
16  public class MetaData
17  {
18      /** Name of this MetaData object. */
19      private final String name;
20  
21      /** Description of this MetaData object. */
22      private final String description;
23  
24      /** The array of object descriptors. */
25      private final ObjectDescriptor[] objectDescriptors;
26  
27      /** MetaData object that indicates no data is expected. */
28      public static final MetaData EMPTY = new MetaData("No data", "No data", new ObjectDescriptor[0]);
29  
30      /** Meta data object to use when none is available. Please do not use this, except when the payload is varying. */
31      public static final MetaData NO_META_DATA = new MetaData("No descriptive meta data provided", "Any payload is accepted",
32              new ObjectDescriptor("No descriptive meta data provided", "Any payload is accepted", Object.class));
33  
34      /**
35       * Construct a new MetaData object that can check an array of Object.
36       * @param name name of the new MetaData object, which cannot be null or the empty string
37       * @param description description of the new MetaData object
38       * @param objectDescriptors array of ObjectDescriptor. This constructor does &lt;b&gt;not&lt;/b&gt; make a deep copy of this
39       *            array; subsequent modification of the contents of the provided <code>objectDescriptors</code> array will
40       *            affect the behavior of the MetaData object.
41       */
42      public MetaData(final String name, final String description, final ObjectDescriptor... objectDescriptors)
43      {
44          Throw.whenNull(name, "name may not be null");
45          Throw.when(name.length() == 0, IllegalArgumentException.class, "name cannot be the empty string");
46          Throw.whenNull(description, "description may not be null");
47          for (int i = 0; i < objectDescriptors.length; i++)
48          {
49              ObjectDescriptor objectDescriptor = objectDescriptors[i];
50              Throw.whenNull(objectDescriptor, "objectDescriptor %d may not be null", i);
51          }
52          this.name = name;
53          this.description = description;
54          this.objectDescriptors = objectDescriptors;
55      }
56  
57      /**
58       * Retrieve the name of this MetaData object.
59       * @return the name of this MetaData object
60       */
61      public String getName()
62      {
63          return this.name;
64      }
65  
66      /**
67       * Retrieve the description of this MetaData object.
68       * @return the description of this MetaData object
69       */
70      public String getDescription()
71      {
72          return this.description;
73      }
74  
75      /**
76       * Retrieve the length of described Object array.
77       * @return the length of the described Object array; returns 0 if this MetaDataObject is not set up to validate an array of
78       *         Object.
79       */
80      public int size()
81      {
82          return this.objectDescriptors.length;
83      }
84  
85      /**
86       * Returns a safe copy of the object descriptors. As the object descriptors are immutable objects, they are not cloned.
87       * @return a safe copy of the object descriptors
88       */
89      public ObjectDescriptor[] getObjectDescriptors()
90      {
91          return this.objectDescriptors.clone();
92      }
93  
94      /**
95       * Retrieve the name of one element in the Object array.
96       * @param index index of the element in the Object array (must be 0 if this MetaData object is not set up to validate an
97       *            array of Object)
98       * @return name of the argument
99       */
100     public String getFieldName(final int index)
101     {
102         return getObjectDescriptor(index).getName();
103     }
104 
105     /**
106      * Retrieve the description of one element in the Object array.
107      * @param index index of the element in the Object array (must be 0 if this MetaData object is not set up to validate an
108      *            array of Object)
109      * @return description of the argument
110      */
111     public String getObjectDescription(final int index)
112     {
113         return getObjectDescriptor(index).getDescription();
114     }
115 
116     /**
117      * Retrieve the java class of one element in the Object array.
118      * @param index index of the element in the Object array (must be 0 if this MetaData object is not set up to validate an
119      *            array of Object)
120      * @return java class of the element
121      */
122     public Class<?> getObjectClass(final int index)
123     {
124         return getObjectDescriptor(index).getObjectClass();
125     }
126 
127     /**
128      * Select one of the ObjectDescriptors.
129      * @param index index of the ObjectDescriptor (must be 0 in case this MetaData object is not set up to validate an array of
130      *            Object)
131      * @return the selected ObjectDescriptor
132      */
133     public ObjectDescriptor getObjectDescriptor(final int index)
134     {
135         Throw.when(index < 0 || index >= this.objectDescriptors.length, IndexOutOfBoundsException.class,
136                 "Index < 0 or index >= number of object descriptors");
137         return this.objectDescriptors[index];
138     }
139 
140     /**
141      * Verify that an Object array has the prescribed composition.
142      * @param objectArray the Object array to verify. If the array is supposed to have 0 length, a null pointer is deemed OK.
143      * @throws NullPointerException when the object array is null and the size of the object descriptors array is not 0 or 1
144      * @throws IndexOutOfBoundsException when size of the object descriptors array is not equal to the size of the object array
145      * @throws ClassCastException when one of the objects is of the wrong class
146      */
147     public final void verifyComposition(final Object[] objectArray)
148     {
149         if ((size() == 0 || size() == 1) && objectArray == null)
150         {
151             return;
152         }
153         if (this.equals(NO_META_DATA)) // anything goes
154         {
155             return;
156         }
157         Throw.whenNull(objectArray, "objectArray may not be null");
158         Throw.when(objectArray.length != size(), IndexOutOfBoundsException.class,
159                 "objectArray for \"%s\" has wrong length (expected %d, got %d)", this.name, size(), objectArray.length);
160         for (int index = 0; index < objectArray.length; index++)
161         {
162             Object object = objectArray[index];
163             if ((null != object) && (!(getObjectClass(index).isAssignableFrom(object.getClass()))))
164             {
165                 throw new ClassCastException(String.format("objectArray[%d] (%s) cannot be used for %s", index,
166                         objectArray[index], getObjectClass(index).getName()));
167             }
168         }
169     }
170 
171     /**
172      * Verify that an Object has the prescribed composition. In order for one object to fit the metadata, the array of expected
173      * objects needs to have a length of 1.
174      * @param object the Object to verify.
175      * @throws IndexOutOfBoundsException when size of the object descriptors array is not 1
176      * @throws ClassCastException when the object is of the wrong class
177      */
178     public final void verifyComposition(final Object object)
179     {
180         Throw.when(this.objectDescriptors.length != 1, IndexOutOfBoundsException.class,
181                 "Testing single object, but length of the object descriptors array is %d", this.objectDescriptors.length);
182         if (this.equals(NO_META_DATA)) // anything goes
183         {
184             return;
185         }
186         Class<?> objectClass = getObjectClass(0);
187         if (!(objectClass.isAssignableFrom(object.getClass())))
188         {
189             throw new ClassCastException(String.format("object (%s) cannot be used for %s", object, objectClass.getName()));
190         }
191     }
192 
193     @Override
194     public int hashCode()
195     {
196         final int prime = 31;
197         int result = 1;
198         result = prime * result + this.description.hashCode();
199         result = prime * result + this.name.hashCode();
200         result = prime * result + Arrays.hashCode(this.objectDescriptors);
201         return result;
202     }
203 
204     @Override
205     @SuppressWarnings("checkstyle:needbraces")
206     public boolean equals(final Object obj)
207     {
208         if (this == obj)
209             return true;
210         if (obj == null)
211             return false;
212         if (getClass() != obj.getClass())
213             return false;
214         MetaData other = (MetaData) obj;
215         if (!this.name.equals(other.name))
216             return false;
217         if (!this.description.equals(other.description))
218             return false;
219         if (!Arrays.equals(this.objectDescriptors, other.objectDescriptors))
220             return false;
221         return true;
222     }
223 
224     @Override
225     public String toString()
226     {
227         return "MetaData [name=" + this.name + ", description=" + this.description + ", objectDescriptors="
228                 + Arrays.toString(this.objectDescriptors) + "]";
229     }
230 
231 }