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    
030    package org.hd.d.pg2k.ai.scorer;
031    
032    import org.hd.d.pg2k.svrCore.MemoryTools;
033    import org.hd.d.pg2k.svrCore.Rnd;
034    
035    /**Immutable (enum) Scorer parameter.
036     * The immutablity of this class/instance depends on that of the enum.
037     * <p>
038     * Since there will usually be a very small total number of possible distinct instances
039     * (no more than the sum of all the sizes of the enums involved,
040     * possibly multiplied by a small number of distinct parameter names)
041     * we make the class Internable to control instance count and thus save some memory.
042     */
043    public final class ScorerParamEnum<E extends Enum<E>> implements ScorerParam,
044                                                                     MemoryTools.Internable
045        {
046        /**Get instance with default value, name, and value, validating the parameters; never null. */
047        public static <E extends Enum<E>> ScorerParamEnum<E> createScorerParamEnum(final E def, final String name, final E value)
048            {
049            if(!AbstractScorer.isValidParameterName(name)) { throw new IllegalArgumentException(); }
050            if((def == null) || (value == null)) { throw new IllegalArgumentException(); }
051            if(def.getClass() != value.getClass()) { throw new IllegalArgumentException(); }
052            return(MemoryTools.intern(new ScorerParamEnum<E>(def, name, value)));
053            }
054    
055        /**Get instance with the default value, validating the parameters; never null. */
056        public static <E extends Enum<E>> ScorerParamEnum<E> createScorerParamEnum(final E def, final String name)
057            { return(createScorerParamEnum(def, name, def)); }
058    
059        /**Construct with all components, parameters assumed validated already. */
060        private ScorerParamEnum(final E def,
061                                final String name, final E value)
062            {
063            assert((def != null) && (value != null) && (def.getClass() == value.getClass()));
064            assert(AbstractScorer.isValidParameterName(name));
065            this.name = name;
066            this.value = value;
067            this.def = def;
068            }
069    
070        /**Get the parameter name; never null or empty. */
071        public String getName() { return(name); }
072    
073        /**Parameter name with allowed syntax as for Java variable name; never null. */
074        public final String name;
075        /**Parameter value from the enumeration; non-null. */
076        public final E value;
077    
078        /**Default value for this parameter from the enumeration; non-null. */
079        public final E def;
080    
081        /**For an enumerated parameter, "perturbing" involves selecting a new value at random.
082         * Some of the time we return the input value untouched,
083         * to stochastically reduce the "jolt" of an average perturbation.
084         */
085        @SuppressWarnings("unchecked")
086        public ScorerParam perturb()
087            {
088            // Some of the time avoid any perturbation at all.
089            if(Rnd.fastRnd.nextBoolean()) { return(this); }
090            // Else compute a new value uniformly (which may be the same as the old one).
091            final Enum[] enumConstants = def.getClass().getEnumConstants();
092            assert((enumConstants != null) && (enumConstants.length > 0));
093            final E en = (E) enumConstants[Rnd.goodRnd.nextInt(enumConstants.length)];
094            if(en == value) { return(this); } // Return unchanged if same value.
095            return(MemoryTools.intern(new ScorerParamEnum<E>(def, name, value))); // Skip validation.
096            }
097    
098    
099        /**Parse the String representation of a parameter returning a value of the same type; never null.
100         * If the argument is null or unparsable or invalid,
101         * then the result has the default value,
102         * else an instance with the requested parameter value is returned.
103         * @return legal value of the same type as this, with the parsed input String's value if possible
104         */
105        @SuppressWarnings("unchecked")
106        public ScorerParam parse(final String v)
107            {
108            E parsedValue = def; // Use default value unless the parse is successful.
109            if(v != null)
110                {
111                try { parsedValue = Enum.valueOf((Class<E>) def.getClass(), v); }
112                catch(final IllegalArgumentException e) { /* Ignore. */ }
113                }
114    
115            // Memory/time optimisation where value is the same as for the current instance.
116            if(parsedValue == value) { return(this); }
117    
118            // Create new instance containing the parsed value.
119            return(MemoryTools.intern(new ScorerParamEnum<E>(def, name, value))); // Skip validation.
120            }
121    
122        /**Use the supplied parameter if possible (correct name/type/bounds), else return the default; never null.
123         * We return this to avoid creating a new instance whenever possible.
124         */
125        public ScorerParam extract(final ScorerParam p)
126            {
127            if((p == this) || equals(p)) { return(this); }
128            if(p.getClass() != getClass()) { return(createScorerParamEnum(def, name)); }
129            final ScorerParamEnum<?> pe = (ScorerParamEnum<?>) p;
130            // Return default if this is not identical in every particular except the value.
131            if((pe.def != def) || !name.equals(pe.name)) { return(createScorerParamEnum(def, name)); }
132            return(pe);
133            }
134    
135    
136        /**Generate the "name=value" text; never null or empty, though the value part may be empty. */
137        public String toNameValueString()
138            {
139            final StringBuilder sb = new StringBuilder(16 + name.length());
140            sb.append(name).append('=').append(value);
141            return(sb.toString());
142            }
143    
144        /**Human-readable representation. */
145        @Override
146        public String toString() { return(toNameValueString()); }
147    
148    
149        /* (non-Javadoc)
150         * @see org.hd.d.pg2k.ai.scorer.ScorerParam#similar(org.hd.d.pg2k.ai.scorer.ScorerParam)
151         */
152        public boolean similar(final ScorerParam p)
153            {
154            if(p == this) { return(true); }
155            if(!(p instanceof ScorerParamEnum)) { return(false); }
156            // Compare enum values directly,
157            // assuming that all non-identical enum values are to be treated as different.
158            return(value == ((ScorerParamEnum<?>) p).value);
159            }
160    
161    
162        /**We include all but the default-value field (assumed highly correlated with the name) in the hash. */
163        @Override public int hashCode() { return(name.hashCode() ^ value.hashCode()); }
164    
165        /**Equality depends on all elements. */
166        @Override public boolean equals(final Object obj)
167            {
168            if(this == obj) {
169                return true;
170                }
171            if(obj == null) {
172                return false;
173                }
174            if(getClass() != obj.getClass()) {
175                return false;
176                }
177            final ScorerParamEnum<?> other = (ScorerParamEnum<?>) obj;
178            if(value != other.value) {
179                return false;
180                }
181            if(def != other.def) {
182                return false;
183                }
184            if(!name.equals(other.name)) {
185                return false;
186                }
187            return true;
188            }
189        }