1   package com.trendmicro.grid.acl.metadata;
2   
3   import net.sf.tinyjee.util.Hex;
4   
5   import javax.xml.bind.annotation.*;
6   import java.io.Externalizable;
7   import java.io.IOException;
8   import java.io.ObjectInput;
9   import java.io.ObjectOutput;
10  import java.lang.reflect.Array;
11  import java.util.Arrays;
12  import java.util.Date;
13  
14  /**
15   * Implements the Java binding for a type safe metadata element used inside a {@link Metadata} package.
16   * <p/>
17   * Note: Every setter will first clear all existing entries before setting a new value.
18   * Therefore it's not possible that 2 different type-aware value getters, both return
19   * a non-null value.
20   *
21   * @author Juergen_Kellerer, 2010-04-19
22   * @version 1.0
23   */
24  @XmlAccessorType(XmlAccessType.PROPERTY)
25  @XmlAccessorOrder(value = XmlAccessOrder.UNDEFINED)
26  @XmlType(name = "meta", namespace = Metadata.NS, propOrder = {
27  		"name", "value", "stringValues", "booleanValues", "numericValues", "dateValues", "binaryValue"})
28  public class Meta implements Cloneable, Externalizable {
29  
30  	private static final long serialVersionUID = -1817738693919240310L;
31  	private static final byte serialVersion = 1;
32  
33  	private static final boolean TRIM_STRING_VALUES =
34  			Boolean.parseBoolean(System.getProperty("gacl.meta.trim.values", Boolean.TRUE.toString()));
35  	private static final boolean TRIM_NAMES =
36  			Boolean.parseBoolean(System.getProperty("gacl.meta.trim.names", Boolean.TRUE.toString()));
37  	private static final boolean PRINT_BINARY_AS_HEX_STRING =
38  			Boolean.parseBoolean(System.getProperty("gacl.meta.print.binary.as.hex.string", Boolean.FALSE.toString()));
39  
40  	String name;
41  
42  	Object value;
43  	DataType valueType;
44  
45  	transient int hashCode;
46  
47  	/**
48  	 * Constructor for de-serialization, don't use directly.
49  	 */
50  	public Meta() {
51  	}
52  
53  	/**
54  	 * Constructs a new instance of an meta element.
55  	 *
56  	 * @param name the unique name of the meta element.
57  	 */
58  	public Meta(String name) {
59  		if (name == null)
60  			throw new IllegalArgumentException("Name cannot be set to 'null'");
61  		setName(name);
62  	}
63  
64  	@XmlID
65  	@XmlAttribute(required = true)
66  	public String getName() {
67  		return name == null ? "" : name;
68  	}
69  
70  	public void setName(String name) {
71  		if (this.name == null || this.name.isEmpty() || this.name.equals(name)) {
72  			this.name = name != null && TRIM_NAMES ? name.trim() : name;
73  		} else {
74  			throw new IllegalStateException("It's illegal to change the name of a named Meta element. " +
75  					"'" + this.name + "' cannot be changed to '" + name + "'.");
76  		}
77  	}
78  
79  	/**
80  	 * Returns the contained value as object instance.
81  	 *
82  	 * @return the contained value as object instance.
83  	 */
84  	@XmlTransient
85  	public Object getAsAnyValue() {
86  		return value;
87  	}
88  
89  	/**
90  	 * Returns the data type of this metadata entry.
91  	 *
92  	 * @return the data type of this metadata entry.
93  	 */
94  	@XmlTransient
95  	public DataType getDataType() {
96  		return valueType;
97  	}
98  
99  	@XmlAttribute
100 	public String getValue() {
101 		return valueType == DataType.STRING && value instanceof String ? (String) value : null;
102 	}
103 
104 	public void setValue(String value) {
105 		this.value = value != null && TRIM_STRING_VALUES ? value.trim() : value;
106 		valueType = DataType.STRING;
107 		hashCode = 0;
108 	}
109 
110 	/**
111 	 * Sets the value and returns the instance of this element (builder pattern).
112 	 *
113 	 * @param value The value to set.
114 	 * @return the instance of this element.
115 	 */
116 	public Meta value(String value) {
117 		setValue(value);
118 		return this;
119 	}
120 
121 	@XmlElement(name = "v", namespace = Metadata.NS)
122 	public StringValue[] getStringValues() {
123 		String[] v = getValues();
124 		if (v == null)
125 			return null;
126 		StringValue[] values = new StringValue[v.length];
127 		for (int i = 0; i < v.length; i++)
128 			values[i] = new StringValue(v[i]);
129 
130 		return values;
131 	}
132 
133 	public void setStringValues(StringValue[] values) {
134 		if (values == null)
135 			setValues();
136 		else {
137 			String[] v = new String[values.length];
138 			for (int i = 0; i < v.length; i++)
139 				v[i] = values[i].getContent();
140 			setValues(v);
141 		}
142 	}
143 
144 	@XmlTransient
145 	public String[] getValues() {
146 		return valueType == DataType.STRING && value instanceof String[] ? (String[]) value : null;
147 	}
148 
149 	public void setValues(String... values) {
150 		value = values;
151 
152 		if (TRIM_STRING_VALUES) {
153 			for (int i = 0; i < values.length; i++) {
154 				if (values[i] != null)
155 					values[i] = values[i].trim();
156 			}
157 		}
158 
159 		valueType = DataType.STRING;
160 		hashCode = 0;
161 	}
162 
163 	/**
164 	 * Sets the values and returns the instance of this element (builder pattern).
165 	 *
166 	 * @param values The values to set.
167 	 * @return the instance of this element.
168 	 */
169 	public Meta value(String... values) {
170 		setValues(values);
171 		return this;
172 	}
173 
174 	@XmlList
175 	@XmlAttribute
176 	public boolean[] getBooleanValues() {
177 		return valueType == DataType.BOOLEAN ? (boolean[]) value : null;
178 	}
179 
180 	public void setBooleanValues(boolean... booleanValues) {
181 		value = booleanValues;
182 		valueType = DataType.BOOLEAN;
183 		hashCode = 0;
184 	}
185 
186 	/**
187 	 * Sets the booleanValues and returns the instance of this element (builder pattern).
188 	 *
189 	 * @param booleanValues The values to set.
190 	 * @return the instance of this element.
191 	 */
192 	public Meta value(boolean... booleanValues) {
193 		setBooleanValues(booleanValues);
194 		return this;
195 	}
196 
197 	@XmlList
198 	@XmlAttribute
199 	public double[] getNumericValues() {
200 		return valueType == DataType.NUMERIC ? (double[]) value : null;
201 	}
202 
203 	public void setNumericValues(double... numericValues) {
204 		value = numericValues;
205 		valueType = DataType.NUMERIC;
206 		hashCode = 0;
207 	}
208 
209 	/**
210 	 * Sets the numericValues and returns the instance of this element (builder pattern).
211 	 *
212 	 * @param numericValues The values to set.
213 	 * @return the instance of this element.
214 	 */
215 	public Meta value(double... numericValues) {
216 		setNumericValues(numericValues);
217 		return this;
218 	}
219 
220 	@XmlList
221 	@XmlAttribute
222 	public Date[] getDateValues() {
223 		return valueType == DataType.DATE ? (Date[]) value : null;
224 	}
225 
226 	public void setDateValues(Date... dateValues) {
227 		value = dateValues;
228 		valueType = DataType.DATE;
229 		hashCode = 0;
230 	}
231 
232 	/**
233 	 * Sets the dateValues and returns the instance of this element (builder pattern).
234 	 *
235 	 * @param dateValues The values to set.
236 	 * @return the instance of this element.
237 	 */
238 	public Meta value(Date... dateValues) {
239 		setDateValues(dateValues);
240 		return this;
241 	}
242 
243 	@XmlAttribute
244 	public byte[] getBinaryValue() {
245 		return valueType == DataType.BINARY ? (byte[]) value : null;
246 	}
247 
248 	public void setBinaryValue(byte[] binaryValue) {
249 		value = binaryValue;
250 		valueType = DataType.BINARY;
251 		hashCode = 0;
252 	}
253 
254 	/**
255 	 * Sets the binaryValue and returns the instance of this element (builder pattern).
256 	 *
257 	 * @param binaryValue The values to set.
258 	 * @return the instance of this element.
259 	 */
260 	public Meta value(byte[] binaryValue) {
261 		setBinaryValue(binaryValue);
262 		return this;
263 	}
264 
265 	/**
266 	 * {@inheritDoc}
267 	 */
268 	@Override
269 	public String toString() {
270 		Object value = this.value;
271 		if (value != null && value.getClass().isArray()) {
272 			if (value instanceof byte[]) {
273 				value = PRINT_BINARY_AS_HEX_STRING ? Hex.encode((byte[]) value) : "<<binary>>";
274 			} else {
275 				StringBuilder b = new StringBuilder();
276 				for (int i = 0, len = Array.getLength(value); i < len; i++)
277 					b.append(i > 0 ? ", " : "").append(Array.get(value, i));
278 				value = b.toString();
279 			}
280 		}
281 
282 		return "Meta{" +
283 				"name='" + name + '\'' +
284 				", type='" + getDataType() + '\'' +
285 				", value='" + value + '\'' +
286 				'}';
287 	}
288 
289 	/**
290 	 * {@inheritDoc}
291 	 */
292 	@Override
293 	public Meta clone() {
294 		try {
295 			return (Meta) super.clone();
296 		} catch (CloneNotSupportedException e) {
297 			throw new RuntimeException(e);
298 		}
299 	}
300 
301 	/**
302 	 * {@inheritDoc}
303 	 */
304 	@Override
305 	public boolean equals(Object o) {
306 		if (this == o) return true;
307 		if (!(o instanceof Meta)) return false;
308 
309 		Meta meta = (Meta) o;
310 
311 		if (valueType != meta.valueType) return false;
312 		if (name != null ? !name.equals(meta.name) : meta.name != null) return false;
313 
314 		if (value != null) {
315 			switch (valueType) {
316 				case STRING:
317 				case DATE:
318 					if (value instanceof Object[])
319 						return Arrays.equals((Object[]) value, (Object[]) meta.value);
320 					return value.equals(meta.value);
321 				case BOOLEAN:
322 					return Arrays.equals((boolean[]) value, (boolean[]) meta.value);
323 				case NUMERIC:
324 					return Arrays.equals((double[]) value, (double[]) meta.value);
325 				case BINARY:
326 					return Arrays.equals((byte[]) value, (byte[]) meta.value);
327 			}
328 			return false;
329 		} else
330 			return meta.value == null;
331 	}
332 
333 	/**
334 	 * {@inheritDoc}
335 	 */
336 	@Override
337 	public int hashCode() {
338 		if (hashCode == 0) {
339 			int result = 0;
340 			if (value != null) {
341 				switch (valueType) {
342 					case STRING:
343 					case DATE:
344 						if (value instanceof Object[])
345 							result = Arrays.hashCode((Object[]) value);
346 						else
347 							result = value.hashCode();
348 						break;
349 					case BOOLEAN:
350 						result = Arrays.hashCode((boolean[]) value);
351 						break;
352 					case NUMERIC:
353 						result = Arrays.hashCode((double[]) value);
354 						break;
355 					case BINARY:
356 						result = Arrays.hashCode((byte[]) value);
357 						break;
358 				}
359 			}
360 
361 			result = 31 * result + (valueType != null ? valueType.hashCode() : 0);
362 			result = 31 * result + (name != null ? name.hashCode() : 0);
363 			hashCode = result;
364 		}
365 		return hashCode;
366 	}
367 
368 	/**
369 	 * {@inheritDoc}
370 	 */
371 	@Override
372 	public void writeExternal(ObjectOutput out) throws IOException {
373 		out.writeByte(serialVersion);
374 		out.writeUTF(name == null ? "" : name);
375 		out.writeInt(valueType.ordinal());
376 		out.writeObject(value);
377 	}
378 
379 	/**
380 	 * {@inheritDoc}
381 	 */
382 	@Override
383 	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
384 		switch (in.readByte()) {
385 			case 1:
386 				name = in.readUTF();
387 				valueType = DataType.values()[in.readInt()];
388 				value = in.readObject();
389 				break;
390 			default:
391 				throw new IOException("Stream is corrupted or was written with a newer unsupported version.");
392 		}
393 	}
394 }