001    package jigcell.compare.data;
002    
003    import java.beans.DefaultPersistenceDelegate;
004    import java.beans.Encoder;
005    import java.beans.Expression;
006    import java.beans.XMLDecoder;
007    import java.beans.XMLEncoder;
008    import java.io.BufferedInputStream;
009    import java.io.BufferedOutputStream;
010    import java.io.File;
011    import java.io.FileInputStream;
012    import java.io.FileOutputStream;
013    import java.io.IOException;
014    import java.io.ObjectInputStream;
015    import java.io.ObjectOutputStream;
016    import java.lang.ref.SoftReference;
017    import java.util.zip.GZIPInputStream;
018    import java.util.zip.GZIPOutputStream;
019    import jigcell.compare.IDataElement;
020    import jigcell.compare.impl.Compare;
021    import jigcell.compare.impl.ExceptionRecorder;
022    
023    /**
024     * A data element that provides a memory sensitive cache wrapped around another data element.
025     *
026     * <p>
027     * This code is licensed under the DARPA BioCOMP Open Source License.  See LICENSE for more details.
028     * </p>
029     *
030     * @author Nicholas Allen
031     */
032    
033    public class CachedDataElement extends DataElement {
034    
035       /**
036        * Prefix for temporary cache files
037        */
038    
039       protected final static String TEMPFILE_PREFIX = "jigcell";
040    
041       /**
042        * Cached exception recorder for this transferer
043        */
044    
045       protected transient ExceptionRecorder exception;
046    
047       /**
048        * File where the data element can be found
049        */
050    
051       protected transient File file;
052    
053       /**
054        * Element for use in memory locking
055        */
056    
057       protected transient IDataElement cache;
058    
059       /**
060        * Data element to cache
061        */
062    
063       protected transient SoftReference ref;
064    
065       /**
066        * Persists the element by creating a new wrapper around the cached element.
067        */
068    
069       protected static class CachedDataElementDelegate extends DefaultPersistenceDelegate {
070          protected Expression instantiate (Object o, Encoder encoder) {
071             return new Expression (o, o.getClass (), "new", new Object [] {((CachedDataElement) o).getCachedElement ()});
072          }
073       }
074    
075       static {
076          Compare.addDelegate (CachedDataElement.class, new CachedDataElementDelegate ());
077       }
078    
079       /**
080        * Creates a new data element cache.
081        *
082        * @param element Element to cache
083        */
084    
085       public CachedDataElement (IDataElement element) throws IOException {
086          if (element == null)
087             throw new IllegalArgumentException ();
088          exception = new ExceptionRecorder ("Error encountered by element store.", true, false, true);
089          createCachedElement (element);
090       }
091    
092       /**
093        * Creates a new data element cache.
094        */
095    
096       private CachedDataElement () {
097          throw new UnsupportedOperationException ();
098       }
099    
100       /**
101        * {@inheritDoc}
102        */
103    
104       public boolean getBooleanValue (long pos) {
105          return getCachedElement ().getBooleanValue (pos);
106       }
107    
108       /**
109        * {@inheritDoc}
110        */
111    
112       public long getIntegralValue (long pos) {
113          return getCachedElement ().getIntegralValue (pos);
114       }
115    
116       /**
117        * {@inheritDoc}
118        */
119    
120       public long getLength () {
121          return getCachedElement ().getLength ();
122       }
123    
124       /**
125        * {@inheritDoc}
126        */
127    
128       public IDataElement getListValue (long pos) {
129          return getCachedElement ().getListValue (pos);
130       }
131    
132       /**
133        * {@inheritDoc}
134        */
135    
136       public String getLiteralValue (long pos) {
137          return getCachedElement ().getLiteralValue (pos);
138       }
139    
140       /**
141        * {@inheritDoc}
142        */
143    
144       public double getRealValue (long pos) {
145          return getCachedElement ().getRealValue (pos);
146       }
147    
148       /**
149        * {@inheritDoc}
150        */
151    
152       public IDataElement.Type getType () {
153          return getCachedElement ().getType ();
154       }
155    
156       /**
157        * {@inheritDoc}
158        */
159    
160       public IDataElement.Type getType (long pos) {
161          return getCachedElement ().getType (pos);
162       }
163    
164       /**
165        * {@inheritDoc}
166        */
167    
168       public synchronized boolean isAvailable () {
169          return lockCount > 0 || ref.get () != null && super.isAvailable ();
170       }
171    
172       /**
173        * {@inheritDoc}
174        */
175    
176       public synchronized void memoryLock () {
177          if (lockCount++ == 0) {
178             cache = getCachedElement ();
179             ref = new SoftReference (cache);
180          }
181       }
182    
183       /**
184        * {@inheritDoc}
185        */
186    
187       public synchronized void memoryUnlock () {
188          if (--lockCount == 0)
189             cache = null;
190       }
191    
192       /**
193        * Creates the backend representation of the cached element.
194        */
195    
196       protected void createCachedElement (IDataElement element) throws IOException {
197          ref = new SoftReference (element);
198          file = File.createTempFile (TEMPFILE_PREFIX, null, null);
199          XMLEncoder encoder = new XMLEncoder (new GZIPOutputStream (new BufferedOutputStream (new FileOutputStream (file))));
200          encoder.setExceptionListener (exception);
201          Compare.updateEncoder (encoder);
202          encoder.writeObject (element);
203          encoder.close ();
204          file.deleteOnExit ();
205       }
206    
207       /**
208        * Retrieves the cached element for use.
209        */
210    
211       protected synchronized IDataElement getCachedElement () {
212          if (cache != null)
213             return cache;
214          IDataElement element = (IDataElement) ref.get ();
215          if (element == null)
216             try {
217                XMLDecoder decoder = new XMLDecoder (new GZIPInputStream (new BufferedInputStream (new FileInputStream (file))), this, exception);
218                element = (IDataElement) decoder.readObject ();
219                decoder.close ();
220                ref = new SoftReference (element);
221             } catch (IOException e) {
222                Compare.assertion ("Unable to read cache file.", e);
223             }
224          return element;
225       }
226    
227       /**
228        * {@inheritDoc}
229        */
230    
231       protected ExceptionRecorder getExceptionRecorder () {
232          return exception;
233       }
234    
235       private void readObject (ObjectInputStream objectIn) throws IOException, ClassNotFoundException {
236          createCachedElement ((IDataElement) objectIn.readObject ());
237          objectIn.defaultReadObject ();
238       }
239    
240       private void writeObject (ObjectOutputStream objectOut) throws IOException {
241          objectOut.writeObject (getCachedElement ());
242          objectOut.defaultWriteObject ();
243       }
244    }