package textbender.a.u.transfer.clipboard; // Copyright 2007, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Textbender Software"), to deal in the Textbender Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Textbender Software, and to permit persons to whom the Textbender Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Textbender Software. THE TEXTBENDER SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE TEXTBENDER SOFTWARE OR THE USE OR OTHER DEALINGS IN THE TEXTBENDER SOFTWARE. import java.io.*; import java.util.*; import java.util.concurrent.atomic.*; import textbender.g.io.*; import textbender.g.lang.*; import textbender.g.util.logging.*; /** Writeable map of index blocks. */ @ThreadRestricted final class IndexBlockMapW extends IndexBlockMap { private static final long serialVersionUID = 0L; private IndexBlockMapW() {} /** The single instance of IndexBlockMapW. */ static IndexBlockMapW i() { return instanceA.get(); } private static final AtomicReference instanceA = new AtomicReference(); /** Creates the single instance of IndexBlockMapW, * and makes it available via {@linkplain #i() i}(). */ static IndexBlockMapW create() throws IOException { final IndexBlockMapW map = _createUnchecked(); if( !instanceA.compareAndSet( /*expect*/null, map )) throw new IllegalStateException(); return map; } static IndexBlockMapW _createUnchecked() throws IOException // FIX will move to IndexBlockMap, as init from persistant store { IndexBlockMapW map = null; // till read from persistent store if( PERSISTANT_STORE_FILE.isFile() ) { ObjectInputStream in = new ObjectInputStream ( new BufferedInputStream( new FileInputStream( PERSISTANT_STORE_FILE ))); try { map = (IndexBlockMapW)in.readObject(); } catch( RuntimeException xR ) { throw xR; } catch( Exception x ) { LoggerX.i(IndexBlockMapW.class).log( LoggerX.WARNING, /*message*/"", x ); } finally{ in.close(); } } if( map == null ) map = new IndexBlockMapW(); return map; } // ------------------------------------------------------------------------------------ /** Returns the index block for a particular {@linkplain IndexBlock#sourceDocumentFile() sourceDocumentFile}, * creating a new one if necessary. * To save file storage space (in the document cache, used by the blocks) * and to keep in-text clip indeces short, * blocks are reused across multiple versions of a document. * * @param transferandDocumentFile designating a copy * of the {@linkplain textbender.a.u.transfer.Transferand#documentFile() transferand source document} * @param geneCount of the document * @param keySD specifying other key particulars of the document */ IndexBlock getOrCreateIndexBlock( final File transferandDocumentFile, final int geneCount, final KeySD keySD, final ClipIndexEncoder clipIndexEncoder ) throws IOException { IndexBlock block = mapSD.get( keySD ); if( block != null && block.size() >= geneCount ) return block; // Create new index block. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - final long clipIndex0; if( list.size() == 0 ) clipIndex0 = 0L; else { IndexBlock blockLast = list.get( list.size() - 1 ); clipIndex0 = blockLast.clipIndex0() + blockLast.size(); } final File sourceDocumentFile = new File ( SourceDocumentFile.DIRECTORY, SourceDocumentFile.filename( clipIndex0, clipIndexEncoder ) ); FileX.createNewDirectory( SourceDocumentFile.DIRECTORY ); FileX.copyAs( sourceDocumentFile, transferandDocumentFile ); block = new IndexBlock // with extra size, allowing reuse across multiple versions of document (until it is reindexed, or its gene count exceeds block size) ( clipIndex0, /*size*/geneCount + geneCount/8 + 50, sourceDocumentFile ); list.add( block ); mapSD.put( keySD, block ); // Persist. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ObjectOutputStream out = new ObjectOutputStream ( new BufferedOutputStream( new FileOutputStream( PERSISTANT_STORE_FILE ))); try { out.writeObject( IndexBlockMapW.this ); // FIX to ensure write suceeds, before clobbering file contents // OPT, to write only the changed part, instead of re-writing entire map } finally{ out.close(); } return block; } // ==================================================================================== /** A key for index block lookup, * per {@linkplain #getOrCreateIndexBlock getOrCreateIndexBlock}. */ static final class KeySD implements Serializable // see mapSD { /** Constructs a KeySD. * * @param revisionLineID per {@linkplain SourceDocumentFile#revisionLineID() SourceDocumentFile} * @param gR per {@linkplain SourceDocumentFile#gR() SourceDocumentFile} */ KeySD( String revisionLineID, String gR ) { this.revisionLineID = revisionLineID; this.gR = gR; } // ------------------------------------------------------------------------------------ /** Re-index counter of the document, per {@linkplain SourceDocumentFile#gR() SourceDocumentFile}. */ String gR() { return gR; } private final String gR; /** Revision-line ID of the document, per {@linkplain SourceDocumentFile#gR() SourceDocumentFile}. */ String revisionLineID() { return revisionLineID; } private final String revisionLineID; // - O b j e c t ---------------------------------------------------------------------- public @Override boolean equals( Object o ) { if( o == null || !getClass().equals( o.getClass() )) return false; KeySD other = (KeySD)o; return gR.equals( other.gR ) && revisionLineID.equals( other.revisionLineID ); } public @Override int hashCode() { return revisionLineID.hashCode() * 31 // preventing overlap due to addition of following + gR.hashCode(); } } //// P r i v a t e /////////////////////////////////////////////////////////////////////// /** Map of index blocks keyed by source document (KeySD). * Used to locate the latest (largest) index block * for a particular version of a source document, * for reuse (per {@linkplain #getOrCreateIndexBlock getOrCreateIndexBlock}). * A source document may have multiple blocks, * but only the last created (hence largest) block is mapped, and reused. */ private final HashMap mapSD = new HashMap(); }