You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ctakes.apache.org by se...@apache.org on 2018/11/09 20:56:33 UTC

svn commit: r1846266 - in /ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core: ae/ cr/ util/

Author: seanfinan
Date: Fri Nov  9 20:56:33 2018
New Revision: 1846266

URL: http://svn.apache.org/viewvc?rev=1846266&view=rev
Log:
ListAnnotator : utilize paragraphs if available
ParagraphAnnotator : fix for malformed text
AbstractFileTreeReader : enhancments for CR, patient level, single-file specification, filename sorting, doc type, doc time
FileTreeReader : extend AbstractFileTreeReader
JdbcCollectionReader : uimafittize
EssentialAnnotationUtil : add createMarkableAssertedCorefs(..) for assertion-sorted corefs
FinishedLogger : logs COMPLETE at the end of a run
MutableUimaContext : wraps normal uimacontext and allows addition of new configuration parameters

Added:
    ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/FinishedLogger.java
    ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/MutableUimaContext.java
Modified:
    ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/ae/ListAnnotator.java
    ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/ae/ParagraphAnnotator.java
    ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/AbstractFileTreeReader.java
    ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/FileTreeReader.java
    ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/JdbcCollectionReader.java
    ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/EssentialAnnotationUtil.java

Modified: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/ae/ListAnnotator.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/ae/ListAnnotator.java?rev=1846266&r1=1846265&r2=1846266&view=diff
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/ae/ListAnnotator.java (original)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/ae/ListAnnotator.java Fri Nov  9 20:56:33 2018
@@ -5,6 +5,7 @@ import org.apache.ctakes.core.resource.F
 import org.apache.ctakes.core.util.Pair;
 import org.apache.ctakes.core.util.regex.RegexSpanFinder;
 import org.apache.ctakes.typesystem.type.textspan.ListEntry;
+import org.apache.ctakes.typesystem.type.textspan.Paragraph;
 import org.apache.ctakes.typesystem.type.textspan.Segment;
 import org.apache.log4j.Logger;
 import org.apache.uima.UimaContext;
@@ -116,10 +117,24 @@ final public class ListAnnotator extends
          LOGGER.info( "Finished processing, no list types defined" );
          return;
       }
-      for ( Segment section : JCasUtil.select( jcas, Segment.class ) ) {
-         final Map<Pair<Integer>, ListType> listTypes = findListTypes( section.getCoveredText() );
-         final Map<Pair<Integer>, ListType> uniqueListTypes = getUniqueListTypes( listTypes );
-         createLists( jcas, uniqueListTypes, section.getCoveredText(), section.getBegin() );
+      final Collection<Paragraph> paragraphs = JCasUtil.select( jcas, Paragraph.class );
+      if ( paragraphs != null && !paragraphs.isEmpty() ) {
+         for ( Paragraph paragraph : paragraphs ) {
+            try {
+               final Map<Pair<Integer>, ListType> listTypes = findListTypes( paragraph.getCoveredText() );
+               final Map<Pair<Integer>, ListType> uniqueListTypes = getUniqueListTypes( listTypes );
+               createLists( jcas, uniqueListTypes, paragraph.getCoveredText(), paragraph.getBegin() );
+            } catch ( StringIndexOutOfBoundsException oobE ) {
+               // I'm not sure how this ever happens.  Paragraph bounds from the dPheParagraphAnnotator are always valid.
+               // I have run ~1000 notes without problem, but one note in Seer causes problems.  Ignore.
+            }
+         }
+      } else {
+         for ( Segment section : JCasUtil.select( jcas, Segment.class ) ) {
+            final Map<Pair<Integer>, ListType> listTypes = findListTypes( section.getCoveredText() );
+            final Map<Pair<Integer>, ListType> uniqueListTypes = getUniqueListTypes( listTypes );
+            createLists( jcas, uniqueListTypes, section.getCoveredText(), section.getBegin() );
+         }
       }
       LOGGER.info( "Finished processing" );
    }
@@ -164,12 +179,16 @@ final public class ListAnnotator extends
                if ( boundsJ.getValue1() >= boundsI.getValue1() && boundsJ.getValue1() <= boundsI.getValue2() ) {
                   removalTypeBounds.add( boundsJ );
                   if ( boundsJ.getValue2() > boundsI.getValue2() ) {
-                     newTypeBounds.put( new Pair<>( boundsI.getValue1(), boundsJ.getValue2() ), boundsI );
+//                     newTypeBounds.put( new Pair<>( boundsI.getValue1(), boundsJ.getValue2() ), boundsI );
+                     // Add J as a second list
+                     newTypeBounds.put( new Pair<>( boundsI.getValue2(), boundsJ.getValue2() ), boundsJ );
                   }
                } else if ( boundsJ.getValue2() >= boundsI.getValue1() && boundsJ.getValue2() <= boundsI.getValue2() ) {
                   removalTypeBounds.add( boundsJ );
                   if ( boundsJ.getValue1() < boundsI.getValue1() ) {
-                     newTypeBounds.put( new Pair<>( boundsJ.getValue1(), boundsI.getValue2() ), boundsI );
+//                     newTypeBounds.put( new Pair<>( boundsJ.getValue1(), boundsI.getValue2() ), boundsI );
+                     // Add J as a second list
+                     newTypeBounds.put( new Pair<>( boundsJ.getValue1(), boundsI.getValue1() ), boundsJ );
                   }
                }
             }

Modified: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/ae/ParagraphAnnotator.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/ae/ParagraphAnnotator.java?rev=1846266&r1=1846265&r2=1846266&view=diff
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/ae/ParagraphAnnotator.java (original)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/ae/ParagraphAnnotator.java Fri Nov  9 20:56:33 2018
@@ -157,27 +157,31 @@ final public class ParagraphAnnotator ex
          if ( leftBounds.getValue1() > 0 ) {
             // Add unspecified generic first paragraph
             paragraphEnd = leftBounds.getValue1();
-            final Paragraph paragraph = new Paragraph( jcas, offset, offset + paragraphEnd );
-            paragraph.addToIndexes();
+            if ( offset < 0 || offset + paragraphEnd < 0 ) {
+               LOGGER.error( "First Paragraph out of bounds " + offset + "," + (offset + paragraphEnd) );
+            } else {
+               final Paragraph paragraph = new Paragraph( jcas, offset, offset + paragraphEnd );
+               paragraph.addToIndexes();
+            }
             // will start the next paragraph with bounds at 0
          }
          final int length = boundsList.size();
          // add segments 1 -> n
          for ( int i = 0; i < length; i++ ) {
             leftBounds = boundsList.get( i );
-            final int paragraphBegin = leftBounds.getValue2();
+            final int paragraphBegin = leftBounds.getValue1();
             if ( i + 1 < length ) {
                paragraphEnd = boundsList.get( i + 1 ).getValue1();
             } else {
                // the last paragraph
                paragraphEnd = text.length();
             }
-            if ( paragraphEnd - paragraphBegin <= 1 ) {
-               // a length <= 1 means that we have one tag right after another, so the paragraph is empty
-               continue;
+            if ( offset + paragraphBegin < 0 || offset + paragraphEnd < 0 ) {
+               LOGGER.error( "Paragraph out of bounds " + (offset + paragraphBegin) + "," + (offset + paragraphEnd) );
+            } else {
+               final Paragraph paragraph = new Paragraph( jcas, offset + paragraphBegin, offset + paragraphEnd );
+               paragraph.addToIndexes();
             }
-            final Paragraph paragraph = new Paragraph( jcas, offset + paragraphBegin, offset + paragraphEnd );
-            paragraph.addToIndexes();
          }
       }
    }

Modified: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/AbstractFileTreeReader.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/AbstractFileTreeReader.java?rev=1846266&r1=1846265&r2=1846266&view=diff
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/AbstractFileTreeReader.java (original)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/AbstractFileTreeReader.java Fri Nov  9 20:56:33 2018
@@ -1,11 +1,15 @@
 package org.apache.ctakes.core.cr;
 
 import org.apache.ctakes.core.config.ConfigParameterConstants;
+import org.apache.ctakes.core.note.NoteSpecs;
+import org.apache.ctakes.core.patient.PatientNoteStore;
 import org.apache.ctakes.core.resource.FileLocator;
 import org.apache.ctakes.core.util.NumberedSuffixComparator;
+import org.apache.ctakes.core.util.SourceMetadataUtil;
 import org.apache.ctakes.typesystem.type.structured.DocumentID;
 import org.apache.ctakes.typesystem.type.structured.DocumentIdPrefix;
 import org.apache.ctakes.typesystem.type.structured.DocumentPath;
+import org.apache.ctakes.typesystem.type.structured.SourceData;
 import org.apache.log4j.Logger;
 import org.apache.uima.UimaContext;
 import org.apache.uima.collection.CollectionException;
@@ -32,7 +36,10 @@ import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.lang.reflect.Array;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
 import java.util.*;
+import java.util.regex.Pattern;
 
 
 /**
@@ -63,10 +70,11 @@ abstract public class AbstractFileTreeRe
     * be used.
     */
    static public final String PARAM_ENCODING = "Encoding";
+   static public final String UNICODE = "unicode";
    @ConfigurationParameter(
          name = PARAM_ENCODING,
          description = "The character encoding used by the input files.",
-         defaultValue = "unicode",
+//         defaultValue = UNICODE,
          mandatory = false
    )
    private String _encoding;
@@ -85,9 +93,58 @@ abstract public class AbstractFileTreeRe
    )
    private String[] _explicitExtensions;
 
+   /**
+    * Name of configuration parameter that must be set to false to remove windows \r characters
+    */
+   public static final String PARAM_KEEP_CR = "KeepCR";
+   @ConfigurationParameter(
+         name = PARAM_KEEP_CR,
+         description = "Keep windows-format carriage return characters at line endings." +
+                       "  This will only keep existing characters, it will not add them.",
+         mandatory = false
+   )
+   private boolean _keepCrChar = true;
+
+   /**
+    * Name of configuration parameter that must be set to true to replace windows "\r\n" sequnces with "\n ".
+    * Useful if windows Carriage Return characters wreak havoc upon trained models but text offsets must be preserved.
+    * This may not play well with components that utilize double-space sequences.
+    */
+   public static final String CR_TO_SPACE = "CRtoSpace";
+   @ConfigurationParameter(
+         name = CR_TO_SPACE,
+         description = "Change windows-format CR + LF character sequences to LF + <Space>.",
+         mandatory = false
+   )
+   private boolean _crToSpace = false;
+
+
+   /**
+    * The patient id for each note is set using a directory name.
+    * By default this is the directory directly under the root directory (PatientLevel=1).
+    * This is appropriate for files such as in rootDir=data/, file in data/patientA/Text1.txt
+    * It can be set to use directory names at any level below.
+    * For instance, using PatientLevel=2 for rootDir=data/, file in data/hospitalX/patientA/Text1.txt
+    * In this manner the notes for the same patient from several sites can be properly collated.
+    */
+   public static final String PATIENT_LEVEL = "PatientLevel";
+   @ConfigurationParameter(
+         name = PATIENT_LEVEL,
+         description = "The level in the directory hierarchy at which patient identifiers exist."
+                       + "Default value is 1; directly under root input directory.",
+         mandatory = false
+   )
+   private int _patientLevel = 1;
+
+   static protected final String UNKNOWN = "Unknown";
+   static private final DateFormat DATE_FORMAT = new SimpleDateFormat( "yyyyMMddhhmm" );
+   static private final Pattern CR_LF = Pattern.compile( "\\r\\n" );
+
    private File _rootDir;
    private Collection<String> _validExtensions;
    private List<File> _files;
+   private Map<File, String> _filePatients;
+   private Map<String, Integer> _patientDocCounts = new HashMap<>();
    private int _currentIndex;
    private Comparator<File> _fileComparator;
 
@@ -110,6 +167,10 @@ abstract public class AbstractFileTreeRe
       return new FileComparator();
    }
 
+   public DateFormat getDateFormat() {
+      return DATE_FORMAT;
+   }
+
    /**
     * Gets the total number of documents that will be returned by this
     * collection reader.
@@ -148,15 +209,15 @@ abstract public class AbstractFileTreeRe
    }
 
    /**
-    * @return any specified valid file encodings.  If none are specified then the default is "Unicode".
+    * @return any specified valid file encodings.  If none are specified then the default is {@link #UNKNOWN}.
     */
    final protected String getValidEncoding() {
       if ( _rootDir == null ) {
          LOGGER.error( "Not yet initialized" );
-         return "Unknown";
+         return UNKNOWN;
       }
       if ( _encoding == null || _encoding.isEmpty() ) {
-         return "Unicode";
+         return UNKNOWN;
       }
       return _encoding;
    }
@@ -185,16 +246,36 @@ abstract public class AbstractFileTreeRe
          throw new ResourceInitializationException( fnfE );
       }
       _validExtensions = createValidExtensions( _explicitExtensions );
-
       _currentIndex = 0;
-      _files = getDescendentFiles( getRootDir(), getValidExtensions() );
+      if ( _rootDir.isFile() ) {
+         // does not check for valid extensions.  With one file just trust the user.
+         final String patient = _rootDir.getParentFile().getName();
+         _files = Collections.singletonList( _rootDir );
+         _filePatients = Collections.singletonMap( _rootDir, patient );
+         PatientNoteStore.getInstance().setWantedDocCount( patient, 1 );
+      } else {
+         // gather all of the files and set the document counts per patient.
+         final File[] children = _rootDir.listFiles();
+         if ( children == null || children.length == 0 ) {
+            _filePatients = Collections.emptyMap();
+            _files = Collections.emptyList();
+            return;
+         }
+         if ( Arrays.stream( children ).noneMatch( File::isDirectory ) ) {
+            _patientLevel = 0;
+         }
+         _filePatients = new HashMap<>();
+         _fileComparator = createFileComparator();
+         _files = getDescendentFiles( _rootDir, _validExtensions, 0 );
+         _patientDocCounts.forEach( ( k, v ) -> PatientNoteStore.getInstance().setWantedDocCount( k, v ) );
+      }
    }
 
    /**
     * @param explicitExtensions array of file extensions as specified in the uima parameters
     * @return a collection of dot-prefixed extensions or none if {@code explicitExtensions} is null or empty
     */
-   static private Collection<String> createValidExtensions( final String... explicitExtensions ) {
+   static protected Collection<String> createValidExtensions( final String... explicitExtensions ) {
       if ( explicitExtensions == null || explicitExtensions.length == 0 ) {
          return Collections.emptyList();
       }
@@ -216,31 +297,38 @@ abstract public class AbstractFileTreeRe
    /**
     * @param parentDir       -
     * @param validExtensions collection of valid extensions or empty collection if all extensions are valid
+    * @param level           directory level beneath the root directory
     * @return List of files descending from the parent directory
     */
-   protected List<File> getDescendentFiles( final File parentDir, final Collection<String> validExtensions ) {
-      if ( _fileComparator == null ) {
-         _fileComparator = createFileComparator();
-      }
+   private List<File> getDescendentFiles( final File parentDir,
+                                          final Collection<String> validExtensions,
+                                          final int level ) {
       final File[] children = parentDir.listFiles();
       if ( children == null || children.length == 0 ) {
          return Collections.emptyList();
       }
       final List<File> childDirs = new ArrayList<>();
-      final List<File> descendentFiles = new ArrayList<>();
+      final List<File> files = new ArrayList<>();
       for ( File child : children ) {
          if ( child.isDirectory() ) {
             childDirs.add( child );
             continue;
          }
          if ( isExtensionValid( child, validExtensions ) && !child.isHidden() ) {
-            descendentFiles.add( child );
+            files.add( child );
          }
       }
-      descendentFiles.sort( _fileComparator );
       childDirs.sort( _fileComparator );
+      files.sort( _fileComparator );
+      final List<File> descendentFiles = new ArrayList<>( files );
       for ( File childDir : childDirs ) {
-         descendentFiles.addAll( getDescendentFiles( childDir, validExtensions ) );
+         descendentFiles.addAll( getDescendentFiles( childDir, validExtensions, level + 1 ) );
+      }
+      if ( level == _patientLevel ) {
+         final String patientId = parentDir.getName();
+         final int count = _patientDocCounts.getOrDefault( patientId, 0 );
+         _patientDocCounts.put( patientId, count + descendentFiles.size() );
+         descendentFiles.forEach( f -> _filePatients.put( f, patientId ) );
       }
       return descendentFiles;
    }
@@ -250,7 +338,7 @@ abstract public class AbstractFileTreeRe
     * @param validExtensions -
     * @return true if validExtensions is empty or contains an extension belonging to the given file
     */
-   static private boolean isExtensionValid( final File file, final Collection<String> validExtensions ) {
+   static protected boolean isExtensionValid( final File file, final Collection<String> validExtensions ) {
       if ( validExtensions.isEmpty() ) {
          return true;
       }
@@ -273,7 +361,7 @@ abstract public class AbstractFileTreeRe
     * @param validExtensions -
     * @return the file name with the longest valid extension removed
     */
-   protected String createDocumentID( final File file, final Collection<String> validExtensions ) {
+   static protected String createDocumentID( final File file, final Collection<String> validExtensions ) {
       final String fileName = file.getName();
       String maxExtension = "";
       for ( String extension : validExtensions ) {
@@ -305,6 +393,47 @@ abstract public class AbstractFileTreeRe
       return parentPath.substring( rootPath.length() + 1 );
    }
 
+   /**
+    * @param documentId -
+    * @return the file name with the longest valid extension removed
+    */
+   protected String createDocumentType( final String documentId ) {
+      final int lastScore = documentId.lastIndexOf( '_' );
+      if ( lastScore < 0 || lastScore == documentId.length() - 1 ) {
+         return NoteSpecs.ID_NAME_CLINICAL_NOTE;
+      }
+      return documentId.substring( lastScore + 1 );
+   }
+
+   /**
+    * @param file -
+    * @return the file's last modification date as a string : {@link #getDateFormat()}
+    */
+   protected String createDocumentTime( final File file ) {
+      final long millis = file.lastModified();
+      return getDateFormat().format( millis );
+   }
+
+   final protected boolean isKeepCrChar() {
+      return _keepCrChar;
+   }
+
+   /**
+    * @param text document text
+    * @return the document text with end of line characters replaced if needed
+    */
+   final protected String handleTextEol( final String text ) {
+      String docText = text;
+      if ( !isKeepCrChar() && !docText.isEmpty() && docText.contains( "\r" ) ) {
+         LOGGER.debug( "Removing Carriage-Return characters ..." );
+         docText = CR_LF.matcher( docText ).replaceAll( "\n" );
+      }
+      if ( !docText.isEmpty() && !docText.endsWith( "\n" ) ) {
+         // Make sure that we end with a newline
+         docText += "\n";
+      }
+      return docText;
+   }
 
    /**
     * {@inheritDoc}
@@ -332,6 +461,13 @@ abstract public class AbstractFileTreeRe
       final String idPrefix = createDocumentIdPrefix( file, getRootDir() );
       documentIdPrefix.setDocumentIdPrefix( idPrefix );
       documentIdPrefix.addToIndexes();
+      final SourceData sourceData = SourceMetadataUtil.getOrCreateSourceData( jcas );
+      final String docType = createDocumentType( id );
+      sourceData.setNoteTypeCode( docType );
+      final String docTime = createDocumentTime( file );
+      sourceData.setSourceRevisionDate( docTime );
+      final String patientId = _filePatients.get( file );
+      SourceMetadataUtil.setPatientIdentifier( jcas, patientId );
       final DocumentPath documentPath = new DocumentPath( jcas );
       documentPath.setDocumentPath( file.getAbsolutePath() );
       documentPath.addToIndexes();

Modified: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/FileTreeReader.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/FileTreeReader.java?rev=1846266&r1=1846265&r2=1846266&view=diff
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/FileTreeReader.java (original)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/FileTreeReader.java Fri Nov  9 20:56:33 2018
@@ -1,24 +1,12 @@
 package org.apache.ctakes.core.cr;
 
 import org.apache.ctakes.core.config.ConfigParameterConstants;
-import org.apache.ctakes.core.patient.PatientNoteStore;
 import org.apache.ctakes.core.pipeline.PipeBitInfo;
-import org.apache.ctakes.core.resource.FileLocator;
-import org.apache.ctakes.core.util.SourceMetadataUtil;
-import org.apache.ctakes.typesystem.type.structured.DocumentID;
-import org.apache.ctakes.typesystem.type.structured.DocumentIdPrefix;
-import org.apache.ctakes.typesystem.type.structured.DocumentPath;
 import org.apache.log4j.Logger;
-import org.apache.uima.UimaContext;
-import org.apache.uima.collection.CollectionException;
 import org.apache.uima.collection.CollectionReader;
-import org.apache.uima.fit.component.JCasCollectionReader_ImplBase;
-import org.apache.uima.fit.descriptor.ConfigurationParameter;
 import org.apache.uima.fit.factory.CollectionReaderFactory;
 import org.apache.uima.jcas.JCas;
 import org.apache.uima.resource.ResourceInitializationException;
-import org.apache.uima.util.Progress;
-import org.apache.uima.util.ProgressImpl;
 
 import java.io.*;
 import java.nio.charset.Charset;
@@ -26,7 +14,6 @@ import java.nio.charset.CharsetDecoder;
 import java.nio.charset.CodingErrorAction;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
-import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -46,278 +33,22 @@ import java.util.stream.Stream;
       role = PipeBitInfo.Role.READER,
       products = { PipeBitInfo.TypeProduct.DOCUMENT_ID, PipeBitInfo.TypeProduct.DOCUMENT_ID_PREFIX }
 )
-final public class FileTreeReader extends JCasCollectionReader_ImplBase {
+final public class FileTreeReader extends AbstractFileTreeReader {
 
    static private final Logger LOGGER = Logger.getLogger( "FileTreeReader" );
 
    /**
-    * Name of configuration parameter that must be set to the path of
-    * a directory containing input files.
+    * @param jCas unpopulated jcas
+    * @param file file to be read
+    * @throws IOException should anything bad happen
     */
-   @ConfigurationParameter(
-         name = ConfigParameterConstants.PARAM_INPUTDIR,
-         description = ConfigParameterConstants.DESC_INPUTDIR
-   )
-   private String _rootDirPath;
-
-   /**
-    * Name of configuration parameter that contains the character encoding used
-    * by the input files.  If not specified, the default system encoding will
-    * be used.
-    */
-   public static final String PARAM_ENCODING = "Encoding";
-   @ConfigurationParameter(
-         name = PARAM_ENCODING,
-         description = "The character encoding used by the input files.",
-         mandatory = false
-   )
-   private String _encoding;
-
-   /**
-    * Name of optional configuration parameter that specifies the extensions
-    * of the files that the collection reader will read.  Values for this
-    * parameter should not begin with a dot <code>'.'</code>.
-    */
-   public static final String PARAM_EXTENSIONS = "Extensions";
-   @ConfigurationParameter(
-         name = PARAM_EXTENSIONS,
-         description = "The extensions of the files that the collection reader will read." +
-                       "  Values for this parameter should not begin with a dot.",
-         mandatory = false
-   )
-   private String[] _explicitExtensions;
-
-   /**
-    * Name of configuration parameter that must be set to false to remove windows \r characters
-    */
-   public static final String PARAM_KEEP_CR = "KeepCR";
-   @ConfigurationParameter(
-         name = PARAM_KEEP_CR,
-         description = "Keep windows-format carriage return characters at line endings." +
-               "  This will only keep existing characters, it will not add them.",
-         mandatory = false
-   )
-   private boolean _keepCrChar = true;
-
-   /**
-    * The patient id for each note is set using a directory name.
-    * By default this is the directory directly under the root directory (PatientLevel=1).
-    * This is appropriate for files such as in rootDir=data/, file in data/patientA/Text1.txt
-    * It can be set to use directory names at any level below.
-    * For instance, using PatientLevel=2 for rootDir=data/, file in data/hospitalX/patientA/Text1.txt
-    * In this manner the notes for the same patient from several sites can be properly collated.
-    */
-   public static final String PATIENT_LEVEL = "PatientLevel";
-   @ConfigurationParameter(
-         name = PATIENT_LEVEL,
-         description = "The level in the directory hierarchy at which patient identifiers exist."
-               + "Default value is 1; directly under root input directory.",
-         mandatory = false
-   )
-   private int _patientLevel = 1;
-
-   private File _rootDir;
-   private Collection<String> _validExtensions;
-   private List<File> _files;
-   private Map<File, String> _filePatients;
-   private int _currentIndex;
-   private Map<String, Integer> _patientDocCounts = new HashMap<>();
-
-   /**
-    * {@inheritDoc}
-    */
-   @Override
-   public void initialize( final UimaContext context ) throws ResourceInitializationException {
-      super.initialize( context );
-      try {
-         _rootDir = FileLocator.getFile( _rootDirPath );
-      } catch ( FileNotFoundException fnfE ) {
-         throw new ResourceInitializationException( fnfE );
-      }
-      _validExtensions = createValidExtensions( _explicitExtensions );
-      _currentIndex = 0;
-      if ( _rootDir.isFile() ) {
-         // does not check for valid extensions.  With one file just trust the user.
-         final String patient = _rootDir.getParentFile().getName();
-         _files = Collections.singletonList( _rootDir );
-         _filePatients = Collections.singletonMap( _rootDir, patient );
-         PatientNoteStore.getInstance().setWantedDocCount( patient, 1 );
-      } else {
-         // gather all of the files and set the document counts per patient.
-         _filePatients = new HashMap<>();
-         _files = getDescendentFiles( _rootDir, _validExtensions, 0 );
-         _patientDocCounts.forEach( ( k, v ) -> PatientNoteStore.getInstance().setWantedDocCount( k, v ) );
-      }
-   }
-
-   /**
-    * @param explicitExtensions array of file extensions as specified in the uima parameters
-    * @return a collection of dot-prefixed extensions or none if {@code explicitExtensions} is null or empty
-    */
-   static Collection<String> createValidExtensions( final String... explicitExtensions ) {
-      if ( explicitExtensions == null || explicitExtensions.length == 0 ) {
-         return Collections.emptyList();
-      }
-      if ( explicitExtensions.length == 1
-           && (explicitExtensions[ 0 ].equals( "*" ) || explicitExtensions[ 0 ].equals( ".*" )) ) {
-         return Collections.emptyList();
-      }
-      final Collection<String> validExtensions = new ArrayList<>( explicitExtensions.length );
-      for ( String extension : explicitExtensions ) {
-         if ( extension.startsWith( "." ) ) {
-            validExtensions.add( extension );
-         } else {
-            validExtensions.add( '.' + extension );
-         }
-      }
-      return validExtensions;
-   }
-
-   /**
-    * @param parentDir       -
-    * @param validExtensions collection of valid extensions or empty collection if all extensions are valid
-    * @param level directory level beneath the root directory
-    * @return List of files descending from the parent directory
-    */
-   private List<File> getDescendentFiles( final File parentDir,
-                                          final Collection<String> validExtensions,
-                                          final int level ) {
-      final File[] children = parentDir.listFiles();
-      if ( children == null || children.length == 0 ) {
-         return Collections.emptyList();
-      }
-      final Collection<File> childDirs = new ArrayList<>();
-      final List<File> descendentFiles = new ArrayList<>();
-      for ( File child : children ) {
-         if ( child.isDirectory() ) {
-            childDirs.add( child );
-            continue;
-         }
-         if ( isExtensionValid( child, validExtensions ) && !child.isHidden() ) {
-            descendentFiles.add( child );
-         }
-      }
-      // TODO copy in TextNumberComparator and delegate ...
-//      Collections.sort( descendentFiles, FileComparator );
-      for ( File childDir : childDirs ) {
-         descendentFiles.addAll( getDescendentFiles( childDir, validExtensions, level + 1 ) );
-      }
-      if ( level == _patientLevel ) {
-         final String patientId = parentDir.getName();
-         final int count = _patientDocCounts.getOrDefault( patientId, 0 );
-         _patientDocCounts.put( patientId, count + descendentFiles.size() );
-         descendentFiles.forEach( f -> _filePatients.put( f, patientId ) );
-      }
-      return descendentFiles;
-   }
-
-   /**
-    * @param file            -
-    * @param validExtensions -
-    * @return true if validExtensions is empty or contains an extension belonging to the given file
-    */
-   static boolean isExtensionValid( final File file, final Collection<String> validExtensions ) {
-      if ( validExtensions.isEmpty() ) {
-         return true;
-      }
-      final String fileName = file.getName();
-      for ( String extension : validExtensions ) {
-         if ( fileName.endsWith( extension ) ) {
-            if ( fileName.equals( extension ) ) {
-               LOGGER.warn( "File " + file.getPath() + " is named as extension " + extension + " ; discarded" );
-               return false;
-            }
-            return true;
-         }
-      }
-      return false;
-   }
-
-   /**
-    * @param file            -
-    * @param validExtensions -
-    * @return the file name with the longest valid extension removed
-    */
-   static String createDocumentID( final File file, final Collection<String> validExtensions ) {
-      final String fileName = file.getName();
-      String maxExtension = "";
-      for ( String extension : validExtensions ) {
-         if ( fileName.endsWith( extension ) && extension.length() > maxExtension.length() ) {
-            maxExtension = extension;
-         }
-      }
-      int lastDot = fileName.lastIndexOf( '.' );
-      if ( !maxExtension.isEmpty() ) {
-         lastDot = fileName.length() - maxExtension.length();
-      }
-      if ( lastDot < 0 ) {
-         return fileName;
-      }
-      return fileName.substring( 0, lastDot );
-   }
-
-   /**
-    * @param file    -
-    * @param rootDir -
-    * @return the subdirectory path between the root directory and the file
-    */
-   static private String createDocumentIdPrefix( final File file, final File rootDir ) {
-      final String parentPath = file.getParent();
-      final String rootPath = rootDir.getPath();
-      if ( parentPath.equals( rootPath ) || !parentPath.startsWith( rootPath ) ) {
-         return "";
-      }
-      return parentPath.substring( rootPath.length() + 1 );
-   }
-
-   /**
-    * Gets the total number of documents that will be returned by this
-    * collection reader.  This is not part of the general collection reader
-    * interface.
-    *
-    * @return the number of documents in the collection
-    */
-   public int getNumberOfDocuments() {
-      return _files.size();
-   }
-
-
-   /**
-    * {@inheritDoc}
-    */
-   @Override
-   public boolean hasNext() {
-      return _currentIndex < _files.size();
-   }
-
-   /**
-    * {@inheritDoc}
-    */
-   @Override
-   public void getNext( final JCas jcas ) throws IOException, CollectionException {
-      final File file = _files.get( _currentIndex );
-      _currentIndex++;
+   protected void readFile( final JCas jCas, final File file ) throws IOException {
       String docText = readFile( file );
-      if ( !docText.isEmpty() && !docText.endsWith( "\n" ) ) {
-         // Make sure that we end with a newline
-         docText += "\n";
-      }
-      jcas.setDocumentText( docText );
-      final DocumentID documentId = new DocumentID( jcas );
-      final String id = createDocumentID( file, _validExtensions );
-      documentId.setDocumentID( id );
-      documentId.addToIndexes();
-      final DocumentIdPrefix documentIdPrefix = new DocumentIdPrefix( jcas );
-      final String idPrefix = createDocumentIdPrefix( file, _rootDir );
-      documentIdPrefix.setDocumentIdPrefix( idPrefix );
-      documentIdPrefix.addToIndexes();
-      final String patientId = _filePatients.get( file );
-      SourceMetadataUtil.setPatientIdentifier( jcas, patientId );
-      final DocumentPath documentPath = new DocumentPath( jcas );
-      documentPath.setDocumentPath( file.getAbsolutePath() );
-      documentPath.addToIndexes();
+      docText = handleTextEol( docText );
+      jCas.setDocumentText( docText );
    }
 
+
    /**
     * Reads file using a Path and stream.  Failing that it calls {@link #readByBuffer(File)}
     *
@@ -326,8 +57,8 @@ final public class FileTreeReader extend
     * @throws IOException if the file could not be read
     */
    private String readFile( final File file ) throws IOException {
-      LOGGER.info( "Reading " + file.getPath() );
-      if ( !_keepCrChar ) {
+      LOGGER.info( "Reading " + file.getPath() + " ..." );
+      if ( !isKeepCrChar() ) {
          try {
             return readByPath( file );
          } catch ( IOException ioE ) {
@@ -347,8 +78,9 @@ final public class FileTreeReader extend
     * @throws IOException if the file could not be read
     */
    private String readByPath( final File file ) throws IOException {
-      if ( _encoding != null && !_encoding.isEmpty() ) {
-         final Charset charset = Charset.forName( _encoding );
+      final String encoding = getValidEncoding();
+      if ( encoding != null && !encoding.isEmpty() && !UNKNOWN.equals( encoding ) ) {
+         final Charset charset = Charset.forName( encoding );
          try ( Stream<String> stream = Files.lines( file.toPath(), charset ) ) {
             return stream.collect( Collectors.joining( "\n" ) );
          }
@@ -372,6 +104,7 @@ final public class FileTreeReader extend
     * @throws IOException if the file could not be read
     */
    private String readByBuffer( final File file ) throws IOException {
+      final String encoding = getValidEncoding();
       // Use 8KB as the default buffer size
       byte[] buffer = new byte[ 8192 ];
       final StringBuilder sb = new StringBuilder();
@@ -381,8 +114,8 @@ final public class FileTreeReader extend
             if ( length < 0 ) {
                break;
             }
-            if ( _encoding != null ) {
-               sb.append( new String( buffer, 0, length, _encoding ) );
+            if ( encoding != null && !encoding.isEmpty() && !UNKNOWN.equals( encoding ) ) {
+               sb.append( new String( buffer, 0, length, encoding ) );
             } else {
                sb.append( new String( buffer, 0, length ) );
             }
@@ -394,24 +127,6 @@ final public class FileTreeReader extend
    }
 
    /**
-    * {@inheritDoc}
-    */
-   @Override
-   public void close() throws IOException {
-   }
-
-   /**
-    * {@inheritDoc}
-    */
-   @Override
-   public Progress[] getProgress() {
-      return new Progress[] {
-            new ProgressImpl( _currentIndex, _files.size(), Progress.ENTITIES )
-      };
-   }
-
-
-   /**
     * Convenience method to create a reader with an input directory
     *
     * @param inputDirectory -

Modified: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/JdbcCollectionReader.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/JdbcCollectionReader.java?rev=1846266&r1=1846265&r2=1846266&view=diff
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/JdbcCollectionReader.java (original)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/cr/JdbcCollectionReader.java Fri Nov  9 20:56:33 2018
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -23,9 +23,12 @@ import org.apache.ctakes.core.resource.F
 import org.apache.ctakes.core.resource.JdbcConnectionResource;
 import org.apache.ctakes.typesystem.type.structured.DocumentID;
 import org.apache.log4j.Logger;
-import org.apache.uima.cas.CAS;
+import org.apache.uima.UimaContext;
 import org.apache.uima.collection.CollectionException;
-import org.apache.uima.collection.CollectionReader_ImplBase;
+import org.apache.uima.fit.component.JCasCollectionReader_ImplBase;
+import org.apache.uima.fit.descriptor.ConfigurationParameter;
+import org.apache.uima.jcas.JCas;
+import org.apache.uima.resource.ResourceAccessException;
 import org.apache.uima.resource.ResourceInitializationException;
 import org.apache.uima.util.Progress;
 import org.apache.uima.util.ProgressImpl;
@@ -41,7 +44,7 @@ import java.util.StringTokenizer;
 
 /**
  * Collection Reader that pulls documents to be processed from a database.
- * 
+ *
  * @author Mayo Clinic
  */
 @PipeBitInfo(
@@ -50,391 +53,368 @@ import java.util.StringTokenizer;
       role = PipeBitInfo.Role.READER,
       products = { PipeBitInfo.TypeProduct.DOCUMENT_ID }
 )
-public class JdbcCollectionReader extends CollectionReader_ImplBase
-{
-
-    // LOG4J logger based on class name
-    private Logger logger = Logger.getLogger(getClass().getName());
+public class JdbcCollectionReader extends JCasCollectionReader_ImplBase {
 
-    /**
-     * SQL statement to retrieve the document.
-     */
-    public static final String PARAM_SQL = "SqlStatement";
-
-    /**
-     * Name of column from resultset that contains the document text. Supported
-     * column types are CHAR, VARCHAR, and CLOB.
-     */
-    public static final String PARAM_DOCTEXT_COL = "DocTextColName";
-
-    /**
-     * Name of external resource for database connection.
-     */
-    public static final String PARAM_DB_CONN_RESRC = "DbConnResrcName";
-
-    /**
-     * Optional parameter. Specifies column names that will be used to form a
-     * document ID.
-     */
-    public static final String PARAM_DOCID_COLS = "DocIdColNames";
-
-    /**
-     * Optional parameter. Specifies delimiter used when document ID is built.
-     */
-    public static final String PARAM_DOCID_DELIMITER = "DocIdDelimiter";
-
-    /**
-     * Optional parameter. Name of external resource for prepared statement
-     * value file. Each line of this file represents prepared statement values
-     * that will be used to substitute for the "?" placeholders. TAB character
-     * \t is used to delimit the values on a single line. The prepared statement
-     * will be called once for each line in this file.
-     */
-    public static final String PARAM_VALUE_FILE_RESRC = "ValueFileResrcName";
-
-    private PreparedStatement queryPrepStmt;
-    private ResultSet rs;
-
-    private String docTextColName;
-    private int docColType;
-    private String docColTypeName;
-
-    // optional, will remain null if not set
-    private String[] docIdColNames = null;
-
-    // default is underscore
-    private String docIdDelimiter = "_";
-
-    private int totalRowCount = 0;
-    private int currRowCount = 0;
-
-    // optional, will remain null if not set
-    // Array of List objects. Each List objects represents a list of prepared
-    // stmt values.
-    private List<String>[] prepStmtValArr = null;
-    private int prepStmtValArrIdx = 0;
-    private boolean usePrepStmtVals = false;
+   // LOG4J logger based on class name
+   private Logger logger = Logger.getLogger( getClass().getName() );
 
+   /**
+    * SQL statement to retrieve the document.
+    */
+   public static final String PARAM_SQL = "SqlStatement";
+   @ConfigurationParameter(
+         name = PARAM_SQL,
+         description = "SQL statement to retrieve the document."
+   )
+   private String _sqlStatement;
+
+   /**
+    * Name of column from resultset that contains the document text. Supported
+    * column types are CHAR, VARCHAR, and CLOB.
+    */
+   public static final String PARAM_DOCTEXT_COL = "DocTextColName";
+   @ConfigurationParameter(
+         name = PARAM_DOCTEXT_COL,
+         description = "Name of column from resultset that contains the document text."
+   )
+   private String _docTextColName;
+
+   /**
+    * Name of external resource for database connection.
+    */
+   public static final String PARAM_DB_CONN_RESRC = "DbConnResrcName";
+   @ConfigurationParameter(
+         name = PARAM_DB_CONN_RESRC,
+         description = "Name of external resource for database connection."
+   )
+   String _resrcName;
+
+   /**
+    * Optional parameter. Specifies column names that will be used to form a
+    * document ID.  Optional, will remain null if not set.
+    */
+   public static final String PARAM_DOCID_COLS = "DocIdColNames";
+   @ConfigurationParameter(
+         name = PARAM_DOCID_COLS,
+         description = "Specifies column names that will be used to form a document ID.",
+         mandatory = false
+   )
+   private String[] _docIdColNames;
+
+
+   /**
+    * Optional parameter. Specifies delimiter used when document ID is built.  Default is an underscore.
+    */
+   public static final String PARAM_DOCID_DELIMITER = "DocIdDelimiter";
+   @ConfigurationParameter(
+         name = PARAM_DOCID_DELIMITER,
+         description = "Specifies delimiter used when document ID is built.",
+         mandatory = false
+   )
+   private String _docIdDelimiter = "_";
+
+
+   /**
+    * Optional parameter. Name of external resource for prepared statement
+    * value file. Each line of this file represents prepared statement values
+    * that will be used to substitute for the "?" placeholders. TAB character
+    * \t is used to delimit the values on a single line. The prepared statement
+    * will be called once for each line in this file.
+    */
+   public static final String PARAM_VALUE_FILE_RESRC = "ValueFileResrcName";
+   @ConfigurationParameter(
+         name = PARAM_VALUE_FILE_RESRC,
+         description = "Name of external resource for prepared statement value file.",
+         mandatory = false
+   )
+   private String _fileResrcName;
+
+
+   private PreparedStatement _preparedStatement;
+   private ResultSet _resultSet;
+
+   private int _docColType;
+   private String _docColTypeName;
+
+
+   private int _totalRowCount = 0;
+   private int _currRowCount = 0;
+
+   // optional, will remain null if not set
+   // Array of List objects. Each List objects represents a list of prepared
+   // stmt values.
+   private List<String>[] _prepStmtValArr = null;
+   private int _prepStmtValArrIdx = 0;
+   private boolean _usePrepStmtVals = false;
+
+   /**
+    * {@inheritDoc}
+    */
    @Override
-    public void initialize() throws ResourceInitializationException
-    {
-        try
-        {
-            String sql = (String) getConfigParameterValue(PARAM_SQL);
-            docTextColName = (String) getConfigParameterValue(PARAM_DOCTEXT_COL);
-            String resrcName = (String) getConfigParameterValue(PARAM_DB_CONN_RESRC);
-            JdbcConnectionResource resrc = (JdbcConnectionResource) getUimaContext().getResourceObject(resrcName);
-
-            docIdColNames = (String[]) getConfigParameterValue(PARAM_DOCID_COLS);
-            if (getConfigParameterValue(PARAM_DOCID_DELIMITER) != null)
-            {
-                docIdDelimiter = (String) getConfigParameterValue(PARAM_DOCID_DELIMITER);
+   public void initialize( final UimaContext context ) throws ResourceInitializationException {
+      super.initialize( context );
+      try {
+         final JdbcConnectionResource connectionResource
+               = (JdbcConnectionResource)context.getResourceObject( _resrcName );
+         final Connection connection = connectionResource.getConnection();
+         _preparedStatement = connection.prepareStatement( _sqlStatement );
+
+         if ( _fileResrcName != null && !_fileResrcName.trim().isEmpty() ) {
+            FileResource fileResrc = (FileResource)getUimaContext().getResourceObject( _fileResrcName );
+            if ( fileResrc != null ) {
+               loadValueFile( fileResrc.getFile() );
+               _usePrepStmtVals = true;
+            } else {
+               logger.error( "Failed to get " + _fileResrcName + " from ResourceManager" );
+               throw new ResourceInitializationException();
             }
+         }
 
-            Connection conn = resrc.getConnection();
-            queryPrepStmt = conn.prepareStatement(sql);
-
-            String fileResrcName = (String) getConfigParameterValue(PARAM_VALUE_FILE_RESRC);
-            if ((fileResrcName != null) && (fileResrcName.trim().length() > 0))
-            {
-                FileResource fileResrc = (FileResource) getUimaContext().getResourceObject(fileResrcName);
-                if (fileResrc != null)
-                {
-                    loadValueFile(fileResrc.getFile());
-                    usePrepStmtVals = true;
-                } else
-                {
-                    throw new Exception("Failed to get " + fileResrcName
-                            + " from ResourceManager");
-                }
-            }
-
-            totalRowCount = getRowCount(conn, sql);
-        } catch (Exception e)
-        {
-            throw new ResourceInitializationException(e);
-        }
-    }
-
-    /**
-     * Loads the prepared statement value file.
-     * 
-     * @param valueFile
-     * @throws IOException
-     */
-    private void loadValueFile(File valueFile) throws IOException
-    {
-        List<String> lineList = new ArrayList<String>();
-        BufferedReader br = new BufferedReader(new FileReader(valueFile));
-        String line = br.readLine();
-        while (line != null && line.trim().length()>0)
-        {
-            lineList.add(line);
-            line = br.readLine();
-        }
-        br.close();
-
-        prepStmtValArr = new List[lineList.size()];
-        for (int i = 0; i < lineList.size(); i++)
-        {
-           String currLine = lineList.get( i );
-            List<String> valList = new ArrayList<String>();
-            StringTokenizer st = new StringTokenizer(currLine, "\t");
-            while (st.hasMoreTokens())
-            {
-                String token = st.nextToken().trim();
-                valList.add(token);
-            }
-            prepStmtValArr[i] = valList;
-        }
-        logger.info("Loaded " + lineList.size() + " lines from value file: "
-                + valueFile.getAbsolutePath());
-    }
-
-    /**
-     * Slice up the query SQL and rebuild a SQL statement that gets a row count;
-     * 
-     * @param querySql
-     * @return
-     */
-    private int getRowCount(Connection conn, String querySql)
-            throws SQLException
-    {
-        StringBuffer sb = new StringBuffer();
-        sb.append("SELECT COUNT(*) ");
-        int idx = querySql.indexOf("FROM");
-        sb.append(querySql.subSequence(idx, querySql.length()));
-        PreparedStatement cntStmt = conn.prepareStatement(sb.toString());
-
-        if (usePrepStmtVals)
-        {
-            int totalCnt = 0;
-            for (int i = 0; i < prepStmtValArr.length; i++)
-            {
-                List<String> valList = prepStmtValArr[i];
-                setPrepStmtValues(cntStmt, valList);
-                ResultSet rs = cntStmt.executeQuery();
-                rs.next();
-                totalCnt += rs.getInt(1);
-            }
-            return totalCnt;
-        } else
-        {
+         _totalRowCount = getRowCount( connection, _sqlStatement );
+      } catch ( ResourceAccessException | SQLException | IOException multE ) {
+         throw new ResourceInitializationException( multE );
+      }
+   }
+
+   /**
+    * Loads the prepared statement value file.
+    *
+    * @param valueFile -
+    * @throws IOException -
+    */
+   private void loadValueFile( File valueFile ) throws IOException {
+      List<String> lineList = new ArrayList<>();
+      BufferedReader br = new BufferedReader( new FileReader( valueFile ) );
+      String line = br.readLine();
+      while ( line != null && line.trim().length() > 0 ) {
+         lineList.add( line );
+         line = br.readLine();
+      }
+      br.close();
+
+      _prepStmtValArr = new List[ lineList.size() ];
+      for ( int i = 0; i < lineList.size(); i++ ) {
+         String currLine = lineList.get( i );
+         List<String> valList = new ArrayList<>();
+         StringTokenizer st = new StringTokenizer( currLine, "\t" );
+         while ( st.hasMoreTokens() ) {
+            String token = st.nextToken().trim();
+            valList.add( token );
+         }
+         _prepStmtValArr[ i ] = valList;
+      }
+      logger.info( "Loaded " + lineList.size() + " lines from value file: "
+                   + valueFile.getAbsolutePath() );
+   }
+
+   /**
+    * Slice up the query SQL and rebuild a SQL statement that gets a row count;
+    *
+    * @param querySql -
+    * @return -
+    */
+   private int getRowCount( Connection conn, String querySql )
+         throws SQLException {
+      StringBuffer sb = new StringBuffer();
+      sb.append( "SELECT COUNT(*) " );
+      int idx = querySql.indexOf( "FROM" );
+      sb.append( querySql.subSequence( idx, querySql.length() ) );
+      PreparedStatement cntStmt = conn.prepareStatement( sb.toString() );
+
+      if ( _usePrepStmtVals ) {
+         int totalCnt = 0;
+         for ( int i = 0; i < _prepStmtValArr.length; i++ ) {
+            List<String> valList = _prepStmtValArr[ i ];
+            setPrepStmtValues( cntStmt, valList );
             ResultSet rs = cntStmt.executeQuery();
             rs.next();
-            return rs.getInt(1);
-        }
-    }
-
-    /**
-     * Helper method that sets the prepared statement values.
-     * 
-     * @param prepStmt
-     * @param valList
-     */
-    private void setPrepStmtValues(PreparedStatement prepStmt, List<String> valList)
-            throws SQLException
-    {
-        prepStmt.clearParameters();
-        for (int i = 0; i < valList.size(); i++)
-        {
-            Object valObj = valList.get(i);
-            prepStmt.setObject(i + 1, valObj);
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see org.apache.uima.collection.CollectionReader#getNext(org.apache.uima.cas.CAS)
-     */
-    @Override
-    public void getNext( CAS cas ) throws IOException, CollectionException
-    {
-        currRowCount++;
-        try
-        {
-            // pull doc text from resultset
-            String document = null;
-            if ((docColType == Types.CHAR)
-                    || (docColType == Types.VARCHAR))
-            {
-                document = rs.getString(docTextColName);
-            } else if (docColType == Types.CLOB)
-            {
-                document = convertToString(rs.getClob(docTextColName));
-            } else
-            {
-                throw new Exception("Unsupported document text column type: "
-                        + docColTypeName);
+            totalCnt += rs.getInt( 1 );
+         }
+         return totalCnt;
+      } else {
+         ResultSet rs = cntStmt.executeQuery();
+         rs.next();
+         return rs.getInt( 1 );
+      }
+   }
+
+   /**
+    * Helper method that sets the prepared statement values.
+    *
+    * @param prepStmt -
+    * @param valList  -
+    */
+   private void setPrepStmtValues( PreparedStatement prepStmt, List<String> valList )
+         throws SQLException {
+      prepStmt.clearParameters();
+      for ( int i = 0; i < valList.size(); i++ ) {
+         Object valObj = valList.get( i );
+         prepStmt.setObject( i + 1, valObj );
+      }
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public void getNext( final JCas jcas ) throws IOException, CollectionException {
+      _currRowCount++;
+      try {
+         // pull doc text from resultset
+         String document = null;
+         if ( (_docColType == Types.CHAR)
+              || (_docColType == Types.VARCHAR) ) {
+            document = _resultSet.getString( _docTextColName );
+         } else if ( _docColType == Types.CLOB ) {
+            document = convertToString( _resultSet.getClob( _docTextColName ) );
+         } else {
+            throw new Exception( "Unsupported document text column type: "
+                                 + _docColTypeName );
+         }
+
+         try {
+            // No CAS Initiliazer, so set document text ourselves.
+            // put document in CAS (assume CAS)
+            jcas.setDocumentText( document );
+
+            DocumentID docIdAnnot = new DocumentID( jcas );
+            docIdAnnot.setDocumentID( getDocumentID( _resultSet ) );
+            docIdAnnot.addToIndexes();
+
+            logger.info( "Reading document with ID="
+                         + docIdAnnot.getDocumentID() );
+         } catch ( Exception e ) {
+            logger.error( "CasInitializer failed to process document: " );
+            logger.error( document );
+            throw e;
+         }
+      } catch ( Exception e ) {
+         throw new CollectionException( e );
+      }
+   }
+
+   /**
+    * Builds a document ID from one or more pieces of query data. If the query
+    * data is not specified, the current row # is used.
+    *
+    * @param rs
+    * @return
+    */
+   private String getDocumentID( ResultSet rs ) throws SQLException {
+      if ( _docIdColNames != null ) {
+         StringBuffer sb = new StringBuffer();
+         for ( int i = 0; i < _docIdColNames.length; i++ ) {
+            String val = rs.getObject( _docIdColNames[ i ] ).toString();
+            sb.append( val );
+            if ( i != (_docIdColNames.length - 1) ) {
+               sb.append( _docIdDelimiter );
             }
+         }
+         return sb.toString();
+      } else {
+         // default is to return row num
+         return String.valueOf( _currRowCount );
+      }
+   }
+
+   /**
+    * Loads the clob data into a String object.
+    *
+    * @param clob
+    * @return
+    * @throws SQLException
+    * @throws IOException
+    */
+   private String convertToString( Clob clob ) throws SQLException, IOException {
+      StringBuffer sb = new StringBuffer();
+      BufferedReader br = new BufferedReader( clob.getCharacterStream() );
+      String line = br.readLine();
+      while ( line != null ) {
+         sb.append( line );
+         sb.append( '\n' );
+         line = br.readLine();
+      }
+      br.close();
+      return sb.toString();
+   }
+
+   /*
+    * (non-Javadoc)
+    *
+    * @see org.apache.uima.collection.base_cpm.BaseCollectionReader#hasNext()
+    */
+   @Override
+   public boolean hasNext() throws IOException, CollectionException {
+      try {
 
-            try
-            {
-               // No CAS Initiliazer, so set document text ourselves.
-                    // put document in CAS (assume CAS)
-                    cas.getJCas().setDocumentText(document);
-
-                DocumentID docIdAnnot = new DocumentID(cas
-                        .getJCas());
-                docIdAnnot.setDocumentID(getDocumentID(rs));
-                docIdAnnot.addToIndexes();
-
-                logger.info("Reading document with ID="
-                        + docIdAnnot.getDocumentID());
-            } catch (Exception e)
-            {
-                logger.error("CasInitializer failed to process document: ");
-                logger.error(document);
-                throw e;
-            }
-        } catch (Exception e)
-        {
-            throw new CollectionException(e);
-        }
-    }
-
-    /**
-     * Builds a document ID from one or more pieces of query data. If the query
-     * data is not specified, the current row # is used.
-     * 
-     * @param rs
-     * @return
-     */
-    private String getDocumentID(ResultSet rs) throws SQLException
-    {
-        if (docIdColNames != null)
-        {
-            StringBuffer sb = new StringBuffer();
-            for (int i = 0; i < docIdColNames.length; i++)
-            {
-                String val = rs.getObject(docIdColNames[i]).toString();
-                sb.append(val);
-                if (i != (docIdColNames.length - 1))
-                {
-                    sb.append(docIdDelimiter);
-                }
-            }
-            return sb.toString();
-        } else
-        {
-            // default is to return row num
-            return String.valueOf(currRowCount);
-        }
-    }
-
-    /**
-     * Loads the clob data into a String object.
-     * 
-     * @param clob
-     * @return
-     * @throws SQLException
-     * @throws IOException
-     */
-    private String convertToString(Clob clob) throws SQLException, IOException
-    {
-        StringBuffer sb = new StringBuffer();
-        BufferedReader br = new BufferedReader(clob.getCharacterStream());
-        String line = br.readLine();
-        while (line != null)
-        {
-            sb.append(line);
-            sb.append('\n');
-            line = br.readLine();
-        }
-        br.close();
-        return sb.toString();
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see org.apache.uima.collection.base_cpm.BaseCollectionReader#hasNext()
-     */
-    @Override
-    public boolean hasNext() throws IOException, CollectionException
-    {
-        try
-        {
-
-            if (rs == null)
-            {
-                if (usePrepStmtVals)
-                {
-                    List<String> valList = prepStmtValArr[prepStmtValArrIdx];
-                    setPrepStmtValues(queryPrepStmt, valList);
-                    prepStmtValArrIdx++;
-                }
-
-                rs = queryPrepStmt.executeQuery();
-
-                // TODO only needs to be done once
-                ResultSetMetaData rsMetaData = rs.getMetaData();
-                int colIdx = rs.findColumn(docTextColName);
-                docColType = rsMetaData.getColumnType(colIdx);
-                docColTypeName = rsMetaData.getColumnTypeName(1);
+         if ( _resultSet == null ) {
+            if ( _usePrepStmtVals ) {
+               List<String> valList = _prepStmtValArr[ _prepStmtValArrIdx ];
+               setPrepStmtValues( _preparedStatement, valList );
+               _prepStmtValArrIdx++;
             }
 
-            boolean hasAnotherRow = rs.next();
-            if (hasAnotherRow == false)
-            {
-                // it's important to close ResultSets as they can accumlate
-                // in the JVM heap. Too many open result sets can inadvertently
-                // cause the DB conn to be closed by the server.
-                rs.close();
-            }
+            _resultSet = _preparedStatement.executeQuery();
 
-            if (usePrepStmtVals)
-            {
-                if ((hasAnotherRow == false)
-                        && (prepStmtValArrIdx < prepStmtValArr.length))
-                {
-                    // the results for the previous prepared statement execution
-                    // have been exhausted, so the statement needs to be
-                    // executed with the next set of values
-
-                    // reset the rs instance variable to NULL
-                    rs = null;
-                    // re-invoke the hasNext() method so the prepared
-                    // statement gets executed again with the next set of values
-                    return this.hasNext();
-                }
+            // TODO only needs to be done once
+            ResultSetMetaData rsMetaData = _resultSet.getMetaData();
+            int colIdx = _resultSet.findColumn( _docTextColName );
+            _docColType = rsMetaData.getColumnType( colIdx );
+            _docColTypeName = rsMetaData.getColumnTypeName( 1 );
+         }
+
+         boolean hasAnotherRow = _resultSet.next();
+         if ( hasAnotherRow == false ) {
+            // it's important to close ResultSets as they can accumlate
+            // in the JVM heap. Too many open result sets can inadvertently
+            // cause the DB conn to be closed by the server.
+            _resultSet.close();
+         }
+
+         if ( _usePrepStmtVals ) {
+            if ( (hasAnotherRow == false)
+                 && (_prepStmtValArrIdx < _prepStmtValArr.length) ) {
+               // the results for the previous prepared statement execution
+               // have been exhausted, so the statement needs to be
+               // executed with the next set of values
+
+               // reset the _resultSet instance variable to NULL
+               _resultSet = null;
+               // re-invoke the hasNext() method so the prepared
+               // statement gets executed again with the next set of values
+               return this.hasNext();
             }
+         }
 
-            return hasAnotherRow;
-        } catch (Exception e)
-        {
-            throw new CollectionException(e);
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see org.apache.uima.collection.base_cpm.BaseCollectionReader#getProgress()
-     */
-    @Override
-    public Progress[] getProgress()
-    {
-        Progress p = new ProgressImpl(currRowCount, totalRowCount,
-                Progress.ENTITIES);
-        return new Progress[] { p };
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see org.apache.uima.collection.base_cpm.BaseCollectionReader#close()
-     */
-    @Override
-    public void close() throws IOException
-    {
-        try
-        {
-            queryPrepStmt.close();
-        } catch (Exception e)
-        {
-            throw new IOException(e.getMessage());
-        }
-    }
+         return hasAnotherRow;
+      } catch ( Exception e ) {
+         throw new CollectionException( e );
+      }
+   }
+
+   /*
+    * (non-Javadoc)
+    *
+    * @see org.apache.uima.collection.base_cpm.BaseCollectionReader#getProgress()
+    */
+   @Override
+   public Progress[] getProgress() {
+      Progress p = new ProgressImpl( _currRowCount, _totalRowCount,
+            Progress.ENTITIES );
+      return new Progress[] { p };
+   }
+
+   /*
+    * (non-Javadoc)
+    *
+    * @see org.apache.uima.collection.base_cpm.BaseCollectionReader#close()
+    */
+   @Override
+   public void close() throws IOException {
+      try {
+         _preparedStatement.close();
+      } catch ( Exception e ) {
+         throw new IOException( e.getMessage() );
+      }
+   }
 }
\ No newline at end of file

Modified: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/EssentialAnnotationUtil.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/EssentialAnnotationUtil.java?rev=1846266&r1=1846265&r2=1846266&view=diff
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/EssentialAnnotationUtil.java (original)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/EssentialAnnotationUtil.java Fri Nov  9 20:56:33 2018
@@ -2,6 +2,7 @@ package org.apache.ctakes.core.util;
 
 
 import org.apache.ctakes.core.util.textspan.TextSpan;
+import org.apache.ctakes.typesystem.type.constants.CONST;
 import org.apache.ctakes.typesystem.type.relation.BinaryTextRelation;
 import org.apache.ctakes.typesystem.type.relation.CollectionTextRelation;
 import org.apache.ctakes.typesystem.type.relation.RelationArgument;
@@ -123,6 +124,66 @@ final public class EssentialAnnotationUt
    }
 
    /**
+    * @param corefs coreference chains
+    * @return a map of markables to indexed chain numbers
+    */
+   static public Map<IdentifiedAnnotation, Collection<Integer>> createMarkableAssertedCorefs(
+         final Collection<CollectionTextRelation> corefs,
+         final Map<Markable, IdentifiedAnnotation> markableAnnotations ) {
+      if ( corefs == null || corefs.isEmpty() ) {
+         return Collections.emptyMap();
+      }
+
+      final List<List<IdentifiedAnnotation>> chains = new ArrayList<>();
+      for ( CollectionTextRelation coref : corefs ) {
+         final Map<String, List<IdentifiedAnnotation>> assertionMap = new HashMap<>();
+         final FSList chainHead = coref.getMembers();
+         final Collection<Markable> markables = FSCollectionFactory.create( chainHead, Markable.class );
+         for ( Markable markable : markables ) {
+            final IdentifiedAnnotation annotation = markableAnnotations.get( markable );
+            final String assertion = getAssertion( annotation );
+            assertionMap.computeIfAbsent( assertion, a -> new ArrayList<>() ).add( annotation );
+         }
+         for ( List<IdentifiedAnnotation> asserted : assertionMap.values() ) {
+            if ( asserted.size() > 1 ) {
+               asserted.sort( Comparator.comparingInt( Annotation::getBegin ) );
+               chains.add( asserted );
+            }
+         }
+      }
+      chains.sort( ( l1, l2 ) -> l1.get( 0 ).getBegin() - l2.get( 0 ).getBegin() );
+
+      final Map<IdentifiedAnnotation, Collection<Integer>> corefMarkables = new HashMap<>();
+      int index = 1;
+      for ( Collection<IdentifiedAnnotation> chain : chains ) {
+         for ( IdentifiedAnnotation annotation : chain ) {
+            corefMarkables.computeIfAbsent( annotation, a -> new ArrayList<>() ).add( index );
+         }
+         index++;
+      }
+      return corefMarkables;
+   }
+
+   static private String getAssertion( final IdentifiedAnnotation annotation ) {
+      final StringBuilder sb = new StringBuilder();
+      if ( annotation.getPolarity() == CONST.NE_POLARITY_NEGATION_PRESENT ) {
+         sb.append( "AFFIRMED" );
+      } else {
+         sb.append( "NEGATED" );
+      }
+      if ( annotation.getUncertainty() == CONST.NE_UNCERTAINTY_PRESENT ) {
+         sb.append( "UNCERTAIN" );
+      }
+      if ( annotation.getGeneric() ) {
+         sb.append( "GENERIC" );
+      }
+      if ( annotation.getConditional() ) {
+         sb.append( "CONDITIONAL" );
+      }
+      return sb.toString();
+   }
+
+   /**
     * This is a bit messy, but necessary.
     *
     * @param jCas   -

Added: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/FinishedLogger.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/FinishedLogger.java?rev=1846266&view=auto
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/FinishedLogger.java (added)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/FinishedLogger.java Fri Nov  9 20:56:33 2018
@@ -0,0 +1,105 @@
+package org.apache.ctakes.core.util;
+
+import org.apache.ctakes.core.pipeline.PipeBitInfo;
+import org.apache.log4j.Logger;
+import org.apache.uima.UimaContext;
+import org.apache.uima.analysis_engine.AnalysisEngineProcessException;
+import org.apache.uima.fit.component.JCasAnnotator_ImplBase;
+import org.apache.uima.jcas.JCas;
+import org.apache.uima.resource.ResourceInitializationException;
+
+import java.util.Date;
+
+/**
+ * @author SPF , chip-nlp
+ * @version %I%
+ * @since 5/21/2018
+ */
+@PipeBitInfo(
+      name = "Finished Logger",
+      description = "Writes a banner message COMPLETE to the log when all processing is finished.",
+      role = PipeBitInfo.Role.SPECIAL
+)
+final public class FinishedLogger extends JCasAnnotator_ImplBase {
+
+   static private final Logger LOGGER = Logger.getLogger( "FinishedLogger" );
+
+   private final long _instantMillis;
+   private long _initMillis;
+   private long _docCount = 0;
+
+   public FinishedLogger() {
+      _instantMillis = System.currentTimeMillis();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public void initialize( final UimaContext context ) throws ResourceInitializationException {
+      super.initialize( context );
+      _initMillis = System.currentTimeMillis();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public void process( final JCas jCas ) throws AnalysisEngineProcessException {
+      _docCount++;
+   }
+
+   public void collectionProcessComplete() throws AnalysisEngineProcessException {
+      super.collectionProcessComplete();
+      final long endMillis = System.currentTimeMillis();
+
+      LOGGER.info( "Run Start Time:               " + getTime( _instantMillis ) );
+      LOGGER.info( "Processing Start Time:        " + getTime( _initMillis ) );
+      LOGGER.info( "Processing End Time:          " + getTime( endMillis ) );
+      LOGGER.info( "Initialization Time Elapsed:  " + getSpan( _initMillis - _instantMillis ) );
+      LOGGER.info( "Processing Time Elapsed:      " + getSpan( endMillis - _initMillis ) );
+      LOGGER.info( "Total Run Time Elapsed:       " + getSpan( endMillis - _instantMillis ) );
+      LOGGER.info( "Documents Processed:          " + _docCount );
+      final long millisPerNote = (endMillis - _initMillis) / _docCount;
+      LOGGER.info( String.format( "Average Seconds per Document: %.2f", (millisPerNote / 1000f) ) );
+
+      final String FINISHED =
+            "\n\n" +
+            " ######    ######   ##     ##  #######   ##       #######  #######  #######\n" +
+            "##    ##  ##    ##  ###   ###  ##    ##  ##       ##         ##     ##     \n" +
+            "##        ##    ##  #### ####  ##    ##  ##       ##         ##     ##     \n" +
+            "##        ##    ##  ## ### ##  #######   ##       #####      ##     #####  \n" +
+            "##        ##    ##  ##     ##  ##        ##       ##         ##     ##     \n" +
+            "##    ##  ##    ##  ##     ##  ##        ##       ##         ##     ##     \n" +
+            " ######    ######   ##     ##  ##        #######  #######    ##     #######\n\n";
+      LOGGER.info( FINISHED );
+   }
+
+   static private String getTime( final long millis ) {
+      return new Date( millis ).toString();
+   }
+
+   static private String getSpan( final long millis ) {
+      long seconds = millis / 1000;
+      long minutes = seconds / 60;
+      seconds %= 60;
+      long hours = minutes / 60;
+      minutes %= 60;
+      long days = hours / 24;
+      hours %= 24;
+      final StringBuilder sb = new StringBuilder();
+      if ( days > 0 ) {
+         sb.append( days ).append( " days, " );
+      }
+      if ( days > 0 || hours > 0 ) {
+         sb.append( hours ).append( " hours, " );
+      }
+      if ( days > 0 || hours > 0 || minutes > 0 ) {
+         sb.append( minutes ).append( " minutes, " );
+      }
+      sb.append( seconds ).append( " seconds" );
+      return sb.toString();
+   }
+
+
+}

Added: ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/MutableUimaContext.java
URL: http://svn.apache.org/viewvc/ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/MutableUimaContext.java?rev=1846266&view=auto
==============================================================================
--- ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/MutableUimaContext.java (added)
+++ ctakes/trunk/ctakes-core/src/main/java/org/apache/ctakes/core/util/MutableUimaContext.java Fri Nov  9 20:56:33 2018
@@ -0,0 +1,318 @@
+package org.apache.ctakes.core.util;
+
+import org.apache.uima.UimaContext;
+import org.apache.uima.cas.AbstractCas;
+import org.apache.uima.cas.SofaID;
+import org.apache.uima.resource.ResourceAccessException;
+import org.apache.uima.resource.ResourceConfigurationException;
+import org.apache.uima.resource.Session;
+import org.apache.uima.util.InstrumentationFacility;
+import org.apache.uima.util.Settings;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * While unconventional, it is occasionally useful to easily add or change configuration parameters in a uima context.
+ *
+ * @author SPF , chip-nlp
+ * @version %I%
+ * @since 10/5/2018
+ */
+final public class MutableUimaContext implements UimaContext {
+
+   static private final String DEFAULT_GROUP = "DEFAULT_GROUP";
+
+   private final UimaContext _delegate;
+
+   private final Map<String, Map<String, Object>> _mutableConfigParameters;
+
+   /**
+    * @param uimaContext a uimacontext upon which to build.
+    */
+   public MutableUimaContext( final UimaContext uimaContext ) {
+      _delegate = uimaContext;
+      _mutableConfigParameters = new HashMap<>();
+      _mutableConfigParameters.put( DEFAULT_GROUP, new HashMap<>() );
+   }
+
+   /**
+    * Add a configuration parameter to the default parameter group.
+    *
+    * @param name  name of parameter
+    * @param value value of parameter
+    */
+   public void setConfigParameter( final String name, final Object value ) {
+      setConfigParameter( DEFAULT_GROUP, name, value );
+   }
+
+   /**
+    * Add a configuration parameter to the given parameter group.
+    *
+    * @param groupName name of the parameter group
+    * @param name      name of parameter
+    * @param value     value of parameter
+    */
+   public void setConfigParameter( final String groupName, final String name, final Object value ) {
+      _mutableConfigParameters.computeIfAbsent( groupName, m -> new HashMap<>() ).put( name, value );
+   }
+
+   private Object getMutableParameterValue( final String groupName, final String name ) {
+      final Map<String, Object> map = _mutableConfigParameters.get( groupName );
+      if ( map != null ) {
+         return map.get( name );
+      }
+      return null;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Object getConfigParameterValue( final String name ) {
+      final Object mutable = getMutableParameterValue( DEFAULT_GROUP, name );
+      if ( mutable != null ) {
+         return mutable;
+      }
+      return _delegate.getConfigParameterValue( name );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Object getConfigParameterValue( final String groupName, final String name ) {
+      final Object mutable = getMutableParameterValue( groupName, name );
+      if ( mutable != null ) {
+         return mutable;
+      }
+      return _delegate.getConfigParameterValue( groupName, name );
+   }
+
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String[] getConfigurationGroupNames() {
+      final Collection<String> groupNames = Arrays.asList( _delegate.getConfigParameterNames() );
+      _mutableConfigParameters.keySet().stream().filter( g -> !groupNames.contains( g ) ).forEach( groupNames::add );
+      return groupNames.toArray( new String[ groupNames.size() ] );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String[] getConfigParameterNames( final String groupName ) {
+      final Collection<String> names = Arrays.asList( _delegate.getConfigParameterNames( groupName ) );
+      final Map<String, Object> map = _mutableConfigParameters.get( groupName );
+      if ( map != null ) {
+         map.keySet().stream().filter( g -> !names.contains( g ) ).forEach( names::add );
+      }
+      return names.toArray( new String[ names.size() ] );
+
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String[] getConfigParameterNames() {
+      return Arrays.stream( getConfigurationGroupNames() )
+                   .map( this::getConfigParameterNames )
+                   .flatMap( Arrays::stream )
+                   .toArray( String[]::new );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String getSharedSettingValue( final String name ) throws ResourceConfigurationException {
+      return _delegate.getSharedSettingValue( name );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String[] getSharedSettingArray( final String name ) throws ResourceConfigurationException {
+      return _delegate.getSharedSettingArray( name );
+
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String[] getSharedSettingNames() {
+      return _delegate.getSharedSettingNames();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Settings getExternalOverrides() {
+      return _delegate.getExternalOverrides();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public org.apache.uima.util.Logger getLogger() {
+      return _delegate.getLogger();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public InstrumentationFacility getInstrumentationFacility() {
+      return _delegate.getInstrumentationFacility();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public URL getResourceURL( final String name ) throws ResourceAccessException {
+      return _delegate.getResourceURL( name );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public URI getResourceURI( final String name ) throws ResourceAccessException {
+      return _delegate.getResourceURI( name );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String getResourceFilePath( final String name ) throws ResourceAccessException {
+      return _delegate.getResourceFilePath( name );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public InputStream getResourceAsStream( final String name ) throws ResourceAccessException {
+      return _delegate.getResourceAsStream( name );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Object getResourceObject( final String name ) throws ResourceAccessException {
+      return _delegate.getResourceObject( name );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public URL getResourceURL( final String name, String[] params ) throws ResourceAccessException {
+      return _delegate.getResourceURL( name, params );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public URI getResourceURI( final String name, String[] params ) throws ResourceAccessException {
+      return _delegate.getResourceURI( name, params );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String getResourceFilePath( final String name, String[] params ) throws ResourceAccessException {
+      return _delegate.getResourceFilePath( name, params );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public InputStream getResourceAsStream( final String name, String[] params ) throws ResourceAccessException {
+      return _delegate.getResourceAsStream( name, params );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Object getResourceObject( final String name, String[] params ) throws ResourceAccessException {
+      return _delegate.getResourceObject( name, params );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String getDataPath() {
+      return _delegate.getDataPath();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Session getSession() {
+      return _delegate.getSession();
+   }
+
+   /**
+    * {@inheritDoc}
+    *
+    * @deprecated
+    */
+   @Override
+   @Deprecated
+   public SofaID mapToSofaID( final String var1 ) {
+      return _delegate.mapToSofaID( var1 );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public String mapSofaIDToComponentSofaName( final String var1 ) {
+      return _delegate.mapSofaIDToComponentSofaName( var1 );
+   }
+
+   /**
+    * {@inheritDoc}
+    *
+    * @deprecated
+    */
+   @Override
+   @Deprecated
+   public SofaID[] getSofaMappings() {
+      return _delegate.getSofaMappings();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public <T extends AbstractCas> T getEmptyCas( final Class<T> var1 ) {
+      return _delegate.getEmptyCas( var1 );
+   }
+
+}
+