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&#46;hd&#46;org/. */
530        private static final long serialVersionUID = -5094323683052972705L;
531        }