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