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-2023 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 <b>not</b>
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<?>; 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 /** {@inheritDoc} */
199 @Override
200 public int hashCode()
201 {
202 final int prime = 31;
203 int result = 1;
204 result = prime * result + this.description.hashCode();
205 result = prime * result + this.name.hashCode();
206 result = prime * result + Arrays.hashCode(this.objectDescriptors);
207 return result;
208 }
209
210 /** {@inheritDoc} */
211 @Override
212 @SuppressWarnings("checkstyle:needbraces")
213 public boolean equals(final Object obj)
214 {
215 if (this == obj)
216 return true;
217 if (obj == null)
218 return false;
219 if (getClass() != obj.getClass())
220 return false;
221 MetaData other = (MetaData) obj;
222 if (!this.name.equals(other.name))
223 return false;
224 if (!this.description.equals(other.description))
225 return false;
226 if (!Arrays.equals(this.objectDescriptors, other.objectDescriptors))
227 return false;
228 return true;
229 }
230
231 /** {@inheritDoc} */
232 @Override
233 public String toString()
234 {
235 return "MetaData [name=" + this.name + ", description=" + this.description + ", objectDescriptors="
236 + Arrays.toString(this.objectDescriptors) + "]";
237 }
238
239 }