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.Serializable;
034 import java.util.Collections;
035 import java.util.Comparator;
036 import java.util.HashMap;
037 import java.util.Iterator;
038 import java.util.Map;
039 import java.util.concurrent.ConcurrentHashMap;
040 import java.util.concurrent.ConcurrentMap;
041 import java.util.concurrent.atomic.AtomicLong;
042
043 import org.hd.d.pg2k.svrCore.CoreConsts;
044 import org.hd.d.pg2k.svrCore.MemoryTools;
045 import org.hd.d.pg2k.svrCore.Rnd;
046
047 import ORG.hd.d.IsDebug;
048
049
050 /**Immutable value of a simple local or global system variable.
051 * This contains the type and value and date-of-change of a system variable.
052 * <p>
053 * This is Serializable to enable it to be sent over network connections,
054 * persisted, etc.
055 * <p>
056 * The types of all legitimate values are immutable,
057 * thus preserving the immutability of instances of this class.
058 * <p>
059 * This class is not Internable because some fields are ignored by equals();
060 * the value may be worth intern()ing however.
061 * <p>
062 * We ensure that the timestamp is unique (and monotonically increasing)
063 * in combination with the definition name for this class loader,
064 * so that in combination with an originating system ID value
065 * it should be possible to recognise and discard duplicate messages
066 * reasonably accurately without adding any more data to each message
067 * (eg some sort of unique ID which might be bulky and slow to create).
068 */
069 public final class SimpleVariableValue implements Serializable,
070 ObjectInputValidation
071 {
072 /**Construct a single variable value.
073 * This type-checks the value, vetoing construction if invalid.
074 * <p>
075 * This is private because though all parameters are checked
076 * we generally only want to add or remove map entries one at a time,
077 * and we want to handle timestamp values carefully.
078 * <p>
079 * We attempt to intern() the value to economise on memory
080 * if we think that it will be worthwhile to so do.
081 *
082 * @param def the variable definition; never null
083 * @param value the variable value; null or correct type
084 * @param globalMap if non-null the Map from
085 * InstanceID to SimpleVariableValue;
086 * a private copy is taken in the constructor
087 * and an empty Map is converted to a null
088 *
089 * @throws java.lang.IllegalArgumentException if the arguments are invalid
090 */
091 private SimpleVariableValue(final SimpleVariableDefinition def,
092 final Object value,
093 final Map<InstanceID,SimpleVariableValue> globalMap,
094 final long timeStamp)
095 throws IllegalArgumentException
096 {
097 this.def = def;
098 // If worthwhile then intern() the value.
099 this.value = ((value != null) && shouldIntern(def)) ? MemoryTools.intern(value) : value;
100 // Make a defensive immutable copy of any non-empty Map,
101 // else use a null (convert an empty Map to null to save space).
102 this.globalMap = ((globalMap == null) || (globalMap.size() == 0)) ? null :
103 Collections.unmodifiableMap(new HashMap<InstanceID,SimpleVariableValue>(globalMap));
104 timestamp = timeStamp;
105
106 try { validateObject(); }
107 catch(final InvalidObjectException e)
108 { throw new IllegalArgumentException(e.getMessage()); }
109 }
110
111 /**Construct a single variable value.
112 * This type-checks the value, vetoing construction if invalid.
113 *
114 * @param def the variable definition; never null
115 * @param value the variable value; null or correct type
116 *
117 * @throws java.lang.IllegalArgumentException if the arguments are invalid
118 */
119 public SimpleVariableValue(final SimpleVariableDefinition def,
120 final Object value)
121 throws IllegalArgumentException
122 {
123 this(def, value, null, createUniqueTimestamp(def));
124 }
125
126 /**Effective tolerance in timestamp value (ms); non-negative.
127 * This allows us to produce timestamps slightly different to reported time
128 * by the number of miliseconds specified.
129 * <p>
130 * This allows values to be issued in (efficient) small bursts without pause,
131 * and allows for the fact that not all JVMs have a true 1ms clock tick,
132 * eg often granularity may be 10ms or 20ms or even more.
133 * <p>
134 * A value of 10 to 100 is probably reasonable,
135 * though this must remain much smaller than the skew tolerance to avoid confusion.
136 */
137 public static final int TIMESTAMP_TOLERANCE_MS = 31;
138 static { assert(TIMESTAMP_TOLERANCE_MS < CoreConsts.MAX_PEER_CLOCK_SKEW_MS/16); }
139
140 /**Shared concurrent map from definition name to last-issued timestamp (null if none yet); never null.
141 * Used in conjunction with the various atomic primitives,
142 * this is thread-safe, lock-free and race-free.
143 * <p>
144 * Limited in size to the number of distinct definitions.
145 */
146 private static final ConcurrentMap<String, AtomicLong> _timestamps = new ConcurrentHashMap<String, AtomicLong>();
147
148 /**Create the timestamp for a new message; strictly positive.
149 * The stream of timestamps produced is monotonically increasing
150 * separately for each definition,
151 * so although maximum message rate may be effectively capped for each definition,
152 * there is no interaction between independent variable/event streams.
153 * <p>
154 * This may wait to be able to create a new timestamp.
155 * <p>
156 * If the clock is set backwards this may wait a long time.
157 *
158 * @return positive partially-unique timestamp
159 */
160 private static long createUniqueTimestamp(final SimpleVariableDefinition def)
161 {
162 assert(def != null);
163
164 final String key = def.getName();
165 final long now = System.currentTimeMillis();
166
167 // Get the timestamp for this def, creating one the first time needed.
168 AtomicLong t;
169 while(null == (t = _timestamps.get(key)))
170 {
171 // Create new timestamp for this def.
172 // Race-free, since only the first succeeds.
173 // If we do win the race then we can return our new timestamp immediately.
174 if(null == _timestamps.putIfAbsent(key, new AtomicLong(now)))
175 { return(now); }
176 }
177
178 for( ; ; )
179 {
180 final long later = System.currentTimeMillis();
181 final long lastStampUsed = t.get();
182 if(later+TIMESTAMP_TOLERANCE_MS <= lastStampUsed)
183 {
184 // Current time is not far enough ahead of the last timestamp issued,
185 // so we must wait for the clock to advance (beyond the last timestamp used)
186 // and try again, often just for one or few ms at a time,
187 // but possibly much longer if system time has been set back...
188 // Random sleep beyond the minimum helps to reduce inter-thread collisions.
189 final long toSleep = 1 + (lastStampUsed-later) + Rnd.fastRnd.nextInt(2+(TIMESTAMP_TOLERANCE_MS/2));
190 assert(toSleep > 0);
191 if(IsDebug.isDebug) { System.err.println("WARNING: SimpleVariableValue: sleeping "+toSleep+"ms for timestamp for "+def.getName()+"..."); }
192 try { Thread.sleep(toSleep); }
193 catch(final InterruptedException e)
194 {
195 Thread.currentThread().interrupt(); // Remember that we were interrupted...
196 throw new RuntimeException(e); // Abort this call immediately...
197 }
198 // Try again...
199 continue;
200 }
201
202 // Potentially the clock is ahead of the previously-used timestamp.
203 // Try to atomically update it to a newer timestamp and return.
204 // If we succeed then we have a new locally-unique (newest) timestamp for this def.
205 final long generatedStamp = Math.max(later, lastStampUsed+1);
206 if(t.compareAndSet(lastStampUsed, generatedStamp))
207 { return(generatedStamp); /* Got it; allocated new, later/unique timestamp. */ }
208 }
209 }
210
211 /**The variable definition; never null. */
212 private final SimpleVariableDefinition def;
213
214 /**Get the variable definition; never null. */
215 public SimpleVariableDefinition getDef()
216 {
217 return(def);
218 }
219
220 /**The variable value; null or a type allowed by the definition. */
221 private final Object value;
222
223 /**Get the variable definition; null or a type allowed by the definition. */
224 public Object getValue()
225 {
226 return(value);
227 }
228
229 /**The time at which the value object was constructed; strictly positive.
230 * This value comes with a deserialised object.
231 */
232 private final long timestamp;
233
234 /**Get the time at which the value object was constructed. */
235 public long getTimestamp()
236 {
237 return(timestamp);
238 }
239
240 /**An immutable Map from InstanceID keys to SimpleVariableValues each of the same definition and with no global map.
241 * This contains an encapsulation of the constiuent values from each
242 * particpating system.
243 * <p>
244 * It must be null for local variables.
245 * <p>
246 * For global variables where this is non-null it must contain
247 * only keys that are distinct InstanceID values,
248 * and only values that are SimpleVariableValues with exactly the same
249 * definition and a null globalMap.
250 * <p>
251 * Is immutable (if non-null) so that it can be returned directly.
252 * <p>
253 * Never an empty Map; null is used in this case.
254 */
255 private final Map<InstanceID,SimpleVariableValue> globalMap;
256
257 /**Gets immutable Map from InstanceID to SimpleVariableValue; may be null or non-empty.
258 * Maps from the ID of the participating system that provided a value
259 * of this global to the value.
260 * <p>
261 * Never an empty Map.
262 */
263 public Map<InstanceID,SimpleVariableValue> getGlobalMap()
264 {
265 return(globalMap);
266 }
267
268
269 /**Returns true if we should probably automatically intern() the value.
270 * We will do this is the value is likely to be bulky (eg a String),
271 * or may exist in many copies and be long-lived (eg an event).
272 */
273 private static boolean shouldIntern(final SimpleVariableDefinition def)
274 {
275 return((def.isEvent() ||
276 (def.getType() == SimpleVariableDefinition.TYPE_STRING)));
277 }
278
279
280 /**Human-readable summary of variable value.
281 * Simply the raw value rendered as a String.
282 * <p>
283 * This also makes it easy to use directly in (say) a JSP
284 * where if the variable is missing entirely we'll get a benign "null",
285 * and to render it simply we don't have to risk calling a method
286 * on a possibly null value.
287 */
288 @Override
289 public String toString()
290 {
291 return(String.valueOf(value));
292 }
293
294 /**Routine to print a full techie-readable description of a variable.
295 * This includes the definition, current value, timestamp,
296 * and all the globalMap values.
297 * <p>
298 * This will be a long (single line) of text with values embedded.
299 */
300 public String getFullDescription()
301 {
302 final StringBuilder result = new StringBuilder(189);
303 result.append("SimpleVariableValue");
304 result.append(":def=").append(def);
305 result.append(":value=").append(value);
306 result.append(":timestamp=").append(timestamp);
307
308 result.append(":globalMap=");
309 if(globalMap == null)
310 {
311 result.append("null");
312 }
313 else
314 {
315 final int size = globalMap.size();
316 result.append("[size=").append(size);
317 for(final Iterator<InstanceID> it = globalMap.keySet().iterator(); it.hasNext(); )
318 {
319 final InstanceID sys = it.next();
320 final SimpleVariableValue svv = globalMap.get(sys);
321
322 result.append(", ");
323 result.append("sysID=").append(sys);
324 result.append("->value=").append(svv.getFullDescription());
325 }
326 result.append("]");
327 }
328
329 return(result.toString());
330 }
331
332
333 /**Equality is based on the definition (name, type, etc) and value.
334 * The timestamp and globalMap values are ignored for the purposes of equality.
335 */
336 @Override
337 public boolean equals(final Object obj)
338 {
339 if(!(obj instanceof SimpleVariableValue)) { return(false); }
340 final SimpleVariableValue o = (SimpleVariableValue) obj;
341
342 // Compare all fields except the timestamp and globalMap.
343 if(value == null) { if(o.value != null) { return(false); } }
344 else if(!value.equals(o.value)) { return(false); }
345 if(!def.equals(o.def)) { return(false); }
346
347 // All relevant fields seem to match, so objects are equal.
348 return(true);
349 }
350
351 /**The hash is built on a subset of the fields.
352 * This implementation uses the hash of the definition and of the value.
353 */
354 @Override
355 public int hashCode()
356 {
357 return(def.hashCode() ^
358 ((value != null) ? value.hashCode() : 0));
359 }
360
361 /**Make a new value identical except for a new/updated globalMap mapping.
362 * The new instance has a globalMap which contains the mapping from
363 * client to value, creating the Map if need be,
364 * and adding or replacing a mapping for client as required.
365 * <p>
366 * Both the client and value must be non-null,
367 * and the value:
368 * <ul>
369 * <li>must not have a globalMap
370 * <li>must have the same definition (or an equal one).
371 * <ul>
372 */
373 public SimpleVariableValue put(final InstanceID client,
374 final SimpleVariableValue mapEntryValue)
375 {
376 return(put(client, mapEntryValue, false));
377 }
378
379 /**Make a new value identical except for a new/updated globalMap mapping.
380 * The new instance has a globalMap which contains the mapping from
381 * client to value, creating the Map if need be,
382 * and adding or replacing a mapping for client as required.
383 * <p>
384 * Both the client and value must be non-null,
385 * and the value:
386 * <ul>
387 * <li>must not have a globalMap
388 * <li>must have the same definition (or an equal one).
389 * <ul>
390 *
391 * @param useNewValue if true, the basic value and timestamp
392 * are taken from the new map entry, rather than from this
393 */
394 public SimpleVariableValue put(final InstanceID client,
395 SimpleVariableValue mapEntryValue,
396 final boolean useNewValue)
397 {
398 if((client == null) || (mapEntryValue == null))
399 { throw new IllegalArgumentException(); }
400
401 if(!def.equals(mapEntryValue.def))
402 { throw new IllegalArgumentException("value must have same definition"); }
403
404 // If the new map entry has a globalMap,
405 // create a version with it stripped out
406 // but with the value and timestamp preserved.
407 if(mapEntryValue.globalMap != null)
408 {
409 mapEntryValue = new SimpleVariableValue(def,
410 mapEntryValue.value,
411 null,
412 mapEntryValue.timestamp);
413 }
414
415 // Make a map with any previous values
416 // and the new mapping...
417 final Map<InstanceID,SimpleVariableValue> newMap = (globalMap == null) ? (new HashMap<InstanceID, SimpleVariableValue>(3)) :
418 (new HashMap<InstanceID, SimpleVariableValue>(globalMap));
419 newMap.put(client, mapEntryValue);
420
421 return(new SimpleVariableValue(def,
422 (useNewValue ? mapEntryValue.value : value),
423 newMap,
424 (useNewValue ? mapEntryValue.timestamp : timestamp)));
425 }
426
427 /**Return instance with all mappings older than specified limit removed.
428 * The argument is a time, presumably in the past.
429 * <p>
430 * Used for trimming away data from dead systems.
431 * <p>
432 * Returns unchanged any instance with a null or empty globalMap,
433 * or where all of the keys in the globalMap are in the specified Set.
434 * Else returns a new, modified value.
435 *
436 * @param oldestAllowed any key older than this excluded from the result
437 */
438 public SimpleVariableValue removeAllKeysOlder(final long oldestAllowed)
439 {
440 // Nothing to remove, so return the original.
441 // (Includes local values.)
442 if(globalMap == null)
443 { return(this); }
444
445 // Make a new map,
446 // copying only values whose keys are new enough.
447 final Map<InstanceID,SimpleVariableValue> newMap = new HashMap<InstanceID,SimpleVariableValue>(1 + 2 * globalMap.size());
448 for(final Iterator<InstanceID> it = globalMap.keySet().iterator(); it.hasNext(); )
449 {
450 final InstanceID k = it.next();
451 final SimpleVariableValue svv = globalMap.get(k);
452 if(svv.getTimestamp() >= oldestAllowed)
453 { newMap.put(k, svv); }
454 }
455
456 // If we didn't actually remove anything then return the original.
457 if(newMap.size() == globalMap.size())
458 { return(this); }
459
460 // Return new instance.
461 return(new SimpleVariableValue(def, value, newMap, timestamp));
462 }
463
464 /**A simple comparator that sorts by definition (and thus name) alone.
465 * This is useful for ordering values in lists when at most one of each
466 * type is present, eg for display or for compression sending over the wire.
467 */
468 public static final Comparator<SimpleVariableValue> compByDef =
469 new Comparator<SimpleVariableValue>()
470 {
471 public final int compare(final SimpleVariableValue svv1,
472 final SimpleVariableValue svv2)
473 { return(svv1.def.compareTo(svv2.def)); }
474 };
475
476
477 /**Deserialise: use constructor for validation, defensive copying, etc. */
478 protected Object readResolve()
479 // throws ObjectStreamException
480 {
481 // Construct new instance of object in the normal defensive way.
482 // We preserve the timestamp.
483 return(new SimpleVariableValue(def, value, globalMap, timestamp));
484 }
485
486 /**Check that the object state is consistent and legal. */
487 public void validateObject()
488 throws InvalidObjectException
489 {
490 if(def == null)
491 { throw new InvalidObjectException("bad object: def must not be null"); }
492 if(!def.checkType(value))
493 { throw new InvalidObjectException("bad object: value type is wrong, expecting "+def+" but given "+value.getClass().getName()); }
494
495 if(timestamp <= 0)
496 { throw new InvalidObjectException("bad object: invalid timestamp"); }
497
498 // If there is any globalMap data then check it thoroughly.
499 // We assume that it has already been defensively copied,
500 // so as not to be mutable from outside this instance.
501 if(globalMap != null)
502 {
503 // Ensure no global data in local variable...
504 if(def.isLocal())
505 { throw new InvalidObjectException("bad object: globalMap data in local variable"); }
506
507 // If non-null,
508 // then check Map content.
509 for(final Iterator it = globalMap.keySet().iterator(); it.hasNext(); )
510 {
511 final Object k = it.next();
512 if(!(k instanceof InstanceID))
513 { throw new InvalidObjectException("bad object: globalMap key not InstanceID"); }
514
515 final Object d = globalMap.get(k);
516 if(!(d instanceof SimpleVariableValue))
517 { throw new InvalidObjectException("bad object: globalMap data not SimpleVariableValue"); }
518
519 final SimpleVariableValue svv = (SimpleVariableValue) d;
520 if(!def.equals(svv.def))
521 { throw new InvalidObjectException("bad object: globalMap data of wrong variable type"); }
522
523 if(svv.globalMap != null)
524 { throw new InvalidObjectException("bad object: globalMap data contains other data"); }
525 }
526 }
527 }
528
529 /**Unique Serialisation class ID generated by http://random.hd.org/. */
530 private static final long serialVersionUID = -5094323683052972705L;
531 }