/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * 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
 * 
 * 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
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.uima.jcas.impl;

// * todo:
// *
// * Compatibility removes at some point: TypeSystemInit and it's caller

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.WeakHashMap;

import org.apache.uima.UIMAFramework;
import org.apache.uima.cas.AbstractCas;
import org.apache.uima.cas.AbstractCas_ImplBase;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.CASException;
import org.apache.uima.cas.CASRuntimeException;
import org.apache.uima.cas.CasOwner;
import org.apache.uima.cas.ConstraintFactory;
import org.apache.uima.cas.FSIndex;
import org.apache.uima.cas.FSIndexRepository;
import org.apache.uima.cas.FSIterator;
import org.apache.uima.cas.FSMatchConstraint;
import org.apache.uima.cas.Feature;
import org.apache.uima.cas.FeaturePath;
import org.apache.uima.cas.FeatureStructure;
import org.apache.uima.cas.FeatureValuePath;
import org.apache.uima.cas.SofaFS;
import org.apache.uima.cas.SofaID;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.TypeSystem;
import org.apache.uima.cas.impl.CASImpl;
import org.apache.uima.cas.impl.FSClassRegistry;
import org.apache.uima.cas.impl.FSGenerator;
import org.apache.uima.cas.impl.FeatureStructureImpl;
import org.apache.uima.cas.impl.LowLevelCAS;
import org.apache.uima.cas.impl.LowLevelException;
import org.apache.uima.cas.impl.LowLevelIndexRepository;
import org.apache.uima.cas.impl.TypeImpl;
import org.apache.uima.cas.impl.TypeSystemImpl;
import org.apache.uima.cas.text.AnnotationIndex;
import org.apache.uima.internal.util.UIMAClassLoader;
import org.apache.uima.jcas.JCas;
import org.apache.uima.jcas.JCasRegistry;
import org.apache.uima.jcas.JFSIndexRepository;
import org.apache.uima.jcas.cas.AnnotationBase;
import org.apache.uima.jcas.cas.FSArray;
import org.apache.uima.jcas.cas.FloatArray;
import org.apache.uima.jcas.cas.IntegerArray;
import org.apache.uima.jcas.cas.Sofa;
import org.apache.uima.jcas.cas.StringArray;
import org.apache.uima.jcas.cas.TOP;
import org.apache.uima.jcas.cas.TOP_Type;
import org.apache.uima.jcas.tcas.Annotation;
import org.apache.uima.util.Level;
import org.apache.uima.util.Logger;

// *********************************
// * Implementation of JCas *
// *********************************

/*
 * 
 * Overview --------
 * 
 * This design uses classes for types, not Interfaces. JCas CAS types are represented in a running
 * server by a collection of classes A set of 2 classes for each loaded equivalent-to-CAS type Foo
 * (Class) Foo_Type (Class)
 * 
 * JCas and Foo_Type style classes have one instantiation per CAS View
 * 
 * Integrated with Framework Design -------------------------------- Assume: Framework collects for
 * each CAS creation all the types that will be defined for this CAS. Note: can create multiple
 * instances of CASs (and JCases) each TAE/App.
 * 
 * Assume: Each JCas instance (associated with a CAS) is single-threaded. if not - must synchronize
 * refs to tables/arrays associated with this
 * 
 * Assume: Each CAS instance only has one associated set of types, specified by a String[] of
 * fully-qual-type-names Implication: if CAS type system changes, create new JCas instance. This
 * allows many things to be "final".
 * 
 * Integrate with UIMA Framework Resource Manager for use of class loaders.
 * 
 * Framework will call getJCas once after the CAS types are completely specified, or when a
 * deserialization has re-initialized a CAS.
 * 
 * Initialize: To get to the type object from Foo, use jcas.getType(Foo.type) To get to the JCas
 * type object from instance of Foo, use this.jcasType.
 * 
 * If the CAS has its contents reset, call jcas.clearData() to reset the corresponding JCas content.
 * 
 * Implementation Notes: At loadtime, Foo and Foo_Type classes are assigned static integer indexes,
 * using the JCasRegistry. These indexes are used in arrays in the jcas instance to go from Foo
 * class (not instances) to Foo_Type instances (one per CAS). Things which require "types" at
 * runtime reference the Foo_Type instances.
 * 
 * Maps: Note: per CAS means per shared JCas instance assoc w/ CAS
 * 
 * (universal) (static field in class) go from Foo class to index unique ID used with next map to
 * locate Foo_Type instance associated with this class If universal - becomes an index for all
 * FooStyle classes loaded (per CAS)
 * 
 * (ArrayList) map index from Foo (static) to Foo_Type instance used in creating new instances.
 * Needs to be one per CAS because a particular "xyz" feature in CAS1 != "xyz" feature in CAS2 If
 * above universal, then can become large array with mostly unused slots. Possibility: reuse
 * no-longer-used slots. Identify no-longer-used slots at CAS Type unload event?
 */

/**
 * implements the supporting infrastructure for JCas model linked with a Cas. There is one logical
 * instance of this instantiated per Cas or CasView. If you hold a reference to a CAS, to get a
 * reference to the corresponding JCas, use the method getJCas(). Likewise, if you hold a reference
 * to this object, you can get a reference to the corresponding CAS object using the method
 * getCas().
 * <p>
 * There are variables here that hold references to constant, frequently needed Cas values, such as
 * 0-length FSArrays, 0-length Strings, etc. These can be used to improve efficiency, since the same
 * object can be shared and used for all instances because it is unchangeable. These objects are
 * reset when the CAS is reset, and are initialized lazily, on first use.
 */

public class JCasImpl extends AbstractCas_ImplBase implements AbstractCas, JCas {
  // * FSIndexRepository - the get method returns the java type *
  // * so FSIndexRepository can't be in the implements clause *

  // constant used in the following function
  /** internal use - constant used in getting constructors */
  final static private Class<?>[] jcasBaseAndType = new Class[] { JCas.class, Type.class };

  final static private Class<?>[] intAnd_Type = new Class[] { int.class, TOP_Type.class };

  
  private static class LoadedJCasType<T extends TOP> {
    final String typeName;

    final int index;

    final boolean isSubtypeOfAnnotationBase;

    final Constructor<? extends TOP_Type> constructorFor_Type;

    final Constructor<T> constructorForType;

    LoadedJCasType(String typeName, Class<? extends TOP_Type> a_TypeClass, ClassLoader cl) {
      Class<? extends TOP_Type> _TypeClass = a_TypeClass;
      this.typeName = typeName;
      int tempindex = 0;
      boolean tempisSubtypeOfAnnotationBase = false;
      Constructor<? extends TOP_Type> tempconstructorFor_Type = null;
      Constructor<T> tempconstructorForType = null;

      String name = _TypeClass.getName();
      try {
        @SuppressWarnings("unchecked")
        Class<T> typeClass = (Class<T>) Class.forName(name.substring(0, name.length() - 5), true, cl); // drop
        // _Type

        Field typeIndexField = null;
        try {
          typeIndexField = _TypeClass.getDeclaredField("typeIndexID");
        } catch (NoSuchFieldException e) {
          try {
            // old version has the index in the base type
            typeIndexField = typeClass.getDeclaredField("typeIndexID");
          } catch (NoSuchFieldException e2) {
            logAndThrow(e2);
          }
        }
        tempindex = typeIndexField.getInt(null); // null - static instance var
        tempconstructorFor_Type = _TypeClass.getDeclaredConstructor(jcasBaseAndType);
        tempconstructorForType = typeClass.getDeclaredConstructor(intAnd_Type);
        tempisSubtypeOfAnnotationBase = AnnotationBase.class.isAssignableFrom(typeClass);
      } catch (SecurityException e) {
        logAndThrow(e);
      } catch (NoSuchMethodException e) {
        logAndThrow(e);
      } catch (ClassNotFoundException e) {
        logAndThrow(e);
      } catch (IllegalArgumentException e) {
        logAndThrow(e);
      } catch (IllegalAccessException e) {
        logAndThrow(e);
      } finally {
        index = tempindex;
        constructorFor_Type = tempconstructorFor_Type;
        constructorForType = tempconstructorForType;
        isSubtypeOfAnnotationBase = tempisSubtypeOfAnnotationBase;
      }
    }

    private void logAndThrow(Exception e) {
      CASRuntimeException casEx = new CASRuntimeException(CASRuntimeException.JCAS_CAS_MISMATCH);
      casEx.initCause(e);
      throw casEx;
    }
  }

  static public class ErrorReport {
    final Exception e;
    final boolean doThrow;
    ErrorReport(Exception e, boolean doThrow) {
      this.e = e;
      this.doThrow = doThrow;
    }
  }

  // *************************************************
  // * Static Data shared with all instances of JCas *
  // *************************************************
  private final static int INITIAL_HASHMAP_SIZE = 256;

  // key = typeSystemImpl instance, value = Maps:
  //   key = class loader instance, value = Maps:
  //     key = string = fully qualified java type names of _Type classes, 
  //     value = instances of LoadedJCasType:
  //       fully-qualified-name-of-CAS-type
  //       the _Type class for this (specific to class loader)
  //       the class loader instance
  

  // Note: cannot use treemap here because keys (class loaders) are not comparable (have no sort
  // order)
  // To use: first look up with key = typeSystemImpl instance (uses object identity ("==") test)
  // Then use value and look up using class loader (uses object identity ("==") test)
  
  // The first two maps (with keys typeSystemImpl and classLoader) are 
  // Weak maps so that they don't hold onto their keys (class loader or type system) if
  // these are no longer in use
  //
  // This is a static map - effectively indexed by all loaded type systems and all classloaders

  // Access to this must be synch'd
  private static Map<TypeSystemImpl, Map<ClassLoader, Map<String, LoadedJCasType<?>>>> typeSystemToLoadedJCasTypesByClassLoader = 
          new WeakHashMap<TypeSystemImpl, Map<ClassLoader, Map<String, LoadedJCasType<?>>>>(4);

  // **********************************************
  // * Data shared among views of a single CAS *
  // * We keep one copy per view set *
  // **********************************************/
  private static class JCasSharedView {
    // ********************************************************
    // * Data shared among all views in a (J)CAS View group *
    // * Access to this data is assumed to be single threaded *
    // ********************************************************

    /**
     * key = CAS addr, value = corresponding Java instance.
     *
     *   The Java instance may be a JCas cover object, or a non-JCas Java cover object of type FeatureStructureImplC
     *   
     *This impl was changed in May 2007 to a
     * design of one cover object per CAS object, when dealing with multiple views. This implements
     * better semantics for co-located components sharing these objects - they all get to share the
     * same object, independent of which view(s) the object may be indexed in. For cases where the
     * user has augmented the definition of the JCas object to have native Java fields, this design
     * is closer to the user's expectation.
     * 
     * The only use for multiple objects, previously, was to implement the 0-argument version of
     * addToIndexes and removeFromIndexes. The new implementation will come close (but not be
     * perfectly the same) as the old implementation, by doing the following:
     * 
     * Changing the "new" and get/next from iterator methods to set the _Type field of the retrieved
     * cover class object (or new object) to the correct view.
     * 
     * Deref objects follow many paths. They all have a default view they create the object with
     * respect to. A simple deref uses the same cas ref the original object had.
     * 
     */
    private JCasHashMap cAddr2Jfs;

    private final Map<ClassLoader, JCasHashMap> cAddr2JfsByClassLoader = new IdentityHashMap<ClassLoader, JCasHashMap>();

    /* convenience holders of CAS constants that may be useful */
    /* initialization done lazily - on first call to getter */

    public StringArray stringArray0L = null;

    public IntegerArray integerArray0L = null;

    public FloatArray floatArray0L = null;

    public FSArray fsArray0L = null;

    // * collection of errors that occur during initialization
    public Collection<ErrorReport> errorSet = new ArrayList<ErrorReport>();

    public ClassLoader currentClassLoader = null;
    
    private ClassLoader cacheClassLoaderInitialized;
    final private Map<ClassLoader, Boolean> isInitializedForClassLoader = Collections.synchronizedMap(new IdentityHashMap<ClassLoader, Boolean>());

    private JCasSharedView(CASImpl aCAS, boolean useJcasCache) {
      setupJCasHashMap(aCAS.getJCasClassLoader(), useJcasCache, aCAS.getHeap().getInitialSize() / 16);
    }
    
    private void setupJCasHashMap(ClassLoader cl, boolean isUsedCache, int initialSize) {
      int size = Math.max(INITIAL_HASHMAP_SIZE, initialSize); 
      cAddr2Jfs = new JCasHashMap(size, isUsedCache);
      cAddr2JfsByClassLoader.put(cl, cAddr2Jfs);
      currentClassLoader = cl;    
    }
  }

  // *******************
  // * Data per (J)CAS *
  // * There may be multiples of these for one base CAS - one per "view"
  // * Access to this data is assumed to be single threaded
  // *******************

  private final JCasSharedView sharedView;

  // not public to protect it from accidents
  private final CASImpl casImpl;

  private final LowLevelIndexRepository ll_IndexRepository;

  private final JFSIndexRepository jfsIndexRepository;
  
  private final boolean isUsedCache;

  /*
   * typeArray is one per CAS because it holds pointers to instances of _Type objects, per CAS. 
   * It is not in the shared view, because the typeArray points to instances that go with the view. 
   * This is used when making instances - it allows each instance to be associated with a view, 
   * for purposes of making addtoIndexes() and removeFromIndexes() work. 
   * It is not final, because it may need to be "extended" if
   * alternate versions of types are loaded from different class loaders, at some point in the
   * execution. The alternate versions are given their own slots in this array.
   */
  private TOP_Type[] typeArray = new TOP_Type[0]; // contents are subtypes of TOP_Type

  // *********************************
  // * Getters for read-only objects *
  // *********************************
  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getFSIndexRepository()
   */
  public FSIndexRepository getFSIndexRepository() {
    return casImpl.getIndexRepository();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getLowLevelIndexRepository()
   */
  public LowLevelIndexRepository getLowLevelIndexRepository() {
    return ll_IndexRepository;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getCas()
   */
  public CAS getCas() {
    return casImpl;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getCasImpl()
   */
  public CASImpl getCasImpl() {
    return casImpl;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getLowLevelCas()
   */
  public LowLevelCAS getLowLevelCas() {
    return casImpl;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getTypeSystem()
   */
  public TypeSystem getTypeSystem() {
    return casImpl.getTypeSystem();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getType(int)
   */
  public TOP_Type getType(final int i) {
    if (i >= typeArray.length || null == typeArray[i]) {
      getTypeInit(i); 
    }
    return typeArray[i];
  }
  
  /**
   * Map from type codes to _Type instances kept per view in the field typeArray
   * @param i
   */
  private void getTypeInit(final int i) {
    // unknown ID. This may be due to a need to update the typeArray
    // due to switching class loaders. This updating is done
    // partially lazily - when needed, beyond the particular CAS instance and
    // view that was being passed to a process method when the
    // class loader switch was done.

    // In order for this to work, all access to the typeArray must be
    // via this getter.

    // Try instantiating the entries in typeArray for this class loader
    instantiateJCas_Types(this.sharedView.currentClassLoader);
    if (i >= typeArray.length || null == typeArray[i]) {

      // unknown ID. Attempt to get offending class.
      Class<? extends TOP> cls = JCasRegistry.getClassForIndex(i);
      if (cls != null) {
        String typeName = cls.getName();
        // is type in type system
        if (this.casImpl.getTypeSystem().getType(typeName) == null) {
          // no - report error that JCAS type was not defined in XML
          // descriptor
          CASRuntimeException casEx = new CASRuntimeException(
              CASRuntimeException.JCAS_TYPE_NOT_IN_CAS, new String[] { typeName });
          throw casEx;
        } else {
          // yes - there was some problem loading the _Type object
          CASRuntimeException casEx = new CASRuntimeException(
              CASRuntimeException.JCAS_MISSING_COVERCLASS, new String[] { typeName + "_Type" });
          throw casEx;
        }

      } else {
        throw new CASRuntimeException(CASRuntimeException.JCAS_UNKNOWN_TYPE_NOT_IN_CAS);
      }
    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getCasType(int)
   */
  public Type getCasType(int i) {
    return getType(i).casType;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getType(org.apache.uima.jcas.cas.TOP)
   */
  public TOP_Type getType(TOP instance) {
    return getType(instance.getTypeIndexID());
  }

  /** throws (an unchecked) CASRuntimeException */
  private void logAndThrow(Exception e) {
    CASRuntimeException casEx = new CASRuntimeException(CASRuntimeException.JCAS_CAS_MISMATCH);
    casEx.initCause(e);
    throw casEx;
  }

  // never called, but have to set values to null because they're final
  private JCasImpl() {
    sharedView = null;
    casImpl = null;
    ll_IndexRepository = null;
    isUsedCache = false;
    throw new RuntimeException("JCas constructor with no args called, should never be called.");
  }

  /*
   * Private constructor, called when new instance (either for new cas, or for old cas but new
   * Annotator/Application class, needed
   * 
   * Called by JCas.getJCas(cas)
   * 
   * The CAS must be initialized when this is called.
   * 
   */
  private JCasImpl(CASImpl cas) throws CASException {

    // * A new instance of JCas exists for each CAS
    // * At this point, some but not necessarily all of the Types have been
    // loaded

    // * the typeArray needs to be big enough to hold all the types
    // * that will be loaded.

    this.casImpl = cas;
    this.isUsedCache = cas.doUseJcasCache();
    if (casImpl != casImpl.getBaseCAS()) {
      sharedView = ((JCasImpl) casImpl.getBaseCAS().getJCas()).sharedView;
      sharedView.errorSet.clear();
    } else {
      sharedView = new JCasSharedView(cas, this.isUsedCache);
    }

    this.ll_IndexRepository = casImpl.ll_getIndexRepository();
    this.jfsIndexRepository = new JFSIndexRepositoryImpl(this, cas.getIndexRepository());

    // * acquire the lock for this thread that is the same lock
    // * used in the getNextIndexIncr operation. This will block
    // * any other (meaning on another thread)
    // * loading of the JCas Type classes until this thread's loading
    // * completes.

    synchronized (JCasImpl.class) {
      ClassLoader cl = cas.getJCasClassLoader();
      instantiateJCas_Types(cl);
    } // end of synchronized block
  }

  /**
   * This is only called when the JCas is already set up
   * 
   * @param cl
   *                class loader to switch to 
   */
  public void switchClassLoader(ClassLoader cl) {

    // first try a fast switch - works if we've switched before to this class loader
    if (!casImpl.getFSClassRegistry().swapInGeneratorsForClassLoader(cl, casImpl)) {
      instantiateJCas_Types(cl);
    }
    final JCasSharedView sv = this.sharedView;
    sv.cAddr2Jfs = sv.cAddr2JfsByClassLoader.get(cl);
    if (null == sv.cAddr2Jfs) {
      // initial size low because this is the use case where a Pear isolation is happening, 
      //   it is more likely that the PEAR is only doing a subset of the JCas things
      sv.setupJCasHashMap(cl, isUsedCache, casImpl.getHeap().getInitialSize() / 1024);
    } else {
      sv.currentClassLoader = cl;
    }
  }
  
  /**
   * There may be several type systems with different defined types loaded and operating at the same
   * time (in different CASes) There is an instance of the loaded JCas Classes for each Type System
   * for each class loader.
   * 
   * @param cl
   * @return a Map from the string of the fully qualified type name to LoadedJCasType instances
   */
  @SuppressWarnings("unchecked")
  private synchronized Map<String, LoadedJCasType<?>> loadJCasClasses(ClassLoader cl) {
    final TypeSystem ts = casImpl.getTypeSystem();
    Iterator<Type> typeIt = ts.getTypeIterator();
    TypeImpl t;
    String casName;
    Map<String, LoadedJCasType<?>> jcasTypes = new HashMap<>();

    // * note that many of these may have already been loaded
    // * load all the others. Actually, we ask to load all the types
    // * and the ones already loaded - we just get their loaded versions
    //   returned.
    // * Loading will run the static init functions.

    while (typeIt.hasNext()) {
      t = (TypeImpl) typeIt.next();
      casName = t.getName();
      String name_Type;
      if (builtInsWithNoJCas.contains(casName))
        continue;
      if (builtInsWithAltNames.contains(casName))
        // * convert uima.cas.Xxx -> org.apache.uima.jcas.cas.Xxx
        // * convert uima.tcas.Annotator -> org.apache.uima.jcas.tcas.Annotator
        try {
          String nameBase = "org.apache.uima.jcas." + casName.substring(5);
          name_Type = nameBase + "_Type";
          jcasTypes.put(nameBase, new LoadedJCasType<>(t.getName(), (Class<? extends TOP_Type>) Class
              .forName(name_Type, true, cl), cl));
        } catch (ClassNotFoundException e1) {
          // OK for DocumentAnnotation, which may not have a cover class.
          // Otherwise, not OK.
          if (!CAS.TYPE_NAME_DOCUMENT_ANNOTATION.equals(casName)) {
            assert false : "never get here because built-ins have java cover types";
            e1.printStackTrace();
          }
        }

      // this is done unconditionally to pick up old style cover functions if
      // any
      // as well as other JCas model types
      try {
        name_Type = casName + "_Type";
        jcasTypes.put(casName, new LoadedJCasType<>(t.getName(), (Class<? extends TOP_Type>) Class.forName(name_Type, true, cl),
            cl));
        // also force the load the plain name without _Type for
        // old-style - that's where
        // the index is incremented
        Class.forName(casName, true, cl);
      } catch (ClassNotFoundException e1) {
        // many classes may not have JCas models, so this is not an
        // error
      }
    }

    // note: this entire method is synchronized
    Map<ClassLoader, Map<String, LoadedJCasType<?>>> classLoaderToLoadedJCasTypes = typeSystemToLoadedJCasTypesByClassLoader.get(casImpl
        .getTypeSystemImpl());
    if (null == classLoaderToLoadedJCasTypes) {
      classLoaderToLoadedJCasTypes = new WeakHashMap<ClassLoader, Map<String, LoadedJCasType<?>>>(4);
      typeSystemToLoadedJCasTypesByClassLoader.put(casImpl.getTypeSystemImpl(),
          classLoaderToLoadedJCasTypes);
    }
    classLoaderToLoadedJCasTypes.put(cl, jcasTypes);

    expandTypeArrayIfNeeded();
    return jcasTypes;
  }

  // note: callers are not synchronized because the typeArray has a
  // separate instance per CAS (actually per CAS view)
  private void expandTypeArrayIfNeeded() {
    if (typeArray.length < JCasRegistry.getNumberOfRegisteredClasses()) {
      TOP_Type[] newTypeArray = new TOP_Type[JCasRegistry.getNumberOfRegisteredClasses()];
      System.arraycopy(typeArray, 0, newTypeArray, 0, typeArray.length);
      typeArray = newTypeArray;
    }
  }
  
  /**
   * called when switching to JCas and the JCas already exists to check if 
   * the JCas needs to have classes loaded for this class loader.  This can happen when the JCas is first
   * instantiated while under the scope of a nested UIMA class loader.  This could be a Pear class loader, or
   * even just an ordinary UIMA Class loader for a pipeline, where the Framework is running an "exit routine" supplied
   * by the user, but loaded in a (for example) initial application loader context.
   * @param cl the class loader in use
   */
  public void maybeInitializeForClassLoader(ClassLoader cl) {
    if (sharedView.cacheClassLoaderInitialized == cl) {
      return;
    }
    if (this.sharedView.isInitializedForClassLoader.get(cl) == null) {
      instantiateJCas_Types(cl);
    }
    sharedView.cacheClassLoaderInitialized = cl;
  }

  /**
   * 
   * @param cl the class loader to use as the initiating loader for loading JCas classes
   */
  public void instantiateJCas_Types(ClassLoader cl) {
    Map<String, LoadedJCasType<?>> loadedJCasTypes = null;
    FSClassRegistry fscr = casImpl.getFSClassRegistry();
    boolean alreadyLoaded;  // means the "classes" have been loaded, but doesn't mean
                            // the _Type instances of those classes have been created.
    boolean anyNewInstances = false;  // true if any new instances of _Type are generated
    FSGenerator<?>[] newFSGeneratorSet;
    synchronized (JCasImpl.class) {
      Map<ClassLoader, Map<String, LoadedJCasType<?>>> classLoaderToLoadedJCasTypes = typeSystemToLoadedJCasTypesByClassLoader.get(casImpl
          .getTypeSystemImpl());
      if (null != classLoaderToLoadedJCasTypes) {
        loadedJCasTypes = classLoaderToLoadedJCasTypes.get(cl);
      }
      alreadyLoaded = (null != loadedJCasTypes);
      if (!alreadyLoaded) {
        loadedJCasTypes = loadJCasClasses(cl);
      }

      expandTypeArrayIfNeeded();
      // if already loaded, can skip making new generators - 
      //   in this case newFSGeneratorSet is never referenced
      //   Set it to null for "safety"
      // If not already loaded, initialize the generators to a clone of the FSClassRegistry generators.
      newFSGeneratorSet = (alreadyLoaded ? null : fscr.getNewFSGeneratorSet());
      for (Iterator<Map.Entry<String, LoadedJCasType<?>>> it = loadedJCasTypes.entrySet().iterator(); it.hasNext();) {
        
        // Explanation for this logic:
        //   Instances of _Types are kept per class loader, per Cas (e.g., in the cas pool)
        //     and per view (to support having the ref to the casImpl be to the right view
        //                   so add-to-indexes works better).
        //                   The "typeArray" field is per view.
        //   When switching class loaders (e.g. a pear in the pipeline), some of the 
        //     _Type instances (e.g. the built-ins) might be already instantiated, but others are not
        //   
        boolean madeNewInstance = makeInstanceOf_Type(it.next().getValue(), alreadyLoaded,
            newFSGeneratorSet); 
        anyNewInstances = madeNewInstance || anyNewInstances;
      }
      
      // speed up - skip rest if nothing to do
      if (!anyNewInstances && 
          sharedView.isInitializedForClassLoader.get(cl) != null) {  // don't skip if need to install the new generators for this class loader UIMA-5055
        return;
      }
      if (!alreadyLoaded) {
        copyDownSuperGenerators(loadedJCasTypes, newFSGeneratorSet);
        if (casImpl.usingBaseClassLoader()) {
          fscr.setBaseGenerators(newFSGeneratorSet); // should be under sync lock
        }
        fscr.saveGeneratorsForClassLoader(cl, newFSGeneratorSet);
      }
    }
    if (alreadyLoaded) {
      fscr.swapInGeneratorsForClassLoader(cl, casImpl);
    } else {
      casImpl.setLocalFsGenerators(newFSGeneratorSet);
    }
    sharedView.isInitializedForClassLoader.put(cl, Boolean.TRUE);
  }

  // note all callers are synchronized
  private void copyDownSuperGenerators(Map<String, LoadedJCasType<?>> jcasTypes, FSGenerator<?>[] fsGenerators) {
    final TypeSystem ts = casImpl.getTypeSystem();
    Iterator<Type> typeIt = ts.getTypeIterator(); // reset iterator to start
    Type topType = ts.getTopType();
    Type superType = null;
    while (typeIt.hasNext()) {
      Type t = typeIt.next();
      if (builtInsWithNoJCas.contains(t.getName()))
        continue;
      // comment here
      if (CAS.TYPE_NAME_DOCUMENT_ANNOTATION.equals(t.getName())) {
        if (jcasTypes.get("org.apache.uima.jcas.tcas.DocumentAnnotation") != null)
          continue;
      } else if (builtInsWithAltNames.contains(t.getName()))
        continue; // because jcasTypes has no entry for these
      if (null != jcasTypes.get(t.getName()))
        continue;
      // we believe that at this point, t is not "top", because top is
      // always loaded
      // find closest supertype that has a loaded cover class
      superType = t;
      String superTypeName;
      do {
        superType = ts.getParent(superType);
        superTypeName = superType.getName();
        if (builtInsWithAltNames.contains(superTypeName)) {
          superTypeName = "org.apache.uima.jcas." + superTypeName.substring(5);
        }
      } while ((null == jcasTypes.get(superTypeName) && !superType.equals(topType)));
      // copy down its generator
      fsGenerators[((TypeImpl) t).getCode()] = fsGenerators[((TypeImpl) superType).getCode()];
    }
  }

  /**
   * creates a new JCas instance that corresponds to a CAS instance. Will be called once by the UIMA
   * framework when it creates the CAS.
   * 
   * @param cas
   *                a CAS instance
   * @return newly created and initialized JCas
   * @throws CASException -
   */
  public static JCas getJCas(CASImpl cas) throws CASException {
    JCasImpl jcas = new JCasImpl(cas);
    reportInitErrors(jcas);
    return jcas;
  }
  
  private static void reportInitErrors(JCasImpl jcas) throws CASException {
    JCasSharedView sv = jcas.sharedView;
    if (sv.errorSet.size() > 0) {
      boolean doThrow = false;
      StringBuffer msg = new StringBuffer(100);
      msg.append('\n');
      for (ErrorReport f : sv.errorSet) {
        msg.append(f.e.getMessage());
        msg.append('\n');
        doThrow = doThrow || f.doThrow;
      }
      if (doThrow) {
        CASException e = new CASException(CASException.JCAS_INIT_ERROR,
            new String[] { msg.toString() });
        throw e;
      } else {
        Logger logger = UIMAFramework.getLogger();
        if (null == logger) {
          throw new CASRuntimeException(CASException.JCAS_INIT_ERROR, new String[] {msg.toString()});
        } else {
          logger.log(Level.WARNING, msg.toString());
        }          
      }
    }
  }

  /**
   * built-in types which have alternate names It really isn't necessary to skip these - they're
   * never instantiated. But it is a very slight performance boost, and it may be safer given
   * possible future changes to these types' implementations.
   */
  private static final Collection<String> builtInsWithNoJCas = new ArrayList<String>();
  static {
    builtInsWithNoJCas.add(CAS.TYPE_NAME_BOOLEAN);
    builtInsWithNoJCas.add(CAS.TYPE_NAME_BYTE);
    builtInsWithNoJCas.add(CAS.TYPE_NAME_SHORT);
    builtInsWithNoJCas.add(CAS.TYPE_NAME_INTEGER);
    builtInsWithNoJCas.add(CAS.TYPE_NAME_LONG);
    builtInsWithNoJCas.add(CAS.TYPE_NAME_FLOAT);
    builtInsWithNoJCas.add(CAS.TYPE_NAME_DOUBLE);
    builtInsWithNoJCas.add(CAS.TYPE_NAME_STRING);
    builtInsWithNoJCas.add(CAS.TYPE_NAME_ARRAY_BASE);
    builtInsWithNoJCas.add(CAS.TYPE_NAME_LIST_BASE);
  }

  private static final Collection<String> builtInsWithAltNames = new ArrayList<String>();
  static { // initialization code
    builtInsWithAltNames.add(CAS.TYPE_NAME_TOP);
    builtInsWithAltNames.add(CAS.TYPE_NAME_STRING_ARRAY);
    builtInsWithAltNames.add(CAS.TYPE_NAME_BOOLEAN_ARRAY);
    builtInsWithAltNames.add(CAS.TYPE_NAME_BYTE_ARRAY);
    builtInsWithAltNames.add(CAS.TYPE_NAME_SHORT_ARRAY);
    builtInsWithAltNames.add(CAS.TYPE_NAME_INTEGER_ARRAY);
    builtInsWithAltNames.add(CAS.TYPE_NAME_LONG_ARRAY);
    builtInsWithAltNames.add(CAS.TYPE_NAME_FS_ARRAY);
    builtInsWithAltNames.add(CAS.TYPE_NAME_FLOAT_ARRAY);
    builtInsWithAltNames.add(CAS.TYPE_NAME_DOUBLE_ARRAY);
    builtInsWithAltNames.add(CAS.TYPE_NAME_EMPTY_FLOAT_LIST);
    builtInsWithAltNames.add(CAS.TYPE_NAME_EMPTY_FS_LIST);
    builtInsWithAltNames.add(CAS.TYPE_NAME_EMPTY_INTEGER_LIST);
    builtInsWithAltNames.add(CAS.TYPE_NAME_EMPTY_STRING_LIST);
    builtInsWithAltNames.add(CAS.TYPE_NAME_FLOAT_LIST);
    builtInsWithAltNames.add(CAS.TYPE_NAME_FS_LIST);
    builtInsWithAltNames.add(CAS.TYPE_NAME_INTEGER_LIST);
    builtInsWithAltNames.add(CAS.TYPE_NAME_STRING_LIST);
    builtInsWithAltNames.add(CAS.TYPE_NAME_NON_EMPTY_FLOAT_LIST);
    builtInsWithAltNames.add(CAS.TYPE_NAME_NON_EMPTY_FS_LIST);
    builtInsWithAltNames.add(CAS.TYPE_NAME_NON_EMPTY_INTEGER_LIST);
    builtInsWithAltNames.add(CAS.TYPE_NAME_NON_EMPTY_STRING_LIST);
    builtInsWithAltNames.add(CAS.TYPE_NAME_SOFA);
    builtInsWithAltNames.add(CAS.TYPE_NAME_ANNOTATION_BASE);
    builtInsWithAltNames.add(CAS.TYPE_NAME_ANNOTATION);
    builtInsWithAltNames.add(CAS.TYPE_NAME_DOCUMENT_ANNOTATION);
  }

  // This generator class is used in place of the generator that is in each of the
  // older JCasGen'd classes

  // It makes use of the passed-in CAS view so one generator works for all Cas Views.
  // It references the xxx_Type instance using the getType call, which will
  // (lazily) instantiate the xxx_Type object if needed (due to switching class loaders:
  // see comment under getType(int))

  static private class JCasFsGenerator<T extends TOP> implements FSGenerator<T> {
    // multiple reader threads in same CAS
    static final ThreadLocal<Object[]> initArgsThreadLocal = new ThreadLocal<Object[]>() {
      protected Object[] initialValue() { return new Object[2]; } };

    private final int type;

    private final Constructor<T> c;

    private final boolean isSubtypeOfAnnotationBase;

    private final int sofaNbrFeatCode;

    private final int annotSofaFeatCode;
    
    JCasFsGenerator(int type, Constructor<T> c, boolean isSubtypeOfAnnotationBase,
        int sofaNbrFeatCode, int annotSofaFeatCode) {
      this.type = type;
      this.c = c;
      this.isSubtypeOfAnnotationBase = isSubtypeOfAnnotationBase;
      this.sofaNbrFeatCode = sofaNbrFeatCode;
      this.annotSofaFeatCode = annotSofaFeatCode;
    }

    /*
     * Called from the CAS's this.svd.localFsGenerators 
     * 
     * Those are set up with either JCas style generators, or the
     * the shared common instances of FeatureStructureImplC for non-JCas classes.
     * 
     */
    // Called in 3 cases
    // 1) a non-JCas call to create a new JCas style FS 
    // 2) a dereference of an existing FS 
    // 3) an iterator
    public T createFS(int addr, CASImpl casView) {
      try {
        JCasImpl jcasView = (JCasImpl) casView.getJCas();
        T fs = jcasView.<T>getJfsFromCaddr(addr);
        if (null != fs) {
          fs.jcasType = jcasView.getType(type);
          return fs;
        }       
        return doCreateFS(addr, casView);
      } catch (CASException e1) {
        logAndThrow(e1, null);
        return null;  // to avoid compile warning
      }
    }
  
    private T doCreateFS(int addr, CASImpl casView) {
      // this funny logic is because although the annotationView should always be set if
      // a type is a subtype of annotation, it isn't always set if an application uses low-level
      // api's. Rather than blow up, we limp along.
      CASImpl maybeAnnotationView = null;
      if (isSubtypeOfAnnotationBase) {
        final int sofaNbr = getSofaNbr(addr, casView);
        if (sofaNbr > 0) {
          maybeAnnotationView = (CASImpl) casView.getView(sofaNbr);
        }
      }
      final CASImpl view = (null != maybeAnnotationView) ? maybeAnnotationView : casView;

      try {
        JCasImpl jcasView = (JCasImpl) view.getJCas();
        final Object[] initargs = initArgsThreadLocal.get();  
        initargs[0] = Integer.valueOf(addr);
        initargs[1] = jcasView.getType(type);
        T fs = null;
        try {
          fs = (T) c.newInstance(initargs);
        } catch (IllegalArgumentException e) {
          logAndThrow(e, jcasView);
        } catch (InstantiationException e) {
          logAndThrow(e, jcasView);
        } catch (IllegalAccessException e) {
          logAndThrow(e, jcasView);
        } catch (InvocationTargetException e) {
          logAndThrow(e, jcasView);
        }
        jcasView.putJfsFromCaddr(addr, fs);
        return fs;
      } catch (CASException e1) {
        logAndThrow(e1, null);
        return null;
      }
    }

    private void logAndThrow(Exception e, JCasImpl jcasView) {
      CASRuntimeException casEx = new CASRuntimeException(
          CASRuntimeException.JCAS_CAS_MISMATCH,
          new String[] { (null == jcasView) ? "-- ignore outer msg, error is can''t get value of jcas from cas"
              : (jcasView.getType(type).casType.getName() + "; exception= "
                  + e.getClass().getName() + "; msg= " + e.getLocalizedMessage()) });
      casEx.initCause(e);
      throw casEx;
    }

    private int getSofaNbr(final int addr, final CASImpl casView) {      
      final int sofa = casView.ll_getIntValue(addr, annotSofaFeatCode, false);
      return (sofa == 0) ? 0 : casView.ll_getIntValue(sofa, sofaNbrFeatCode);
    }
    
    public int getTypeIndex() {
      return type;
    }
  }

  // per JCas instance - so don't need to synch.
  private final Object[] constructorArgsFor_Type = new Object[2];

  /**
   * Make the instance of the JCas xxx_Type class for this CAS. Note: not all types will have
   * xxx_Type. Instance creation does the typeSystemInit kind of function, as well.
   * 
   * @param jcasTypeInfo -
   * @param alreadyLoaded -
   * @param fsGenerators updated by side effect with new instances of the _Type class
   * @return true if a new instance of a _Type class was created
   */
  private <T extends TOP> boolean makeInstanceOf_Type(LoadedJCasType<T> jcasTypeInfo, boolean alreadyLoaded,
      FSGenerator<?>[] fsGenerators) {
    
    // return without doing anything if the _Type instance is already existing
    //   this happens when a JCas has some _Type instances made (e.g, the
    //     built-in ones) but the class loader was switched.  Some of the
    //     _Type instances for the new class loader can share previously 
    //     instantiated _Type instances, but others may be different
    //     (due to different impls of the _Type class loaded by the different
    //     class loader).
    //   This can also happen in the case where JCasImpl.getType is called for a non-existing class.
    //     What happens in this case is that the getType code has to assume that
    //     perhaps none of the _Type instances were made for this JCas (yet), because
    //     these are created lazily - so it calls instantiateJCas_Types to make them.
    //     If they were already made, this next test short circuits this.
    int typeIndex = jcasTypeInfo.index;
    TypeImpl casType = (TypeImpl) casImpl.getTypeSystem().getType(jcasTypeInfo.typeName);
    FSGenerator<?> generator = (fsGenerators == null) ? null : fsGenerators[casType.getCode()];
    if (typeArray[typeIndex] != null &&
        // https://issues.apache.org/jira/browse/UIMA-6243
        // Catch the case where
        //   a previously loaded JCas class for this type happened, but
        //   the type in the fsGenerators is not for this JCas class
        //     For example, this happens in the test case
        //       JCasClassLoaderTest.thatTypeSystemCanComeFromItsOwnClassLoader
        //       where the AddATokenAnnotator instantiates the _Type in the typeArray,
        //         only within that annotator's process scope,
        //         and outside of its scope, it is not instantiated
        //       In that case, the JCas class is copied-down from some supertype
        (alreadyLoaded ||
          // ! already loaded here
          (! (generator instanceof JCasFsGenerator) ||
            // generator is an instanceof JCasFsGenerator here
            // if the next is false, this falls thru and we instantiate 
            // a loaded version of the JCas type into the generators
           ((JCasFsGenerator<?>)generator).getTypeIndex() == typeIndex) )       
       ) {
      return false;
    }
    
    Constructor<?> c_Type = jcasTypeInfo.constructorFor_Type;
    Constructor<T> cType = jcasTypeInfo.constructorForType;
    

    try {
      constructorArgsFor_Type[0] = this;
      constructorArgsFor_Type[1] = casType;
      if (typeArray[typeIndex] == null) {
        TOP_Type x_Type_instance = (TOP_Type) c_Type.newInstance(constructorArgsFor_Type);
        typeArray[typeIndex] = x_Type_instance;
      }
      // install the standard generator
      // this is sharable by all views, since the CAS is passed to the generator
      // Also sharable by all in a CasPool, except for "swapping" due to PEARs/Classloaders.
      if (!alreadyLoaded) {
//        final TypeSystemImpl ts = casImpl.getTypeSystemImpl();
        fsGenerators[casType.getCode()] = new JCasFsGenerator<T>(typeIndex, cType,
            jcasTypeInfo.isSubtypeOfAnnotationBase, TypeSystemImpl.sofaNumFeatCode, TypeSystemImpl.annotSofaFeatCode);
        // this.casImpl.getFSClassRegistry().loadJCasGeneratorForType(typeIndex, cType, casType,
        // jcasTypeInfo.isSubtypeOfAnnotationBase);
      }
    } catch (SecurityException e) {
      logAndThrow(e);
    } catch (InstantiationException e) {
      logAndThrow(e);
    } catch (IllegalAccessException e) {
      logAndThrow(e);
    } catch (InvocationTargetException e) {
      logAndThrow(e);
    } catch (ArrayIndexOutOfBoundsException e) {
      logAndThrow(e);
    }
    return true;
  }

  /**
   * Make the instance of the JCas xxx_Type class for this CAS. Note: not all types will have
   * xxx_Type. Instance creation does the typeSystemInit kind of function, as well.
   */
  /*
   * private void makeInstanceOf_Type(Type casType, Class clas, CASImpl cas) { Constructor c; Field
   * typeIndexField = null; int typeIndex; try { c = clas.getDeclaredConstructor(jcasBaseAndType);
   * try {
   * 
   * typeIndexField = clas.getDeclaredField("typeIndexID"); } catch (NoSuchFieldException e) { try { //
   * old version has the index in the base type String name = clas.getName(); Class clas2 =
   * Class.forName(name.substring(0, name.length() - 5), true, cas .getJCasClassLoader()); // drop
   * _Type typeIndexField = clas2.getDeclaredField("typeIndexID"); } catch (NoSuchFieldException e2) {
   * logAndThrow(e2); } catch (ClassNotFoundException e3) { logAndThrow(e3); } } typeIndex =
   * typeIndexField.getInt(null); // null - static instance var TOP_Type x_Type_instance =
   * (TOP_Type) c.newInstance(new Object[] { this, casType }); typeArray[typeIndex] =
   * x_Type_instance; } catch (SecurityException e) { logAndThrow(e); } catch (NoSuchMethodException
   * e) { logAndThrow(e); } catch (InstantiationException e) { logAndThrow(e); } catch
   * (IllegalAccessException e) { logAndThrow(e); } catch (InvocationTargetException e) {
   * logAndThrow(e); } catch (ArrayIndexOutOfBoundsException e) { logAndThrow(e); } }
   */

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getRequiredType(java.lang.String)
   */
  public Type getRequiredType(String s) throws CASException {
    Type t = getTypeSystem().getType(s);
    if (null == t) {
      CASException casEx = new CASException(CASException.JCAS_TYPENOTFOUND_ERROR,
          new String[] { s });
      throw casEx;
    }
    return t;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getRequiredFeature(org.apache.uima.cas.Type, java.lang.String)
   */
  public Feature getRequiredFeature(Type t, String s) throws CASException {
    Feature f = t.getFeatureByBaseName(s);
    if (null == f) {
      CASException casEx = new CASException(CASException.JCAS_FEATURENOTFOUND_ERROR, new String[] {
          t.getName(), s });
      throw casEx;
    }
    return f;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getRequiredFeatureDE(org.apache.uima.cas.Type, java.lang.String,
   *      java.lang.String, boolean)
   */

  public Feature getRequiredFeatureDE(Type t, String s, String rangeName, boolean featOkTst) {
    Feature f = t.getFeatureByBaseName(s);
    Type rangeType = this.getTypeSystem().getType(rangeName);
    if (null == f && featOkTst) {
      CASException casEx = new CASException(CASException.JCAS_FEATURENOTFOUND_ERROR, new String[] {
          t.getName(), s });
      sharedView.errorSet.add(new ErrorReport(casEx, false));  // false - no throw
    }
    if (null != f)
      try {
        casImpl.checkTypingConditions(t, rangeType, f);
      } catch (LowLevelException e) {
        CASException casEx = new CASException(CASException.JCAS_FEATURE_WRONG_TYPE, new String[] {
            t.getName(), s, rangeName, f.getRange().toString() });
        sharedView.errorSet.add(new ErrorReport(casEx, true));
      }
    return f;
  }

  /**
   * Internal - throw missing feature exception at runtime
   * 
   * @param feat -
   * @param type -
   */
  public void throwFeatMissing(String feat, String type) {
    CASRuntimeException e = new CASRuntimeException(CASRuntimeException.INAPPROP_FEAT,
        new String[] { feat, type });
    throw e;
  }


  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#putJfsFromCaddr(int, org.apache.uima.cas.FeatureStructure)
   */
  public void putJfsFromCaddr(int casAddr, FeatureStructure fs) {
    sharedView.cAddr2Jfs.put((FeatureStructureImpl) fs);
  }

  /*
   * (non-Javadoc)
   * 
   * Generics: extends FeatureStructure, not TOP, because
   * when the JCas is being used, but a particular type instance doesn't have a JCas cover class,
   * this holds instances of FeatureStructureC - the shared Class for non-JCas Java cover objects.
   * 
   * @see org.apache.uima.jcas.JCas#getJfsFromCaddr(int)
   */
  @SuppressWarnings("unchecked")
  public <T extends TOP> T getJfsFromCaddr(int casAddr) {
    return (T) sharedView.cAddr2Jfs.getReserve(casAddr);
  }

  public void showJfsFromCaddrHistogram() {
    sharedView.cAddr2Jfs.showHistogram();
  }

  // * Implementation of part of the Cas interface as part of JCas*

  /*
   * (Internal Use only) called by the CAS reset function - clears the hashtable holding the
   * associations. 
   */
  public static void clearData(CAS cas) {
    JCasImpl jcas = (JCasImpl) ((CASImpl) cas).getExistingJCas();
    final JCasSharedView sv = jcas.sharedView;
    sv.stringArray0L = null;
    sv.floatArray0L = null;
    sv.fsArray0L = null;
    sv.integerArray0L = null;

//    JCasHashMap currentMap = sv.cAddr2JfsByClassLoader.get(  ((CASImpl) cas).getJCasClassLoader()  );
    for (Iterator<Map.Entry<ClassLoader, JCasHashMap>> it = sv.cAddr2JfsByClassLoader.entrySet().iterator(); it.hasNext();) {
      Map.Entry<ClassLoader, JCasHashMap> e = it.next();
      e.getValue().clear();  // implements resize as well
      ClassLoader cl = e.getKey();
      JCasHashMap jcas_hashmap = e.getValue();
      jcas_hashmap.clear();
      if (cl instanceof UIMAClassLoader) {
        if (((UIMAClassLoader) cl).isClosed()) {
          it.remove();
          if (sv.cAddr2Jfs == jcas_hashmap) {
            sv.cAddr2Jfs = null;
          }
        }            
      }
      
//      sv.cAddr2Jfs = e.getValue();
//      sv.cAddr2Jfs.clear();  // implements resize as well
//      if (! e.getValue().equals(currentMap)) {
//        it.remove();
//      }
    }
//    sv.cAddr2Jfs = sv.cAddr2JfsByClassLoader
//        .get(((CASImpl) cas).getJCasClassLoader());
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#reset()
   */
  public void reset() {
    casImpl.reset();
  }

  private final static int NULL = 0;

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#checkArrayBounds(int, int)
   */
  public final void checkArrayBounds(int fsRef, int pos) {
    if (NULL == fsRef) {
      LowLevelException e = new LowLevelException(LowLevelException.NULL_ARRAY_ACCESS);
      // note - need to add this to ll_runtimeException
      e.addArgument(Integer.toString(pos));
      throw e;
    }
    final int arrayLength = casImpl.ll_getArraySize(fsRef);
    if (pos < 0 || pos >= arrayLength) {
      LowLevelException e = new LowLevelException(LowLevelException.ARRAY_INDEX_OUT_OF_RANGE);
      e.addArgument(Integer.toString(pos));
      throw e;
    }
  }

  // *****************
  // * Sofa support *
  // *****************

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getSofa(org.apache.uima.cas.SofaID)
   */
  public Sofa getSofa(SofaID sofaID) {
    return (Sofa) casImpl.getSofa(sofaID);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getSofa()
   */
  public Sofa getSofa() {
    return (Sofa) casImpl.getSofa();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#createView(java.lang.String)
   */
  public JCas createView(String sofaID) throws CASException {
    return casImpl.createView(sofaID).getJCas();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getJCas(org.apache.uima.jcas.cas.Sofa)
   */
  public JCas getJCas(Sofa sofa) throws CASException {
    return casImpl.getView(sofa).getJCas();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getSofaIterator()
   */
  public FSIterator<SofaFS> getSofaIterator() {
    return casImpl.getSofaIterator();
  }

  // *****************
  // * Index support *
  // *****************

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getJFSIndexRepository()
   */
  public JFSIndexRepository getJFSIndexRepository() {
    return jfsIndexRepository;
  }

  // ****************
  // * TCas support *
  // ****************

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getDocumentAnnotationFs()
   */
  public TOP getDocumentAnnotationFs() {
    return (TOP) casImpl.getDocumentAnnotation();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getDocumentText()
   */
  public String getDocumentText() {
    return casImpl.getDocumentText();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getSofaDataString()
   */
  public String getSofaDataString() {
    return casImpl.getSofaDataString();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getSofaDataArray()
   */
  public FeatureStructure getSofaDataArray() {
    return casImpl.getSofaDataArray();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getSofaDataURI()
   */
  public String getSofaDataURI() {
    return casImpl.getSofaDataURI();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getSofaMimeType()
   */
  public String getSofaMimeType() {
    return casImpl.getSofaMimeType();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#setDocumentText(java.lang.String)
   */
  public void setDocumentText(String text) throws CASRuntimeException {
    casImpl.setDocumentText(text);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#setSofaDataString(java.lang.String, java.lang.String)
   */
  public void setSofaDataString(String text, String mime) throws CASRuntimeException {
    casImpl.setSofaDataString(text, mime);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#setSofaDataArray(org.apache.uima.jcas.cas.TOP, java.lang.String)
   */
  public void setSofaDataArray(FeatureStructure array, String mime) throws CASRuntimeException {
    casImpl.setSofaDataArray(array, mime);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#setSofaDataURI(java.lang.String, java.lang.String)
   */
  public void setSofaDataURI(String uri, String mime) throws CASRuntimeException {
    casImpl.setSofaDataURI(uri, mime);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getDocumentLanguage()
   */
  public String getDocumentLanguage() {
    return casImpl.getDocumentLanguage();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#setDocumentLanguage(java.lang.String)
   */
  public void setDocumentLanguage(String language) throws CASRuntimeException {
    casImpl.setDocumentLanguage(language);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getSofaDataStream()
   */
  public InputStream getSofaDataStream() {
    return casImpl.getSofaDataStream();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getConstraintFactory()
   */
  public ConstraintFactory getConstraintFactory() {
    return casImpl.getConstraintFactory();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#createFeaturePath()
   */
  public FeaturePath createFeaturePath() {
    return casImpl.createFeaturePath();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#createFilteredIterator(org.apache.uima.cas.FSIterator,
   *      org.apache.uima.cas.FSMatchConstraint)
   */
  public <T extends FeatureStructure> FSIterator<T> createFilteredIterator(FSIterator<T> it, FSMatchConstraint constraint) {
    return casImpl.createFilteredIterator(it, constraint);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getStringArray0L()
   */

  public StringArray getStringArray0L() {
    if (null == sharedView.stringArray0L)
      sharedView.stringArray0L = new StringArray(this, 0);
    return sharedView.stringArray0L;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getIntegerArray0L()
   */
  public IntegerArray getIntegerArray0L() {
    if (null == sharedView.integerArray0L)
      sharedView.integerArray0L = new IntegerArray(this, 0);
    return sharedView.integerArray0L;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getFloatArray0L()
   */
  public FloatArray getFloatArray0L() {
    if (null == sharedView.floatArray0L)
      sharedView.floatArray0L = new FloatArray(this, 0);
    return sharedView.floatArray0L;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getFSArray0L()
   */
  public FSArray getFSArray0L() {
    if (null == sharedView.fsArray0L)
      sharedView.fsArray0L = new FSArray(this, 0);
    return sharedView.fsArray0L;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#processInit()
   */
  public void processInit() {
    // unused
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.cas.AbstractCas_ImplBase#setOwn
   */
  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#setOwner(org.apache.uima.cas.CasOwner)
   */
  public void setOwner(CasOwner aCasOwner) {
    casImpl.setOwner(aCasOwner);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#release()
   */
  public void release() {
    casImpl.release();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getView(java.lang.String)
   */
  public JCas getView(String localViewName) throws CASException {
//    try { // defer because can break some use cases see UIMA-5869
      return casImpl.getView(localViewName).getJCas();
//    } catch (CASRuntimeException e) {
//      throw new CASException(e); // https://issues.apache.org/jira/browse/UIMA-5869
//    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getView(org.apache.uima.cas.SofaFS)
   */
  public JCas getView(SofaFS aSofa) throws CASException {
    return casImpl.getView(aSofa).getJCas();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#addFsToIndexes(org.apache.uima.cas.FeatureStructure)
   */
  public void addFsToIndexes(FeatureStructure instance) {
    casImpl.addFsToIndexes(instance);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#removeFsFromIndexes(org.apache.uima.cas.FeatureStructure)
   */
  public void removeFsFromIndexes(FeatureStructure instance) {
    casImpl.removeFsFromIndexes(instance);
  }

  
  /* (non-Javadoc)
   * @see org.apache.uima.jcas.JCas#removeAllIncludingSubtypes(int)
   */
  public void removeAllIncludingSubtypes(int i) {
    getFSIndexRepository().removeAllIncludingSubtypes(getCasType(i));    
  }

  /* (non-Javadoc)
   * @see org.apache.uima.jcas.JCas#removeAllExcludingSubtypes(int)
   */
  public void removeAllExcludingSubtypes(int i) {
    getFSIndexRepository().removeAllExcludingSubtypes(getCasType(i));    
  }

  /**
   * @see org.apache.uima.cas.CAS#fs2listIterator(FSIterator)
   */
  public <T extends FeatureStructure> ListIterator<T> fs2listIterator(FSIterator<T> it) {
    return casImpl.fs2listIterator(it);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.cas.BaseCas#createFeatureValuePath(java.lang.String)
   */
  public FeatureValuePath createFeatureValuePath(String featureValuePath)
      throws CASRuntimeException {
    return casImpl.createFeatureValuePath(featureValuePath);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.cas.BaseCas#createSofa(org.apache.uima.cas.SofaID, java.lang.String)
   * @deprecated
   */
  public SofaFS createSofa(SofaID sofaID, String mimeType) {
    // extract absolute SofaName string from the ID
    return casImpl.createSofa(sofaID, mimeType);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.cas.BaseCas#getIndexRepository()
   */
  public FSIndexRepository getIndexRepository() {
    return casImpl.getIndexRepository();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.cas.BaseCas#getViewName()
   */
  public String getViewName() {
    return casImpl.getViewName();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.cas.BaseCas#size()
   */
  public int size() {
    // TODO improve this to account for JCas
    // structure sizes
    return casImpl.size();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getAnnotationIndex()
   */
  public AnnotationIndex<Annotation> getAnnotationIndex() {
    return casImpl.<Annotation>getAnnotationIndex();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getAnnotationIndex(org.apache.uima.cas.Type)
   */
  @Override
  public <T extends Annotation> AnnotationIndex<T> getAnnotationIndex(Type type) throws CASRuntimeException {
    return (AnnotationIndex<T>) casImpl.<T>getAnnotationIndex(type);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getAnnotationIndex(int)
   */
  public <T extends Annotation> AnnotationIndex<T> getAnnotationIndex(int type) throws CASRuntimeException {
    return (AnnotationIndex<T>) casImpl.<T>getAnnotationIndex(this.getCasType(type));
  }
  
  public <T extends Annotation> AnnotationIndex<T> getAnnotationIndex(Class<T> clazz) {
    return getAnnotationIndex(getCasType(clazz));
  }


  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getViewIterator()
   */
  public Iterator<JCas> getViewIterator() throws CASException {
    List<JCas> viewList = new ArrayList<JCas>();
    Iterator<CAS> casViewIter = casImpl.getViewIterator();
    while (casViewIter.hasNext()) {
      viewList.add((casViewIter.next()).getJCas());
    }
    return viewList.iterator();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.jcas.JCas#getViewIterator(java.lang.String)
   */
  public Iterator<JCas> getViewIterator(String localViewNamePrefix) throws CASException {
    List<JCas> viewList = new ArrayList<JCas>();
    Iterator<CAS> casViewIter = casImpl.getViewIterator(localViewNamePrefix);
    while (casViewIter.hasNext()) {
      viewList.add((casViewIter.next()).getJCas());
    }
    return viewList.iterator();
  }

  /* (non-Javadoc)
   * @see org.apache.uima.jcas.JCas#protectIndexes()
   */
  @Override
  public AutoCloseable protectIndexes() {
    return casImpl.protectIndexes();
  }

  /* (non-Javadoc)
   * @see org.apache.uima.jcas.JCas#protectIndexes(java.lang.Runnable)
   */
  @Override
  public void protectIndexes(Runnable runnable) {
    casImpl.protectIndexes(runnable);  
  }
  
  /**
   * Static method to get the corresponding Type for a JCas class object 
   */
  private int getTypeRegistryIndex(Class<? extends TOP> clazz) {
    try {
      return clazz.getField("type").getInt(clazz);
    } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
      throw new RuntimeException(e);  // should never happen
    }
  }
  
  /**
   * Return the UIMA Type object corresponding to this JCas's JCas cover class
   *   (Note: different JCas's, with different type systems, may share the same cover class impl)
   * @param clazz a JCas cover class
   * @return the corresponding UIMA Type object
   */
  public Type getCasType(Class<? extends TOP> clazz) {
    return getCasType(getTypeRegistryIndex(clazz));
  }

  @Override
  public <T extends TOP> FSIterator<T> getAllIndexedFS(Class<T> clazz) {
    return getFSIndexRepository().getAllIndexedFS(getCasType(clazz));
  }

  @Override
  public <T extends TOP> FSIndex<T> getIndex(String label, Class<T> clazz) {
    return getFSIndexRepository().getIndex(label, getCasType(clazz));
  }     
}
