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