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 }