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 <b>not</b> 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 }