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;
030
031 import java.io.InvalidObjectException;
032 import java.io.ObjectInputValidation;
033 import java.io.Serializable;
034
035 import org.hd.d.pg2k.svrCore.Tuple.ComparableTriple;
036
037 /**Immutable, Serializable collection of the basic static attributes of an exhibit.
038 * This is designed to be fast to do comparisons against, and to be
039 * transportable across a network, persistable to disc, and easy to run
040 * filtering queries against.
041 * <p>
042 * This verifies that its contents make sense at construction and
043 * after deserialisation.
044 * <p>
045 * This is keyed on the exhibit underlying name, but should be easy to
046 * use with other comparators.
047 * <p>
048 * The timestamp can be used to compare that the version of the object
049 * held is up-to-date.
050 * <p>
051 * Some core values are made available as public final fields
052 * for speed of access.
053 */
054 public final class ExhibitStaticAttr implements Comparable<ExhibitStaticAttr>,
055 Serializable, ObjectInputValidation
056 {
057 /**The full (relative) path/name of the exhibit; never null.
058 * Must be unique and acceptable to ExhibitName.
059 * <p>
060 * The field type is CharSequence to allow reading in of old String-valued instances
061 * but allows holding of ExhibitFileName instances at run-time.
062 * <p>
063 * All run-time instances will have the same type,
064 * except possibly fleetingly during (de)serialisation.
065 */
066 private final CharSequence filePath;
067
068 /**The length of the exhibit in bytes; strictly positive. */
069 public final long length;
070
071 /**The Java-style timestamp (last-modified-time) of the exhibit (ms); strictly positive.
072 * This must be valid Gallery timestamp
073 * which means not before the Gallery was created
074 * and not too far into the future allowing for clock skew...
075 */
076 public final long timestamp;
077
078
079 /**All comparison is done on the name.
080 * Relies on hash of underlying data type, assuming all run-time filePath instances will have same type.
081 */
082 @Override
083 public final int hashCode()
084 { return(filePath.hashCode()); }
085
086 /**All comparison is done on the name only, NOT on all fields.
087 * Relies on all run-time filePath instances will have same type.
088 */
089 @Override
090 public final boolean equals(final Object o)
091 {
092 if(o == this) { return(true); }
093 if(!(o instanceof ExhibitStaticAttr)) { return(false); }
094 // assert(filePath.getClass() == ((ExhibitStaticAttr) o).filePath.getClass()) : "underlying classes must be identical";
095 // return(filePath.equals(((ExhibitStaticAttr) o).filePath));
096 return(TextUtils.contentEquals(filePath, ((ExhibitStaticAttr) o).filePath));
097 }
098
099 /**True if always EFP is always used as the normal internal form.
100 * Other types may be used temporarily during (de)serialisation.
101 */
102 private static final boolean ALWAYS_USE_EFP = true;
103
104 /**Returns true iff the run-time type of filePath is an ExhibitFullName. */
105 private boolean isEFP()
106 { return(ALWAYS_USE_EFP || Name.ExhibitFull.class.equals(filePath.getClass())); }
107
108 /**Get the file path (full exhibit name) as a String; never null.
109 * This operation may be expensive and slow.
110 *
111 * @deprecated getExhibitFullName() or getCharSequence() preferred
112 */
113 @Deprecated
114 public String getFilePath()
115 {
116 return(filePath.toString()); // May force a conversion if not already String.
117 }
118
119 /**Get the full exhibit name; never null.
120 * This operation may be expensive and slow initially,
121 * but will become the preferred option.
122 */
123 public Name.ExhibitFull getExhibitFullName()
124 {
125 if(!isEFP())
126 { return(Name.ExhibitFull.create(filePath)); }
127 else // Already an ExhibitFullName: just cast to return it.
128 { return((Name.ExhibitFull) filePath); }
129 }
130
131 /**Get the file path (full exhibit name) as an immutable CharSequence; never null.
132 * This avoids any possible expensive value conversion
133 * for callers that don't care about the exact representation.
134 */
135 public CharSequence getCharSequence()
136 {
137 return(filePath);
138 }
139
140 /**Human-readable summary contains at least the name. */
141 @Override public String toString()
142 { return(filePath + "["+length+"b]"); }
143
144 /**All comparison is done on the exhibit name alone. */
145 public final int compareTo(final ExhibitStaticAttr o)
146 { return(TextUtils.compare(filePath, o.filePath)); }
147
148 /**Tests that instances are identical in all parameters. */
149 public final boolean isIdentical(final ExhibitStaticAttr other)
150 {
151 if(other == this) { return(true); }
152 // We compare fields in the order that will probably
153 // fail fastest for non-identical exhibits.
154 return((timestamp == other.timestamp) &&
155 (length == other.length) &&
156 TextUtils.contentEquals(filePath, other.filePath));
157 }
158
159 /**Extract compound key that does depend on all the fields; never null.
160 * Suitable for hashed and sorted collections.
161 */
162 public Comparable<?> getKey()
163 { return(new ComparableTriple<Name, Long, Long>(getExhibitFullName(), timestamp, length)); } // FIXME: expensive conversion to String
164
165 /**Construct new immutable instance.
166 * Try to force sharing of the potentially large and numerous paths/names
167 * to avoid memory bloat, even though this costs some time.
168 *
169 * @throws IllegalArgumentException in the case of a null or invalid filePath
170 */
171 public ExhibitStaticAttr(final CharSequence _filePath,
172 final long _length,
173 final long _timestamp)
174 throws IllegalArgumentException
175 {
176 if(null == _filePath) { throw new IllegalArgumentException(); }
177
178 // Force filePath to preferred internal representation.
179 // Also defensive copy of potentially-mutable CharSequence input.
180 filePath = (_filePath instanceof Name.ExhibitFull) ? _filePath : Name.ExhibitFull.create(_filePath);
181 length = _length;
182 timestamp = _timestamp;
183
184 // Verify what we've been given.
185 try { validateObject(); }
186 catch(final InvalidObjectException e)
187 { throw new IllegalArgumentException(e); }
188 }
189
190 /**Generate a lookup key with just the name significant; never null.
191 * @throws IllegalArgumentException in the case of a null or invalid filePath
192 */
193 public static ExhibitStaticAttr makeNameOnlyKey(final CharSequence filePath)
194 { return(new ExhibitStaticAttr(filePath)); }
195
196 /**Used by makeNameOnlyKey() to make a name-only key efficiently.
197 * Skips some checking but will still ensure that only a valid instance is created.
198 * @throws IllegalArgumentException in the case of a null or invalid filePath
199 */
200 private ExhibitStaticAttr(final CharSequence _filePath)
201 {
202 // Force filePath to preferred internal representation.
203 // Also defensive copy of potentially-mutable CharSequence input.
204 filePath = (_filePath instanceof Name.ExhibitFull) ? _filePath : Name.ExhibitFull.create(_filePath);
205 length = 1;
206 timestamp = CoreConsts.GALLERY_EPOC_START;
207 // Skip full (re)validation.
208 }
209
210
211 /**My initial version number. */
212 private static final long serialVersionUID = -8233812711746526095L;
213
214 /**Deserialise: use constructor for validation, defensive copying, etc. */
215 protected Object readResolve()
216 // throws ObjectStreamException
217 {
218 // Construct new instance of object in normal defensive way.
219 // This also ensures that we get a chance to convert to our preferred internal form.
220 return(new ExhibitStaticAttr(filePath, length, timestamp));
221 }
222
223 /**Validate fields/state.
224 * Called in the constructor and possibly after de-serialising.
225 * <p>
226 * Barf if something bad is found.
227 * (Maybe allow some extra info in debug version.)
228 */
229 public void validateObject()
230 throws InvalidObjectException
231 {
232 // Check that all components are sane and safe
233 // (eg strings not dangerously long).
234 if(null == filePath)
235 { throw new InvalidObjectException("bad object: null name"); }
236 // If run-time type is not an EFP then must recheck syntax explicitly here.
237 if(!isEFP() && !ExhibitName.validNameSyntax(filePath))
238 { throw new InvalidObjectException("bad object: name = ``" + filePath + "''"); }
239 if(length <= 0)
240 { throw new InvalidObjectException("bad object: length = " + length); }
241 if(!GenUtils.isValidGalleryTimestamp(timestamp))
242 { throw new InvalidObjectException("bad object: timestamp = " + timestamp + " ("+(new java.util.Date(timestamp))+") for exhibit " + filePath); }
243 }
244 }