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        }