gt:           itemConstructor = itemClass.getConstructor(paramsArray);
context: /*
   *  DocumentStaxUtils.java
   *
   *  Copyright (c) 1995-2012, The University of Sheffield. See the file
   *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
   *
   *  This file is part of GATE (see http://gate.ac.uk/), and is free
   *  software, licenced under the GNU Library General Public License,
   *  Version 2, June 1991 (in the distribution as file licence.html,
   *  and also available at http://gate.ac.uk/gate/licence.html).
   *
   *  Ian Roberts, 20/Jul/2006
   *
   *  $Id: DocumentStaxUtils.java 15333 2012-02-07 13:18:33Z ian_roberts $
   */
  package gate.corpora;
  
  import java.io.BufferedWriter;
  import java.io.File;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.io.OutputStreamWriter;
  import java.io.StringWriter;
  import java.lang.reflect.Constructor;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.SortedSet;
  import java.util.StringTokenizer;
  import java.util.TreeSet;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import javax.xml.stream.XMLInputFactory;
  import javax.xml.stream.XMLOutputFactory;
  import javax.xml.stream.XMLStreamConstants;
  import javax.xml.stream.XMLStreamException;
  import javax.xml.stream.XMLStreamReader;
  import javax.xml.stream.XMLStreamWriter;
  
  import gate.Annotation;
  import gate.AnnotationSet;
  import gate.Document;
  import gate.DocumentContent;
  import gate.Factory;
  import gate.FeatureMap;
  import gate.Gate;
  import gate.TextualDocument;
  import gate.event.StatusListener;
  import gate.util.GateException;
  import gate.util.GateRuntimeException;
  import gate.util.InvalidOffsetException;
  import gate.util.Out;
  
  /**
   * This class provides support for reading and writing GATE XML format
   * using StAX (the Streaming API for XML).
   */
  public class DocumentStaxUtils {
  
    private static XMLInputFactory inputFactory = null;
  
    /**
     * The char used to replace characters in text content that are
     * illegal in XML.
     */
    public static final char INVALID_CHARACTER_REPLACEMENT = ' ';
  
    /**
     * The number of &lt; signs after which we encode a string using CDATA
     * rather than writeCharacters.
     */
    public static final int LT_THRESHOLD = 5;
  
    /**
     * Reads GATE XML format data from the given XMLStreamReader and puts
     * the content and annotation sets into the given Document, replacing
     * its current content. The reader must be positioned on the opening
     * GateDocument tag (i.e. the last event was a START_ELEMENT for which
     * getLocalName returns "GateDocument"), and when the method returns
     * the reader will be left positioned on the corresponding closing
     * tag.
     * 
     * @param xsr the source of the XML to parse
     * @param doc the document to update
     * @throws XMLStreamException
     */
    public static void readGateXmlDocument(XMLStreamReader xsr, Document doc)
            throws XMLStreamException {
      readGateXmlDocument(xsr, doc, null);
    }
  
    /**
     * Reads GATE XML format data from the given XMLStreamReader and puts
     * the content and annotation sets into the given Document, replacing
     * its current content. The reader must be positioned on the opening
     * GateDocument tag (i.e. the last event was a START_ELEMENT for which
     * getLocalName returns "GateDocument"), and when the method returns
     * the reader will be left positioned on the corresponding closing
     * tag.
     * 
     * @param xsr the source of the XML to parse
     * @param doc the document to update
     * @param statusListener optional status listener to receive status
     *          messages
     * @throws XMLStreamException
     */
    public static void readGateXmlDocument(XMLStreamReader xsr, Document doc,
            StatusListener statusListener) throws XMLStreamException {
      DocumentContent savedContent = null;
  
      // check the precondition
      xsr.require(XMLStreamConstants.START_ELEMENT, null, "GateDocument");
  
      // process the document features
      xsr.nextTag();
      xsr.require(XMLStreamConstants.START_ELEMENT, null, "GateDocumentFeatures");
  
      if(statusListener != null) {
        statusListener.statusChanged("Reading document features");
      }
      FeatureMap documentFeatures = readFeatureMap(xsr);
  
      // read document text, building the map of node IDs to offsets
      xsr.nextTag();
      xsr.require(XMLStreamConstants.START_ELEMENT, null, "TextWithNodes");
  
      Map<Integer, Long> nodeIdToOffsetMap = new HashMap<Integer, Long>();
      if(statusListener != null) {
        statusListener.statusChanged("Reading document content");
      }
      String documentText = readTextWithNodes(xsr, nodeIdToOffsetMap);
  
      // save the content, in case anything goes wrong later
      savedContent = doc.getContent();
      // set the document content to the text with nodes text.
      doc.setContent(new DocumentContentImpl(documentText));
  
      try {
        int numAnnots = 0;
        // process annotation sets, using the node map built above
        Integer maxAnnotId = null;
        // initially, we don't know whether annotation IDs are required or
        // not
        Boolean requireAnnotationIds = null;
        int eventType = xsr.nextTag();
        while(eventType == XMLStreamConstants.START_ELEMENT) {
          xsr.require(XMLStreamConstants.START_ELEMENT, null, "AnnotationSet");
          String annotationSetName = xsr.getAttributeValue(null, "Name");
          AnnotationSet annotationSet = null;
          if(annotationSetName == null) {
            if(statusListener != null) {
              statusListener.statusChanged("Reading default annotation set");
            }
            annotationSet = doc.getAnnotations();
          }
          else {
            if(statusListener != null) {
              statusListener.statusChanged("Reading \"" + annotationSetName
                      + "\" annotation set");
            }
            annotationSet = doc.getAnnotations(annotationSetName);
          }
          annotationSet.clear();
          SortedSet<Integer> annotIdsInSet = new TreeSet<Integer>();
          requireAnnotationIds = readAnnotationSet(xsr, annotationSet,
                  nodeIdToOffsetMap, annotIdsInSet, requireAnnotationIds);
          if(annotIdsInSet.size() > 0
                  && (maxAnnotId == null || annotIdsInSet.last().intValue() > maxAnnotId
                          .intValue())) {
            maxAnnotId = annotIdsInSet.last();
          }
          numAnnots += annotIdsInSet.size();
          // readAnnotationSet leaves reader positioned on the
          // </AnnotationSet> tag, so nextTag takes us to either the next
          // <AnnotationSet> or to the </GateDocument>
          eventType = xsr.nextTag();
        }
  
        // check we are on the end document tag
        xsr.require(XMLStreamConstants.END_ELEMENT, null, "GateDocument");
  
        doc.setFeatures(documentFeatures);
  
        // set the ID generator, if doc is a DocumentImpl
        if(doc instanceof DocumentImpl && maxAnnotId != null) {
          ((DocumentImpl)doc).setNextAnnotationId(maxAnnotId.intValue() + 1);
        }
        if(statusListener != null) {
          statusListener.statusChanged("Finished.  " + numAnnots
                  + " annotation(s) processed");
        }
      }
      // in case of exception, reset document content to the unparsed XML
      catch(XMLStreamException xse) {
        doc.setContent(savedContent);
        throw xse;
      }
      catch(RuntimeException re) {
        doc.setContent(savedContent);
        throw re;
      }
    }
  
    /**
     * Processes an AnnotationSet element from the given reader and fills
     * the given annotation set with the corresponding annotations. The
     * reader must initially be positioned on the starting AnnotationSet
     * tag and will be left positioned on the correspnding closing tag.
     * 
     * @param xsr the reader
     * @param annotationSet the annotation set to fill.
     * @param nodeIdToOffsetMap a map mapping node IDs (Integer) to their
     *          offsets in the text (Long). If null, we assume that the
     *          node ids and offsets are the same (useful if parsing an
     *          annotation set in isolation).
     * @param allAnnotIds a set to contain all annotation IDs specified in
     *          the annotation set. It should initially be empty and will
     *          be updated if any of the annotations in this set specify
     *          an ID.
     * @param requireAnnotationIds whether annotations are required to
     *          specify their IDs. If true, it is an error for an
     *          annotation to omit the Id attribute. If false, it is an
     *          error for the Id to be present. If null, we have not yet
     *          determined what style of XML this is.
     * @return <code>requireAnnotationIds</code>. If the passed in
     *         value was null, and we have since determined what it should
     *         be, the updated value is returned.
     * @throws XMLStreamException
     */
    public static Boolean readAnnotationSet(XMLStreamReader xsr,
            AnnotationSet annotationSet, Map<Integer, Long> nodeIdToOffsetMap,
            Set<Integer> allAnnotIds, Boolean requireAnnotationIds)
            throws XMLStreamException {
      List<AnnotationObject> collectedAnnots = new ArrayList<AnnotationObject>();
      while(xsr.nextTag() == XMLStreamConstants.START_ELEMENT) {
        xsr.require(XMLStreamConstants.START_ELEMENT, null, "Annotation");
        AnnotationObject annObj = new AnnotationObject();
        annObj.setElemName(xsr.getAttributeValue(null, "Type"));
        try {
          int startNodeId = Integer.parseInt(xsr.getAttributeValue(null,
                  "StartNode"));
          if(nodeIdToOffsetMap != null) {
            Long startOffset = nodeIdToOffsetMap.get(new Integer(startNodeId));
            if(startOffset != null) {
              annObj.setStart(startOffset);
            }
            else {
              throw new XMLStreamException("Invalid start node ID", xsr
                      .getLocation());
            }
          }
          else {
            // no offset map, so just use the ID as an offset
            annObj.setStart(new Long(startNodeId));
          }
        }
        catch(NumberFormatException nfe) {
          throw new XMLStreamException("Non-integer value found for StartNode",
                  xsr.getLocation());
        }
  
        try {
          int endNodeId = Integer
                  .parseInt(xsr.getAttributeValue(null, "EndNode"));
          if(nodeIdToOffsetMap != null) {
            Long endOffset = nodeIdToOffsetMap.get(new Integer(endNodeId));
            if(endOffset != null) {
              annObj.setEnd(endOffset);
            }
            else {
              throw new XMLStreamException("Invalid end node ID", xsr
                      .getLocation());
            }
          }
          else {
            // no offset map, so just use the ID as an offset
            annObj.setEnd(new Long(endNodeId));
          }
        }
        catch(NumberFormatException nfe) {
          throw new XMLStreamException("Non-integer value found for EndNode", xsr
                  .getLocation());
        }
  
        String annotIdString = xsr.getAttributeValue(null, "Id");
        if(annotIdString == null) {
          if(requireAnnotationIds == null) {
            // if one annotation doesn't specify Id than all must
            requireAnnotationIds = Boolean.FALSE;
          }
          else {
            if(requireAnnotationIds.booleanValue()) {
              // if we were expecting an Id but didn't get one...
              throw new XMLStreamException(
                      "New style GATE XML format requires that every annotation "
                              + "specify its Id, but an annotation with no Id was found",
                      xsr.getLocation());
            }
          }
        }
        else {
          // we have an ID attribute
          if(requireAnnotationIds == null) {
            // if one annotation specifies an Id then all must
            requireAnnotationIds = Boolean.TRUE;
          }
          else {
            if(!requireAnnotationIds.booleanValue()) {
              // if we were expecting not to have an Id but got one...
              throw new XMLStreamException(
                      "Old style GATE XML format requires that no annotation "
                              + "specifies its Id, but an annotation with an Id was found",
                      xsr.getLocation());
            }
          }
          try {
            Integer annotationId = Integer.valueOf(annotIdString);
            if(allAnnotIds.contains(annotationId)) {
              throw new XMLStreamException("Annotation IDs must be unique "
                      + "within an annotation set. Found duplicate ID", xsr
                      .getLocation());
            }
            allAnnotIds.add(annotationId);
            annObj.setId(annotationId);
          }
          catch(NumberFormatException nfe) {
            throw new XMLStreamException("Non-integer annotation ID found", xsr
                    .getLocation());
          }
        }
  
        // get the features of this annotation
        annObj.setFM(readFeatureMap(xsr));
        // readFeatureMap leaves xsr on the </Annotation> tag
        collectedAnnots.add(annObj);
      }
  
      // now process all found annotations.to add to the set
      Iterator<AnnotationObject> collectedAnnotsIt = collectedAnnots.iterator();
      while(collectedAnnotsIt.hasNext()) {
        AnnotationObject annObj = collectedAnnotsIt.next();
        try {
          if(annObj.getId() != null) {
            annotationSet.add(annObj.getId(), annObj.getStart(), annObj.getEnd(),
                    annObj.getElemName(), annObj.getFM());
          }
          else {
            annotationSet.add(annObj.getStart(), annObj.getEnd(), annObj
                    .getElemName(), annObj.getFM());
          }
        }
        catch(InvalidOffsetException ioe) {
          // really shouldn't happen, but could if we're not using an id
          // to offset map
          throw new XMLStreamException("Invalid offset when creating annotation "
                  + annObj, ioe);
        }
      }
      return requireAnnotationIds;
    }
  
    /**
     * Processes the TextWithNodes element from this XMLStreamReader,
     * returning the text content of the document. The supplied map is
     * updated with the offset of each Node element encountered. The
     * reader must be positioned on the starting TextWithNodes tag and
     * will be returned positioned on the corresponding closing tag.
     * 
     * @param xsr
     * @param nodeIdToOffsetMap
     * @return the text content of the document
     */
    public static String readTextWithNodes(XMLStreamReader xsr,
            Map<Integer, Long> nodeIdToOffsetMap) throws XMLStreamException {
      StringBuffer textBuf = new StringBuffer(20480);
      int eventType;
      while((eventType = xsr.next()) != XMLStreamConstants.END_ELEMENT) {
        switch(eventType) {
          case XMLStreamConstants.CHARACTERS:
            textBuf.append(xsr.getTextCharacters(), xsr.getTextStart(), xsr
                    .getTextLength());
            break;
  
          case XMLStreamConstants.START_ELEMENT:
            // only Node elements allowed
            xsr.require(XMLStreamConstants.START_ELEMENT, null, "Node");
            String idString = xsr.getAttributeValue(null, "id");
            if(idString == null) {
              throw new XMLStreamException("Node element has no id", xsr
                      .getLocation());
            }
            try {
              Integer id = Integer.valueOf(idString);
              Long offset = new Long(textBuf.length());
              nodeIdToOffsetMap.put(id, offset);
            }
            catch(NumberFormatException nfe) {
              throw new XMLStreamException("Node element must have "
                      + "integer id", xsr.getLocation());
            }
  
            // Node element must be empty
            if(xsr.next() != XMLStreamConstants.END_ELEMENT) {
              throw new XMLStreamException("Node element within TextWithNodes "
                      + "must be empty.", xsr.getLocation());
            }
            break;
  
          default:
            // do nothing - ignore comments, PIs...
        }
      }
      return textBuf.toString();
    }
  
    /**
     * Processes a GateDocumentFeatures or Annotation element to build a
     * feature map. The element is expected to contain Feature children,
     * each with a Name and Value. The reader will be returned positioned
     * on the closing GateDocumentFeatures or Annotation tag.
     * 
     * @throws XMLStreamException
     */
    public static FeatureMap readFeatureMap(XMLStreamReader xsr)
            throws XMLStreamException {
      FeatureMap fm = Factory.newFeatureMap();
      while(xsr.nextTag() == XMLStreamConstants.START_ELEMENT) {
        xsr.require(XMLStreamConstants.START_ELEMENT, null, "Feature");
        Object featureName = null;
        Object featureValue = null;
        while(xsr.nextTag() == XMLStreamConstants.START_ELEMENT) {
          if("Name".equals(xsr.getLocalName())) {
            featureName = readFeatureNameOrValue(xsr);
          }
          else if("Value".equals(xsr.getLocalName())) {
            featureValue = readFeatureNameOrValue(xsr);
          }
          else {
            throw new XMLStreamException("Feature element should contain "
                    + "only Name and Value children", xsr.getLocation());
          }
        }
        fm.put(featureName, featureValue);
      }
      return fm;
    }
  
    /**
     * Read the name or value of a feature. The reader must be initially
     * positioned on an element with className and optional itemClassName
     * attributes, and text content convertable to this class. It will be
     * returned on the corresponding end tag.
     * 
     * @param xsr the reader
     * @return the name or value represented by this element.
     * @throws XMLStreamException
     */
    static Object readFeatureNameOrValue(XMLStreamReader xsr)
            throws XMLStreamException {
      String className = xsr.getAttributeValue(null, "className");
      if(className == null) {
        className = "java.lang.String";
      }
      String itemClassName = xsr.getAttributeValue(null, "itemClassName");
      if(itemClassName == null) {
        itemClassName = "java.lang.String";
      }
      // get the string representation of the name/value
      StringBuffer stringRep = new StringBuffer(1024);
      int eventType;
      while((eventType = xsr.next()) != XMLStreamConstants.END_ELEMENT) {
        switch(eventType) {
          case XMLStreamConstants.CHARACTERS:
            stringRep.append(xsr.getTextCharacters(), xsr.getTextStart(), xsr
                    .getTextLength());
            break;
  		  
          case XMLStreamConstants.CDATA:
            stringRep.append(xsr.getTextCharacters(), xsr.getTextStart(), xsr
                    .getTextLength());
            break;
  
          case XMLStreamConstants.START_ELEMENT:
            throw new XMLStreamException("Elements not allowed within "
                    + "feature name or value element.", xsr.getLocation());
  
          default:
            // do nothing - ignore comments, PIs, etc.
        }
      }
  
      // shortcut - if class name is java.lang.String, just return the
      // string representation directly
      if("java.lang.String".equals(className)) {
        return stringRep.toString();
      }
  
      // otherwise, do some fancy reflection
      Class theClass = null;
      try {
        theClass = Class.forName(className, true, Gate.getClassLoader());
      }
      catch(ClassNotFoundException cnfe) {
        // give up and just return the String
        return stringRep.toString();
      }
  
      if(java.util.Collection.class.isAssignableFrom(theClass)) {
        Class itemClass = null;
        Constructor itemConstructor = null;
        Collection featObject = null;
  
        boolean addItemAsString = false;
  
        // construct the collection object to use as the feature value
        try {
          featObject = (Collection)theClass.newInstance();
        }
        // if we can't instantiate the collection class at all, give up
        // and return the value as a string
        catch(IllegalAccessException iae) {
          return stringRep.toString();
        }
        catch(InstantiationException ie) {
          return stringRep.toString();
        }
  
        // common case - itemClass *is* java.lang.String, so we can
        // avoid all the reflection
        if("java.lang.String".equals(itemClassName)) {
          addItemAsString = true;
        }
        else {
          try {
            itemClass = Class.forName(itemClassName, true, Gate.getClassLoader());
            // Let's detect if itemClass takes a constructor with a String
            // as param
            Class[] paramsArray = new Class[1];
            paramsArray[0] = java.lang.String.class;
  