001 /*
002 Copyright (c) 1996-2011, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029 package org.hd.d.pg2k.svrCore.vars;
030
031 import java.io.InvalidObjectException;
032 import java.io.ObjectInputValidation;
033 import java.io.ObjectStreamException;
034 import java.io.Serializable;
035 import java.util.Collections;
036 import java.util.EnumSet;
037 import java.util.Map;
038 import java.util.Set;
039
040 import org.hd.d.pg2k.svrCore.MemoryTools;
041
042
043 /**Immutable definition of a simple local or global system variable.
044 * This defines the type, default value, scope, writability, etc
045 * of a single system variable.
046 * <p>
047 * Some variables may also be events,
048 * and gain some extra functionality such as a record of the values over time.
049 * <p>
050 * Names of variables are in the same scheme as Java properties and class names,
051 * ie structured hierarchical alphanumeric ASCII strings,
052 * with sections separated by "." dots,
053 * and with the more significant components on the left.
054 * <p>
055 * These variables may be limited in scope to a single JVM,
056 * or may be global,
057 * or may have local values that are automatically merged into a global version.
058 * <p>
059 * (We may in future allow these definitions to be provided centrally
060 * and distributed via GenProps, for example,
061 * possibly merged with or filtered by a built-in set,
062 * if we have the need and can avoid security issues for example.)
063 * <p>
064 * The Comparable ordering is on the name;
065 * behaviour is undefined if two different variable definitions
066 * with the same name are compared.
067 */
068 public final class SimpleVariableDefinition implements Serializable,
069 ObjectInputValidation,
070 MemoryTools.Internable,
071 Comparable<SimpleVariableDefinition>
072 {
073 /**Absolute maximum different event values that can be held; strictly positive.
074 * Individual event variables may impose a smaller limit;
075 * this is here to impose a bearable upper limit on system resources.
076 * <p>
077 * This should probably not be lots smaller than the number of exhibits,
078 * for example to allow accurate retention of sparse votes.
079 * <p>
080 * Small enough that that rank (etc) can be stored as a short rather than int to reduce footprint.
081 */
082 public static final int EVENT_ABS_MAX_DIFF_VALUES = Short.MAX_VALUE;
083
084 /**Construct a single variable definition.
085 *
086 * @param name the variable name; never null or empty
087 * @param type the variable type; one of the TYPE_XXX values
088 * @param persistent if true then the value persists through a
089 * system/JVM restart if possible; usually false
090 * @param readOnly if true then the variable can never be explicitly set;
091 * it might be synthesised by specialised getter methods, for example
092 * @param event true if this is an event rather than just a simple value
093 * @param maxDiffEventCount maximum number of different event values recorded;
094 * never negative, must be zero for non-event variables
095 * @param evPeriodSubset for an event non-empty set of periods/intervals
096 * for which events are records (null means all periods/intervals);
097 * must be null if not an event
098 *
099 * @throws java.lang.IllegalArgumentException if the arguments are invalid
100 */
101 public SimpleVariableDefinition(final String name,
102 final int type,
103 final boolean local,
104 final boolean persistent,
105 final boolean readOnly,
106 final boolean event,
107 final int maxDiffEventCount,
108 final EnumSet<EventPeriod> evPeriodSubset)
109 throws IllegalArgumentException
110 {
111 this.name = MemoryTools.intern(name);
112 this.type = type;
113 this.local = local;
114 this.persistent = persistent;
115 this.readOnly = readOnly;
116 this.event = event;
117 this.maxDiffEventCount = maxDiffEventCount;
118 // Take defensive (and efficient) read-only copy of set if not null.
119 // Also convert full set to null to canonicalise and to save memory.
120 this.evPeriodSubset = ((evPeriodSubset == null) || (EnumSet.complementOf(evPeriodSubset).isEmpty())) ? null :
121 EnumSet.copyOf(evPeriodSubset);
122
123 try { validateObject(); }
124 catch(final InvalidObjectException e)
125 { throw new IllegalArgumentException(e.getMessage()); }
126 }
127
128 /**Simplified constructor setting many of the typical defaults.
129 * Essentially a local, read-write, non-persistent value.
130 *
131 * @param name the variable name; never null or empty
132 * @param type the variable type; one of the TYPE_XXX values
133 *
134 * @throws java.lang.IllegalArgumentException if the arguments are invalid
135 */
136 public SimpleVariableDefinition(final String name,
137 final int type)
138 throws IllegalArgumentException
139 {
140 this(name,
141 type,
142 true, // Local.
143 false, // Non-persistent.
144 false, // Read-write.
145 false, 0, null); // Not an event.
146 }
147
148 /**The variable name; never null or empty. */
149 private final String name;
150
151 /**Get the variable name; never null or empty. */
152 public String getName()
153 {
154 return(name);
155 }
156
157 /**The variable type; one of the TYPE_XXX values. */
158 private final int type;
159
160 /**Get the variable type; one of the TYPE_XXX values. */
161 public int getType()
162 {
163 return(type);
164 }
165
166 /**True if the variable value is to persist through restarts. */
167 private final boolean persistent;
168
169 /**Get the veriable persistence; defaults to false. */
170 public boolean isPersistent()
171 {
172 return(persistent);
173 }
174
175 /**True if the variable is read-only; defaults to false. */
176 private final boolean readOnly;
177
178 /**Get the variable writablity; true if read-only. */
179 public boolean isReadOnly()
180 {
181 return(readOnly);
182 }
183
184 /**True if the variable is read-only; defaults to false. */
185 private final boolean event;
186
187 /**True if this reprents an event rather than a simple value. */
188 public boolean isEvent()
189 {
190 return(event);
191 }
192
193 /**Event period/interval subset (immutable) to be collected, or null if not an event or all to be collected.
194 * If this is non-null then it is non-empty.
195 */
196 private final EnumSet<EventPeriod> evPeriodSubset;
197
198 /**Get unmodifiable view of the event period/interval subset to be collected, or null if not an event or all event periods to be collected.
199 * Returns a read-only view to protect our internal state.
200 */
201 public Set<EventPeriod> getEvPeriodSubset()
202 {
203 if(evPeriodSubset == null) { return(null); }
204 return(Collections.unmodifiableSet(evPeriodSubset));
205 }
206
207 /**True if the variable is local; defaults to true. */
208 private final boolean local;
209
210 /**Get the variable scope; true if local.
211 * Local values never get propagated out of their local system (often JVM),
212 * and don't get merged into a map,
213 * and are thus cheaper and simpler to manage than globalmap values.
214 *
215 * @return true if this is a local variable
216 */
217 public boolean isLocal()
218 {
219 return(local);
220 }
221
222 /**Maximum number of different event values retained; defaults to 0. */
223 private final int maxDiffEventCount;
224
225 /**Get maximum number of different event values retained; non-negative, zero if not an event */
226 public int getMaxDiffEventCount()
227 {
228 return(maxDiffEventCount);
229 }
230
231
232 /**Put a (high) upper bound on name length in character; strictly positive.
233 * This is much longer than we ever expect to use,
234 * and is mainly to reduce the possibility of accidents,
235 * for example during deserialisation.
236 * <p>
237 * Limiting this also makes it more likely that we can use an event name
238 * as part of a filename on a range of systems, eg *nix and Windows.
239 * <p>
240 * We will apply this to (String) event values too.
241 */
242 public static final int MAX_NAME_LEN = 128;
243
244
245 /**Variable type: "none"; can only be null. */
246 public static final int TYPE_NONE = 0;
247
248 /**Variable type: InstanceID; can only be of type InstanceID. */
249 public static final int TYPE_IID = 1;
250
251 /**Variable type: String; can only be of type java.lang.String. */
252 public static final int TYPE_STRING = 2;
253
254 /**Variable type: Number; can only be of type java.lang.Number.
255 * In practice, for security reasons,
256 * only java.lang.* implementation types are allowed.
257 */
258 public static final int TYPE_NUMBER = 3;
259
260 /**Minimum legal TYPE_XXX value. */
261 public static final int TYPE__MIN = TYPE_NONE;
262
263 /**Maxiumum legal TYPE_XXX value. */
264 public static final int TYPE__MAX = TYPE_NUMBER;
265
266 /**Checks the type of the value passed is valid for this definition.
267 * Note that null is always a valid value for any var type.
268 *
269 * @return true if value is of valid type for this definition,
270 * false otherwise
271 */
272 public boolean checkType(final Object value)
273 {
274 // Null is always valid.
275 if(value == null)
276 { return(true); }
277
278 switch(type)
279 {
280 case TYPE_NONE: // Can only be null.
281 {
282 return(false);
283 }
284
285 case TYPE_IID: // Can only be InstanceID.
286 {
287 return(value instanceof InstanceID);
288 }
289
290 case TYPE_STRING: // Can only be java.lang.String.
291 {
292 return(value instanceof java.lang.String);
293 }
294
295 case TYPE_NUMBER: // Can only be java.lang impl of java.lang.Number.
296 {
297 if(value instanceof java.lang.Number)
298 {
299 // For extra security (eg to ensure immutability)
300 // check that this is an immutable JDK impl.
301 return(value.getClass().getName().startsWith("java.lang."));
302 }
303 break;
304 }
305 }
306
307 // Failed to type-check.
308 return(false);
309 }
310
311 /**Human-readable summary of variable definition. */
312 @Override
313 public String toString()
314 {
315 final StringBuilder sb = new StringBuilder(77);
316 sb.append("name=").append(name);
317 sb.append(":type=").append(type);
318 if(local) { sb.append(":local"); }
319 if(persistent) { sb.append(":persistent"); }
320 if(readOnly) { sb.append(":readOnly"); }
321 if(event) { sb.append(":event"); }
322 if(event) { sb.append(":maxDiffEventCount=").append(maxDiffEventCount); }
323 return(sb.toString());
324 }
325
326
327 /**Comparable ordering is on name alone in default String order. */
328 public int compareTo(final SimpleVariableDefinition o)
329 {
330 return(name.compareTo(o.name));
331 }
332
333 /**Equality is based on the name and all the other parameters; ie equal definitions are identical.
334 * This definition of equals() allows use of MemoryTools.intern().
335 * <p>
336 * This tests fields in an order likely to give the best performance.
337 */
338 @Override
339 public boolean equals(final Object o)
340 {
341 if(this == o)
342 {
343 return true;
344 }
345 if(!(o instanceof SimpleVariableDefinition))
346 {
347 return false;
348 }
349
350 final SimpleVariableDefinition simpleVariableDefinition = (SimpleVariableDefinition) o;
351
352 if(type != simpleVariableDefinition.type)
353 {
354 return false;
355 }
356 if(local != simpleVariableDefinition.local)
357 {
358 return false;
359 }
360 if(event != simpleVariableDefinition.event)
361 {
362 return false;
363 }
364 if(maxDiffEventCount != simpleVariableDefinition.maxDiffEventCount)
365 {
366 return false;
367 }
368 if(persistent != simpleVariableDefinition.persistent)
369 {
370 return false;
371 }
372 if(readOnly != simpleVariableDefinition.readOnly)
373 {
374 return false;
375 }
376 if(!name.equals(simpleVariableDefinition.name))
377 {
378 return false;
379 }
380
381 if((evPeriodSubset == null))
382 {
383 if(simpleVariableDefinition.evPeriodSubset != null) { return(false); }
384 }
385 else if(!evPeriodSubset.equals(simpleVariableDefinition.evPeriodSubset))
386 {
387 return false;
388 }
389
390 return true;
391 }
392
393 /**The hash is built on a subset of the fields.
394 * This implementation just uses the hash of the name.
395 */
396 @Override
397 public int hashCode()
398 {
399 return(name.hashCode());
400 }
401
402 /**Deserialise: use constructor for validation, defensive copying, etc.
403 * We also use this to eliminate duplicate copies and thus conserve memory.
404 * We attempt to effectively intern() the entire definition and also the name.
405 *
406 * @return identical, de-duped, defensively-copied, non-null instance
407 */
408 protected Object readResolve()
409 throws ObjectStreamException
410 {
411 // We first try to look up this definition in the
412 // canonical set of definitions.
413 // If we find it there and the canonical definition is identical
414 // (and the canonical set actually exists; during bootstrapping it may be null!)
415 // then we return that.
416 // However, we do NOT allow deserialisation of a definition with the same name
417 // but a different type/flags/etc since that could break type-safety.
418 final Map<String,SimpleVariableDefinition> m = SystemVariables.nameToDef;
419 if(m != null) // Static initialisation is finished.
420 {
421 final SimpleVariableDefinition d = m.get(name);
422
423 // Replace with canonical copy if possible.
424 if(this.equals(d)) { return(d); }
425
426 // Disallow clashing definitions for type-safety's sake.
427 if(d != null)
428 { throw new InvalidObjectException("Attempting to deserialise SimpleVariableDefinition with name and type that clash with that of a system variable: " + d + " vs " + this); }
429 }
430
431 // Else we resort to more general intern() methods.
432 return(MemoryTools.intern(new SimpleVariableDefinition(name,
433 type, local, persistent, readOnly, event, maxDiffEventCount, evPeriodSubset)));
434 }
435
436 // /**Deserialise. */
437 // private void readObject(ObjectInputStream in)
438 // throws IOException, ClassNotFoundException
439 // {
440 // in.defaultReadObject();
441 // validateObject(); // Validate state immediately.
442 // }
443
444 public void validateObject()
445 throws InvalidObjectException
446 {
447 if((name == null) ||
448 (name.length() < 1) || (name.length() > MAX_NAME_LEN))
449 { throw new InvalidObjectException("bad object: name must not be null nor empty nor too long"); }
450 // Check for basic validity of name.
451 // Should look like a fully-qualified plain-ASCII Java class/member name (using dots).
452 // Should thus also be safe as a filename component (no slashes for example).
453 for(int i = name.length(); --i >=0; )
454 {
455 final char c = name.charAt(i);
456 if((c < 32) || (c > 126))
457 { throw new InvalidObjectException("bad object: name contains non-printable ASCII"); }
458 if((c != '.') && !Character.isLetterOrDigit(c))
459 { throw new InvalidObjectException("bad object: name contains something other than dot or alphanumeric"); }
460 }
461
462 if((type < TYPE__MIN) || (type > TYPE__MAX))
463 { throw new InvalidObjectException("bad object: invalid type"); }
464
465 if((maxDiffEventCount < 0) || (maxDiffEventCount > EVENT_ABS_MAX_DIFF_VALUES))
466 { throw new InvalidObjectException("bad object: invalid maxDiffEventCount"); }
467 if(!isEvent() && (maxDiffEventCount != 0))
468 { throw new InvalidObjectException("bad object: not event but non-zero maxDiffEventCount"); }
469 if(!isEvent() && (evPeriodSubset != null))
470 { throw new InvalidObjectException("bad object: not event but non-null evPeriodSubset"); }
471
472 // The set should be non-empty and non-full (a proper sub-set) if non-null.
473 if((evPeriodSubset != null) &&
474 (evPeriodSubset.isEmpty() || EnumSet.complementOf(evPeriodSubset).isEmpty()))
475 { throw new InvalidObjectException("bad object: event set non-null and empty or full"); }
476 }
477
478 /**Unique Serialisation class ID generated by http://random.hd.org/. */
479 private static final long serialVersionUID = 2361192791747313629L;
480 }