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 }