DocumentImpl.java
0001 /*
0002  *  DocumentImpl.java
0003  *
0004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
0005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0006  *
0007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0008  *  software, licenced under the GNU Library General Public License,
0009  *  Version 2, June 1991 (in the distribution as file licence.html,
0010  *  and also available at http://gate.ac.uk/gate/licence.html).
0011  *
0012  *  Hamish Cunningham, 11/Feb/2000
0013  *
0014  *  $Id: DocumentImpl.java 19676 2016-10-14 05:51:04Z markagreenwood $
0015  */
0016 package gate.corpora;
0017 
0018 import gate.Annotation;
0019 import gate.AnnotationSet;
0020 import gate.DataStore;
0021 import gate.DocumentContent;
0022 import gate.DocumentFormat;
0023 import gate.Factory;
0024 import gate.FeatureMap;
0025 import gate.Gate;
0026 import gate.GateConstants;
0027 import gate.Node;
0028 import gate.Resource;
0029 import gate.TextualDocument;
0030 import gate.annotation.AnnotationSetImpl;
0031 import gate.creole.AbstractLanguageResource;
0032 import gate.creole.ResourceInstantiationException;
0033 import gate.creole.metadata.CreoleParameter;
0034 import gate.creole.metadata.CreoleResource;
0035 import gate.creole.metadata.Optional;
0036 import gate.event.CreoleEvent;
0037 import gate.event.CreoleListener;
0038 import gate.event.DatastoreEvent;
0039 import gate.event.DatastoreListener;
0040 import gate.event.DocumentEvent;
0041 import gate.event.DocumentListener;
0042 import gate.event.StatusListener;
0043 import gate.util.DocumentFormatException;
0044 import gate.util.Err;
0045 import gate.util.GateRuntimeException;
0046 import gate.util.InvalidOffsetException;
0047 import gate.util.OptionsMap;
0048 import gate.util.Out;
0049 import gate.util.SimpleFeatureMapImpl;
0050 import gate.util.Strings;
0051 
0052 import java.io.IOException;
0053 import java.net.URL;
0054 import java.util.ArrayList;
0055 import java.util.Collection;
0056 import java.util.Collections;
0057 import java.util.Comparator;
0058 import java.util.HashMap;
0059 import java.util.Iterator;
0060 import java.util.LinkedList;
0061 import java.util.List;
0062 import java.util.Map;
0063 import java.util.Set;
0064 import java.util.SortedMap;
0065 import java.util.Stack;
0066 import java.util.TreeMap;
0067 import java.util.TreeSet;
0068 import java.util.Vector;
0069 
0070 /**
0071  * Represents the commonalities between all sorts of documents.
0072  
0073  <H2>Editing</H2>
0074  
0075  <P>
0076  * The DocumentImpl class implements the Document interface. The
0077  * DocumentContentImpl class models the textual or audio-visual materials which
0078  * are the source and content of Documents. The AnnotationSetImpl class supplies
0079  * annotations on Documents.
0080  
0081  <P>
0082  * Abbreviations:
0083  
0084  <UL>
0085  <LI> DC = DocumentContent
0086  <LI> D = Document
0087  <LI> AS = AnnotationSet
0088  </UL>
0089  
0090  <P>
0091  * We add an edit method to each of these classes; for DC and AS the methods are
0092  * package private; D has the public method.
0093  
0094  <PRE>
0095  
0096  * void edit(Long start, Long end, DocumentContent replacement) throws
0097  * InvalidOffsetException;
0098  
0099  </PRE>
0100  
0101  <P>
0102  * D receives edit requests and forwards them to DC and AS. On DC, this method
0103  * makes a change to the content - e.g. replacing a String range from start to
0104  * end with replacement. (Deletions are catered for by having replacement =
0105  * null.) D then calls AS.edit on each of its annotation sets.
0106  
0107  <P>
0108  * On AS, edit calls replacement.size() (i.e. DC.size()) to figure out how long
0109  * the replacement is (0 for null). It then considers annotations that terminate
0110  * (start or end) in the altered or deleted range as invalid; annotations that
0111  * terminate after the range have their offsets adjusted. I.e.:
0112  <UL>
0113  <LI> the nodes that pointed inside the old modified area are invalid now and
0114  * will be deleted along with the connected annotations;
0115  <LI> the nodes that are before the start of the modified area remain
0116  * untouched;
0117  <LI> the nodes that are after the end of the affected area will have the
0118  * offset changed according to the formula below.
0119  </UL>
0120  
0121  <P>
0122  * A note re. AS and annotations: annotations no longer have offsets as in the
0123  * old model, they now have nodes, and nodes have offsets.
0124  
0125  <P>
0126  * To implement AS.edit, we have several indices:
0127  
0128  <PRE>
0129  
0130  * HashMap annotsByStartNode, annotsByEndNode;
0131  
0132  </PRE>
0133  
0134  * which map node ids to annotations;
0135  
0136  <PRE>
0137  
0138  * RBTreeMap nodesByOffset;
0139  
0140  </PRE>
0141  
0142  * which maps offset to Nodes.
0143  
0144  <P>
0145  * When we get an edit request, we traverse that part of the nodesByOffset tree
0146  * representing the altered or deleted range of the DC. For each node found, we
0147  * delete any annotations that terminate on the node, and then delete the node
0148  * itself. We then traverse the rest of the tree, changing the offset on all
0149  * remaining nodes by:
0150  
0151  <PRE>
0152  
0153  * newOffset = oldOffset - ( (end - start) - // size of mod ( (replacement ==
0154  * null) ? 0 : replacement.size() ) // size of repl );
0155  
0156  </PRE>
0157  
0158  * Note that we use the same convention as e.g. java.lang.String: start offsets
0159  * are inclusive; end offsets are exclusive. I.e. for string "abcd" range 1-3 =
0160  * "bc". Examples, for a node with offset 4:
0161  
0162  <PRE>
0163  
0164  * edit(1, 3, "BC"); newOffset = 4 - ( (3 - 1) - 2 ) = 4
0165  
0166  * edit(1, 3, null); newOffset = 4 - ( (3 - 1) - 0 ) = 2
0167  
0168  * edit(1, 3, "BBCC"); newOffset = 4 - ( (3 - 1) - 4 ) = 6
0169  
0170  </PRE>
0171  */
0172 @CreoleResource(name = "GATE Document", interfaceName = "gate.Document",
0173     comment = "GATE transient document.", icon = "document",
0174     helpURL = "http://gate.ac.uk/userguide/sec:developer:documents")
0175 public class DocumentImpl extends AbstractLanguageResource implements
0176                                                           TextualDocument,
0177                                                           CreoleListener,
0178                                                           DatastoreListener {
0179   /** Debug flag */
0180   private static final boolean DEBUG = false;
0181 
0182   /**
0183    * If you set this flag to true the original content of the document will be
0184    * kept in the document feature. <br>
0185    * Default value is false to avoid the unnecessary waste of memory
0186    */
0187   private Boolean preserveOriginalContent = new Boolean(false);
0188 
0189   /**
0190    * If you set this flag to true the repositioning information for the document
0191    * will be kept in the document feature. <br>
0192    * Default value is false to avoid the unnecessary waste of time and memory
0193    */
0194   private Boolean collectRepositioningInfo = new Boolean(false);
0195 
0196   /**
0197    * This is a variable which contains the latest crossed over annotation found
0198    * during export with preserving format, i.e., toXml(annotations) method.
0199    */
0200   private Annotation crossedOverAnnotation = null;
0201 
0202   
0203   /** Flag to determine whether to serialize namespace information held as
0204    *  annotation features into namespace prefix and URI in the XML
0205    */
0206   private boolean serializeNamespaceInfo = false;
0207   /** Feature name used for namespace uri in namespaced elements */
0208   private String namespaceURIFeature = null;
0209   /** Feature name used for namespace prefix in namespaced elements */
0210   private String namespacePrefixFeature = null;
0211 
0212   
0213   /** Default construction. Content left empty. */
0214   public DocumentImpl() {
0215     content = new DocumentContentImpl();
0216     stringContent = "";
0217 
0218     /** We will attempt to serialize namespace if
0219      *  three parameters are set in the global or local config file:
0220      *  ADD_NAMESPACE_FEATURES: boolean flag
0221      *  ELEMENT_NAMESPACE_URI: feature name used to hold namespace URI
0222      *  ELEMENT_NAMESPACE_PREFIX: feature name used to hold namespace prefix
0223      */
0224     OptionsMap configData = Gate.getUserConfig();
0225 
0226     boolean addNSFeature = Boolean.parseBoolean((String)configData.get(GateConstants.ADD_NAMESPACE_FEATURES));
0227     namespaceURIFeature = (StringconfigData.get(GateConstants.ELEMENT_NAMESPACE_URI);
0228     namespacePrefixFeature = (StringconfigData.get(GateConstants.ELEMENT_NAMESPACE_PREFIX);
0229 
0230     serializeNamespaceInfo = (addNSFeature && namespacePrefixFeature != null && !namespacePrefixFeature.isEmpty() && namespaceURIFeature != null && !namespaceURIFeature.isEmpty());
0231 
0232   // default construction
0233 
0234   /** Cover unpredictable Features creation */
0235   @Override
0236   public FeatureMap getFeatures() {
0237     if(features == null) {
0238       features = new SimpleFeatureMapImpl();
0239     }
0240     return features;
0241   }
0242 
0243   /** Initialise this resource, and return it. */
0244   @Override
0245   public Resource init() throws ResourceInstantiationException {
0246     // set up the source URL and create the content
0247     if(sourceUrl == null) {
0248       if(stringContent == null) { throw new ResourceInstantiationException(
0249               "The sourceURL and document's content were null.")}
0250       content = new DocumentContentImpl(stringContent);
0251       getFeatures().put("gate.SourceURL""created from String");
0252     else {
0253       //try {
0254         //content = new DocumentContentImpl(sourceUrl, getEncoding(),
0255         //        sourceUrlStartOffset, sourceUrlEndOffset);
0256         content = null;
0257         getFeatures().put("gate.SourceURL", sourceUrl.toExternalForm());
0258       //} catch(IOException e) {
0259       //  throw new ResourceInstantiationException("DocumentImpl.init: " + e);
0260       //}
0261     }
0262     if(preserveOriginalContent.booleanValue() && content != null) {
0263       String originalContent = new String(((DocumentContentImpl)content)
0264               .getOriginalContent());
0265       getFeatures().put(GateConstants.ORIGINAL_DOCUMENT_CONTENT_FEATURE_NAME,
0266               originalContent);
0267     // if
0268     // set up a DocumentFormat if markup unpacking required
0269     if(getMarkupAware().booleanValue()) {
0270       DocumentFormat docFormat = null;
0271       // if a specific MIME type has been given, use it
0272       if(this.mimeType != null && this.mimeType.length() 0) {
0273         MimeType theType = DocumentFormat.getMimeTypeForString(mimeType);
0274         if(theType == null) {
0275           throw new ResourceInstantiationException("MIME type \""
0276               this.mimeType + " has no registered DocumentFormat");
0277         }
0278 
0279         docFormat = DocumentFormat.getDocumentFormat(this, theType);
0280       }
0281       else {
0282         docFormat = DocumentFormat.getDocumentFormat(this, sourceUrl);
0283       }
0284       try {
0285         if(docFormat != null) {
0286           StatusListener sListener = (StatusListener)gate.Gate
0287                   .getListeners().get("gate.event.StatusListener");
0288           if(sListener != nulldocFormat.addStatusListener(sListener);
0289           // set the flag if true and if the document format support collecting
0290           docFormat.setShouldCollectRepositioning(collectRepositioningInfo);
0291           if(docFormat.getShouldCollectRepositioning().booleanValue()) {
0292             // unpack with collectiong of repositioning information
0293             RepositioningInfo info = new RepositioningInfo();
0294             String origContent = (String)getFeatures().get(
0295                     GateConstants.ORIGINAL_DOCUMENT_CONTENT_FEATURE_NAME);
0296             RepositioningInfo ampCodingInfo = new RepositioningInfo();
0297             if(origContent != null) {
0298               boolean shouldCorrectCR = docFormat instanceof XmlDocumentFormat;
0299               collectInformationForAmpCodding(origContent, ampCodingInfo,
0300                       shouldCorrectCR);
0301               if(docFormat.getMimeType().equals(new MimeType("text","html"))) {
0302                 collectInformationForWS(origContent, ampCodingInfo);
0303               // if
0304             // if
0305             docFormat.unpackMarkup(this, info, ampCodingInfo);
0306             if(origContent != null && docFormat instanceof XmlDocumentFormat) {
0307               // CRLF correction of RepositioningInfo
0308               correctRepositioningForCRLFInXML(origContent, info);
0309             // if
0310             getFeatures().put(
0311                     GateConstants.DOCUMENT_REPOSITIONING_INFO_FEATURE_NAME,
0312                     info);
0313           else {
0314             // normal old fashioned unpack
0315             docFormat.unpackMarkup(this);
0316           }
0317           docFormat.removeStatusListener(sListener);
0318         // if format != null
0319       catch(DocumentFormatException e) {
0320         throw new ResourceInstantiationException(
0321                 "Couldn't unpack markup in document "
0322                         (sourceUrl != null ? sourceUrl.toExternalForm() "")
0323                         "!", e);
0324       }
0325     // if markup aware
0326     // try{
0327     // FileWriter fw = new FileWriter("d:/temp/doccontent.txt");
0328     // fw.write(getContent().toString());
0329     // fw.flush();
0330     // fw.close();
0331     // }catch(IOException ioe){
0332     // ioe.printStackTrace();
0333     // }
0334     return this;
0335   // init()
0336 
0337   /**
0338    * Correct repositioning information for substitution of "\r\n" with "\n"
0339    */
0340   private void correctRepositioningForCRLFInXML(String content,
0341           RepositioningInfo info) {
0342     int index = -1;
0343     do {
0344       index = content.indexOf("\r\n", index + 1);
0345       if(index != -1) {
0346         info.correctInformationOriginalMove(index, 1);
0347       // if
0348     while(index != -1);
0349   // correctRepositioningForCRLF
0350 
0351   /**
0352    * Collect information for substitution of "&xxx;" with "y"
0353    
0354    * It couldn't be collected a position information about some unicode and
0355    * &-coded symbols during parsing. The parser "hide" the information about the
0356    * position of such kind of parsed text. So, there is minimal chance to have
0357    * &-coded symbol inside the covered by repositioning records area. The new
0358    * record should be created for every coded symbol outside the existing
0359    * records. <BR>
0360    * If <code>shouldCorrectCR</code> flag is <code>true</code> the
0361    * correction for CRLF substitution is performed.
0362    */
0363   private void collectInformationForAmpCodding(String content,
0364           RepositioningInfo info, boolean shouldCorrectCR) {
0365     if(content == null || info == nullreturn;
0366     int ampIndex = -1;
0367     int semiIndex;
0368     do {
0369       ampIndex = content.indexOf('&', ampIndex + 1);
0370       if(ampIndex != -1) {
0371         semiIndex = content.indexOf(';', ampIndex + 1);
0372         // have semicolon and it is near enough for amp codding
0373         if(semiIndex != -&& (semiIndex - ampIndex8) {
0374           info.addPositionInfo(ampIndex, semiIndex - ampIndex + 101);
0375         else {
0376           // no semicolon or it is too far
0377           // analyse for amp codding without semicolon
0378           int maxEnd = Math.min(ampIndex + 8, content.length());
0379           String ampCandidate = content.substring(ampIndex, maxEnd);
0380           int ampCodingSize = analyseAmpCodding(ampCandidate);
0381           if(ampCodingSize != -1) {
0382             info.addPositionInfo(ampIndex, ampCodingSize, 01);
0383           // if
0384         // if - semicolon found
0385       // if - ampersand found
0386     while(ampIndex != -1);
0387     // correct the collected information to adjust it's positions
0388     // with reported by the parser
0389     int index = -1;
0390     if(shouldCorrectCR) {
0391       do {
0392         index = content.indexOf("\r\n", index + 1);
0393         if(index != -1) {
0394           info.correctInformationOriginalMove(index, -1);
0395         // if
0396       while(index != -1);
0397     // if
0398   // collectInformationForAmpCodding
0399 
0400   /**
0401    * This function compute size of the ampersand codded sequence when semicolin
0402    * is not present.
0403    */
0404   private int analyseAmpCodding(String content) {
0405     int result = -1;
0406     try {
0407       char ch = content.charAt(1);
0408       switch(ch){
0409         case 'l'// &lt
0410         case 'L'// &lt
0411           if(content.charAt(2== 't' || content.charAt(2== 'T') {
0412             result = 3;
0413           // if
0414           break;
0415         case 'g'// &gt
0416         case 'G'// &gt
0417           if(content.charAt(2== 't' || content.charAt(2== 'T') {
0418             result = 3;
0419           // if
0420           break;
0421         case 'a'// &amp
0422         case 'A'// &amp
0423           if(content.substring(24).equalsIgnoreCase("mp")) {
0424             result = 4;
0425           // if
0426           break;
0427         case 'q'// &quot
0428         case 'Q'// &quot
0429           if(content.substring(25).equalsIgnoreCase("uot")) {
0430             result = 5;
0431           // if
0432           break;
0433         case '#'// #number (example &#145, &#x4C38)
0434           int endIndex = 2;
0435           boolean hexCoded = false;
0436           if(content.charAt(2== 'x' || content.charAt(2== 'X') {
0437             // Hex codding
0438             ++endIndex;
0439             hexCoded = true;
0440           // if
0441           while(endIndex < && isNumber(content.charAt(endIndex), hexCoded)) {
0442             ++endIndex;
0443           // while
0444           result = endIndex;
0445           break;
0446       // switch
0447     catch(StringIndexOutOfBoundsException ex) {
0448       // do nothing
0449     // catch
0450     return result;
0451   // analyseAmpCodding
0452 
0453   /** Check for numeric range. If hex is true the A..F range is included */
0454   private boolean isNumber(char ch, boolean hex) {
0455     if(ch >= '0' && ch <= '9'return true;
0456     if(hex) {
0457       if(ch >= 'A' && ch <= 'F'return true;
0458       if(ch >= 'a' && ch <= 'f'return true;
0459     // if
0460     return false;
0461   // isNumber
0462 
0463   /**
0464    * HTML parser perform substitution of multiple whitespaces (WS) with a single
0465    * WS. To create correct repositioning information structure we should keep
0466    * the information for such multiple WS. <BR>
0467    * The criteria for WS is <code>(ch <= ' ')</code>.
0468    */
0469   private void collectInformationForWS(String content, RepositioningInfo info) {
0470     if(content == null || info == nullreturn;
0471     // analyse the content and correct the repositioning information
0472     char ch;
0473     int startWS, endWS;
0474     startWS = endWS = -1;
0475     int contentLength = content.length();
0476     for(int i = 0; i < contentLength; ++i) {
0477       ch = content.charAt(i);
0478       // is whitespace
0479       if(ch <= ' ') {
0480         if(startWS == -1) {
0481           startWS = i;
0482         // if
0483         endWS = i;
0484       else {
0485         if(endWS - startWS > 0) {
0486           // put the repositioning information about the WS substitution
0487           info
0488                   .addPositionInfo(startWS, (endWS - startWS + 1),
0489                           01);
0490         // if
0491         // clear positions
0492         startWS = endWS = -1;
0493       }// if
0494     // for
0495   // collectInformationForWS
0496 
0497   /** Clear all the data members of the object. */
0498   @Override
0499   public void cleanup() {
0500     defaultAnnots = null;
0501     if((namedAnnotSets != null&& (!namedAnnotSets.isEmpty()))
0502       namedAnnotSets.clear();
0503     if(DEBUGOut.prln("Document cleanup called");
0504     if(this.lrPersistentId != null)
0505       Gate.getCreoleRegister().removeCreoleListener(this);
0506     if(this.getDataStore() != null)
0507       this.getDataStore().removeDatastoreListener(this);
0508   // cleanup()
0509 
0510 
0511   /** Get the specific MIME type for this document, if set */
0512   public String getMimeType() {
0513     return mimeType;
0514   }
0515   
0516   /** Set the specific MIME type for this document */
0517   @Optional
0518   @CreoleParameter(
0519       comment = "MIME type of the document.  If unspecified it will be "
0520             "inferred from the file extension, etc.")
0521   public void setMimeType(String newMimeType) {
0522     this.mimeType = newMimeType;
0523   }
0524   
0525   /** Documents are identified by URLs */
0526   @Override
0527   public URL getSourceUrl() {
0528     return sourceUrl;
0529   }
0530 
0531   /** Set method for the document's URL */
0532   @Override
0533   @CreoleParameter(disjunction = "source", priority = 1, comment = "Source URL",
0534       suffixes = "txt;text;xml;xhtm;xhtml;html;htm;sgml;sgm;mail;email;eml;rtf;pdf;doc;ppt;pptx;docx;xls;xlsx;ods;odt;odp;iob;conll")
0535   public void setSourceUrl(URL sourceUrl) {
0536     this.sourceUrl = sourceUrl;
0537   // setSourceUrl
0538 
0539   /**
0540    * Documents may be packed within files; in this case an optional pair of
0541    * offsets refer to the location of the document.
0542    */
0543   @Override
0544   public Long[] getSourceUrlOffsets() {
0545     Long[] sourceUrlOffsets = new Long[2];
0546     sourceUrlOffsets[0= sourceUrlStartOffset;
0547     sourceUrlOffsets[1= sourceUrlEndOffset;
0548     return sourceUrlOffsets;
0549   // getSourceUrlOffsets
0550 
0551   /**
0552    * Allow/disallow preserving of the original document content. If is <B>true</B>
0553    * the original content will be retrieved from the DocumentContent object and
0554    * preserved as document feature.
0555    */
0556   @Override
0557   @CreoleParameter(comment = "Should the document preserve the original content?",
0558       defaultValue = "false")
0559   public void setPreserveOriginalContent(Boolean b) {
0560     preserveOriginalContent = b;
0561   // setPreserveOriginalContent
0562 
0563   /**
0564    * Get the preserving of content status of the Document.
0565    
0566    @return whether the Document should preserve it's original content.
0567    */
0568   @Override
0569   public Boolean getPreserveOriginalContent() {
0570     return preserveOriginalContent;
0571   // getPreserveOriginalContent
0572 
0573   /**
0574    * Allow/disallow collecting of repositioning information. If is <B>true</B>
0575    * information will be retrieved and preserved as document feature.<BR>
0576    * Preserving of repositioning information give the possibilities for
0577    * converting of coordinates between the original document content and
0578    * extracted from the document text.
0579    */
0580   @Override
0581   @CreoleParameter(defaultValue = "false",
0582       comment = "Should the document collect repositioning information")
0583   public void setCollectRepositioningInfo(Boolean b) {
0584     collectRepositioningInfo = b;
0585   // setCollectRepositioningInfo
0586 
0587   /**
0588    * Get the collectiong and preserving of repositioning information for the
0589    * Document. <BR>
0590    * Preserving of repositioning information give the possibilities for
0591    * converting of coordinates between the original document content and
0592    * extracted from the document text.
0593    
0594    @return whether the Document should collect and preserve information.
0595    */
0596   @Override
0597   public Boolean getCollectRepositioningInfo() {
0598     return collectRepositioningInfo;
0599   // getCollectRepositioningInfo
0600 
0601   /**
0602    * Documents may be packed within files; in this case an optional pair of
0603    * offsets refer to the location of the document. This method gets the start
0604    * offset.
0605    */
0606   @Override
0607   public Long getSourceUrlStartOffset() {
0608     return sourceUrlStartOffset;
0609   }
0610 
0611   /**
0612    * Documents may be packed within files; in this case an optional pair of
0613    * offsets refer to the location of the document. This method sets the start
0614    * offset.
0615    */
0616   @Override
0617   @Optional
0618   @CreoleParameter(
0619       comment = "Start offset for documents based on ranges")
0620   public void setSourceUrlStartOffset(Long sourceUrlStartOffset) {
0621     this.sourceUrlStartOffset = sourceUrlStartOffset;
0622   // setSourceUrlStartOffset
0623 
0624   /**
0625    * Documents may be packed within files; in this case an optional pair of
0626    * offsets refer to the location of the document. This method gets the end
0627    * offset.
0628    */
0629   @Override
0630   public Long getSourceUrlEndOffset() {
0631     return sourceUrlEndOffset;
0632   }
0633 
0634   /**
0635    * Documents may be packed within files; in this case an optional pair of
0636    * offsets refer to the location of the document. This method sets the end
0637    * offset.
0638    */
0639   @Override
0640   @Optional
0641   @CreoleParameter(
0642       comment = "End offset for documents based on ranges")
0643   public void setSourceUrlEndOffset(Long sourceUrlEndOffset) {
0644     this.sourceUrlEndOffset = sourceUrlEndOffset;
0645   // setSourceUrlStartOffset
0646 
0647   /** The content of the document: a String for text; MPEG for video; etc. */
0648   @Override
0649   public DocumentContent getContent() {
0650     if (content == null) {
0651       if (sourceUrl != null) {
0652         try {
0653           content = new DocumentContentImpl(sourceUrl, getEncoding(), sourceUrlStartOffset, sourceUrlEndOffset);
0654           if(preserveOriginalContent.booleanValue()) {
0655             String originalContent = new String(((DocumentContentImpl)content)
0656                     .getOriginalContent());
0657             getFeatures().put(GateConstants.ORIGINAL_DOCUMENT_CONTENT_FEATURE_NAME,
0658                     originalContent);
0659           }
0660         catch(IOException e) {
0661           // TODO Auto-generated catch block
0662           e.printStackTrace();
0663         }
0664       }
0665       else {
0666         content = new DocumentContentImpl("");
0667       }
0668     }
0669     return content;
0670   }
0671 
0672   /** Set method for the document content */
0673   @Override
0674   public void setContent(DocumentContent content) {
0675     this.content = content;
0676     // stringContent is a parameter, not a normal field, and
0677     // should not be overwritten here.
0678     //this.stringContent = content.toString();
0679   }
0680 
0681   /** Get the encoding of the document content source */
0682   @Override
0683   public String getEncoding() {
0684     // we need to make sure we ALWAYS have an encoding
0685     if(encoding == null || encoding.trim().length() == 0) {
0686       // no encoding definded: use the platform default
0687       encoding = java.nio.charset.Charset.forName(
0688               System.getProperty("file.encoding")).name();
0689     }
0690     return encoding;
0691   }
0692 
0693   /** Set the encoding of the document content source */
0694   @Optional
0695   @CreoleParameter(comment = "Encoding")
0696   public void setEncoding(String encoding) {
0697     this.encoding = encoding;
0698   }
0699 
0700   /**
0701    * Get the default set of annotations. The set is created if it doesn't exist
0702    * yet.
0703    */
0704   @Override
0705   public AnnotationSet getAnnotations() {
0706     if(defaultAnnots == null) {
0707       defaultAnnots = new AnnotationSetImpl(this);
0708       fireAnnotationSetAdded(new DocumentEvent(this,
0709               DocumentEvent.ANNOTATION_SET_ADDED, null));
0710     }// if
0711     return defaultAnnots;
0712   // getAnnotations()
0713 
0714   /**
0715    * Get a named set of annotations. Creates a new set if one with this name
0716    * doesn't exist yet. If the provided name is null or the empty string then
0717    * it returns the default annotation set.
0718    */
0719   @Override
0720   public AnnotationSet getAnnotations(String name) {
0721     if(name == null || "".equals(name)) return getAnnotations();
0722     if(namedAnnotSets == null) {
0723       namedAnnotSets = new HashMap<String, AnnotationSet>();
0724     }
0725     AnnotationSet namedSet = namedAnnotSets.get(name);
0726     if(namedSet == null) {
0727       namedSet = new AnnotationSetImpl(this, name);
0728       namedAnnotSets.put(name, namedSet);
0729       DocumentEvent evt = new DocumentEvent(this,
0730               DocumentEvent.ANNOTATION_SET_ADDED, name);
0731       fireAnnotationSetAdded(evt);
0732     }
0733     return namedSet;
0734   // getAnnotations(name)
0735 
0736   /**
0737    * Make the document markup-aware. This will trigger the creation of a
0738    * DocumentFormat object at Document initialisation time; the DocumentFormat
0739    * object will unpack the markup in the Document and add it as annotations.
0740    * Documents are <B>not</B> markup-aware by default.
0741    
0742    @param newMarkupAware
0743    *          markup awareness status.
0744    */
0745   @Override
0746   @CreoleParameter(defaultValue = "true",
0747       comment = "Should the document read the original markup?")
0748   public void setMarkupAware(Boolean newMarkupAware) {
0749     this.markupAware = newMarkupAware;
0750   }
0751 
0752   /**
0753    * Get the markup awareness status of the Document. <B>Documents are
0754    * markup-aware by default.</B>
0755    
0756    @return whether the Document is markup aware.
0757    */
0758   @Override
0759   public Boolean getMarkupAware() {
0760     return markupAware;
0761   }
0762 
0763   /**
0764    * Returns an XML document aming to preserve the original markups( the
0765    * original markup will be in the same place and format as it was before
0766    * processing the document) and include (if possible) the annotations
0767    * specified in the aSourceAnnotationSet. It is equivalent to
0768    * toXml(aSourceAnnotationSet, true).
0769    */
0770   @Override
0771   public String toXml(Set<Annotation> aSourceAnnotationSet) {
0772     return toXml(aSourceAnnotationSet, true);
0773   }
0774 
0775   /**
0776    * Returns an XML document aming to preserve the original markups( the
0777    * original markup will be in the same place and format as it was before
0778    * processing the document) and include (if possible) the annotations
0779    * specified in the aSourceAnnotationSet. <b>Warning:</b> Annotations from
0780    * the aSourceAnnotationSet will be lost if they will cause a crosed over
0781    * situation.
0782    
0783    @param aSourceAnnotationSet
0784    *          is an annotation set containing all the annotations that will be
0785    *          combined with the original marup set. If the param is
0786    *          <code>null</code> it will only dump the original markups.
0787    @param includeFeatures
0788    *          is a boolean that controls whether the annotation features should
0789    *          be included or not. If false, only the annotation type is included
0790    *          in the tag.
0791    @return a string representing an XML document containing the original
0792    *         markup + dumped annotations form the aSourceAnnotationSet
0793    */
0794   @Override
0795   @SuppressWarnings("unused")
0796   public String toXml(Set<Annotation> aSourceAnnotationSet, boolean includeFeatures) {
0797     if(hasOriginalContentFeatures()) { return saveAnnotationSetAsXmlInOrig(
0798             aSourceAnnotationSet, includeFeatures)// if
0799     AnnotationSet originalMarkupsAnnotSet = this
0800             .getAnnotations(GateConstants.ORIGINAL_MARKUPS_ANNOT_SET_NAME);
0801     // Create a dumping annotation set on the document. It will be used for
0802     // dumping annotations...
0803     // AnnotationSet dumpingSet = new AnnotationSetImpl((Document) this);
0804     List<Annotation> dumpingList = new ArrayList<Annotation>(originalMarkupsAnnotSet.size());
0805     // This set will be constructed inside this method. If is not empty, the
0806     // annotation contained will be lost.
0807     /*
0808      * if (!dumpingSet.isEmpty()){ Out.prln("WARNING: The dumping annotation set
0809      * was not empty."+ "All annotation it contained were lost.");
0810      * dumpingSet.clear(); }// End if
0811      */
0812     StatusListener sListener = (StatusListener)gate.Gate
0813             .getListeners().get("gate.event.StatusListener");
0814     // Construct the dumping set in that way that all annotations will verify
0815     // the condition that there are not annotations which are crossed.
0816     // First add all annotation from the original markups
0817     if(sListener != null)
0818       sListener.statusChanged("Constructing the dumping annotation set.");
0819     // dumpingSet.addAll(originalMarkupsAnnotSet);
0820     dumpingList.addAll(originalMarkupsAnnotSet);
0821     // Then take all the annotations from aSourceAnnotationSet and verify if
0822     // they can be inserted safely into the dumpingSet. Where not possible,
0823     // report.
0824     if(aSourceAnnotationSet != null) {
0825       Iterator<Annotation> iter = aSourceAnnotationSet.iterator();
0826       while(iter.hasNext()) {
0827         Annotation currentAnnot = iter.next();
0828         if(insertsSafety(dumpingList, currentAnnot)) {
0829           // dumpingSet.add(currentAnnot);
0830           dumpingList.add(currentAnnot);
0831         else if(crossedOverAnnotation != null && DEBUG) {
0832           try {
0833             Out.prln("Warning: Annotations were found to violate the "
0834                     "crossed over condition: \n"
0835                     "1. ["
0836                     + getContent().getContent(
0837                             crossedOverAnnotation.getStartNode().getOffset(),
0838                             crossedOverAnnotation.getEndNode().getOffset())
0839                     " ("
0840                     + crossedOverAnnotation.getType()
0841                     ": "
0842                     + crossedOverAnnotation.getStartNode().getOffset()
0843                     ";"
0844                     + crossedOverAnnotation.getEndNode().getOffset()
0845                     ")]\n"
0846                     "2. ["
0847                     + getContent().getContent(
0848                             currentAnnot.getStartNode().getOffset(),
0849                             currentAnnot.getEndNode().getOffset()) " ("
0850                     + currentAnnot.getType() ": "
0851                     + currentAnnot.getStartNode().getOffset() ";"
0852                     + currentAnnot.getEndNode().getOffset()
0853                     ")]\nThe second one will be discarded.\n");
0854           catch(gate.util.InvalidOffsetException ex) {
0855             throw new GateRuntimeException(ex.getMessage());
0856           }
0857         }// End if
0858       }// End while
0859     }// End if
0860     // kalina: order the dumping list by start offset
0861     Collections.sort(dumpingList, new gate.util.OffsetComparator());
0862     // The dumpingSet is ready to be exported as XML
0863     // Here we go.
0864     if(sListener != null)
0865       sListener.statusChanged("Dumping annotations as XML");
0866     StringBuffer xmlDoc = new StringBuffer(
0867             DocumentXmlUtils.DOC_SIZE_MULTIPLICATION_FACTOR
0868             (this.getContent().size().intValue()));
0869     // Add xml header if original format was xml
0870     String mimeType = getFeatures() == null null (String)getFeatures().get(
0871             "MimeType");
0872     boolean wasXML = mimeType != null && mimeType.equalsIgnoreCase("text/xml");
0873     if(wasXML) {
0874       xmlDoc.append("<?xml version=\"1.0\" encoding=\"");
0875       xmlDoc.append(getEncoding());
0876       xmlDoc.append("\" ?>");
0877       xmlDoc.append(Strings.getNl());
0878     }// ENd if
0879     // Identify and extract the root annotation from the dumpingSet.
0880     theRootAnnotation = identifyTheRootAnnotation(dumpingList);
0881     // If a root annotation has been identified then add it explicitly at the
0882     // beginning of the document
0883     if(theRootAnnotation != null) {
0884       dumpingList.remove(theRootAnnotation);
0885       xmlDoc.append(writeStartTag(theRootAnnotation, includeFeatures));
0886     }// End if
0887     // Construct and append the rest of the document
0888     xmlDoc.append(saveAnnotationSetAsXml(dumpingList, includeFeatures));
0889     // If a root annotation has been identified then add it eplicitley at the
0890     // end of the document
0891     if(theRootAnnotation != null) {
0892       xmlDoc.append(writeEndTag(theRootAnnotation));
0893     }// End if
0894     if(sListener != nullsListener.statusChanged("Done.");
0895     return xmlDoc.toString();
0896   }// End toXml()
0897 
0898   /**
0899    * This method verifies if aSourceAnnotation can ve inserted safety into the
0900    * aTargetAnnotSet. Safety means that it doesn't violate the crossed over
0901    * contition with any annotation from the aTargetAnnotSet.
0902    
0903    @param aTargetAnnotSet
0904    *          the annotation set to include the aSourceAnnotation
0905    @param aSourceAnnotation
0906    *          the annotation to be inserted into the aTargetAnnotSet
0907    @return true if the annotation inserts safety, or false otherwise.
0908    */
0909   private boolean insertsSafety(AnnotationSet aTargetAnnotSet,
0910           Annotation aSourceAnnotation) {
0911     if(aTargetAnnotSet == null || aSourceAnnotation == null) {
0912       this.crossedOverAnnotation = null;
0913       return false;
0914     }
0915     if(aSourceAnnotation.getStartNode() == null
0916             || aSourceAnnotation.getStartNode().getOffset() == null) {
0917       this.crossedOverAnnotation = null;
0918       return false;
0919     }
0920     if(aSourceAnnotation.getEndNode() == null
0921             || aSourceAnnotation.getEndNode().getOffset() == null) {
0922       this.crossedOverAnnotation = null;
0923       return false;
0924     }
0925     // Get the start and end offsets
0926     Long start = aSourceAnnotation.getStartNode().getOffset();
0927     Long end = aSourceAnnotation.getEndNode().getOffset();
0928     // Read aSourceAnnotation offsets long
0929     long s2 = start.longValue();
0930     long e2 = end.longValue();
0931     // Obtain a set with all annotations annotations that overlap
0932     // totaly or partially with the interval defined by the two provided offsets
0933     AnnotationSet as = aTargetAnnotSet.get(start, end);
0934     // Investigate all the annotations from as to see if there is one that
0935     // comes in conflict with aSourceAnnotation
0936     Iterator<Annotation> it = as.iterator();
0937     while(it.hasNext()) {
0938       Annotation ann = it.next();
0939       // Read ann offsets
0940       long s1 = ann.getStartNode().getOffset().longValue();
0941       long e1 = ann.getEndNode().getOffset().longValue();
0942       if(s1 < s2 && s2 < e1 && e1 < e2) {
0943         this.crossedOverAnnotation = ann;
0944         return false;
0945       }
0946       if(s2 < s1 && s1 < e2 && e2 < e1) {
0947         this.crossedOverAnnotation = ann;
0948         return false;
0949       }
0950     }// End while
0951     return true;
0952   }// insertsSafety()
0953 
0954   private boolean insertsSafety(List<Annotation> aTargetAnnotList,
0955           Annotation aSourceAnnotation) {
0956     if(aTargetAnnotList == null || aSourceAnnotation == null) {
0957       this.crossedOverAnnotation = null;
0958       return false;
0959     }
0960     if(aSourceAnnotation.getStartNode() == null
0961             || aSourceAnnotation.getStartNode().getOffset() == null) {
0962       this.crossedOverAnnotation = null;
0963       return false;
0964     }
0965     if(aSourceAnnotation.getEndNode() == null
0966             || aSourceAnnotation.getEndNode().getOffset() == null) {
0967       this.crossedOverAnnotation = null;
0968       return false;
0969     }
0970     // Get the start and end offsets
0971     Long start = aSourceAnnotation.getStartNode().getOffset();
0972     Long end = aSourceAnnotation.getEndNode().getOffset();
0973     // Read aSourceAnnotation offsets long
0974     long s2 = start.longValue();
0975     long e2 = end.longValue();
0976     // Obtain a set with all annotations annotations that overlap
0977     // totaly or partially with the interval defined by the two provided offsets
0978     List<Annotation> as = new ArrayList<Annotation>();
0979     for(int i = 0; i < aTargetAnnotList.size(); i++) {
0980       Annotation annot = aTargetAnnotList.get(i);
0981       if(annot.getStartNode().getOffset().longValue() >= s2
0982               && annot.getStartNode().getOffset().longValue() <= e2)
0983         as.add(annot);
0984       else if(annot.getEndNode().getOffset().longValue() >= s2
0985               && annot.getEndNode().getOffset().longValue() <= e2)
0986         as.add(annot);
0987     }
0988     // Investigate all the annotations from as to see if there is one that
0989     // comes in conflict with aSourceAnnotation
0990     Iterator<Annotation> it = as.iterator();
0991     while(it.hasNext()) {
0992       Annotation ann = it.next();
0993       // Read ann offsets
0994       long s1 = ann.getStartNode().getOffset().longValue();
0995       long e1 = ann.getEndNode().getOffset().longValue();
0996       if(s1 < s2 && s2 < e1 && e1 < e2) {
0997         this.crossedOverAnnotation = ann;
0998         return false;
0999       }
1000       if(s2 < s1 && s1 < e2 && e2 < e1) {
1001         this.crossedOverAnnotation = ann;
1002         return false;
1003       }
1004     }// End while
1005     return true;
1006   }// insertsSafety()
1007 
1008   /**
1009    * This method saves all the annotations from aDumpAnnotSet and combines them
1010    * with the document content.
1011    
1012    @param aDumpAnnotSet
1013    *          is a GATE annotation set prepared to be used on the raw text from
1014    *          document content. If aDumpAnnotSet is <b>null<b> then an empty
1015    *          string will be returned.
1016    @param includeFeatures
1017    *          is a boolean, which controls whether the annotation features and
1018    *          gate ID are included or not.
1019    @return The XML document obtained from raw text + the information from the
1020    *         dump annotation set.
1021    */
1022   @SuppressWarnings("unused")
1023   private String saveAnnotationSetAsXml(AnnotationSet aDumpAnnotSet,
1024           boolean includeFeatures) {
1025     String content = null;
1026     if(this.getContent() == null)
1027       content = new String("");
1028     else content = this.getContent().toString();
1029     StringBuffer docContStrBuff =
1030       DocumentXmlUtils.filterNonXmlChars(new StringBuffer(content));
1031     if(aDumpAnnotSet == nullreturn docContStrBuff.toString();
1032     TreeMap<Long, Character> offsets2CharsMap = new TreeMap<Long, Character>();
1033     if(this.getContent().size().longValue() != 0) {
1034       // Fill the offsets2CharsMap with all the indices where
1035       // special chars appear
1036       buildEntityMapFromString(content, offsets2CharsMap);
1037     }// End if
1038     // The saving alghorithm is as follows:
1039     // /////////////////////////////////////////
1040     // Construct a set of annot with all IDs in asc order.
1041     // All annotations that end at that offset swap their place in descending
1042     // order. For each node write all the tags from left to right.
1043     // Construct the node set
1044     TreeSet<Long> offsets = new TreeSet<Long>();
1045     Iterator<Annotation> iter = aDumpAnnotSet.iterator();
1046     while(iter.hasNext()) {
1047       Annotation annot = iter.next();
1048       offsets.add(annot.getStartNode().getOffset());
1049       offsets.add(annot.getEndNode().getOffset());
1050     }// End while
1051     // ofsets is sorted in ascending order.
1052     // Iterate this set in descending order and remove an offset at each
1053     // iteration
1054     while(!offsets.isEmpty()) {
1055       Long offset = offsets.last();
1056       // Remove the offset from the set
1057       offsets.remove(offset);
1058       // Now, use it.
1059       // Returns a list with annotations that needs to be serialized in that
1060       // offset.
1061       List<Annotation> annotations = getAnnotationsForOffset(aDumpAnnotSet, offset);
1062       // Attention: the annotation are serialized from left to right
1063       // StringBuffer tmpBuff = new StringBuffer("");
1064       StringBuffer tmpBuff = new StringBuffer(DOC_SIZE_MULTIPLICATION_FACTOR_AS
1065               (this.getContent().size().intValue()));
1066       Stack<Annotation> stack = new Stack<Annotation>();
1067       // Iterate through all these annotations and serialize them
1068       Iterator<Annotation> it = annotations.iterator();
1069       while(it.hasNext()) {
1070         Annotation a = it.next();
1071         it.remove();
1072         // Test if a Ends at offset
1073         if(offset.equals(a.getEndNode().getOffset())) {
1074           // Test if a Starts at offset
1075           if(offset.equals(a.getStartNode().getOffset())) {
1076             // Here, the annotation a Starts and Ends at the offset
1077             if(null != a.getFeatures().get("isEmptyAndSpan")
1078                     && "true".equals(a.getFeatures().get(
1079                             "isEmptyAndSpan"))) {
1080               // Assert: annotation a with start == end and isEmptyAndSpan
1081               tmpBuff.append(writeStartTag(a, includeFeatures));
1082               stack.push(a);
1083             else {
1084               // Assert annotation a with start == end and an empty tag
1085               tmpBuff.append(writeEmptyTag(a));
1086               // The annotation is removed from dumped set
1087               aDumpAnnotSet.remove(a);
1088             }// End if
1089           else {
1090             // Here the annotation a Ends at the offset.
1091             // In this case empty the stack and write the end tag
1092             if(!stack.isEmpty()) {
1093               while(!stack.isEmpty()) {
1094                 Annotation a1 = stack.pop();
1095                 tmpBuff.append(writeEndTag(a1));
1096               }// End while
1097             }// End if
1098             tmpBuff.append(writeEndTag(a));
1099           }// End if
1100         else {
1101           // The annotation a does NOT end at the offset. Let's see if it starts
1102           // at the offset
1103           if(offset.equals(a.getStartNode().getOffset())) {
1104             // The annotation a starts at the offset.
1105             // In this case empty the stack and write the end tag
1106             if(!stack.isEmpty()) {
1107               while(!stack.isEmpty()) {
1108                 Annotation a1 = stack.pop();
1109                 tmpBuff.append(writeEndTag(a1));
1110               }// End while
1111             }// End if
1112             tmpBuff.append(writeStartTag(a, includeFeatures));
1113             // The annotation is removed from dumped set
1114             aDumpAnnotSet.remove(a);
1115           }// End if ( offset.equals(a.getStartNode().getOffset()) )
1116         }// End if ( offset.equals(a.getEndNode().getOffset()) )
1117       }// End while(it.hasNext()){
1118       // In this case empty the stack and write the end tag
1119       if(!stack.isEmpty()) {
1120         while(!stack.isEmpty()) {
1121           Annotation a1 = stack.pop();
1122           tmpBuff.append(writeEndTag(a1));
1123         }// End while
1124       }// End if
1125       // Before inserting tmpBuff into docContStrBuff we need to check
1126       // if there are chars to be replaced and if there are, they would be
1127       // replaced.
1128       if(!offsets2CharsMap.isEmpty()) {
1129         Long offsChar = offsets2CharsMap.lastKey();
1130         while(!offsets2CharsMap.isEmpty()
1131                 && offsChar.intValue() >= offset.intValue()) {
1132           // Replace the char at offsChar with its corresponding entity form
1133           // the entitiesMap.
1134           docContStrBuff.replace(offsChar.intValue(), offsChar.intValue() 1,
1135                   DocumentXmlUtils.entitiesMap.get(offsets2CharsMap
1136                           .get(offsChar)));
1137           // Discard the offsChar after it was used.
1138           offsets2CharsMap.remove(offsChar);
1139           // Investigate next offsChar
1140           if(!offsets2CharsMap.isEmpty())
1141             offsChar = offsets2CharsMap.lastKey();
1142         }// End while
1143       }// End if
1144       // Insert tmpBuff to the location where it belongs in docContStrBuff
1145       docContStrBuff.insert(offset.intValue(), tmpBuff.toString());
1146     }// End while(!offsets.isEmpty())
1147     // Need to replace the entities in the remaining text, if there is any text
1148     // So, if there are any more items in offsets2CharsMap they need to be
1149     // replaced
1150     while(!offsets2CharsMap.isEmpty()) {
1151       Long offsChar = offsets2CharsMap.lastKey();
1152       // Replace the char with its entity
1153       docContStrBuff.replace(offsChar.intValue(), offsChar.intValue() 1,
1154               DocumentXmlUtils.entitiesMap
1155                       .get(offsets2CharsMap.get(offsChar)));
1156       // remove the offset from the map
1157       offsets2CharsMap.remove(offsChar);
1158     }// End while
1159     return docContStrBuff.toString();
1160   }// saveAnnotationSetAsXml()
1161 
1162   private String saveAnnotationSetAsXml(List<Annotation> aDumpAnnotList,
1163           boolean includeFeatures) {
1164     String content;
1165     if(this.getContent() == null)
1166       content = "";
1167     else content = this.getContent().toString();
1168     StringBuffer docContStrBuff =
1169       DocumentXmlUtils.filterNonXmlChars(new StringBuffer(content));
1170     if(aDumpAnnotList == nullreturn docContStrBuff.toString();
1171     StringBuffer resultStrBuff = new StringBuffer(
1172             DOC_SIZE_MULTIPLICATION_FACTOR_AS
1173                     (this.getContent().size().intValue()));
1174     // last offset position used to extract portions of text
1175     Long lastOffset = 0L;
1176     TreeMap<Long, Character> offsets2CharsMap = new TreeMap<Long, Character>();
1177     HashMap<Long, List<Annotation>> annotsForOffset =
1178       new HashMap<Long, List<Annotation>>(100);
1179     if(this.getContent().size() != 0) {
1180       // Fill the offsets2CharsMap with all the indices where
1181       // special chars appear
1182       buildEntityMapFromString(content, offsets2CharsMap);
1183     }// End if
1184     // The saving alghorithm is as follows:
1185     // /////////////////////////////////////////
1186     // Construct a set of annot with all IDs in asc order.
1187     // All annotations that end at that offset swap their place in descending
1188     // order. For each node write all the tags from left to right.
1189     // Construct the node set
1190     TreeSet<Long> offsets = new TreeSet<Long>();
1191     Iterator<Annotation> iter = aDumpAnnotList.iterator();
1192     Annotation annot;
1193     Long start;
1194     Long end;
1195     while(iter.hasNext()) {
1196       annot = iter.next();
1197       start = annot.getStartNode().getOffset();
1198       end = annot.getEndNode().getOffset();
1199       offsets.add(start);
1200       offsets.add(end);
1201       if(annotsForOffset.containsKey(start)) {
1202         annotsForOffset.get(start).add(annot);
1203       else {
1204         List<Annotation> newList = new ArrayList<Annotation>(10);
1205         newList.add(annot);
1206         annotsForOffset.put(start, newList);
1207       }
1208       if(annotsForOffset.containsKey(end)) {
1209         annotsForOffset.get(end).add(annot);
1210       else {
1211         List<Annotation> newList = new ArrayList<Annotation>(10);
1212         newList.add(annot);
1213         annotsForOffset.put(end, newList);
1214       }
1215     }// End while
1216     // ofsets is sorted in ascending order.
1217     // Iterate this set in descending order and remove an offset at each
1218     // iteration
1219     Iterator<Long> offsetIt = offsets.iterator();
1220     Long offset;
1221     List<Annotation> annotations;
1222     // This don't have to be a large buffer - just for tags
1223     StringBuffer tmpBuff = new StringBuffer(255);
1224     Stack<Annotation> stack = new Stack<Annotation>();
1225     while(offsetIt.hasNext()) {
1226       offset = offsetIt.next();
1227       // Now, use it.
1228       // Returns a list with annotations that needs to be serialized in that
1229       // offset.
1230       annotations = annotsForOffset.get(offset);
1231       // order annotations in list for offset to print tags in correct order
1232       annotations = getAnnotationsForOffset(annotations, offset);
1233       // clear structures
1234       tmpBuff.setLength(0);
1235       stack.clear();
1236       // Iterate through all these annotations and serialize them
1237       Iterator<Annotation> it = annotations.iterator();
1238       Annotation a;
1239       Annotation annStack;
1240       while(it.hasNext()) {
1241         a = it.next();
1242         // Test if a Ends at offset
1243         if(offset.equals(a.getEndNode().getOffset())) {
1244           // Test if a Starts at offset
1245           if(offset.equals(a.getStartNode().getOffset())) {
1246             // Here, the annotation a Starts and Ends at the offset
1247             if(null != a.getFeatures().get("isEmptyAndSpan")
1248                     && "true".equals(a.getFeatures().get(
1249                             "isEmptyAndSpan"))) {
1250               // Assert: annotation a with start == end and isEmptyAndSpan
1251               tmpBuff.append(writeStartTag(a, includeFeatures));
1252               stack.push(a);
1253             else {
1254               // Assert annotation a with start == end and an empty tag
1255               tmpBuff.append(writeEmptyTag(a));
1256               // The annotation is removed from dumped set
1257               aDumpAnnotList.remove(a);
1258             }// End if
1259           else {
1260             // Here the annotation a Ends at the offset.
1261             // In this case empty the stack and write the end tag
1262             if(!stack.isEmpty()) {
1263               while(!stack.isEmpty()) {
1264                 annStack = stack.pop();
1265                 tmpBuff.append(writeEndTag(annStack));
1266               }// End while
1267             }// End if
1268             tmpBuff.append(writeEndTag(a));
1269           }// End if
1270         else {
1271           // The annotation a does NOT end at the offset. Let's see if it starts
1272           // at the offset
1273           if(offset.equals(a.getStartNode().getOffset())) {
1274             // The annotation a starts at the offset.
1275             // In this case empty the stack and write the end tag
1276             if(!stack.isEmpty()) {
1277               while(!stack.isEmpty()) {
1278                 annStack = stack.pop();
1279                 tmpBuff.append(writeEndTag(annStack));
1280               }// End while
1281             }// End if
1282             tmpBuff.append(writeStartTag(a, includeFeatures));
1283             // The annotation is removed from dumped set
1284           }// End if ( offset.equals(a.getStartNode().getOffset()) )
1285         }// End if ( offset.equals(a.getEndNode().getOffset()) )
1286       }// End while(it.hasNext()){
1287       // In this case empty the stack and write the end tag
1288       if(!stack.isEmpty()) {
1289         while(!stack.isEmpty()) {
1290           annStack = stack.pop();
1291           tmpBuff.append(writeEndTag(annStack));
1292         }// End while
1293       }// End if
1294       // extract text from content and replace spec chars
1295       StringBuffer partText = new StringBuffer();
1296       SortedMap<Long, Character> offsetsInRange = offsets2CharsMap.subMap(lastOffset, offset);
1297       Long tmpOffset;
1298       Long tmpLastOffset = lastOffset;
1299       String replacement;
1300       // Before inserting tmpBuff into the buffer we need to check
1301       // if there are chars to be replaced in range
1302       while(!offsetsInRange.isEmpty()) {
1303         tmpOffset = offsetsInRange.firstKey();
1304         replacement = DocumentXmlUtils.entitiesMap.get(
1305           offsets2CharsMap.get(tmpOffset));
1306         partText.append(docContStrBuff.substring(
1307           tmpLastOffset.intValue(), tmpOffset.intValue()));
1308         partText.append(replacement);
1309         tmpLastOffset = tmpOffset + 1;
1310         offsetsInRange.remove(tmpOffset);
1311       }
1312       partText.append(docContStrBuff.substring(
1313         tmpLastOffset.intValue(), offset.intValue()));
1314       resultStrBuff.append(partText);
1315       // Insert tmpBuff to the result string
1316       resultStrBuff.append(tmpBuff.toString());
1317       lastOffset = offset;
1318     }// End while(!offsets.isEmpty())
1319     // get text to the end of content
1320     // extract text from content and replace spec chars
1321     StringBuffer partText = new StringBuffer();
1322     SortedMap<Long, Character> offsetsInRange = offsets2CharsMap.subMap(
1323       lastOffset, (longdocContStrBuff.length());
1324     Long tmpOffset;
1325     Long tmpLastOffset = lastOffset;
1326     String replacement;
1327     // Need to replace the entities in the remaining text, if there is any text
1328     // So, if there are any more items in offsets2CharsMap for remaining text
1329     // they need to be replaced
1330     while(!offsetsInRange.isEmpty()) {
1331       tmpOffset = offsetsInRange.firstKey();
1332       replacement = DocumentXmlUtils.entitiesMap.get(
1333         offsets2CharsMap.get(tmpOffset));
1334       partText.append(docContStrBuff.substring(
1335         tmpLastOffset.intValue(), tmpOffset.intValue()));
1336       partText.append(replacement);
1337       tmpLastOffset = tmpOffset + 1;
1338       offsetsInRange.remove(tmpOffset);
1339     }
1340     partText.append(docContStrBuff.substring(
1341       tmpLastOffset.intValue(), docContStrBuff.length()));
1342     resultStrBuff.append(partText);
1343     return resultStrBuff.toString();
1344   }// saveAnnotationSetAsXml()
1345 
1346   /*
1347    * Old method created by Cristian. Create content backward.
1348    
1349    * private String saveAnnotationSetAsXml(List aDumpAnnotList, boolean
1350    * includeFeatures){ String content = null; if (this.getContent()== null)
1351    * content = new String(""); else content = this.getContent().toString();
1352    * StringBuffer docContStrBuff = filterNonXmlChars(new StringBuffer(content));
1353    * if (aDumpAnnotList == null) return docContStrBuff.toString();
1354    
1355    * TreeMap offsets2CharsMap = new TreeMap(); HashMap annotsForOffset = new
1356    * HashMap(100); if (this.getContent().size().longValue() != 0){ // Fill the
1357    * offsets2CharsMap with all the indices where // special chars appear
1358    * buildEntityMapFromString(content,offsets2CharsMap); }//End if // The saving
1359    * alghorithm is as follows: /////////////////////////////////////////// //
1360    * Construct a set of annot with all IDs in asc order. // All annotations that
1361    * end at that offset swap their place in descending // order. For each node
1362    * write all the tags from left to right. // Construct the node set TreeSet
1363    * offsets = new TreeSet(); Iterator iter = aDumpAnnotList.iterator(); while
1364    * (iter.hasNext()){ Annotation annot = (Annotation) iter.next();
1365    * offsets.add(annot.getStartNode().getOffset());
1366    * offsets.add(annot.getEndNode().getOffset()); if
1367    * (annotsForOffset.containsKey(annot.getStartNode().getOffset())) { ((List)
1368    * annotsForOffset.get(annot.getStartNode().getOffset())).add(annot); } else {
1369    * List newList = new ArrayList(10); newList.add(annot);
1370    * annotsForOffset.put(annot.getStartNode().getOffset(), newList); } if
1371    * (annotsForOffset.containsKey(annot.getEndNode().getOffset())) { ((List)
1372    * annotsForOffset.get(annot.getEndNode().getOffset())).add(annot); } else {
1373    * List newList = new ArrayList(10); newList.add(annot);
1374    * annotsForOffset.put(annot.getEndNode().getOffset(), newList); } }// End
1375    * while // ofsets is sorted in ascending order. // Iterate this set in
1376    * descending order and remove an offset at each // iteration while
1377    * (!offsets.isEmpty()){ Long offset = (Long)offsets.last(); // Remove the
1378    * offset from the set offsets.remove(offset); // Now, use it. // Returns a
1379    * list with annotations that needs to be serialized in that // offset. //
1380    * List annotations = getAnnotationsForOffset(aDumpAnnotList,offset); List
1381    * annotations = (List) annotsForOffset.get(offset); annotations =
1382    * getAnnotationsForOffset(annotations,offset); // Attention: the annotation
1383    * are serialized from left to right // StringBuffer tmpBuff = new
1384    * StringBuffer(""); StringBuffer tmpBuff = new StringBuffer(
1385    * DOC_SIZE_MULTIPLICATION_FACTOR*(this.getContent().size().intValue()));
1386    * Stack stack = new Stack(); // Iterate through all these annotations and
1387    * serialize them Iterator it = annotations.iterator(); while(it.hasNext()){
1388    * Annotation a = (Annotation) it.next(); it.remove(); // Test if a Ends at
1389    * offset if ( offset.equals(a.getEndNode().getOffset()) ){ // Test if a
1390    * Starts at offset if ( offset.equals(a.getStartNode().getOffset()) ){ //
1391    * Here, the annotation a Starts and Ends at the offset if ( null !=
1392    * a.getFeatures().get("isEmptyAndSpan") &&
1393    * "true".equals((String)a.getFeatures().get("isEmptyAndSpan"))){ // Assert:
1394    * annotation a with start == end and isEmptyAndSpan
1395    * tmpBuff.append(writeStartTag(a, includeFeatures)); stack.push(a); }else{ //
1396    * Assert annotation a with start == end and an empty tag
1397    * tmpBuff.append(writeEmptyTag(a)); // The annotation is removed from dumped
1398    * set aDumpAnnotList.remove(a); }// End if }else{ // Here the annotation a
1399    * Ends at the offset. // In this case empty the stack and write the end tag
1400    * if (!stack.isEmpty()){ while(!stack.isEmpty()){ Annotation a1 =
1401    * (Annotation)stack.pop(); tmpBuff.append(writeEndTag(a1)); }// End while }//
1402    * End if tmpBuff.append(writeEndTag(a)); }// End if }else{ // The annotation
1403    * a does NOT end at the offset. Let's see if it starts // at the offset if (
1404    * offset.equals(a.getStartNode().getOffset()) ){ // The annotation a starts
1405    * at the offset. // In this case empty the stack and write the end tag if
1406    * (!stack.isEmpty()){ while(!stack.isEmpty()){ Annotation a1 =
1407    * (Annotation)stack.pop(); tmpBuff.append(writeEndTag(a1)); }// End while }//
1408    * End if tmpBuff.append(writeStartTag(a, includeFeatures)); // The annotation
1409    * is removed from dumped set aDumpAnnotList.remove(a); }// End if (
1410    * offset.equals(a.getStartNode().getOffset()) ) }// End if (
1411    * offset.equals(a.getEndNode().getOffset()) ) }// End while(it.hasNext()){ //
1412    * In this case empty the stack and write the end tag if (!stack.isEmpty()){
1413    * while(!stack.isEmpty()){ Annotation a1 = (Annotation)stack.pop();
1414    * tmpBuff.append(writeEndTag(a1)); }// End while }// End if // Before
1415    * inserting tmpBuff into docContStrBuff we need to check // if there are
1416    * chars to be replaced and if there are, they would be // replaced. if
1417    * (!offsets2CharsMap.isEmpty()){ Long offsChar = (Long)
1418    * offsets2CharsMap.lastKey(); while( !offsets2CharsMap.isEmpty() &&
1419    * offsChar.intValue() >= offset.intValue()){ // Replace the char at offsChar
1420    * with its corresponding entity form // the entitiesMap.
1421    * docContStrBuff.replace(offsChar.intValue(),offsChar.intValue()+1,
1422    * (String)entitiesMap.get((Character)offsets2CharsMap.get(offsChar))); //
1423    * Discard the offsChar after it was used. offsets2CharsMap.remove(offsChar); //
1424    * Investigate next offsChar if (!offsets2CharsMap.isEmpty()) offsChar =
1425    * (Long) offsets2CharsMap.lastKey(); }// End while }// End if // Insert
1426    * tmpBuff to the location where it belongs in docContStrBuff
1427    * docContStrBuff.insert(offset.intValue(),tmpBuff.toString()); }// End
1428    * while(!offsets.isEmpty()) // Need to replace the entities in the remaining
1429    * text, if there is any text // So, if there are any more items in
1430    * offsets2CharsMap they need to be // replaced while
1431    * (!offsets2CharsMap.isEmpty()){ Long offsChar = (Long)
1432    * offsets2CharsMap.lastKey(); // Replace the char with its entity
1433    * docContStrBuff.replace(offsChar.intValue(),offsChar.intValue()+1,
1434    * (String)entitiesMap.get((Character)offsets2CharsMap.get(offsChar))); //
1435    * remove the offset from the map offsets2CharsMap.remove(offsChar); }// End
1436    * while return docContStrBuff.toString(); }// saveAnnotationSetAsXml()
1437    */
1438   /**
1439    * Return true only if the document has features for original content and
1440    * repositioning information.
1441    */
1442   private boolean hasOriginalContentFeatures() {
1443     FeatureMap features = getFeatures();
1444     boolean result = false;
1445     result = (features
1446             .get(GateConstants.ORIGINAL_DOCUMENT_CONTENT_FEATURE_NAME!= null)
1447             && (features
1448                     .get(GateConstants.DOCUMENT_REPOSITIONING_INFO_FEATURE_NAME!= null);
1449     return result;
1450   // hasOriginalContentFeatures
1451 
1452   /**
1453    * This method saves all the annotations from aDumpAnnotSet and combines them
1454    * with the original document content, if preserved as feature.
1455    
1456    @param aSourceAnnotationSet
1457    *          is a GATE annotation set prepared to be used on the raw text from
1458    *          document content. If aDumpAnnotSet is <b>null<b> then an empty
1459    *          string will be returned.
1460    @param includeFeatures
1461    *          is a boolean, which controls whether the annotation features and
1462    *          gate ID are included or not.
1463    @return The XML document obtained from raw text + the information from the
1464    *         dump annotation set.
1465    */
1466   private String saveAnnotationSetAsXmlInOrig(Set<Annotation> aSourceAnnotationSet,
1467           boolean includeFeatures) {
1468     StringBuffer docContStrBuff;
1469     String origContent;
1470     origContent = (String)features
1471             .get(GateConstants.ORIGINAL_DOCUMENT_CONTENT_FEATURE_NAME);
1472     if(origContent == null) {
1473       origContent = "";
1474     // if
1475     long originalContentSize = origContent.length();
1476     RepositioningInfo repositioning = (RepositioningInfo)getFeatures().get(
1477             GateConstants.DOCUMENT_REPOSITIONING_INFO_FEATURE_NAME);
1478     docContStrBuff = new StringBuffer(origContent);
1479     if(aSourceAnnotationSet == nullreturn docContStrBuff.toString();
1480     StatusListener sListener = (StatusListener)gate.Gate
1481             .getListeners().get("gate.event.StatusListener");
1482     AnnotationSet originalMarkupsAnnotSet = this
1483             .getAnnotations(GateConstants.ORIGINAL_MARKUPS_ANNOT_SET_NAME);
1484     // Create a dumping annotation set on the document. It will be used for
1485     // dumping annotations...
1486     AnnotationSet dumpingSet = new AnnotationSetImpl(this);
1487     if(sListener != null)
1488       sListener.statusChanged("Constructing the dumping annotation set.");
1489     // Then take all the annotations from aSourceAnnotationSet and verify if
1490     // they can be inserted safely into the dumpingSet. Where not possible,
1491     // report.
1492     if(aSourceAnnotationSet != null) {
1493       Iterator<Annotation> iter = aSourceAnnotationSet.iterator();
1494       Annotation currentAnnot;
1495       while(iter.hasNext()) {
1496         currentAnnot = iter.next();
1497         if(insertsSafety(originalMarkupsAnnotSet, currentAnnot)
1498                 && insertsSafety(dumpingSet, currentAnnot)) {
1499           dumpingSet.add(currentAnnot);
1500         else {
1501           Out.prln("Warning: Annotation with ID=" + currentAnnot.getId()
1502                   ", startOffset=" + currentAnnot.getStartNode().getOffset()
1503                   ", endOffset=" + currentAnnot.getEndNode().getOffset()
1504                   ", type=" + currentAnnot.getType()
1505                   " was found to violate the"
1506                   " crossed over condition. It will be discarded");
1507         }// End if
1508       }// End while
1509     }// End if
1510     // The dumpingSet is ready to be exported as XML
1511     // Here we go.
1512     if(sListener != null)
1513       sListener.statusChanged("Dumping annotations as XML");
1514     // /////////////////////////////////////////
1515     // Construct a set of annot with all IDs in asc order.
1516     // All annotations that end at that offset swap their place in descending
1517     // order. For each node write all the tags from left to right.
1518     // Construct the node set
1519     TreeSet<Long> offsets = new TreeSet<Long>();
1520     Iterator<Annotation> iter = aSourceAnnotationSet.iterator();
1521     while(iter.hasNext()) {
1522       Annotation annot = iter.next();
1523       offsets.add(annot.getStartNode().getOffset());
1524       offsets.add(annot.getEndNode().getOffset());
1525     }// End while
1526     // ofsets is sorted in ascending order.
1527     // Iterate this set in descending order and remove an offset at each
1528     // iteration
1529     while(!offsets.isEmpty()) {
1530       Long offset = offsets.last();
1531       // Remove the offset from the set
1532       offsets.remove(offset);
1533       // Now, use it.
1534       // Returns a list with annotations that needs to be serialized in that
1535       // offset.
1536       List<Annotation> annotations = getAnnotationsForOffset(aSourceAnnotationSet, offset);
1537       // Attention: the annotation are serialized from left to right
1538       StringBuffer tmpBuff = new StringBuffer("");
1539       Stack<Annotation> stack = new Stack<Annotation>();
1540       // Iterate through all these annotations and serialize them
1541       Iterator<Annotation> it = annotations.iterator();
1542       Annotation a = null;
1543       while(it.hasNext()) {
1544         a = it.next();
1545         it.remove();
1546         // Test if a Ends at offset
1547         if(offset.equals(a.getEndNode().getOffset())) {
1548           // Test if a Starts at offset
1549           if(offset.equals(a.getStartNode().getOffset())) {
1550             // Here, the annotation a Starts and Ends at the offset
1551             if(null != a.getFeatures().get("isEmptyAndSpan")
1552                     && "true".equals(a.getFeatures().get(
1553                             "isEmptyAndSpan"))) {
1554               // Assert: annotation a with start == end and isEmptyAndSpan
1555               tmpBuff.append(writeStartTag(a, includeFeatures, false));
1556               stack.push(a);
1557             else {
1558               // Assert annotation a with start == end and an empty tag
1559               tmpBuff.append(writeEmptyTag(a, false));
1560               // The annotation is removed from dumped set
1561               aSourceAnnotationSet.remove(a);
1562             }// End if
1563           else {
1564             // Here the annotation a Ends at the offset.
1565             // In this case empty the stack and write the end tag
1566             while(!stack.isEmpty()) {
1567               Annotation a1 = stack.pop();
1568               tmpBuff.append(writeEndTag(a1));
1569             }// End while
1570             tmpBuff.append(writeEndTag(a));
1571           }// End if
1572         else {
1573           // The annotation a does NOT end at the offset. Let's see if it starts
1574           // at the offset
1575           if(offset.equals(a.getStartNode().getOffset())) {
1576             // The annotation a starts at the offset.
1577             // In this case empty the stack and write the end tag
1578             while(!stack.isEmpty()) {
1579               Annotation a1 = stack.pop();
1580               tmpBuff.append(writeEndTag(a1));
1581             }// End while
1582             tmpBuff.append(writeStartTag(a, includeFeatures, false));
1583             // The annotation is removed from dumped set
1584             aSourceAnnotationSet.remove(a);
1585           }// End if ( offset.equals(a.getStartNode().getOffset()) )
1586         }// End if ( offset.equals(a.getEndNode().getOffset()) )
1587       }// End while(it.hasNext()){
1588       // In this case empty the stack and write the end tag
1589       while(!stack.isEmpty()) {
1590         Annotation a1 = stack.pop();
1591         tmpBuff.append(writeEndTag(a1));
1592       }// End while
1593       long originalPosition = -1;
1594       boolean backPositioning = a != null
1595               && offset.equals(a.getEndNode().getOffset());
1596       if(backPositioning) {
1597         // end of the annotation correction
1598         originalPosition = repositioning
1599                 .getOriginalPos(offset.intValue()true);
1600       // if
1601       if(originalPosition == -1) {
1602         originalPosition = repositioning.getOriginalPos(offset.intValue());
1603       // if
1604       // Insert tmpBuff to the location where it belongs in docContStrBuff
1605       if(originalPosition != -&& originalPosition <= originalContentSize) {
1606         docContStrBuff.insert((int)originalPosition, tmpBuff.toString());
1607       else {
1608         Out.prln("Error in the repositioning. The offset (" + offset.intValue()
1609                 ") could not be positioned in the original document. \n"
1610                 "Calculated position is: " + originalPosition
1611                 " placed back: " + backPositioning);
1612       // if
1613     }// End while(!offsets.isEmpty())
1614     if(theRootAnnotation != null)
1615       docContStrBuff.append(writeEndTag(theRootAnnotation));
1616     return docContStrBuff.toString();
1617   // saveAnnotationSetAsXmlInOrig()
1618 
1619   /**
1620    * This method returns a list with annotations ordered that way that they can
1621    * be serialized from left to right, at the offset. If one of the params is
1622    * null then an empty list will be returned.
1623    
1624    @param aDumpAnnotSet
1625    *          is a set containing all annotations that will be dumped.
1626    @param offset
1627    *          represent the offset at witch the annotation must start AND/OR
1628    *          end.
1629    @return a list with those annotations that need to be serialized.
1630    */
1631   private List<Annotation> getAnnotationsForOffset(Set<Annotation> aDumpAnnotSet, Long offset) {
1632     List<Annotation> annotationList = new LinkedList<Annotation>();
1633     if(aDumpAnnotSet == null || offset == nullreturn annotationList;
1634     Set<Annotation> annotThatStartAtOffset = new TreeSet<Annotation>(new AnnotationComparator(
1635             ORDER_ON_END_OFFSET, DESC));
1636     Set<Annotation> annotThatEndAtOffset = new TreeSet<Annotation>(new AnnotationComparator(
1637             ORDER_ON_START_OFFSET, DESC));
1638     Set<Annotation> annotThatStartAndEndAtOffset = new TreeSet<Annotation>(new AnnotationComparator(
1639             ORDER_ON_ANNOT_ID, ASC));
1640     // Fill these tree lists with annotation tat start, end or start and
1641     // end at the offset.
1642     Iterator<Annotation> iter = aDumpAnnotSet.iterator();
1643     while(iter.hasNext()) {
1644       Annotation ann = iter.next();
1645       if(offset.equals(ann.getStartNode().getOffset())) {
1646         if(offset.equals(ann.getEndNode().getOffset()))
1647           annotThatStartAndEndAtOffset.add(ann);
1648         else annotThatStartAtOffset.add(ann);
1649       else {
1650         if(offset.equals(ann.getEndNode().getOffset()))
1651           annotThatEndAtOffset.add(ann);
1652       }// End if
1653     }// End while
1654     annotationList.addAll(annotThatEndAtOffset);
1655     annotThatEndAtOffset = null;
1656     annotationList.addAll(annotThatStartAtOffset);
1657     annotThatStartAtOffset = null;
1658     iter = annotThatStartAndEndAtOffset.iterator();
1659     while(iter.hasNext()) {
1660       Annotation ann = iter.next();
1661       Iterator<Annotation> it = annotationList.iterator();
1662       boolean breaked = false;
1663       while(it.hasNext()) {
1664         Annotation annFromList = it.next();
1665         if(annFromList.getId().intValue() > ann.getId().intValue()) {
1666           annotationList.add(annotationList.indexOf(annFromList), ann);
1667           breaked = true;
1668           break;
1669         }// End if
1670       }// End while
1671       if(!breakedannotationList.add(ann);
1672       iter.remove();
1673     }// End while
1674     return annotationList;
1675   }// getAnnotationsForOffset()
1676 
1677   private List<Annotation> getAnnotationsForOffset(List<Annotation> aDumpAnnotList, Long offset) {
1678     List<Annotation> annotationList = new ArrayList<Annotation>();
1679     if(aDumpAnnotList == null || offset == nullreturn annotationList;
1680     Set<Annotation> annotThatStartAtOffset;
1681     Set<Annotation> annotThatEndAtOffset;
1682     Set<Annotation> annotThatStartAndEndAtOffset;
1683     annotThatStartAtOffset = new TreeSet<Annotation>(new AnnotationComparator(
1684             ORDER_ON_END_OFFSET, DESC));
1685     annotThatEndAtOffset = new TreeSet<Annotation>(new AnnotationComparator(
1686             ORDER_ON_START_OFFSET, DESC));
1687     annotThatStartAndEndAtOffset = new TreeSet<Annotation>(new AnnotationComparator(
1688             ORDER_ON_ANNOT_ID, ASC));
1689     // Fill these tree lists with annotation tat start, end or start and
1690     // end at the offset.
1691     Iterator<Annotation> iter = aDumpAnnotList.iterator();
1692     while(iter.hasNext()) {
1693       Annotation ann = iter.next();
1694       if(offset.equals(ann.getStartNode().getOffset())) {
1695         if(offset.equals(ann.getEndNode().getOffset()))
1696           annotThatStartAndEndAtOffset.add(ann);
1697         else annotThatStartAtOffset.add(ann);
1698       else {
1699         if(offset.equals(ann.getEndNode().getOffset()))
1700           annotThatEndAtOffset.add(ann);
1701       }// End if
1702     }// End while
1703     annotationList.addAll(annotThatEndAtOffset);
1704     annotationList.addAll(annotThatStartAtOffset);
1705     annotThatEndAtOffset = null;
1706     annotThatStartAtOffset = null;
1707     iter = annotThatStartAndEndAtOffset.iterator();
1708     while(iter.hasNext()) {
1709       Annotation ann = iter.next();
1710       Iterator<Annotation> it = annotationList.iterator();
1711       boolean breaked = false;
1712       while(it.hasNext()) {
1713         Annotation annFromList = it.next();
1714         if(annFromList.getId().intValue() > ann.getId().intValue()) {
1715           annotationList.add(annotationList.indexOf(annFromList), ann);
1716           breaked = true;
1717           break;
1718         }// End if
1719       }// End while
1720       if(!breakedannotationList.add(ann);
1721       iter.remove();
1722     }// End while
1723     return annotationList;
1724   }// getAnnotationsForOffset()
1725 
1726   private String writeStartTag(Annotation annot, boolean includeFeatures) {
1727     return writeStartTag(annot, includeFeatures, true);
1728   // writeStartTag
1729 
1730   /** Returns a string representing a start tag based on the input annot */
1731   private String writeStartTag(Annotation annot, boolean includeFeatures,
1732           boolean includeNamespace) {
1733 
1734     // Get the annot feature used to store the namespace prefix, if it
1735     // has been defined
1736     String nsPrefix = null;
1737     
1738     if (serializeNamespaceInfo)
1739       nsPrefix = (String)annot.getFeatures().get(namespacePrefixFeature);
1740 
1741     AnnotationSet originalMarkupsAnnotSet = this
1742             .getAnnotations(GateConstants.ORIGINAL_MARKUPS_ANNOT_SET_NAME);
1743     StringBuffer strBuff = new StringBuffer("");
1744     if(annot == nullreturn strBuff.toString();
1745     // if (!addGatePreserveFormatTag && isRootTag){
1746     if(theRootAnnotation != null
1747             && annot.getId().equals(theRootAnnotation.getId())) {
1748       // the features are included either if desired or if that's an annotation
1749       // from the original markup of the document. We don't want for example to
1750       // spoil all links in an HTML file!
1751       if(includeFeatures) {
1752         strBuff.append("<");
1753         if (nsPrefix != null && !nsPrefix.isEmpty())
1754           strBuff.append(nsPrefix + ":");
1755         strBuff.append(annot.getType());
1756         strBuff.append(" ");
1757         if(includeNamespace) {
1758           // but don't add the gate ns declaration if it's already there!
1759           if (annot.getFeatures().get("xmlns:gate"== null)
1760             strBuff.append("xmlns:gate=\"http://www.gate.ac.uk\"");
1761           strBuff.append(" gate:");
1762         }
1763         strBuff.append("gateId=\"");
1764         strBuff.append(annot.getId());
1765         strBuff.append("\"");
1766         strBuff.append(" ");
1767         if(includeNamespace) {
1768           strBuff.append("gate:");
1769         }
1770         strBuff.append("annotMaxId=\"");
1771         strBuff.append(nextAnnotationId);
1772         strBuff.append("\"");
1773         strBuff.append(writeFeatures(annot.getFeatures(), includeNamespace));
1774         strBuff.append(">");
1775       else if(originalMarkupsAnnotSet.contains(annot)) {
1776         strBuff.append("<");
1777         if (nsPrefix != null && !nsPrefix.isEmpty())
1778           strBuff.append(nsPrefix + ":");
1779         strBuff.append(annot.getType());
1780         strBuff.append(writeFeatures(annot.getFeatures(), includeNamespace));
1781         strBuff.append(">");
1782       else {
1783         strBuff.append("<");
1784         if (nsPrefix != null && !nsPrefix.isEmpty())
1785           strBuff.append(nsPrefix + ":");
1786         strBuff.append(annot.getType());
1787         strBuff.append(">");
1788       }
1789     else {
1790       // the features are included either if desired or if that's an annotation
1791       // from the original markup of the document. We don't want for example to
1792       // spoil all links in an HTML file!
1793       if(includeFeatures) {
1794         strBuff.append("<");
1795         if (nsPrefix != null && !nsPrefix.isEmpty())
1796           strBuff.append(nsPrefix + ":");
1797         strBuff.append(annot.getType());
1798         strBuff.append(" ");
1799         if(includeNamespace) {
1800           strBuff.append("gate:");
1801         // if includeNamespaces
1802         strBuff.append("gateId=\"");
1803         strBuff.append(annot.getId());
1804         strBuff.append("\"");
1805         strBuff.append(writeFeatures(annot.getFeatures(), includeNamespace));
1806         strBuff.append(">");
1807       else if(originalMarkupsAnnotSet.contains(annot)) {
1808         strBuff.append("<");
1809         if (nsPrefix != null && !nsPrefix.isEmpty())
1810           strBuff.append(nsPrefix + ":");
1811         strBuff.append(annot.getType());
1812         strBuff.append(writeFeatures(annot.getFeatures(), includeNamespace));
1813         strBuff.append(">");
1814       else {
1815         strBuff.append("<");
1816         if (nsPrefix != null && !nsPrefix.isEmpty())
1817           strBuff.append(nsPrefix + ":");
1818         strBuff.append(annot.getType());
1819         strBuff.append(">");
1820       }
1821     }// End if
1822     return strBuff.toString();
1823   }// writeStartTag()
1824 
1825   /**
1826    * Identifies the root annotations inside an annotation set. The root
1827    * annotation is the one that starts at offset 0, and has the greatest span.
1828    * If there are more than one with this function, then the annotation with the
1829    * smalled ID wil be selected as root. If none is identified it will return
1830    * null.
1831    
1832    @param anAnnotationSet
1833    *          The annotation set possibly containing the root annotation.
1834    @return The root annotation or null is it fails
1835    */
1836   @SuppressWarnings("unused")
1837   private Annotation identifyTheRootAnnotation(AnnotationSet anAnnotationSet) {
1838     if(anAnnotationSet == nullreturn null;
1839     // If the starting node of this annotation is not null, then the annotation
1840     // set will not have a root annotation.
1841     Node startNode = anAnnotationSet.firstNode();
1842     Node endNode = anAnnotationSet.lastNode();
1843     // This is placed here just to speed things up. The alghorithm bellow can
1844     // can identity the annotation that span over the entire set and with the
1845     // smallest ID. However the root annotation will have to have the start
1846     // offset equal to 0.
1847     if(startNode.getOffset().longValue() != 0return null;
1848     // Go anf find the annotation.
1849     Annotation theRootAnnotation = null;
1850     // Check if there are annotations starting at offset 0. If there are, then
1851     // check all of them to see which one has the greatest span. Basically its
1852     // END offset should be the bigest offset from the input annotation set.
1853     long start = startNode.getOffset().longValue();
1854     long end = endNode.getOffset().longValue();
1855     for(Iterator<Annotation> it = anAnnotationSet.iterator(); it.hasNext();) {
1856       Annotation currentAnnot = it.next();
1857       // If the currentAnnot has both its Start and End equals to the Start and
1858       // end of the AnnotationSet then check to see if its ID is the smallest.
1859       if((start == currentAnnot.getStartNode().getOffset().longValue())
1860               && (end == currentAnnot.getEndNode().getOffset().longValue())) {
1861         // The currentAnnotation has is a potencial root one.
1862         if(theRootAnnotation == null)
1863           theRootAnnotation = currentAnnot;
1864         else {
1865           // If its ID is greater that the currentAnnot then update the root
1866           if(theRootAnnotation.getId().intValue() > currentAnnot.getId()
1867                   .intValue()) theRootAnnotation = currentAnnot;
1868         }// End if
1869       }// End if
1870     }// End for
1871     return theRootAnnotation;
1872   }// End identifyTheRootAnnotation()
1873 
1874   private Annotation identifyTheRootAnnotation(List<Annotation> anAnnotationList) {
1875     if(anAnnotationList == null || anAnnotationList.isEmpty()) return null;
1876     // If the first annotation in the list (which is sorted by start offset)
1877     // does not have an offset = 0, then there's no root tag.
1878     if(anAnnotationList.get(0).getStartNode().getOffset()
1879             .longValue() 0return null;
1880     // If there's a single annotation and it starts at the start (which we
1881     // already know it does), make sure it ends at the end.
1882     if(anAnnotationList.size() == 1) {
1883       Annotation onlyAnn = anAnnotationList.get(0);
1884       if(onlyAnn.getEndNode().getOffset().equals(getContent().size()))
1885         return onlyAnn;
1886       return null;
1887     }
1888     // find the limits
1889     long start = 0// we know this already
1890     long end = 0// end = 0 will be improved by the next loop
1891     for(int i = 0; i < anAnnotationList.size(); i++) {
1892       Annotation anAnnotation = anAnnotationList.get(i);
1893       long localEnd = anAnnotation.getEndNode().getOffset().longValue();
1894       if(localEnd > endend = localEnd;
1895     }
1896     // Go and find the annotation.
1897     // look at all annotations that start at 0 and end at end
1898     // if there are several, choose the one with the smallest ID
1899     Annotation theRootAnnotation = null;
1900     for(int i = 0; i < anAnnotationList.size(); i++) {
1901       Annotation currentAnnot = anAnnotationList.get(i);
1902       long localStart = currentAnnot.getStartNode().getOffset().longValue();
1903       long localEnd = currentAnnot.getEndNode().getOffset().longValue();
1904       // If the currentAnnot has both its Start and End equals to the Start and
1905       // end of the AnnotationSet then check to see if its ID is the smallest.
1906       if((start == localStart&& (end == localEnd)) {
1907         // The currentAnnotation has is a potential root one.
1908         if(theRootAnnotation == null)
1909           theRootAnnotation = currentAnnot;
1910         else {
1911           // If root's ID is greater that the currentAnnot then update the root
1912           if(theRootAnnotation.getId().intValue() > currentAnnot.getId()
1913                   .intValue()) theRootAnnotation = currentAnnot;
1914         }// End if
1915       }// End if
1916     }// End for
1917     return theRootAnnotation;
1918   }// End identifyTheRootAnnotation()
1919 
1920   /**
1921    * This method takes aScanString and searches for those chars from entitiesMap
1922    * that appear in the string. A tree map(offset2Char) is filled using as key
1923    * the offsets where those Chars appear and the Char. If one of the params is
1924    * null the method simply returns.
1925    */
1926   private void buildEntityMapFromString(String aScanString, TreeMap<Long, Character> aMapToFill) {
1927     if(aScanString == null || aMapToFill == nullreturn;
1928     if(DocumentXmlUtils.entitiesMap == null || DocumentXmlUtils.entitiesMap.isEmpty()) {
1929       Err.prln("WARNING: Entities map was not initialised !");
1930       return;
1931     }// End if
1932     // Fill the Map with the offsets of the special chars
1933     Iterator<Character> entitiesMapIterator = DocumentXmlUtils.entitiesMap.keySet().iterator();
1934     Character c;
1935     int fromIndex;
1936     while(entitiesMapIterator.hasNext()) {
1937       c = entitiesMapIterator.next();
1938       fromIndex = 0;
1939       while(-!= fromIndex) {
1940         fromIndex = aScanString.indexOf(c.charValue(), fromIndex);
1941         if(-!= fromIndex) {
1942           aMapToFill.put(new Long(fromIndex), c);
1943           fromIndex++;
1944         }// End if
1945       }// End while
1946     }// End while
1947   }// buildEntityMapFromString();
1948 
1949   private String writeEmptyTag(Annotation annot) {
1950     return writeEmptyTag(annot, true);
1951   // writeEmptyTag
1952 
1953   
1954   /** Returns a string representing an empty tag based on the input annot */
1955   private String writeEmptyTag(Annotation annot, boolean includeNamespace) {
1956     // Get the annot feature used to store the namespace prefix, if it
1957     // has been defined
1958     String nsPrefix = null;
1959     if (serializeNamespaceInfo)
1960       nsPrefix = (String)annot.getFeatures().get(namespacePrefixFeature);
1961 
1962     StringBuffer strBuff = new StringBuffer("");
1963     if(annot == nullreturn strBuff.toString();
1964     strBuff.append("<");
1965     if (nsPrefix != null && !nsPrefix.isEmpty())
1966           strBuff.append(nsPrefix + ":");
1967     strBuff.append(annot.getType());
1968     AnnotationSet originalMarkupsAnnotSet = this
1969             .getAnnotations(GateConstants.ORIGINAL_MARKUPS_ANNOT_SET_NAME);
1970     if(!originalMarkupsAnnotSet.contains(annot)) {
1971       strBuff.append(" gateId=\"");
1972       strBuff.append(annot.getId());
1973       strBuff.append("\"");
1974     }
1975     strBuff.append(writeFeatures(annot.getFeatures(), includeNamespace));
1976     strBuff.append("/>");
1977     return strBuff.toString();
1978   }// writeEmptyTag()
1979 
1980   /** Returns a string representing an end tag based on the input annot */
1981   private String writeEndTag(Annotation annot) {
1982     // Get the annot feature used to store the namespace prefix, if it
1983     // has been defined
1984     String nsPrefix = null;
1985     if (serializeNamespaceInfo)
1986       nsPrefix = (String)annot.getFeatures().get(namespacePrefixFeature);
1987 
1988     StringBuffer strBuff = new StringBuffer("");
1989     if(annot == nullreturn strBuff.toString();
1990     /*
1991      * if (annot.getType().indexOf(" ") != -1) Out.prln("Warning: Truncating end
1992      * tag to first word for annot type \"" +annot.getType()+ "\". ");
1993      */
1994     strBuff.append("</");
1995     if (nsPrefix != null && !nsPrefix.isEmpty())
1996           strBuff.append(nsPrefix + ":");
1997     strBuff.append(annot.getType() ">");
1998     return strBuff.toString();
1999   }// writeEndTag()
2000 
2001   /** Returns a string representing a FeatureMap serialized as XML attributes */
2002   private String writeFeatures(FeatureMap feat, boolean includeNamespace) {
2003     StringBuffer strBuff = new StringBuffer("");
2004     if(feat == nullreturn strBuff.toString();
2005     Iterator<Object> it = feat.keySet().iterator();
2006     while(it.hasNext()) {
2007       Object key = it.next();
2008       Object value = feat.get(key);
2009       if((key != null&& (value != null)) {
2010         /**
2011          * Eliminate namespace prefix feature and rename namespace uri feature
2012          * to xmlns:prefix=uri
2013          * if these have been specified in the markup and in the config
2014          */
2015         if (serializeNamespaceInfo) {
2016           String nsPrefix = "xmlns:" (String)feat.get(namespacePrefixFeature);
2017 
2018           if (nsPrefix.equals(key.toString())) continue;
2019           if (namespacePrefixFeature.equals(key.toString())) continue;
2020           
2021           if (namespaceURIFeature.equals(key.toString())) {
2022             strBuff.append(" ");
2023             strBuff.append(nsPrefix + "=\"" + value.toString() "\"");
2024             return strBuff.toString();
2025           }
2026         }
2027         // Eliminate a feature inserted at reading time and which help to
2028         // take some decissions at saving time
2029         if("isEmptyAndSpan".equals(key.toString())) continue;
2030         if(!String.class.isAssignableFrom(key.getClass())) {
2031           Out.prln("Warning:Found a feature NAME(" + key
2032                   ") that isn't a String.(feature discarded)");
2033           continue;
2034         }// End if
2035         if(!(String.class.isAssignableFrom(value.getClass())
2036                 || Number.class.isAssignableFrom(value.getClass()) || java.util.Collection.class
2037                 .isAssignableFrom(value.getClass()) || Boolean.class.isAssignableFrom(value.getClass()))) {
2038           Out.prln("Warning:Found a feature VALUE(" + value
2039                   ") that doesn't came"
2040                   " from String, Number, Boolean, or Collection.(feature discarded)");
2041           continue;
2042         }// End if
2043         if("matches".equals(key)) {
2044           strBuff.append(" ");
2045           if(includeNamespace) {
2046             strBuff.append("gate:");
2047           }
2048           // strBuff.append(key);
2049           // replace non XML chars in attribute name
2050           strBuff.append(DocumentXmlUtils.combinedNormalisation(key
2051                   .toString()));
2052           strBuff.append("=\"");
2053         else {
2054           strBuff.append(" ");
2055           // strBuff.append(key);
2056           // replace non XML chars in attribute name
2057           strBuff.append(DocumentXmlUtils.combinedNormalisation(key
2058                   .toString()));
2059           strBuff.append("=\"");
2060         }
2061         if(java.util.Collection.class.isAssignableFrom(value.getClass())) {
2062           @SuppressWarnings("unchecked")
2063           Iterator<Object> valueIter = ((Collection<Object>)value).iterator();
2064           while(valueIter.hasNext()) {
2065             Object item = valueIter.next();
2066             if(!(String.class.isAssignableFrom(item.getClass()) || Number.class
2067                     .isAssignableFrom(item.getClass()))) continue;
2068             // strBuff.append(item);
2069             // replace non XML chars in collection item
2070             strBuff.append(DocumentXmlUtils.combinedNormalisation(item
2071                     .toString()));
2072             strBuff.append(";");
2073           }// End while
2074           if(strBuff.charAt(strBuff.length() 1== ';')
2075             strBuff.deleteCharAt(strBuff.length() 1);
2076         else {
2077           // strBuff.append(value);
2078           // replace non XML chars in attribute value
2079           strBuff.append(DocumentXmlUtils.combinedNormalisation(value
2080                   .toString()));
2081         }// End if
2082         strBuff.append("\"");
2083       }// End if
2084     }// End while
2085     return strBuff.toString();
2086   }// writeFeatures()
2087 
2088   /**
2089    * Returns a GateXml document that is a custom XML format for wich there is a
2090    * reader inside GATE called gate.xml.GateFormatXmlHandler. What it does is to
2091    * serialize a GATE document in an XML format.
2092    
2093    * Implementation note: this method simply delegates to the static {@link
2094    * DocumentStaxUtils#toXml(gate.Document)} method
2095    
2096    @return a string representing a Gate Xml document.
2097    */
2098   @Override
2099   public String toXml() {
2100     return DocumentStaxUtils.toXml(this);
2101     //return DocumentXmlUtils.toXml(this);
2102   }// toXml
2103 
2104   /**
2105    * Returns a map (possibly empty) with the named annotation sets. It returns <code>null</code>
2106    * if no named annotaton set exists.
2107    */
2108   @Override
2109   public Map<String, AnnotationSet> getNamedAnnotationSets() {
2110     if (namedAnnotSets == null) {
2111       namedAnnotSets = new HashMap<String, AnnotationSet>();
2112     }
2113     return namedAnnotSets;
2114   // getNamedAnnotationSets
2115 
2116   @Override
2117   public Set<String> getAnnotationSetNames() {
2118     if (namedAnnotSets == null) {
2119       namedAnnotSets = new HashMap<String, AnnotationSet>();
2120     }
2121     return namedAnnotSets.keySet();
2122   }
2123 
2124   /**
2125    * Removes one of the named annotation sets. Note that the default annotation
2126    * set cannot be removed.
2127    
2128    @param name
2129    *          the name of the annotation set to be removed
2130    */
2131   @Override
2132   public void removeAnnotationSet(String name) {
2133     if(namedAnnotSets != null) {
2134       AnnotationSet removed = namedAnnotSets.remove(name);
2135       if(removed != null) {
2136         fireAnnotationSetRemoved(new DocumentEvent(this,
2137                 DocumentEvent.ANNOTATION_SET_REMOVED, name));
2138       }
2139     }
2140   }
2141 
2142   /** Propagate edit changes to the document content and annotations. */
2143   @Override
2144   public void edit(Long start, Long end, DocumentContent replacement)
2145           throws InvalidOffsetException {
2146     if(!isValidOffsetRange(start, end)) throw new InvalidOffsetException("Offsets: "+start+"/"+end);
2147     if(getContent() != null)
2148       ((DocumentContentImpl)getContent()).edit(start, end, replacement);
2149     if(defaultAnnots != null)
2150       ((AnnotationSetImpl)defaultAnnots).edit(start, end, replacement);
2151     if(namedAnnotSets != null) {
2152       Iterator<AnnotationSet> iter = namedAnnotSets.values().iterator();
2153       while(iter.hasNext())
2154         ((AnnotationSetImpl)iter.next()).edit(start, end, replacement);
2155     }
2156     // let the listeners know
2157     fireContentEdited(new DocumentEvent(this, DocumentEvent.CONTENT_EDITED,
2158             start, end));
2159   // edit(start,end,replacement)
2160 
2161   /**
2162    * Check that an offset is valid, i.e. it is non-null, greater than or equal
2163    * to 0 and less than the size of the document content.
2164    */
2165   public boolean isValidOffset(Long offset) {
2166     if(offset == nullreturn false;
2167     long o = offset.longValue();
2168     if(o > getContent().size().longValue() || o < 0return false;
2169     return true;
2170   // isValidOffset
2171 
2172   /**
2173    * Check that both start and end are valid offsets and that they constitute a
2174    * valid offset range, i.e. start is greater than or equal to long.
2175    */
2176   public boolean isValidOffsetRange(Long start, Long end) {
2177     return isValidOffset(start&& isValidOffset(end)
2178             && start.longValue() <= end.longValue();
2179   // isValidOffsetRange(start,end)
2180 
2181   /** Sets the nextAnnotationId */
2182   public void setNextAnnotationId(int aNextAnnotationId) {
2183     nextAnnotationId = aNextAnnotationId;
2184   }// setNextAnnotationId();
2185 
2186   /** Generate and return the next annotation ID */
2187   public Integer getNextAnnotationId() {
2188     return new Integer(nextAnnotationId++);
2189   // getNextAnnotationId
2190   
2191   /** look at the next annotation ID without incrementing it */
2192   public Integer peakAtNextAnnotationId() {
2193     return nextAnnotationId;
2194   }
2195 
2196   /** Generate and return the next node ID */
2197   public Integer getNextNodeId() {
2198     return new Integer(nextNodeId++);
2199   }
2200 
2201   /** Ordering based on URL.toString() and the URL offsets (if any) */
2202   @Override
2203   public int compareTo(Object othrows ClassCastException {
2204     DocumentImpl other = (DocumentImpl)o;
2205     return getOrderingString().compareTo(other.getOrderingString());
2206   // compareTo
2207 
2208   /**
2209    * Utility method to produce a string for comparison in ordering. String is
2210    * based on the source URL and offsets.
2211    */
2212   protected String getOrderingString() {
2213     if(sourceUrl == nullreturn toString();
2214     StringBuffer orderingString = new StringBuffer(sourceUrl.toString());
2215     if(sourceUrlStartOffset != null && sourceUrlEndOffset != null) {
2216       orderingString.append(sourceUrlStartOffset.toString());
2217       orderingString.append(sourceUrlEndOffset.toString());
2218     }
2219     return orderingString.toString();
2220   // getOrderingString()
2221 
2222   /** The id of the next new annotation */
2223   protected int nextAnnotationId = 0;
2224 
2225   /** The id of the next new node */
2226   protected int nextNodeId = 0;
2227 
2228   /** The source URL */
2229   protected URL sourceUrl;
2230 
2231   /** The document's MIME type.  Only relevant if the document is markup aware,
2232    * and if omitted, DocumentFormat will attempt to determine the format to use
2233    * heuristically.
2234    */
2235   protected String mimeType;
2236 
2237   /** The document's URL name. */
2238   /** The content of the document */
2239   protected DocumentContent content;
2240 
2241   /** The encoding of the source of the document content */
2242   protected String encoding = null;
2243 
2244   // Data needed in toXml(AnnotationSet) methos
2245   /**
2246    * This field indicates whether or not to add the tag called
2247    * GatePreserveFormat to the document. HTML, XML, SGML docs won't have this
2248    * tag added
2249    */
2250   // private boolean addGatePreserveFormatTag = false;
2251   /**
2252    * Used by the XML dump preserving format method
2253    */
2254   private Annotation theRootAnnotation = null;
2255 
2256   /**
2257    * This field is used when creating StringBuffers for saveAnnotationSetAsXML()
2258    * methods. The size of the StringBuffer will be docDonctent.size() multiplied
2259    * by this value. It is aimed to improve the performance of StringBuffer
2260    */
2261   private static final int DOC_SIZE_MULTIPLICATION_FACTOR_AS = 3;
2262 
2263   /**
2264    * Constant used in the inner class AnnotationComparator to order annotations
2265    * on their start offset
2266    */
2267   private final int ORDER_ON_START_OFFSET = 0;
2268 
2269   /**
2270    * Constant used in the inner class AnnotationComparator to order annotations
2271    * on their end offset
2272    */
2273   private final int ORDER_ON_END_OFFSET = 1;
2274 
2275   /**
2276    * Constant used in the inner class AnnotationComparator to order annotations
2277    * on their ID
2278    */
2279   private final int ORDER_ON_ANNOT_ID = 2;
2280 
2281   /**
2282    * Constant used in the inner class AnnotationComparator to order annotations
2283    * ascending
2284    */
2285   private final int ASC = 3;
2286 
2287   /**
2288    * Constant used in the inner class AnnotationComparator to order annotations
2289    * descending
2290    */
2291   private final int DESC = -3;
2292 
2293   /**
2294    * The range that the content comes from at the source URL (or null if none).
2295    */
2296   // protected Long[] sourceUrlOffsets;
2297   /**
2298    * The start of the range that the content comes from at the source URL (or
2299    * null if none).
2300    */
2301   protected Long sourceUrlStartOffset;
2302 
2303   /**
2304    * The end of the range that the content comes from at the source URL (or null
2305    * if none).
2306    */
2307   protected Long sourceUrlEndOffset;
2308 
2309   /** The default annotation set */
2310   protected AnnotationSet defaultAnnots;
2311 
2312   /** Named sets of annotations */
2313   protected Map<String, AnnotationSet> namedAnnotSets;
2314 
2315   /**
2316    * A property of the document that will be set when the user wants to create
2317    * the document from a string, as opposed to from a URL.
2318    */
2319   private String stringContent;
2320 
2321   /**
2322    * The stringContent of a document is a property of the document that will be
2323    * set when the user wants to create the document from a string, as opposed to
2324    * from a URL. <B>Use the <TT>getContent</TT> method instead to get the
2325    * actual document content.</B>
2326    */
2327   public String getStringContent() {
2328     return stringContent;
2329   }
2330 
2331   /**
2332    * The stringContent of a document is a property of the document that will be
2333    * set when the user wants to create the document from a string, as opposed to
2334    * from a URL. <B>Use the <TT>setContent</TT> method instead to update the
2335    * actual document content.</B>
2336    */
2337   @CreoleParameter(disjunction = "source", priority = 2,
2338       comment = "The content of the document")
2339   public void setStringContent(String stringContent) {
2340     this.stringContent = stringContent;
2341   // set StringContent
2342 
2343   /** Is the document markup-aware? */
2344   protected Boolean markupAware = new Boolean(false);
2345 
2346   // /** Hash code */
2347   // public int hashCode() {
2348   // int code = getContent().hashCode();
2349   // int memberCode = (defaultAnnots == null) ? 0 : defaultAnnots.hashCode();
2350   // code += memberCode;
2351   // memberCode = (encoding == null) ? 0 : encoding.hashCode();
2352   // code += memberCode;
2353   // memberCode = (features == null) ? 0 : features.hashCode();
2354   // code += memberCode;
2355   // code += (markupAware.booleanValue()) ? 0 : 1;
2356   // memberCode = (namedAnnotSets == null) ? 0 : namedAnnotSets.hashCode();
2357   // code += memberCode;
2358   // code += nextAnnotationId;
2359   // code += nextNodeId;
2360   // memberCode = (sourceUrl == null) ? 0 : sourceUrl.hashCode();
2361   // code += memberCode;
2362   // memberCode =
2363   // (sourceUrlStartOffset == null) ? 0 : sourceUrlStartOffset.hashCode();
2364   // code += memberCode;
2365   // memberCode =
2366   // (sourceUrlEndOffset == null) ? 0 : sourceUrlEndOffset.hashCode();
2367   // code += memberCode;
2368   // return code;
2369   // } // hashcode
2370   /** String respresentation */
2371   @Override
2372   public String toString() {
2373     String n = Strings.getNl();
2374     StringBuffer s = new StringBuffer("DocumentImpl: " + n);
2375     s.append("  content:" + content + n);
2376     s.append("  defaultAnnots:" + defaultAnnots + n);
2377     s.append("  encoding:" + encoding + n);
2378     s.append("  features:" + features + n);
2379     s.append("  markupAware:" + markupAware + n);
2380     s.append("  namedAnnotSets:" + namedAnnotSets + n);
2381     s.append("  nextAnnotationId:" + nextAnnotationId + n);
2382     s.append("  nextNodeId:" + nextNodeId + n);
2383     s.append("  sourceUrl:" + sourceUrl + n);
2384     s.append("  sourceUrlStartOffset:" + sourceUrlStartOffset + n);
2385     s.append("  sourceUrlEndOffset:" + sourceUrlEndOffset + n);
2386     s.append(n);
2387     return s.toString();
2388   // toString
2389 
2390   /** Freeze the serialization UID. */
2391   static final long serialVersionUID = -8456893608311510260L;
2392 
2393   /** Inner class needed to compare annotations */
2394   class AnnotationComparator implements Comparator<Annotation> {
2395     int orderOn = -1;
2396 
2397     int orderType = ASC;
2398 
2399     /**
2400      * Constructs a comparator according to one of three sorter types:
2401      * ORDER_ON_ANNOT_TYPE, ORDER_ON_END_OFFSET, ORDER_ON_START_OFFSET
2402      */
2403     public AnnotationComparator(int anOrderOn, int anOrderType) {
2404       orderOn = anOrderOn;
2405       orderType = anOrderType;
2406     }// AnnotationComparator()
2407 
2408     /** This method must be implemented according to Comparator interface */
2409     @Override
2410     public int compare(Annotation a1, Annotation a2) {
2411 
2412       // ORDER_ON_START_OFFSET ?
2413       if(orderOn == ORDER_ON_START_OFFSET) {
2414         int result = a1.getStartNode().getOffset().compareTo(
2415                 a2.getStartNode().getOffset());
2416         if(orderType == ASC) {
2417           // ASC
2418           // If they are equal then their ID will decide.
2419           if(result == 0return a1.getId().compareTo(a2.getId());
2420           return result;
2421         else {
2422           // DESC
2423           if(result == 0return -(a1.getId().compareTo(a2.getId()));
2424           return -result;
2425         }// End if (orderType == ASC)
2426       }// End if (orderOn == ORDER_ON_START_OFFSET)
2427       // ORDER_ON_END_OFFSET ?
2428       if(orderOn == ORDER_ON_END_OFFSET) {
2429         int result = a1.getEndNode().getOffset().compareTo(
2430                 a2.getEndNode().getOffset());
2431         if(orderType == ASC) {
2432           // ASC
2433           // If they are equal then their ID will decide.
2434           if(result == 0return -(a1.getId().compareTo(a2.getId()));
2435           return result;
2436         else {
2437           // DESC
2438           // If they are equal then their ID will decide.
2439           if(result == 0return a1.getId().compareTo(a2.getId());
2440           return -result;
2441         }// End if (orderType == ASC)
2442       }// End if (orderOn == ORDER_ON_END_OFFSET)
2443       // ORDER_ON_ANNOT_ID ?
2444       if(orderOn == ORDER_ON_ANNOT_ID) {
2445         if(orderType == ASC)
2446           return a1.getId().compareTo(a2.getId());
2447         else return -(a1.getId().compareTo(a2.getId()));
2448       }// End if
2449       return 0;
2450     }// compare()
2451   // End inner class AnnotationComparator
2452 
2453   private transient Vector<DocumentListener> documentListeners;
2454 
2455   @Override
2456   public synchronized void removeDocumentListener(DocumentListener l) {
2457     if(documentListeners != null && documentListeners.contains(l)) {
2458       @SuppressWarnings("unchecked")
2459       Vector<DocumentListener> v = (Vector<DocumentListener>)documentListeners.clone();
2460       v.removeElement(l);
2461       documentListeners = v;
2462     }
2463   }
2464 
2465   @Override
2466   public synchronized void addDocumentListener(DocumentListener l) {
2467     @SuppressWarnings("unchecked")
2468     Vector<DocumentListener> v = documentListeners == null
2469             new Vector<DocumentListener>(2)
2470             (Vector<DocumentListener>)documentListeners.clone();
2471     if(!v.contains(l)) {
2472       v.addElement(l);
2473       documentListeners = v;
2474     }
2475   }
2476 
2477   protected void fireAnnotationSetAdded(DocumentEvent e) {
2478     if(documentListeners != null) {
2479       Vector<DocumentListener> listeners = documentListeners;
2480       int count = listeners.size();
2481       for(int i = 0; i < count; i++) {
2482         listeners.elementAt(i).annotationSetAdded(e);
2483       }
2484     }
2485   }
2486 
2487   protected void fireAnnotationSetRemoved(DocumentEvent e) {
2488     if(documentListeners != null) {
2489       Vector<DocumentListener> listeners = documentListeners;
2490       int count = listeners.size();
2491       for(int i = 0; i < count; i++) {
2492         listeners.elementAt(i).annotationSetRemoved(e);
2493       }
2494     }
2495   }
2496 
2497   protected void fireContentEdited(DocumentEvent e) {
2498     if(documentListeners != null) {
2499       Vector<DocumentListener> listeners = documentListeners;
2500       int count = listeners.size();
2501       for(int i = 0; i < count; i++) {
2502         listeners.elementAt(i).contentEdited(e);
2503       }
2504     }
2505   }
2506 
2507   @Override
2508   public void resourceLoaded(CreoleEvent e) {
2509   }
2510 
2511   @Override
2512   public void resourceUnloaded(CreoleEvent e) {
2513   }
2514 
2515   @Override
2516   public void datastoreOpened(CreoleEvent e) {
2517   }
2518 
2519   @Override
2520   public void datastoreCreated(CreoleEvent e) {
2521   }
2522 
2523   @Override
2524   public void resourceRenamed(Resource resource, String oldName, String newName) {
2525   }
2526 
2527   @Override
2528   public void datastoreClosed(CreoleEvent e) {
2529     if(!e.getDatastore().equals(this.getDataStore())) return;
2530     // close this lr, since it cannot stay open when the DS it comes from
2531     // is closed
2532     Factory.deleteResource(this);
2533   }
2534 
2535   @Override
2536   public void setLRPersistenceId(Object lrID) {
2537     super.setLRPersistenceId(lrID);
2538     // make persistent documents listen to the creole register
2539     // for events about their DS
2540     Gate.getCreoleRegister().addCreoleListener(this);
2541   }
2542 
2543   @Override
2544   public void resourceAdopted(DatastoreEvent evt) {
2545   }
2546 
2547   @Override
2548   public void resourceDeleted(DatastoreEvent evt) {
2549     if(!evt.getSource().equals(this.getDataStore())) return;
2550     // if an open document is deleted from a DS, then
2551     // it must close itself immediately, as is no longer valid
2552     if(evt.getResourceID().equals(this.getLRPersistenceId()))
2553       Factory.deleteResource(this);
2554   }
2555 
2556   @Override
2557   public void resourceWritten(DatastoreEvent evt) {
2558   }
2559 
2560   @Override
2561   public void setDataStore(DataStore dataStore)
2562           throws gate.persist.PersistenceException {
2563     super.setDataStore(dataStore);
2564     if(this.dataStore != nullthis.dataStore.addDatastoreListener(this);
2565   }
2566 
2567   /**
2568    * This method added by Shafirin Andrey, to allow access to protected member
2569    {@link #defaultAnnots} Required for JAPE-Debugger.
2570    */
2571   public void setDefaultAnnotations(AnnotationSet defaultAnnotations) {
2572     defaultAnnots = defaultAnnotations;
2573   }
2574 // class DocumentImpl