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 org.w3c.dom.*; import org.w3c.dom.ls.*; import textbender._.*; import textbender.d.gene.*; import textbender.g.lang.*; import textbender.g.util.*; import textbender.g.xml.dom.*; import static textbender._.Textbender.TEXTBENDER_NAMESPACE; /** Maps between clip indeces and g-indeces. */ @ThreadRestricted final class IndexBlock implements Comparable, Serializable { private static final long serialVersionUID = 0L; /** Constructs an IndexBlock. * * @param clipIndex0 per {@linkplain #clipIndex0() clipIndex0}() * @param size per {@linkplain #size() size}() * @param sourceDocumentFile per {@linkplain #sourceDocumentFile() sourceDocumentFile}() */ IndexBlock( long clipIndex0, int size, File sourceDocumentFile ) { this.clipIndex0 = clipIndex0; this.size = size; this.sourceDocumentFile = sourceDocumentFile; } // ------------------------------------------------------------------------------------ /** The leading clip-index of this block. */ long clipIndex0() { return clipIndex0; } private final long clipIndex0; /** @param gIndex of a gene of the {@linkplain #sourceDocumentFile() document} @return clipIndex to which that gene is mapped */ long clipIndexOf( int gIndex ) { if( gIndex >= size ) throw new IndexOutOfBoundsException( Integer.toString( gIndex )); return clipIndex0 + gIndex; } /** @param clipIndex of a clip gene * @return gIndex of the corresponding source gene * in the {@linkplain #sourceDocumentFile() document} */ int gIndexOf( final long clipIndex ) { long gIndexL = clipIndex - clipIndex0; if( gIndexL < 0 || gIndexL >= size ) throw new IndexOutOfBoundsException( Long.toString( clipIndex )); return (int)gIndexL; } /** Capacity of this block, in terms of unique clip indeces it can map. * The size is at least as large * as the {@linkplain #sourceDocumentFile() document}'s gene count. */ int size() { return size; } private final int size; /** Cached copy of the transfer source document. *

* Since index blocks are * {@linkplain IndexBlockMapW#getOrCreateIndexBlock used for multiple soure document versions}, * the source document may include changes made since the clip-gene * was originally created, when the transfer was initiated. * Using it to complete the transfer (carrying meta-data over to the target, etc.) * may lead to postive ancestry errors (over-attribution), * and the appearance that transfer occured from the later version * of the soure. *

*

* This anachronism could be corrected at the cost of * increased storage space, and longer clip indeces in the text; * or of more complicated code. But its consequences are mild. * Usually the anomaly will be rare, especially if replacement is not over-delayed. * However, when a transfer is made from an older version of a document, * while a newer one is still in the cache, even immediate replacement will not help. *

*/ File sourceDocumentFile() { return sourceDocumentFile; } private final File sourceDocumentFile; /** Returns parser results for the {@linkplain #sourceDocumentFile() document}, * and caches them for later calls. * * @param domLS DOM implementation to use * @param parser parser to use * @param b string builder to use, overwriting its existing content * @param uuidStringifier UUID stringifier to use * * @return parser results, or null if the last call resulted in an exception * * @throws TextbenderException if document cannot be parsed */ SourceParse sourceParse( final DOMImplementationLS domLS, final LSParser parser, final StringBuilder b, final UUIDStringifier uuidStringifier ) throws IOException, TextbenderException { if( sourceParse != null ) return sourceParse; Document document = null; // till read { final StringWriter errorStringWriter = new StringWriter(); final PrintWriter errorPrintWriter = new PrintWriter( errorStringWriter ); final DOMErrorHandlerPW errorHandler = new DOMErrorHandlerPW( errorPrintWriter ); TextbenderException xParse = null; // thus far final DOMConfiguration domConfig = parser.getDomConfig(); domConfig.setParameter( "error-handler", errorHandler ); // domConfig.setParameter( "validate", true ); domConfig.setParameter ( "resource-resolver", // new textbender.d.gene.xhtml.RecombinantXHTML.DOMResourceResolver( domLS ) new textbender.d.gene.xhtml.RecombinantXHTML.DOMResourceResolverMin( domLS ) ); final LSInput lsInput = domLS.createLSInput(); final InputStream in = new BufferedInputStream ( new FileInputStream( sourceDocumentFile )); try { lsInput.setByteStream( in ); document = parser.parse( lsInput ); } catch( LSException x ) { xParse = new TextbenderException( x ); } // fatal parse exception, will also be reported to the errorHandler, though what follows does not depend on it finally{ in.close(); } if( errorHandler.count() > 0 ) { errorPrintWriter.flush(); xParse = new TextbenderException ( "Unable to parse original source. Reading file:" + "\n " + sourceDocumentFile + "\n" + "Errors/warnings from parser: " + Integer.toString( errorHandler.count() ) + "\n" + errorStringWriter.toString() ); } if( xParse != null ) throw xParse; } sourceParse = new SourceParse( document, b ); return sourceParse; } private transient SourceParse sourceParse; // - C o m p a r a b l e -------------------------------------------------------------- /** Compares based on {@linkplain #clipIndex0 clipIndex0}. * * @see #equals(Object) */ public int compareTo( IndexBlock other ) { return Long.signum( clipIndex0 - other.clipIndex0 ); } // - O b j e c t ---------------------------------------------------------------------- /** Returns true iff o is an index block * with the same {@linkplain #clipIndex0 clipIndex0}. * * @see #compareTo(IndexBlock) */ public @Override boolean equals( Object o ) { if( !(o instanceof IndexBlock )) return false; IndexBlock that = (IndexBlock)o; return this.clipIndex0 == that.clipIndex0; } // ==================================================================================== /** Parser results... */ static class SourceParse { SourceParse( final Document document, final StringBuilder b ) throws TextbenderException { final Element metaData = DocumentRT.findMetaData( document ); if( metaData == null ) throw new TextbenderException( DocumentRT.FIND_META_DATA_FAILURE_MESSAGE ); gg = Gene.ensureGG( metaData, b ); gList = new ElementListNL( gg.getElementsByTagNameNS( TEXTBENDER_NAMESPACE, "g" )); geneList = Arrays.asList( new Element[gList.size()] ); initRecursively( document ); } private void initRecursively( final Node node ) { if( node instanceof Element ) { final Element element = (Element)node; int gIndex = Gene.gIndexOf( element ); if( gIndex >= 0 ) geneList.set( gIndex, element ); } for( Node child = node.getFirstChild(); child != null; child = child.getNextSibling() ) { initRecursively( child ); } } final List geneList; final Element gg; final List gList; } }