/*
 * 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.cas.impl;

import static org.apache.uima.cas.impl.SlotKinds.SlotKind.Slot_Boolean;
import static org.apache.uima.cas.impl.SlotKinds.SlotKind.Slot_BooleanRef;
import static org.apache.uima.cas.impl.SlotKinds.SlotKind.Slot_Byte;
import static org.apache.uima.cas.impl.SlotKinds.SlotKind.Slot_ByteRef;
import static org.apache.uima.cas.impl.SlotKinds.SlotKind.Slot_DoubleRef;
import static org.apache.uima.cas.impl.SlotKinds.SlotKind.Slot_Float;
import static org.apache.uima.cas.impl.SlotKinds.SlotKind.Slot_HeapRef;
import static org.apache.uima.cas.impl.SlotKinds.SlotKind.Slot_Int;
import static org.apache.uima.cas.impl.SlotKinds.SlotKind.Slot_LongRef;
import static org.apache.uima.cas.impl.SlotKinds.SlotKind.Slot_Short;
import static org.apache.uima.cas.impl.SlotKinds.SlotKind.Slot_ShortRef;
import static org.apache.uima.cas.impl.SlotKinds.SlotKind.Slot_StrRef;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.invoke.MutableCallSite;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.WeakHashMap;
import java.util.stream.Collectors;

import org.apache.uima.UIMAFramework;
import org.apache.uima.UIMARuntimeException;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.CASRuntimeException;
import org.apache.uima.cas.Feature;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.TypeNameSpace;
import org.apache.uima.cas.TypeSystem;
import org.apache.uima.cas.admin.CASAdminException;
import org.apache.uima.cas.admin.TypeSystemMgr;
import org.apache.uima.cas.impl.FSClassRegistry.JCasClassInfo;
import org.apache.uima.cas.impl.SlotKinds.SlotKind;
import org.apache.uima.internal.util.Misc;
import org.apache.uima.jcas.JCasRegistry;
import org.apache.uima.jcas.cas.AnnotationBase;
import org.apache.uima.jcas.cas.BooleanArray;
import org.apache.uima.jcas.cas.ByteArray;
import org.apache.uima.jcas.cas.DoubleArray;
import org.apache.uima.jcas.cas.EmptyFSList;
import org.apache.uima.jcas.cas.EmptyFloatList;
import org.apache.uima.jcas.cas.EmptyIntegerList;
import org.apache.uima.jcas.cas.EmptyStringList;
import org.apache.uima.jcas.cas.FSArray;
import org.apache.uima.jcas.cas.FSList;
import org.apache.uima.jcas.cas.FloatArray;
import org.apache.uima.jcas.cas.FloatList;
import org.apache.uima.jcas.cas.IntegerArray;
import org.apache.uima.jcas.cas.IntegerList;
import org.apache.uima.jcas.cas.LongArray;
import org.apache.uima.jcas.cas.NonEmptyFSList;
import org.apache.uima.jcas.cas.NonEmptyFloatList;
import org.apache.uima.jcas.cas.NonEmptyIntegerList;
import org.apache.uima.jcas.cas.NonEmptyStringList;
import org.apache.uima.jcas.cas.ShortArray;
import org.apache.uima.jcas.cas.Sofa;
import org.apache.uima.jcas.cas.StringArray;
import org.apache.uima.jcas.cas.StringList;
import org.apache.uima.jcas.cas.TOP;
import org.apache.uima.jcas.tcas.Annotation;
import org.apache.uima.util.Logger;
import org.apache.uima.util.impl.Constants;

// @formatter:off
/**
 * Type system implementation.
 * 
 * This class has static final (constant) values for built-in type codes, feature codes, feature offsets, and feature isInt.
 *   - these are used  
 *     -- for various type-specific internal use 
 *     -- static final constants in the built-in JCas class definitions
 *   - these are set by hand as final constants  
 *    
 * Threading:  An instance of this object needs to be thread safe after creation, because multiple threads
 *     are reading info from it.
 *   - During creation, only one thread is creating. 
 * 
 * Association with Class Loaders, after type system is committed
 *   Because JCas classes are generated from the merged type system, and then loaded under some class loader,
 *   several use cases need to be accommodated.
 *   
 *     Multiple type systems committed in sequence within one UIMA application.
 *       - Under one class loader
 *         -- therefore, they share one JCas loaded classes definition
 *       - OK provided type system is compatible with JCas loaded definitions
 *       
 * At commit time, a type system is compared with existing ones; if it is equal, then
 *   the typeImpl and featureImpls and all other instance vars are replaced with existing ones.
 *   -- this removes issues in type system mapping needed between "equal" type systems to get feature validation to work.
 *   -- implemented via custom hashcode and equals.
 *         
 */
// @formatter:on
public class TypeSystemImpl implements TypeSystem, TypeSystemMgr, LowLevelTypeSystem {

  private static final boolean IS_TRACE_JCAS_EXPAND = false; // set to show jcas expands of types
  private final boolean debug = false; // flag to set on when hit entity annotation

  /**
   * Define this JVM property to disable equal type system consolidation. When a type system is
   * committed, it normally is compared with other committed type systems that are still available
   * (i.e. not garbage collected) to see if an existing type system is equal to the new one.
   * 
   * - if so, then the existing one is reused.
   * 
   * This may cause problems for applications which have obtained references to types and features
   * of a type system before it is committed; the committed version may have different (but "equal")
   * types and features.
   * 
   */
  public static final String DISABLE_TYPESYSTEM_CONSOLIDATION = "uima.disable_typesystem_consolidation";

  /**
   * Define this JVM property to enable strictly checking the source of a type with respect to its
   * type system. Normally (i.e. when typesystem consolidation is enabled), types that are
   * semantically equivalent should come from the same type system instances. There could be bugs
   * (in client or framework code) or legacy code which simply ignores this requirement and still
   * works. Therefore, this property allow to restore the old non-strict-checking behavior.
   */
  public static final String ENABLE_STRICT_TYPE_SOURCE_CHECK = "uima.enable_strict_type_source_check";

  /**
   * public for test case
   */
  public static final boolean IS_DISABLE_TYPESYSTEM_CONSOLIDATION = // true || // debug
          Misc.getNoValueSystemProperty(DISABLE_TYPESYSTEM_CONSOLIDATION);

  public static final boolean IS_ENABLE_STRICT_TYPE_SOURCE_CHECK = // true || // debug
          Misc.getNoValueSystemProperty(ENABLE_STRICT_TYPE_SOURCE_CHECK);

  // private final static String DECOMPILE_JCAS = "uima.decompile.jcas";
  // private final static boolean IS_DECOMPILE_JCAS = Misc.getNoValueSystemProperty(DECOMPILE_JCAS);
  // private final static Set<String> decompiled = (IS_DECOMPILE_JCAS) ? new HashSet<String>(256) :
  // null;

  static private final MethodHandle MHC_MINUS_1 = MethodHandles.constant(int.class, -1);

  /**
   * Type code that is returned on unknown type names.
   */
  static final int UNKNOWN_TYPE_CODE = 0;

  private static final int LEAST_TYPE_CODE = 1;

  // private static final int INVALID_TYPE_CODE = 0;
  private static final int LEAST_FEATURE_CODE = 1;

  static final String ARRAY_TYPE_SUFFIX = "[]";

  /**
   * HEAP_STORED_ARRAY flag is kept for ser/deserialization compatibility
   */
  private static final boolean HEAP_STORED_ARRAY = true;

  /**
   * For a given class loader, prevent redefinitions of the same type system from
   * 
   */
  // private static final Map<ClassLoader, Set<TypeSystemImpl>> committedTypeSystemsByClassLoader =
  // Collections.newSetFromMap(
  // new WeakHashMap<TypeSystemImpl, Boolean>()));

  private final static int INIT_SIZE_ARRAYS_BUILT_IN_TYPES = 64; // approximate... used for array
                                                                 // sizing only

  // static maps ok for now - only built-in mappings stored here
  // which are the same for all type system instances
  /**
   * Map from component name to built-in array name
   */
  private final static Map<String, String> builtInArrayComponentName2ArrayTypeName = new HashMap<>(
          9);

  static {
    builtInArrayComponentName2ArrayTypeName.put(CAS.TYPE_NAME_TOP, CAS.TYPE_NAME_FS_ARRAY);
    builtInArrayComponentName2ArrayTypeName.put(CAS.TYPE_NAME_BOOLEAN, CAS.TYPE_NAME_BOOLEAN_ARRAY);
    builtInArrayComponentName2ArrayTypeName.put(CAS.TYPE_NAME_BYTE, CAS.TYPE_NAME_BYTE_ARRAY);
    builtInArrayComponentName2ArrayTypeName.put(CAS.TYPE_NAME_SHORT, CAS.TYPE_NAME_SHORT_ARRAY);
    builtInArrayComponentName2ArrayTypeName.put(CAS.TYPE_NAME_INTEGER, CAS.TYPE_NAME_INTEGER_ARRAY);
    builtInArrayComponentName2ArrayTypeName.put(CAS.TYPE_NAME_FLOAT, CAS.TYPE_NAME_FLOAT_ARRAY);
    builtInArrayComponentName2ArrayTypeName.put(CAS.TYPE_NAME_LONG, CAS.TYPE_NAME_LONG_ARRAY);
    builtInArrayComponentName2ArrayTypeName.put(CAS.TYPE_NAME_DOUBLE, CAS.TYPE_NAME_DOUBLE_ARRAY);
    builtInArrayComponentName2ArrayTypeName.put(CAS.TYPE_NAME_STRING, CAS.TYPE_NAME_STRING_ARRAY);
    // builtInArrayComponentName2ArrayTypeName.put(CAS.TYPE_NAME_JAVA_OBJECT,
    // CAS.TYPE_NAME_JAVA_OBJECT_ARRAY);

  }

  private final static Map<String, SlotKind> slotKindsForNonArrays = new HashMap<>(9);
  static {
    slotKindsForNonArrays.put(CAS.TYPE_NAME_STRING, Slot_StrRef);
    slotKindsForNonArrays.put(CAS.TYPE_NAME_INTEGER, Slot_Int);
    slotKindsForNonArrays.put(CAS.TYPE_NAME_BOOLEAN, Slot_Boolean);
    slotKindsForNonArrays.put(CAS.TYPE_NAME_BYTE, Slot_Byte);
    slotKindsForNonArrays.put(CAS.TYPE_NAME_SHORT, Slot_Short);
    slotKindsForNonArrays.put(CAS.TYPE_NAME_FLOAT, Slot_Float);
    slotKindsForNonArrays.put(CAS.TYPE_NAME_LONG, Slot_LongRef);
    slotKindsForNonArrays.put(CAS.TYPE_NAME_DOUBLE, Slot_DoubleRef);
  }

  // private static final Object GLOBAL_TYPESYS_LOCK = new Object();

  // private static final Set<String> builtInsWithAltNames = new HashSet<String>();
  // static {Misc.addAll(builtInsWithAltNames,
  // CAS.TYPE_NAME_TOP,
  // CAS.TYPE_NAME_STRING_ARRAY,
  // CAS.TYPE_NAME_BOOLEAN_ARRAY,
  // CAS.TYPE_NAME_BYTE_ARRAY,
  // CAS.TYPE_NAME_SHORT_ARRAY,
  // CAS.TYPE_NAME_INTEGER_ARRAY,
  // CAS.TYPE_NAME_LONG_ARRAY,
  // CAS.TYPE_NAME_FS_ARRAY,
  // CAS.TYPE_NAME_FLOAT_ARRAY,
  // CAS.TYPE_NAME_DOUBLE_ARRAY,
  // CAS.TYPE_NAME_EMPTY_FLOAT_LIST,
  // CAS.TYPE_NAME_EMPTY_FS_LIST,
  // CAS.TYPE_NAME_EMPTY_INTEGER_LIST,
  // CAS.TYPE_NAME_EMPTY_STRING_LIST,
  // CAS.TYPE_NAME_FLOAT_LIST,
  // CAS.TYPE_NAME_FS_LIST,
  // CAS.TYPE_NAME_INTEGER_LIST,
  // CAS.TYPE_NAME_STRING_LIST,
  // CAS.TYPE_NAME_NON_EMPTY_FLOAT_LIST,
  // CAS.TYPE_NAME_NON_EMPTY_FS_LIST,
  // CAS.TYPE_NAME_NON_EMPTY_INTEGER_LIST,
  // CAS.TYPE_NAME_NON_EMPTY_STRING_LIST,
  // CAS.TYPE_NAME_SOFA,
  // CAS.TYPE_NAME_ANNOTATION_BASE,
  // CAS.TYPE_NAME_ANNOTATION,
  // CAS.TYPE_NAME_DOCUMENT_ANNOTATION
  //// CAS.TYPE_NAME_JAVA_OBJECT_ARRAY
  // );
  // }

  final private static Map<TypeSystemImpl, WeakReference<TypeSystemImpl>> committedTypeSystems = Collections
          .synchronizedMap(new WeakHashMap<>());

  // /** OBSOLETE with BETA and later levels
  // * used to pass the type being loaded reference to the JCas static initializer code,
  // * referenced from the TypeSystemImpl.getAdjustedFeatureOffset([featurename]) method.
  // */
  // public final static ThreadLocal<TypeImpl> typeBeingLoadedThreadLocal = new
  // ThreadLocal<TypeImpl>();

  /******************************************
   * I N S T A N C E V A R I A B L E S *
   ******************************************/
  // private final TypeImpl[] allTypesForByteCodeGen;
  //
  // private FeatureStructureClassGen featureStructureClassGen = new FeatureStructureClassGen();

  // private FSClassRegistry fsClassRegistry; // a new instance is created at commit time.

  // the loaded JCas cover classes, generators. index is typecode; value is JCas cover class which
  // may belong to a supertype.
  // OK because the instance of this class FSClassRegistry is one per Type System
  // not final because set after all types are defined and committed

  // no longer used July 2016 - info moved into individual typeImpls
  // JCasClassInfo[] jcasClassesInfo;

  /**
   * Map from built-in array name to component Type
   */
  private final Map<String, TypeImpl> arrayName2ComponentType = new HashMap<>(9);

  // not static in general because need different instances for each type system
  // because instances have direct subtypes
  final TypeImpl topType;
  public final TypeImpl intType;
  public final TypeImpl stringType;
  public final TypeImpl floatType;
  final TypeImpl arrayBaseType;
  final TypeImpl_array intArrayType;
  final TypeImpl_array floatArrayType;
  final TypeImpl_array stringArrayType;
  final TypeImpl_array fsArrayType;
  final TypeImpl_array topArrayType; // same as fsArrayType
  public final TypeImpl sofaType; // public needed for CasCopier
  public final TypeImpl annotType;
  public final TypeImpl annotBaseType;
  public final TypeImpl docType;
  public final TypeImpl byteType;
  final TypeImpl_array byteArrayType;
  public final TypeImpl booleanType;
  final TypeImpl_array booleanArrayType;
  public final TypeImpl shortType;
  final TypeImpl_array shortArrayType;
  public final TypeImpl longType;
  final TypeImpl_array longArrayType;
  public final TypeImpl doubleType;
  final TypeImpl_array doubleArrayType;

  // final TypeImpl_javaObject javaObjectType; // for Map, List, etc.
  // final TypeImpl_array javaObjectArrayType; // for arrays of these
  final TypeImpl listBaseType;
  public final TypeImpl_list intListType;
  public final TypeImpl_list floatListType;
  public final TypeImpl_list stringListType;
  public final TypeImpl_list fsListType;
  public final TypeImpl_list intEListType;
  public final TypeImpl_list floatEListType;
  public final TypeImpl_list stringEListType;
  public final TypeImpl_list fsEListType;
  public final TypeImpl_list intNeListType;
  public final TypeImpl_list floatNeListType;
  public final TypeImpl_list stringNeListType;
  public final TypeImpl_list fsNeListType;

  // no longer built-in for better backwards compatibility
  // public final TypeImpl fsArrayListType;
  // public final TypeImpl intArrayListType;
  // public final TypeImpl fsHashSetType;

  // /**
  // * List indexed by typecode
  // * Value is an List<TypeImpl> of the directly subsumed types (just one level below)
  // */
  // private final List<List<TypeImpl>> directSubtypes = new ArrayList<>(); // ordered collection of
  // Lists of direct subtypes
  // {directSubtypes.add(null);} // 0th element skipped
  // List<List<TypeImpl>> getDirectSubtypes() { return directSubtypes; }
  //
  // /**
  // * @param typecode of type having subtypes
  // * @return List of direct subtypes
  // */
  // List<TypeImpl> getDirectSubtypes(int typecode) {
  // if (typecode >= directSubtypes.size()) {
  // List<TypeImpl> r = new ArrayList<>();
  // directSubtypes.add(typecode, r);
  // return r;
  // }
  // return directSubtypes.get(typecode);
  // }

  /**
   * Map from fully qualified type name to TypeImpl
   */
  final Map<String, TypeImpl> typeName2TypeImpl = new HashMap<>(64);

  /**
   * Map from array component Type to the corresponding array type
   */
  private Map<Type, Type> arrayComponentTypeToArrayType = new IdentityHashMap<>();

  /**
   * An ArrayList, unsynchronized, indexed by typeCode, of Type objects
   */
  final List<TypeImpl> types = new ArrayList<>(INIT_SIZE_ARRAYS_BUILT_IN_TYPES);
  {
    types.add(null);
  } // use up slot 0

  /**
   * Used to go from a JCas class's JCasTypeID to the corresponding UIMA type for this type system.
   * - when doing "new Foo(jcas)" - when referring to a type using its JCas typeID
   * 
   * Used as a map, the key is the JCas loaded type id, set once when each JCas class is loaded.
   * value is the corresponding TypeImpl
   * 
   * When multiple type systems are being initialized in parallel, this list maybe updated on
   * different threads. Access to it is synchronized on the object itself
   * 
   * A single JCas type ID (corresponding to a single JCas type) might be used for many UIMA types.
   * but they are always in a type hierarchy, and the top most one is the one stored here
   */
  private final List<TypeImpl> jcasRegisteredTypes = new ArrayList<>(
          INIT_SIZE_ARRAYS_BUILT_IN_TYPES);
  /**
   * An ArrayList, unsynchronized, indexed by feature code, of FeatureImpl objects
   */
  final List<FeatureImpl> features = new ArrayList<>(INIT_SIZE_ARRAYS_BUILT_IN_TYPES);
  {
    features.add(null);
  } // use up slot 0

  // // An ArrayList (unsynchronized) of FeatureImpl objects.
  // private final List<Feature> features;

  // /**
  // * array of parent typeCodes indexed by typeCode
  // */
  // private final IntVector parents;

  // // String sets for string subtypes.
  // private final List<String[]> stringSets;

  // // This map contains an entry for every subtype of the string type. The value is a pointer into
  // // the stringSets array list.
  // private final IntRedBlackTree stringSetMap = new IntRedBlackTree();

  // Is the type system locked?
  // Not supported: multiple threads working on the *same* type system at a time.
  private boolean locked = false;

  // private int numTypeNames = 0;
  //
  // private int numFeatureNames = 0;

  // map from type code to TypeInfo instance for that type code,
  // set up lazily
  // TypeInfo[] typeInfoArray;

  // saw evidence that in some cases the setup is called on the same instance on two threads
  // must be volatile to force the right memory barriers
  volatile boolean areBuiltInTypesSetup = false;

  // private final DecompilerSettings decompilerSettings = (IS_DECOMPILE_JCAS) ?
  // DecompilerSettings.javaDefaults() : null;

  FeatureImpl startFeat;
  FeatureImpl endFeat;
  FeatureImpl langFeat;
  FeatureImpl sofaNum;
  FeatureImpl sofaId;
  FeatureImpl sofaMime;
  FeatureImpl sofaArray;
  FeatureImpl sofaString;
  FeatureImpl sofaUri;
  FeatureImpl annotBaseSofaFeat;

  /**
   * Cache for implementing map from type code -> FsGenerator Shared by all CASes using this type
   * system
   *
   * maps from classloader to generator set for non-pears
   * 
   */
  private final Map<ClassLoader, FsGenerator3[]> generatorsByClassLoader = new IdentityHashMap<>();

  /**
   * Cache for implementing map from type code -> FsGenerator Shared by all CASes using this type
   * system
   *
   * maps from classloader to generator set for pears
   * 
   */
  private final Map<ClassLoader, FsGenerator3[]> generators4pearsByClassLoader = new IdentityHashMap<>();

  private int nextI; // temp value used in computing adjusted offsets
  private int nextR; // temp value used in computing adjusted offsets
  private Map<String, JCasClassInfo> type2jcci; // temp value used in computing adjusted offsets
  private Lookup lookup;
  private ClassLoader cl_for_commit;
  private boolean skip_loading_user_jcas = false;

  public TypeSystemImpl() {

    // set up meta info (TypeImpl) for built-in types

   // @formatter:off
    /****************************************************************
     *   D O   N O T   C H A N G E   T H I S   O R D E R I N G ! !  *
     *   ---   -----   -----------   -------   -------------------
     ****************************************************************/
   // @formatter:on

    // Create top type.
    topType = new TypeImpl(CAS.TYPE_NAME_TOP, this, null, TOP.class);

    // Add basic data types.
    intType = new TypeImpl_primitive(CAS.TYPE_NAME_INTEGER, this, topType, int.class);
    floatType = new TypeImpl_primitive(CAS.TYPE_NAME_FLOAT, this, topType, float.class);
    stringType = new TypeImpl_string(CAS.TYPE_NAME_STRING, this, topType, String.class);

    // Add arrays.
    arrayBaseType = new TypeImpl(CAS.TYPE_NAME_ARRAY_BASE, this, topType);
    topArrayType = fsArrayType = addArrayType(topType, Slot_HeapRef, HEAP_STORED_ARRAY,
            FSArray.class);
    floatArrayType = addArrayType(floatType, Slot_Float, HEAP_STORED_ARRAY, FloatArray.class);
    intArrayType = addArrayType(intType, Slot_Int, HEAP_STORED_ARRAY, IntegerArray.class);
    stringArrayType = addArrayType(stringType, Slot_StrRef, HEAP_STORED_ARRAY, StringArray.class);

 // @formatter:off
    /**********************************************************************************************
     * Add Lists                                                                                  *
     *   The Tail feature is not factored out into listBaseType because it returns a typed value. *
     **********************************************************************************************/
 // @formatter:on
    listBaseType = new TypeImpl(CAS.TYPE_NAME_LIST_BASE, this, topType);

    // FS list
    fsListType = new TypeImpl_list(CAS.TYPE_NAME_FS_LIST, topType, this, listBaseType,
            FSList.class);
    fsEListType = new TypeImpl_list(CAS.TYPE_NAME_EMPTY_FS_LIST, topType, this, fsListType,
            EmptyFSList.class);
    fsNeListType = new TypeImpl_list(CAS.TYPE_NAME_NON_EMPTY_FS_LIST, topType, this, fsListType,
            NonEmptyFSList.class);
    addFeature(CAS.FEATURE_BASE_NAME_TAIL, fsNeListType, fsListType, true);
    addFeature(CAS.FEATURE_BASE_NAME_HEAD, fsNeListType, topType, true);

    // Float list
    floatListType = new TypeImpl_list(CAS.TYPE_NAME_FLOAT_LIST, floatType, this, listBaseType,
            FloatList.class);
    floatEListType = new TypeImpl_list(CAS.TYPE_NAME_EMPTY_FLOAT_LIST, floatType, this,
            floatListType, EmptyFloatList.class);
    floatNeListType = new TypeImpl_list(CAS.TYPE_NAME_NON_EMPTY_FLOAT_LIST, floatType, this,
            floatListType, NonEmptyFloatList.class);
    addFeature(CAS.FEATURE_BASE_NAME_TAIL, floatNeListType, floatListType, true);
    addFeature(CAS.FEATURE_BASE_NAME_HEAD, floatNeListType, floatType, false);

    // Integer list
    intListType = new TypeImpl_list(CAS.TYPE_NAME_INTEGER_LIST, intType, this, listBaseType,
            IntegerList.class);
    intEListType = new TypeImpl_list(CAS.TYPE_NAME_EMPTY_INTEGER_LIST, intType, this, intListType,
            EmptyIntegerList.class);
    intNeListType = new TypeImpl_list(CAS.TYPE_NAME_NON_EMPTY_INTEGER_LIST, intType, this,
            intListType, NonEmptyIntegerList.class);
    addFeature(CAS.FEATURE_BASE_NAME_TAIL, intNeListType, intListType, true);
    addFeature(CAS.FEATURE_BASE_NAME_HEAD, intNeListType, intType, false);

    // String list
    stringListType = new TypeImpl_list(CAS.TYPE_NAME_STRING_LIST, stringType, this, listBaseType,
            StringList.class);
    stringEListType = new TypeImpl_list(CAS.TYPE_NAME_EMPTY_STRING_LIST, stringType, this,
            stringListType, EmptyStringList.class);
    stringNeListType = new TypeImpl_list(CAS.TYPE_NAME_NON_EMPTY_STRING_LIST, stringType, this,
            stringListType, NonEmptyStringList.class);
    addFeature(CAS.FEATURE_BASE_NAME_TAIL, stringNeListType, stringListType, true);
    addFeature(CAS.FEATURE_BASE_NAME_HEAD, stringNeListType, stringType, false);

    booleanType = new TypeImpl_primitive(CAS.TYPE_NAME_BOOLEAN, this, topType, boolean.class);
    byteType = new TypeImpl_primitive(CAS.TYPE_NAME_BYTE, this, topType, byte.class);
    shortType = new TypeImpl_primitive(CAS.TYPE_NAME_SHORT, this, topType, short.class);
    longType = new TypeImpl_primitive(CAS.TYPE_NAME_LONG, this, topType, long.class);
    doubleType = new TypeImpl_primitive(CAS.TYPE_NAME_DOUBLE, this, topType, double.class);

    // array type initialization must follow the component type it's based on
    booleanArrayType = addArrayType(booleanType, Slot_BooleanRef, !HEAP_STORED_ARRAY,
            BooleanArray.class); // yes, byteref
    byteArrayType = addArrayType(byteType, Slot_ByteRef, !HEAP_STORED_ARRAY, ByteArray.class);
    shortArrayType = addArrayType(shortType, Slot_ShortRef, !HEAP_STORED_ARRAY, ShortArray.class);
    longArrayType = addArrayType(longType, Slot_LongRef, !HEAP_STORED_ARRAY, LongArray.class);
    doubleArrayType = addArrayType(doubleType, Slot_DoubleRef, !HEAP_STORED_ARRAY,
            DoubleArray.class);

    // Sofa Stuff
    sofaType = new TypeImpl(CAS.TYPE_NAME_SOFA, this, topType, Sofa.class);
    sofaNum = (FeatureImpl) addFeature(CAS.FEATURE_BASE_NAME_SOFANUM, sofaType, intType, false);
    sofaId = (FeatureImpl) addFeature(CAS.FEATURE_BASE_NAME_SOFAID, sofaType, stringType, false);
    sofaMime = (FeatureImpl) addFeature(CAS.FEATURE_BASE_NAME_SOFAMIME, sofaType, stringType,
            false);
    // Type localSofa = addType(CAS.TYPE_NAME_LOCALSOFA, sofa);
    sofaArray = (FeatureImpl) addFeature(CAS.FEATURE_BASE_NAME_SOFAARRAY, sofaType, topType, true);
    sofaString = (FeatureImpl) addFeature(CAS.FEATURE_BASE_NAME_SOFASTRING, sofaType, stringType,
            false);
    // Type remoteSofa = addType(CAS.TYPE_NAME_REMOTESOFA, sofa);
    sofaUri = (FeatureImpl) addFeature(CAS.FEATURE_BASE_NAME_SOFAURI, sofaType, stringType, false);

    // Annotations
    annotBaseType = new TypeImpl_annotBase(CAS.TYPE_NAME_ANNOTATION_BASE, this, topType,
            AnnotationBase.class);
    annotBaseSofaFeat = (FeatureImpl) addFeature(CAS.FEATURE_BASE_NAME_SOFA, annotBaseType,
            sofaType, false);

    annotType = new TypeImpl_annot(CAS.TYPE_NAME_ANNOTATION, this, annotBaseType, Annotation.class);
    startFeat = (FeatureImpl) addFeature(CAS.FEATURE_BASE_NAME_BEGIN, annotType, intType, false);
    endFeat = (FeatureImpl) addFeature(CAS.FEATURE_BASE_NAME_END, annotType, intType, false);

    docType = new TypeImpl_annot(CAS.TYPE_NAME_DOCUMENT_ANNOTATION, this, annotType,
            Annotation.class);
    langFeat = (FeatureImpl) addFeature(CAS.FEATURE_BASE_NAME_LANGUAGE, docType, stringType, false);

    // fsArrayListType = new TypeImpl(CAS.TYPE_NAME_FS_ARRAY_LIST, this, topType);
    // addFeature(CAS.FEATURE_BASE_NAME_FS_ARRAY, fsArrayListType, fsArrayType);
    //
    // intArrayListType = new TypeImpl(CAS.TYPE_NAME_INT_ARRAY_LIST, this, topType);
    // addFeature(CAS.FEATURE_BASE_NAME_INT_ARRAY, intArrayListType, intArrayType);
    //
    // fsHashSetType = new TypeImpl(CAS.TYPE_NAME_FS_HASH_SET, this, topType);
    // addFeature(CAS.FEATURE_BASE_NAME_FS_ARRAY, fsHashSetType, fsArrayType);

    // javaObjectType = new TypeImpl_javaObject(CAS.TYPE_NAME_JAVA_OBJECT, this, topType,
    // Object.class);
    // javaObjectArrayType = addArrayType(javaObjectType, null, HEAP_STORED_ARRAY,
    // JavaObjectArray.class);

    arrayName2ComponentType.put(CAS.TYPE_NAME_FS_ARRAY, topType);
    arrayName2ComponentType.put(CAS.TYPE_NAME_BOOLEAN_ARRAY, booleanType);
    arrayName2ComponentType.put(CAS.TYPE_NAME_BYTE_ARRAY, byteType);
    arrayName2ComponentType.put(CAS.TYPE_NAME_SHORT_ARRAY, shortType);
    arrayName2ComponentType.put(CAS.TYPE_NAME_INTEGER_ARRAY, intType);
    arrayName2ComponentType.put(CAS.TYPE_NAME_FLOAT_ARRAY, floatType);
    arrayName2ComponentType.put(CAS.TYPE_NAME_LONG_ARRAY, longType);
    arrayName2ComponentType.put(CAS.TYPE_NAME_DOUBLE_ARRAY, doubleType);
    arrayName2ComponentType.put(CAS.TYPE_NAME_STRING_ARRAY, stringType);
    // arrayName2ComponentType.put(CAS.TYPE_NAME_JAVA_OBJECT_ARRAY, javaObjectType);

    // // initialize the decompiler settings to read the class definition
    // // from the classloader of this class. Needs to be fixed to work with PEARs
    // // or custom UIMA class loaders.
    //
    // if (IS_DECOMPILE_JCAS) {
    // ITypeLoader tl = new ITypeLoader() {
    //
    // @Override
    // public boolean tryLoadType(String internalName, Buffer buffer) {
    //
    // // get the classloader to use to read the class as a resource
    // ClassLoader cl = this.getClass().getClassLoader();
    //
    // // read the class as a resource, and put into temporary byte array output stream
    // // because we need to know the length
    //
    // internalName = internalName.replace('.', '/') + ".class";
    // InputStream stream = cl.getResourceAsStream(internalName);
    // if (stream == null) {
    // return false;
    // }
    // ByteArrayOutputStream baos = new ByteArrayOutputStream(1024 * 16);
    // byte[] b = new byte[1024 * 16];
    // int numberRead;
    // try {
    // while (0 <= (numberRead = stream.read(b))){
    // baos.write(b, 0, numberRead);
    // }
    // } catch (IOException e) {
    // throw new RuntimeException(e);
    // }
    //
    // // Copy result (based on length) into output buffer spot
    // int length = baos.size();
    // b = baos.toByteArray();
    // buffer.reset(length);
    // System.arraycopy(b, 0, buffer.array(), 0, length);
    //
    // return true;
    // }
    // };
    // decompilerSettings.setTypeLoader(tl);
    // }

    // Lock individual types.
    setTypeFinal(intType);
    setTypeFinal(floatType);
    setTypeFinal(stringType);
    topType.setFeatureFinal();
    setTypeFinal(arrayBaseType);
    setTypeFinal(fsArrayType);
    setTypeFinal(intArrayType);
    setTypeFinal(floatArrayType);
    setTypeFinal(stringArrayType);
    setTypeFinal(sofaType);

    setTypeFinal(byteType);
    setTypeFinal(booleanType);
    setTypeFinal(shortType);
    setTypeFinal(longType);
    setTypeFinal(doubleType);
    setTypeFinal(booleanArrayType);
    setTypeFinal(byteArrayType);
    setTypeFinal(shortArrayType);
    setTypeFinal(longArrayType);
    setTypeFinal(doubleArrayType);
    // setTypeFinal(javaObjectArrayType);

    setTypeFinal(fsListType);
    setTypeFinal(floatListType);
    setTypeFinal(stringListType);
    setTypeFinal(intListType);
    setTypeFinal(fsEListType);
    setTypeFinal(floatEListType);
    setTypeFinal(stringEListType);
    setTypeFinal(intEListType);
    setTypeFinal(fsNeListType);
    setTypeFinal(floatNeListType);
    setTypeFinal(stringNeListType);
    setTypeFinal(intNeListType);

    topType.setBuiltIn();
    listBaseType.setBuiltIn();
    fsListType.setBuiltIn();
    fsEListType.setBuiltIn();
    fsNeListType.setBuiltIn();
    floatListType.setBuiltIn();
    floatEListType.setBuiltIn();
    floatNeListType.setBuiltIn();
    intListType.setBuiltIn();
    intEListType.setBuiltIn();
    intNeListType.setBuiltIn();
    stringListType.setBuiltIn();
    stringEListType.setBuiltIn();
    stringNeListType.setBuiltIn();
    annotType.setBuiltIn();
    annotBaseType.setBuiltIn();

    // setTypeFinal(fsArrayListType);
    // setTypeFinal(intArrayListType);
    // setTypeFinal(fsHashSetType);

    listBaseType.setFeatureFinal();
    fsListType.setFeatureFinal();
    fsEListType.setFeatureFinal();
    fsNeListType.setFeatureFinal();
    floatListType.setFeatureFinal();
    floatEListType.setFeatureFinal();
    floatNeListType.setFeatureFinal();
    intListType.setFeatureFinal();
    intEListType.setFeatureFinal();
    intNeListType.setFeatureFinal();
    stringListType.setFeatureFinal();
    stringEListType.setFeatureFinal();
    stringNeListType.setFeatureFinal();
    annotType.setFeatureFinal();
    annotBaseType.setFeatureFinal();

    // fsArrayListType.setFeatureFinal();
    // intListType.setFeatureFinal();
    // fsHashSetType.setFeatureFinal();

    // allTypesForByteCodeGen = new TypeImpl[] {
    // booleanType,
    // byteType,
    // shortType,
    // intType,
    // floatType,
    // longType,
    // doubleType,
    //
    // stringType,
    // topType,
    // javaObjectType,
    //
    // booleanArrayType,
    // byteArrayType,
    // shortArrayType,
    // intArrayType,
    // floatArrayType,
    // longArrayType,
    // doubleArrayType,
    // stringArrayType,
    // topArrayType,
    // javaObjectArrayType};

  }

  // Some implementation helpers for users of the type system.
  final int getSmallestType() {
    return LEAST_TYPE_CODE;
  }

  final int getSmallestFeature() {
    return LEAST_FEATURE_CODE;
  }

  public final int getTypeArraySize() {
    return types.size(); // getNumberOfTypes() + getSmallestType();
  }

  public Vector<Feature> getIntroFeatures(Type type) {
    Vector<Feature> feats = new Vector<>();
    List<Feature> appropFeats = type.getFeatures();
    final int max = appropFeats.size();
    Feature feat;
    for (int i = 0; i < max; i++) {
      feat = appropFeats.get(i);
      if (feat.getDomain() == type) {
        feats.add(feat);
      }
    }
    return feats;
  }

  @Override
  public Type getParent(Type t) {
    return ((TypeImpl) t).getSuperType();
  }

  // int ll_computeArrayParentFromComponentType(int componentType) {
  // if (ll_isPrimitiveType(componentType) ||
  // // note: not using top - until we can confirm this is set
  // // in all cases
  // (ll_getTypeForCode(componentType).getName().equals(TypeSystem.TYPE_NAME_TOP))) {
  // return arrayBaseTypeCode;
  // }
  // // is a subtype of FSArray.
  // // note: not using this.fsArray - until we can confirm this is set in
  // // all cases
  // return fsArrayTypeCode;
  // // return ll_getArrayType(ll_getParentType(componentType));
  // }

  // /**
  // * Check if feature is appropriate for type (i.e., type is subsumed by domain type of feature).
  // * @param type -
  // * @param feat -
  // * @return true if feature is appropriate for type (i.e., type is subsumed by domain type of
  // feature).
  // */
  // public boolean isApprop(int type, int feat) {
  //
  // return subsumes(intro(feat), type);
  // }

  public final int getLargestTypeCode() {
    return getNumberOfTypes();
  }

  /**
   * 
   * @param typecode
   *          to check if it's in the range of valid type codes
   * @return true if it is
   */
  public boolean isType(int typecode) {
    return ((typecode >= LEAST_TYPE_CODE) && (typecode <= getLargestTypeCode()));
  }

  /**
   * Get a type object for a given name.
   * 
   * @param typeName
   *          The name of the type.
   * @return A type object, or <code>null</code> if no such type exists.
   */
  @Override
  public TypeImpl getType(String typeName) {
    return typeName2TypeImpl.get(typeName);
  }

  /**
   * Get an feature object for a given code.
   * 
   * @param featCode
   *          The code of the feature.
   * @return A feature object, or <code>null</code> if no such feature exists.
   */
  // public Feature getFeature(int featCode) {
  // return (Feature) this.features.get(featCode);
  // }

  /**
   * Get an feature object for a given name.
   * 
   * @param featureName
   *          The name of the feature.
   * @return An feature object, or <code>null</code> if no such feature exists.
   */
  @Override
  public FeatureImpl getFeatureByFullName(String featureName) {
    int split = featureName.indexOf(TypeSystem.FEATURE_SEPARATOR);
    return getFeature(featureName.substring(0, split), featureName.substring(split + 1));
  }

  /**
   * For component xyz, returns "xyz[]"
   * 
   * @param componentType
   *          the component type
   * @return the name of the component + []
   */
  private static final String getArrayTypeName(String typeName) {
    final String arrayTypeName = builtInArrayComponentName2ArrayTypeName.get(typeName);
    return (null == arrayTypeName) ? typeName + ARRAY_TYPE_SUFFIX : arrayTypeName;
  }

  static final String getArrayComponentName(String arrayTypeName) {
    return arrayTypeName.substring(0, arrayTypeName.length() - 2);
  }

  static boolean isArrayTypeNameButNotBuiltIn(String typeName) {
    return typeName.endsWith(ARRAY_TYPE_SUFFIX);
  }

  // private final TypeImpl getBuiltinArrayComponent(String typeName) {
  // // if typeName is not contained in the map, the "get" returns null
  // // if (arrayName2ComponentType.containsKey(typeName)) {
  // return arrayName2ComponentType.get(typeName);
  // // }
  // // return null;
  // }

  void newTypeChecks(String typeName, Type superType) {
    if (typeName.endsWith(ARRAY_TYPE_SUFFIX)) {
      checkTypeSyntax(typeName.substring(0, typeName.length() - 2));
    } else {
      if (this.locked) {
        throw new CASAdminException(CASAdminException.TYPE_SYSTEM_LOCKED);
      }

      if (superType != null && superType.isInheritanceFinal()) {
        throw new CASAdminException(CASAdminException.TYPE_IS_INH_FINAL, superType);
      }
      checkTypeSyntax(typeName);
    }
  }

  void newTypeCheckNoInheritanceFinalCheck(String typeName, Type superType) {
    if (this.locked) {
      throw new CASAdminException(CASAdminException.TYPE_SYSTEM_LOCKED);
    }
    checkTypeSyntax(typeName);
  }

  /**
   * Method checkTypeSyntax.
   * 
   * @param typeName
   *          -
   */
  private void checkTypeSyntax(String typeName) throws CASAdminException {
    if (!TypeSystemUtils.isTypeName(typeName)) {
      throw new CASAdminException(CASAdminException.BAD_TYPE_SYNTAX, typeName);
    }
  }

//@formatter:off
  /**
   * Add a new type to the type system.
   * Called to add types for new (not built-in) types, 
   *   except for:
   *     arrays
   *     string subtypes
   *     primitives
   *     
   *   All of these have special addType methods
   * @param typeName
   *          The name of the new type.
   * @param superType
   *          The type node under which the new type should be attached.
   * @return The new type, or <code>null</code> if <code>typeName</code> is already in use.
   */
//@formatter:on
  @Override
  public TypeImpl addType(String typeName, Type superType) throws CASAdminException {

    newTypeChecks(typeName, superType);

    if (null != typeName2TypeImpl.get(typeName)) {
      return null;
    }

    final TypeImpl supertypeimpl = (TypeImpl) superType;
    TypeImpl ti = supertypeimpl.isAnnotationType()
            ? new TypeImpl_annot(typeName, this, supertypeimpl, Annotation.class)
            : supertypeimpl.isAnnotationBaseType()
                    ? new TypeImpl_annotBase(typeName, this, supertypeimpl, AnnotationBase.class)
                    : new TypeImpl(typeName, this, supertypeimpl);
    return ti;
  }

  public boolean isInInt(Type rangeType) {
    return (rangeType == null) ? false
            : (rangeType.isPrimitive() && !subsumes(stringType, rangeType));
  }

  @Override
  public Feature addFeature(String featureName, Type domainType, Type rangeType)
          throws CASAdminException {
    return addFeature(featureName, domainType, rangeType, true);
  }

  /**
   * @see TypeSystemMgr#addFeature(String, Type, Type)
   */
  @Override
  public Feature addFeature(String shortFeatName, Type domainType, Type rangeType,
          boolean multipleReferencesAllowed) throws CASAdminException {

    if (this.locked) {
      throw new CASAdminException(CASAdminException.TYPE_SYSTEM_LOCKED);
    }

    FeatureImpl existingFeature = (FeatureImpl) getFeature(domainType, shortFeatName);
    if (existingFeature != null) {
      ((TypeImpl) domainType).checkExistingFeatureCompatible(existingFeature, rangeType);
      TypeImpl highestDefiningType = existingFeature.getHighestDefiningType();
      if (highestDefiningType != domainType && subsumes(domainType, highestDefiningType)) {
        // never happens in existing test cases 6/2017 schor
        Misc.internalError(); // logically can never happen
        // Note: The other case, where a type and subtype are defined, and then
        // features are added in any order, in particular, a
        // supertype feature is added after a subtype feature of the same name/range has been added,
        // causes the subtype's feature to be "deleted" and "inherited" instead.
        // code: checkAndAdjustFeatureInSubtypes in TypeImpl
        // existingFeature.setHighestDefiningType(domainType);
      }
      return existingFeature;
    }

    /** Can't add feature to type "{0}" since it is feature final. */
    if (domainType.isFeatureFinal()) {
      throw new CASAdminException(CASAdminException.TYPE_IS_FEATURE_FINAL, domainType.getName());
    }

    if (!TypeSystemUtils.isIdentifier(shortFeatName)) {
      throw new CASAdminException(CASAdminException.BAD_FEATURE_SYNTAX, shortFeatName);
    }

    // at end of FeatureImpl constructor, TypeImpl.addFeature is called
    // which does checks, calls checkAndAdjustFeatureInSubtypes, etc.
    return new FeatureImpl((TypeImpl) domainType, shortFeatName, (TypeImpl) rangeType, this,
            multipleReferencesAllowed, getSlotKindFromType(rangeType));
  }

//@formatter:off
  /**
   * Given a range type, return the corresponding slot kind
   *   string -> Slot_StrRef, long and double -> ...Ref
   *   boolean, byte, int, short, float, -> withoutRef
   *   
   * @param rangeType
   * @return
   */
//@formatter:on
  static SlotKind getSlotKindFromType(Type rangeType) {
    SlotKind slotKind = rangeType.isStringOrStringSubtype() ? Slot_StrRef
            : slotKindsForNonArrays.get(rangeType.getName());
    return (null == slotKind) ? Slot_HeapRef : slotKind;
  }

  /**
   * Get an iterator over all types, in no particular order.
   * 
   * @return The iterator.
   */
  @Override
  public Iterator<Type> getTypeIterator() {
    // trick to convert List<TypeImpl> to List<Type> with some safety
    Iterator<Type> it = Collections.<Type> unmodifiableList(types).iterator();
    it.next();
    return it;
  }

  // @Override
  // public Iterator<Feature> getFeatures() {
  // Iterator<Feature> it = this.features.iterator();
  // // The first element is null, so skip it.
  // it.next();
  // return it;
  // }

  /**
   * Get the top type, i.e., the root of the type system.
   * 
   * @return The top type.
   */
  @Override
  public TypeImpl getTopType() {
    return this.topType;
  }

  public TypeImpl getTopTypeImpl() {
    return this.topType;
  }

  /**
   * Return the list of all types subsumed by the input type. Note: the list does not include the
   * type itself.
   * 
   * @param type
   *          Input type.
   * @return The list of types subsumed by <code>type</code>.
   */
  @Override
  public List<Type> getProperlySubsumedTypes(Type type) {
    return ((TypeImpl) type).getAllSubtypes().collect(Collectors.toList());
  }

  /**
   * Get a vector of the types directly subsumed by a given type.
   * 
   * @param type
   *          The input type.
   * @return A vector of the directly subsumed types.
   */
  @Override
  public Vector<Type> getDirectlySubsumedTypes(Type type) {
    return new Vector<>(getDirectSubtypes(type));
  }

  @Override
  public List<Type> getDirectSubtypes(Type type) {
    TypeImpl ti = (TypeImpl) type;
    return Collections.<Type> unmodifiableList(ti.getDirectSubtypes());
  }
  //
  // /**
  // *
  // * @param type whose direct instantiable subtypes to iterate over
  // * @return an iterator over the direct instantiable subtypes
  // */
  // public Iterator<Type> getDirectSubtypesIterator(final Type type) {
  //
  // return new Iterator<Type>() {
  //
  // private IntVector sub = (type.isArray()) ? zeroLengthIntVector :
  // TypeSystemImpl.this.tree.get(((TypeImpl) type).getCode());
  // private int pos = 0;
  //
  // private boolean isTop = (((TypeImpl)type).getCode() == top);
  //
  // {
  // if (isTop) {
  // skipOverNonCreatables();
  // }
  // }
  //
  // @Override
  // public boolean hasNext() {
  // return pos < sub.size();
  // }
  //
  // @Override
  // public Type next() {
  // if (!hasNext()) {
  // throw new NoSuchElementException();
  // }
  // Type result = TypeSystemImpl.this.types.get(sub.get(pos));
  // pos++;
  // if (isTop) {
  // skipOverNonCreatables();
  // }
  // return result;
  // }
  //
  // @Override
  // public void remove() {
  // throw new UnsupportedOperationException();
  // }
  //
  // private void skipOverNonCreatables() {
  // if (!hasNext()) {
  // return;
  // }
  // int typeCode = sub.get(pos);
  // while (! ll_isPrimitiveArrayType(typeCode) &&
  // ! casMetadata.creatableType[typeCode]) {
  // pos++;
  // if (!hasNext()) {
  // break;
  // }
  // typeCode = sub.get(pos);
  // }
  // }
  // };
  // }

  public boolean directlySubsumes(TypeImpl t1, TypeImpl t2) {
    return t1.getDirectSubtypes().contains(t2);
  }

//@formatter:off
  /**
   * Does one type inherit from the other?
   * 
   * There are two versions.
   * 
   *  Fast: but only works after commit:  aTypeImplInstance.subsumes(otherTypeImpl)
   *  Slower: this routine.
   *  This routine is called from fast one if not committed.
   * 
   * @param superType
   *          Supertype.
   * @param subType
   *          Subtype.
   * @return <code>true</code> iff <code>sub</code> inherits from <code>super</code>.
   */
//@formatter:on
  @Override
  public boolean subsumes(Type superType, Type subType) {
    if (superType == subType) {
      return true;
    }

    if (isCommitted()) {
      return ((TypeImpl) superType).subsumes((TypeImpl) subType);
    }

    if (superType.isArray()) {
      return ((TypeImpl_array) superType).subsumes((TypeImpl) subType); // doesn't need to be
                                                                        // committed
    }

    if (subType == fsArrayType) {
      return superType == topType || superType == arrayBaseType;
    }

    if (subType.isArray()) {
      // If the subtype is an array, and the supertype is not, then the
      // supertype must be top, or the abstract array base.
      return ((superType == topType) || (superType == arrayBaseType));
    }

    return ((TypeImpl) subType).hasSupertype((TypeImpl) superType);
  }

  // /**
  // * Get the overall number of features defined in the type system.
  // * @return -
  // */
  // public int getNumberOfFeatures() {
  // if (this.isCommitted()) {
  // return this.numFeatureNames;
  // }
  // return this.featureNameST.size();
  // }

  /**
   * Get the overall number of types defined in the type system.
   * 
   * @return - the number of types defined in the type system.
   */
  public int getNumberOfTypes() {
    return types.size() - 1; // because slot 0 is skipped
  }

  /**
   * Get the overall number of features defined in the type system.
   * 
   * @return - the number of features defined in the type system.
   */
  public int getNumberOfFeatures() {
    return features.size() - 1; // because slot 0 is skipped
  }

  /// **
  // *
  // * @param feat -
  // * @return the domain type for a feature.
  // */
  // public int intro(int feat) {
  // return this.intro.get(feat);
  // }
  //
  /// **
  // * Get the range type for a feature.
  // * @param feat -
  // * @return -
  // */
  // public int range(int feat) {
  // return this.featRange.get(feat);
  // }

  // Unification is trivial, since we don't have multiple inheritance.
  public int unify(int t1, int t2) {
    if (this.subsumes(t1, t2)) {
      return t2;
    } else if (this.subsumes(t2, t1)) {
      return t1;
    } else {
      return -1;
    }
  }

  // int addFeature(String shortName, int domain, int range) {
  // return addFeature(shortName, domain, range, true);
  // }
  //
  // /**
  // * Add a new feature to the type system.
  // * @param shortName -
  // * @param domain -
  // * @param range -
  // * @param multiRefsAllowed -
  // * @return -
  // */
  // int addFeature(String shortName, int domain, int range, boolean multiRefsAllowed) {
  // // Since we just looked up the domain in the symbol table, we know it
  // // exists.
  // String name = this.typeNameST.getSymbol(domain) + TypeSystem.FEATURE_SEPARATOR + shortName;
  // // Create a list of the domain type and all its subtypes.
  // // Type t = getType(domain);
  // // if (t == null) {
  // // System.out.println("Type is null");
  // // }
  // TypeImpl domainType = (TypeImpl) ll_getTypeForCode(domain);
  // List<Type> typesLocal = getProperlySubsumedTypes(domainType);
  // typesLocal.add(ll_getTypeForCode(domain));
  // // For each type, check that the feature doesn't already exist.
  // int max = typesLocal.size();
  // for (int i = 0; i < max; i++) {
  // String featureName = (typesLocal.get(i)).getName() + FEATURE_SEPARATOR + shortName;
  // if (this.featureMap.containsKey(featureName)) {
  // // We have already added this feature. If the range of the
  // // duplicate
  // // feature is identical, we don't do anything and just return.
  // // Else,
  // // we throw an exception.
  // Feature oldFeature = getFeatureByFullName(featureName);
  // Type oldDomain = oldFeature.getDomain();
  // Type oldRange = oldFeature.getRange();
  // if (range == ll_getCodeForType(oldRange)) {
  // return -1;
  // }
  // CASAdminException e = new CASAdminException(CASAdminException.DUPLICATE_FEATURE);
  // e.addArgument(shortName);
  // e.addArgument(ll_getTypeForCode(domain).getName());
  // e.addArgument(ll_getTypeForCode(range).getName());
  // e.addArgument(oldDomain.getName());
  // e.addArgument(oldRange.getName());
  // throw e;
  // }
  // } // Add name to symbol table.
  // int feat = this.featureNameST.set(name);
  // // Add entries for all subtypes.
  // for (int i = 0; i < max; i++) {
  // this.featureMap.put((typesLocal.get(i)).getName() + FEATURE_SEPARATOR + shortName,
  // feat);
  // }
  // this.intro.add(domain);
  // this.featRange.add(range);
  // max = this.typeNameST.size();
  // for (int i = 1; i <= max; i++) {
  // if (subsumes(domain, i)) {
  // (this.approp.get(i)).add(feat);
  // }
  // }
  // FeatureImpl fi = new FeatureImpl(feat, name, this, multiRefsAllowed);
  // this.features.add(fi);
  // domainType.addIntroducedFeature(fi);
  // return feat;
  // }

  /**
   * Check if the first argument subsumes the second
   * 
   * @param superType
   *          -
   * @param type
   *          -
   * @return true if first argument subsumes the second
   */
  public boolean subsumes(int superType, int type) {
    return this.ll_subsumes(superType, type);
  }

  // private void updateSubsumption(int type, int superType) {
  // final int max = this.typeNameST.size();
  // for (int i = 1; i <= max; i++) {
  // if (subsumes(i, superType)) {
  // addSubsubsumption(i, type);
  // }
  // }
  // addSubsubsumption(type, type);
  // }
  //
  // private void addSubsubsumption(int superType, int type) {
  // (this.subsumes.get(superType)).set(type);
  // }
  //
  // private void newType() {
  // // The assumption for the implementation is that new types will
  // // always be added at the end.
  // this.tree.add(new IntVector());
  // this.subsumes.add(new BitSet());
  // this.approp.add(new IntVector());
  // }

  // Only used for serialization code.
  // SymbolTable getTypeNameST() {
  // return this.typeNameST;
  // }
  //
  // private final String getTypeString(Type t) {
  // return t.getName() + " (" + ll_getCodeForType(t) + ")";
  // }
  //
  // private final String getFeatureString(Feature f) {
  // return f.getName() + " (" + ll_getCodeForFeature(f) + ")";
  // }

  /**
   * This writes out the type hierarchy in a human-readable form.
   *
   */
  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append(String.format("Type System <%,d>:%n", System.identityHashCode(this)));
    topType.prettyPrintWithSubTypes(sb, 2); // 2 is indent
    return sb.toString();
  }

  /**
   * @see org.apache.uima.cas.admin.TypeSystemMgr#commit()
   */
  @Override
  public TypeSystemImpl commit() {
    return commit(this.getClass().getClassLoader()); // default if not called with a CAS context
  }

  /**
   * @see org.apache.uima.cas.admin.TypeSystemMgr#commit(ClassLoader)
   */
  @Override
  public TypeSystemImpl commit(ClassLoader cl) {
    synchronized (this) {
      if (this.locked) {
        // is a no-op if already loaded for this Class Loader
        // otherwise, need to load and set up generators for this class loader
        getGeneratorsForClassLoader(cl, false); // false - is not pear
        return this; // might be called multiple times, but only need to do once
      }

      // because subsumes depends on it
      // and generator initialization uses subsumes
      // this.numCommittedTypes = this.getNumberOfTypes(); // do before
      // this.numTypeNames = this.typeNameST.size();
      // this.numFeatureNames = this.featureNameST.size();
      // this.typeInfoArray = new TypeInfo[getTypeArraySize()];
      // cas.commitTypeSystem -
      // because it will call the type system iterator
      // this.casMetadata.setupFeaturesAndCreatableTypes();

      TypeSystemImpl maybeConsolidatedTypesystem;
      if (IS_DISABLE_TYPESYSTEM_CONSOLIDATION) {
        maybeConsolidatedTypesystem = finalizeCommit(cl);
      } else {
        maybeConsolidatedTypesystem = committedTypeSystems
                .computeIfAbsent(this, _key -> new WeakReference<>(finalizeCommit(cl))).get();
      }

      // This call is here for the case where a commit happens, but no subsequent new CAS or switch
      // classloader call is done. For example, a "reinit" in an existing CAS
      // This call internally calls the code to load JCas classes for this class loader.
      // FSClassRegistry.loadJCasForTSandClassLoader(this, true, cl);
      // the following is a no-op if the generators already set up for this class loader
      maybeConsolidatedTypesystem.getGeneratorsForClassLoader(cl, false); // false - is not pear

      if (IS_TRACE_JCAS_EXPAND) {
        if (this == maybeConsolidatedTypesystem) {
          System.out.format("debug type system impl commited new type system and loaded JCas %d%n",
                  this.hashCode());
          System.out.println(Misc.getCallers(1, 50).toString());
        } else {
          System.out.format(
                  "debug type system impl commit: consolidated a type system with a previous one, %d%n",
                  maybeConsolidatedTypesystem.hashCode());
          System.out.println(Misc.getCallers(1, 50).toString());
        }
      }

      return maybeConsolidatedTypesystem;
    } // of sync block
  }

  private TypeSystemImpl finalizeCommit(ClassLoader cl) {
    topType.computeDepthFirstCode(1); // fixes up ordering, supers before subs. also sets hasRef.

    computeFeatureOffsets(topType, 0);

    // if (IS_DECOMPILE_JCAS) {
    // synchronized(GLOBAL_TYPESYS_LOCK) {
    // for(Type t : getAllTypes()) {
    // decompile(t);
    // }
    // }
    // }

    type2jcci = FSClassRegistry.get_className_to_jcci(cl, false); // is not pear
    lookup = FSClassRegistry.getLookup(cl);
    cl_for_commit = cl;

    computeAdjustedFeatureOffsets(topType); // must preceed the FSClassRegistry JCas stuff below

    // Load all the available JCas classes (if not already loaded).
    // Has to follow above, because information computed above is used when
    // loading and checking JCas classes

    // not done here, done in caller (CASImpl commitTypeSystem),
    // when it gets the generators for the class loader for this type system for the first time.
    // fsClassRegistry = new FSClassRegistry(this, true);
    // FSClassRegistry.loadAtTypeSystemCommitTime(this, true, cl);

    this.locked = true;

    return this;
  }

//@formatter:off
  /**
   * This is the actual offset for the feature, in either the int or ref array
   * 
   * Offsets for super types come before types, because
   *   multiple subtypes can share the same super type
   *   
   * Offsets due to JCas defined features are set before those from type systems, because
   *   the same JCas class might be used with different type system,
   *   and this increases the chance that the assignment is still valid.
   *   
   * Special handling for JCas defined features which are missing in the type system.
   *   - these are allocated artificial "feature impls", which participate in the offset
   *     setting, but not in anything else (like serialization)
   * 
   * Special handling for JCas super types which have no corresponding uima types
   *   - features inferred from these are incorporated for purposes of computing offsets (only), because
   *     some later type system might have these.
   * 
   * Sets offset into 2 places:
   *   - the FeatureImpl
   *   - a set of arrays in the type:
   *       one mapping offset to featureImpl for refs
   *       one mapping offset to featureImpl for ints
   *       one mapping offset to non-fs-refs (for efficiency in enquing these before refs) 
   * 
   * also sets the number-of-used slots (int / ref) for allocating, in the type
   * 
   * @param ti
   *          - the type
   */
//@formatter:on
  private void computeAdjustedFeatureOffsets(TypeImpl ti) {

    List<FeatureImpl> tempIntFis = new ArrayList<>();
    List<FeatureImpl> tempRefFis = new ArrayList<>();
    List<FeatureImpl> tempNsr = new ArrayList<>();

    if (ti != topType) {
      // initialize these offset -> fi maps from supertype
      ti.initAdjOffset2FeatureMaps(tempIntFis, tempRefFis, tempNsr);
      nextI = ti.getSuperType().nbrOfUsedIntDataSlots;
      nextR = ti.getSuperType().nbrOfUsedRefDataSlots;
    } else {
      nextI = 0;
      nextR = 0;
    }

    // all the JCas slots come first, because their offsets can't easily change
    // this includes any superClass of a jcas class which is not in this type system

    if (!skip_loading_user_jcas) {
      JCasClassInfo jcci = getOrCreateJcci(ti);

      if (jcci != null) {
        addJCasOffsetsWithSupers(jcci.jcasClass, tempIntFis, tempRefFis, tempNsr);
      }
    }

    for (final FeatureImpl fi : ti.getMergedStaticFeaturesIntroducedByThisType()) {
      if (fi.getAdjustedOffset() == -1) {
        // wasn't set due to jcas class, above
        setFeatureAdjustedOffset(fi, tempIntFis, tempRefFis, tempNsr);
        if (debug) {
          System.out.println("debug setting adjOffset for feat: " + fi.getShortName() + " to "
                  + fi.getAdjustedOffset());
        }
      }
    }

    ti.nbrOfUsedIntDataSlots = nextI;
    ti.nbrOfUsedRefDataSlots = nextR;

    ti.setStaticMergedIntFeaturesList((tempIntFis.size() == 0) ? Constants.EMPTY_FEATURE_ARRAY
            : tempIntFis.toArray(new FeatureImpl[tempIntFis.size()]));
    ti.setStaticMergedRefFeaturesList((tempRefFis.size() == 0) ? Constants.EMPTY_FEATURE_ARRAY
            : tempRefFis.toArray(new FeatureImpl[tempRefFis.size()]));
    ti.setStaticMergedNonSofaFsRefs((tempNsr.size() == 0) ? Constants.EMPTY_FEATURE_ARRAY
            : tempNsr.toArray(new FeatureImpl[tempNsr.size()]));

    // ti.hasOnlyInts = ti.nbrOfUsedIntDataSlots > 0 && ti.nbrOfUsedRefDataSlots == 0;
    // ti.hasOnlyRefs = ti.nbrOfUsedRefDataSlots > 0 && ti.nbrOfUsedIntDataSlots == 0;
    // ti.hasNoSlots = ti.nbrOfUsedIntDataSlots == 0 && ti.nbrOfUsedRefDataSlots == 0;

    for (TypeImpl sub : ti.getDirectSubtypes()) {
      computeAdjustedFeatureOffsets(sub);
    }
  }

//@formatter:off
  /**
   * Insures that any super class jcas-defined features, 
   *   not already defined (due to having a corresponding type in this
   *   type system)
   *   get their features done first
   *   
   * Walking up the super chain:
   *   - could encounter a class which has no jcci (it's not a jcas class)
   *     but has a super class which is one) - so don't stop the up walk.
   *     
   * Stop the up walk when
   *   - reach the TOP or Object
   *   - reach a class which has a corresponding uima type (the assumption is that
   *     this type is a super type
   *     
   * @param ti the type associated with clazz, or null, if none   
   * @param clazz the class whose supertypes are being scanned.  May not be a JCas class
   * @param tempIntFis the array of int offsets (refs to feature impls (maybe jcas-only)
   * @param tempRefFis the array of ref offsets (refs to feature impls (maybe jcas-only)
   * @param tempNsrFis a list of non fs-ref ref values
   */
//@formatter:on
  private void addJCasOffsetsWithSupers(Class<?> clazz, List<FeatureImpl> tempIntFis,
          List<FeatureImpl> tempRefFis, List<FeatureImpl> tempNsrFis) {

    Class<?> superClass = clazz.getSuperclass();
    // **********************
    // STOP if get to top *
    // **********************
    if (superClass == Object.class) {
      return;
    }

    String superClassName = superClass.getName();
    String uimaSuperTypeName = Misc.javaClassName2UimaTypeName(superClassName);
    if (this.getType(uimaSuperTypeName) != null) {

      // ****************************
      // STOP if get to UIMA type *
      // ****************************/

      // But process this jcas class level
      // then return to recursively process other jcas class levels.
      String className = clazz.getName();
      String uimaTypeName = Misc.javaClassName2UimaTypeName(className);
      TypeImpl ti = this.getType(uimaTypeName);
      if (ti != null) {
        maybeAddJCasOffsets(ti, tempIntFis, tempRefFis, tempNsrFis);
      }
      return;
    }

    // recurse up the superclass chain for all jcci's not having UIMA types
    // If they exist, they will have been loaded/created by FSClassRegistry.augmentFeaturesFromJCas
    // some intermediate superclasses may not have jccis, just skip over them.

    addJCasOffsetsWithSupers(superClass, tempIntFis, tempRefFis, tempNsrFis);
    // maybeAddJCasOffsets(clazz, tempIntFis, tempRefFis, tempNsrFis); // already done by above
    // statement
  }

  /**
   * 
   * @param ti
   *          the type having offsets set up for
   * @param jcci
   *          a corresponding jcci, either for this type or for a super type when the super type is
   *          not in this uima type system
   * @param tempIntFis
   *          list to augment with additional slots
   * @param tempRefFis
   *          list to augment with additional slots
   */
  private void maybeAddJCasOffsets(TypeImpl ti, List<FeatureImpl> tempIntFis,
          List<FeatureImpl> tempRefFis, List<FeatureImpl> tempNsrFis) {

    JCasClassInfo jcci = getOrCreateJcci(ti);
    if (null != jcci) { // could be null if class is not a JCas class
      addJCasOffsets(jcci, tempIntFis, tempRefFis, tempNsrFis);
    }
  }

  private void addJCasOffsets(JCasClassInfo jcci, List<FeatureImpl> tempIntFis,
          List<FeatureImpl> tempRefFis, List<FeatureImpl> tempNsrFis) {

    List<FeatureImpl> added = IS_TRACE_JCAS_EXPAND ? (new ArrayList<>(0)) : null;
    for (FSClassRegistry.JCasClassFeatureInfo jcci_feat : jcci.features) {
      TypeImpl rangeType = getType(jcci_feat.uimaRangeName); // could be null
      TypeImpl ti = jcci.getUimaType(this); // could be null
      FeatureImpl fi = null;
      if (ti != null) {
        fi = ti.getFeatureByBaseName(jcci_feat.shortName); // could be null
      }
      if (fi == null) { //
        // no feature for this type in this type system, but in the JCas.
        // create a FeatureImpl_jcas_only, to hold the offset info to use for
        // later in the
        // update of the CallSites to install the offsets
        fi = new FeatureImpl_jcas_only(jcci_feat.shortName, rangeType);
        if (IS_TRACE_JCAS_EXPAND) {
          added.add(fi);
        }
      }
      if (fi.getAdjustedOffset() != -1) {
        if (debug) {
          System.out.println("debug fi adjusted offset not -1, is " + fi.getAdjustedOffset());
          System.out.println("debug " + fi.toString());
        }
      } else {
        setFeatureAdjustedOffset(fi, tempIntFis, tempRefFis, tempNsrFis);
      }
    }

    if (IS_TRACE_JCAS_EXPAND && added.size() > 0) {
      System.out.format("debug trace jcas added: %d slots, %s%n", added.size(), Misc.ppList(added));
    }
  }

  void setFeatureAdjustedOffset(FeatureImpl fi, List<FeatureImpl> tempIntFis,
          List<FeatureImpl> tempRefFis, List<FeatureImpl> tempNsr) {
    boolean isInt = fi.isInInt;
    fi.setAdjustedOffset(isInt ? nextI : nextR);
    setOffset2Feat(tempIntFis, tempRefFis, tempNsr, fi, isInt ? (nextI++) : (nextR++));
    if (fi.isLongOrDouble) {
      nextI++;
    }
  }

  void setOffset2Feat(List<FeatureImpl> tempIntFis, List<FeatureImpl> tempRefFis,
          List<FeatureImpl> tempNsr, FeatureImpl fi, int next) {
    boolean is_jcas_only = fi instanceof FeatureImpl_jcas_only;
    if (fi.isInInt) {
      // assert tempIntFis.size() == next; // could have added slots from JCas
      tempIntFis.add(is_jcas_only ? null : fi);
      if (fi.getRangeImpl().isLongOrDouble) {
        tempIntFis.add(null);
      }
    } else {
      // assert tempRefFis.size() == next; // could have added slots from JCas
      tempRefFis.add(is_jcas_only ? null : fi);
      TypeImpl range = fi.getRangeImpl();

      if (!is_jcas_only && range.isRefType && range != sofaType) {
        tempNsr.add(fi);
      }
    }
  }

  private JCasClassInfo getOrCreateJcci(TypeImpl ti) {
    return FSClassRegistry.getOrCreateJCasClassInfo(ti, cl_for_commit, type2jcci, lookup);
  }

  JCasClassInfo getJcci(String typeName) {
    return type2jcci.get(typeName);
  }

  /**
   * Feature "ids" - offsets without adjusting for whether or not they're in the class itself
   * 
   * @param ti
   *          a type to compute these for
   * @param next
   *          - the next offset
   */
  private void computeFeatureOffsets(TypeImpl ti, int next) {

    for (FeatureImpl fi : ti.getMergedStaticFeaturesIntroducedByThisType()) {
      fi.setOffset(next++);
    }

    ti.highestOffset = next - 1; // highest index value, 0 based index

    for (TypeImpl sub : ti.getDirectSubtypes()) {
      computeFeatureOffsets(sub, next);
    }
  }

  // private void decompile(Type t) {
  // String name = t.getName();
  // if (name.endsWith(ARRAY_TYPE_SUFFIX)) return;
  // if (decompiled.contains(name)) return;
  // decompiled.add(name);
  //
  // if(builtInsWithAltNames.contains(name) )
  // name = "org.apache.uima.jcas."+ name.substring(5 /* "uima.".length() */);
  //
  // String h = System.getProperty(DECOMPILE_JCAS);
  // File file = new File(h + "decompiled");
  // file.mkdir();
  //
  // UimaDecompiler ud = new UimaDecompiler(this.getClass().getClassLoader(), file);
  // ud.decompileToOutputDirectory(name.replace('.', '/'));
  // }

  /**
   * @see org.apache.uima.cas.admin.TypeSystemMgr#isCommitted()
   */
  @Override
  public boolean isCommitted() {
    return this.locked;
  }

  /**
   * @param typecode
   *          for a type
   * @return true if type is AnnotationBase or a subtype of it
   */
  public boolean isAnnotationBaseOrSubtype(int typecode) {
    return isAnnotationBaseOrSubtype(ll_getTypeForCode(typecode));
  }

  public boolean isAnnotationBaseOrSubtype(Type type) {
    return ((TypeImpl) type).isAnnotationBaseType();
  }

  /**
   * @param type
   *          a type
   * @return true if type is Annotation or a subtype of it
   */
  public boolean isAnnotationOrSubtype(Type type) {
    return ((TypeImpl) type).isAnnotationType();
  }

  // dangerous, and not needed, not in any interface
  // public void setCommitted(boolean b) {
  // this.locked = b;
  // }

  /*
   * @deprecated
   */
  @Deprecated
  public Feature getFeature(String featureName) {
    return getFeatureByFullName(featureName);
  }

  /**
   * @see org.apache.uima.cas.admin.TypeSystemMgr#setFeatureFinal(org.apache.uima.cas.Type)
   */
  @Override
  public void setFeatureFinal(Type type) {
    ((TypeImpl) type).setFeatureFinal();
  }

  /**
   * @see org.apache.uima.cas.admin.TypeSystemMgr#setInheritanceFinal(org.apache.uima.cas.Type)
   */
  @Override
  public void setInheritanceFinal(Type type) {
    ((TypeImpl) type).setInheritanceFinal();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.cas.TypeSystem#getTypeNameSpace(java.lang.String)
   */
  @Override
  public TypeNameSpace getTypeNameSpace(String name) {
    if (!TypeSystemUtils.isTypeNameSpaceName(name)) {
      return null;
    }
    return new TypeNameSpaceImpl(name, this);
  }

  /**
   * @see TypeSystem#getArrayType(Type)
   * @param componentType
   *          The type of the elements of the resulting array type. This can be any type, even
   *          another array type.
   * @return The array type with the corresponding component type. If it doesn't exist, a new
   *         TypeImplArray is created for it.
   */
  @Override
  public Type getArrayType(Type componentType) {
    TypeImpl ti = (TypeImpl) arrayComponentTypeToArrayType.get(componentType);
    if (null == ti) {
      // This path only happens when
      // a Feature is declared to be a non-built-in (and therefore non-primitive) array
      // that is, an FsArray of another declared uima type.
      ti = addArrayType(componentType, getSlotKindFromType(componentType), HEAP_STORED_ARRAY,
              FSArray.class);
    }
    return ti;
  }

  /**
   * @see org.apache.uima.cas.admin.TypeSystemMgr#addStringSubtype
   */
  @Override
  public Type addStringSubtype(String typeName, String[] stringList) throws CASAdminException {
    Set<String> allowedValues = new HashSet<>(Arrays.asList(stringList));
    TypeImpl supertype = this.stringType;
    // Check type name syntax.
    checkTypeSyntax(typeName);

    TypeImpl existingTi = getType(typeName);
    if (existingTi != null) {
      if (!(existingTi instanceof TypeImpl_stringSubtype)) {
        throw new CASAdminException(CASAdminException.STRING_SUBTYPE_REDEFINE_NAME_CONFLICT,
                typeName, existingTi.toString());
      }
      Set<String> existingAllowedValues = ((TypeImpl_stringSubtype) existingTi).getAllowedValues();
      if (!existingAllowedValues.equals(allowedValues)) {
        // this type is already defined with identical allowed values, return
        // existing one
        return existingTi;
      }
      throw new CASAdminException(CASAdminException.STRING_SUBTYPE_CONFLICTING_ALLOWED_VALUES,
              typeName,
              Misc.addElementsToStringBuilder(new StringBuilder(), existingAllowedValues)
                      .toString(),
              Misc.addElementsToStringBuilder(new StringBuilder(), allowedValues).toString());
    }

    // Create the type.
    TypeImpl_stringSubtype type = new TypeImpl_stringSubtype(typeName, this, supertype,
            allowedValues);
    type.setFeatureFinal();
    type.setInheritanceFinal();
    return type;
  }

  /**
   * Add an array type. This is called for builtin array types, and when processing a Feature
   * specification that represents an FSArray of a particular type
   * 
   * @param componentType
   *          the component type
   * @return a TypeImplArray
   */
  TypeImpl_array addArrayType(Type componentType, SlotKind slotKind, boolean isHeapStoredArray,
          Class<?> javaClass) {
    if (isCommitted()) {
      throw new CASRuntimeException(CASRuntimeException.ADD_ARRAY_TYPE_AFTER_TS_COMMITTED,
              componentType.getName() + TypeSystemImpl.ARRAY_TYPE_SUFFIX);
    }
    String arrayTypeName = getArrayTypeName(componentType.getName());
    // either fsArray or TOP
    TypeImpl supertype = computeArrayParentFromComponentType(componentType);
    TypeImpl_array ti = new TypeImpl_array(arrayTypeName, (TypeImpl) componentType, this, supertype,
            slotKind, isHeapStoredArray, javaClass);
    this.arrayComponentTypeToArrayType.put(componentType, ti);
    // the reverse - going from array type to component type is done via the getComponentType method
    // of TypeImplArray
    return ti;
  }

  // TypeImplList addTypeList(TypeImpl elementType) {
  // return new TypeImplList(CAS.TYPE_NAME_LIST, elementType, this, topType);
  // }
  //
  // TypeImplMap addTypeMap(TypeImpl keyType, TypeImpl valueType) {
  // return new TypeImplMap(CAS.TYPE_NAME_MAP, keyType, valueType, this, topType);
  // }

  // /**
  // * Used to support generic access to static-compiled features.
  // * Return ordered-by-offset array of FeatureImpls of a particular range class
  // * @param valueType - one of booleanType, booleanArrayType, stringType, topType, fsArrayType,
  // etc.
  // * see list in allTypes1 in TypeSystemImpl.
  // * @return a stream of FeatureImpls having a range of the specified type
  // */
  // public Stream<FeatureImpl> getTypeFeaturesByRangeType(TypeImpl type, TypeImpl range) {
  // return type.getFeaturesAsStream().filter(fi -> ((TypeImpl)
  // fi.getRange()).consolidateType(topType, fsArrayType) == range);
  // }

  private static void setTypeFinal(Type type) {
    TypeImpl t = (TypeImpl) type;
    t.setFeatureFinal();
    t.setInheritanceFinal();
    t.setBuiltIn();
  }

  private FeatureImpl getFeature(String typeName, String featureShortName) {
    Type type = getType(typeName);
    if (null == type) {
      // System.out.println("debug - get feature for typename: " + typeName + ", feature: " +
      // featureShortName
      // + " returned null when getting the type.");
      return null;
    }
    return getFeature(getType(typeName), featureShortName);
  }

  private FeatureImpl getFeature(Type type, String featureShortName) {
    return ((TypeImpl) type).getFeatureByBaseName(featureShortName);
  }

  /**
   * @param ti
   *          the type
   * @param featureShortName
   *          the name of the feature
   * @return the offset in the storage array for this feature
   */
  public int getFeatureOffset(TypeImpl ti, String featureShortName) {
    FeatureImpl fi = (FeatureImpl) ti.getFeatureByBaseName(featureShortName);
    return fi.getOffset();
  }

  // public TypeImpl[] getAllTypesForByteCodeGen() {
  // return allTypesForByteCodeGen;
  // }
  //
  //
  // /**
  // * Each instance holds info needed in binary serialization
  // * of data for a particular type
  // */
  // class TypeInfo {
  // // constant data about a particular type
  // public final TypeImpl type; // for debug
  // /**
  // * Array of slot kinds; index 0 is for 1st slot after feature code, length = number of slots
  // excluding type code
  // */
  // public final SlotKind[] slotKinds;
  // public final int[] strRefOffsets;
  //
  // public final boolean isArray;
  // public final boolean isHeapStoredArray; // true if array elements are
  // // stored on the main heap
  //
  // public TypeInfo(TypeImpl type) {
  //
  // this.type = type;
  // List<Feature> features = type.getFeatures();
  //
  // isArray = type.isArray(); // feature structure array types named
  // // type-of-fs[]
  // isHeapStoredArray = (type == intArrayType) || (type == floatArrayType)
  // || (type == fsArrayType) || (type == stringArrayType)
  // || (TypeSystemImpl.isArrayTypeNameButNotBuiltIn(type.getName()));
  //
  // final ArrayList<Integer> strRefsTemp = new ArrayList<Integer>();
  // // set up slot kinds
  // if (isArray) {
  // // slotKinds has 2 slots: 1st is for array length, 2nd is the slotkind
  // // for the array element
  // SlotKind arrayKind;
  // if (isHeapStoredArray) {
  // if (type == intArrayType) {
  // arrayKind = Slot_Int;
  // } else if (type == floatArrayType) {
  // arrayKind = Slot_Float;
  // } else if (type == stringArrayType) {
  // arrayKind = Slot_StrRef;
  // } else {
  // arrayKind = Slot_HeapRef;
  // }
  // } else {
  //
  // // array, but not heap-store-array
  // if (type == booleanArrayType || type == byteArrayType) {
  // arrayKind = Slot_ByteRef;
  // } else if (type == shortArrayType) {
  // arrayKind = Slot_ShortRef;
  // } else if (type == longArrayType) {
  // arrayKind = Slot_LongRef;
  // } else if (type == doubleArrayType) {
  // arrayKind = Slot_DoubleRef;
  // } else {
  // throw new RuntimeException("never get here");
  // }
  // }
  //
  // slotKinds = new SlotKind[] { Slot_ArrayLength, arrayKind };
  // strRefOffsets = INT0;
  //
  // } else {
  //
  // // set up slot kinds for non-arrays
  // ArrayList<SlotKind> slots = new ArrayList<SlotKind>();
  // int i = -1;
  // for (Feature feat : features) {
  // i++;
  // TypeImpl slotType = (TypeImpl) feat.getRange();
  //
  // if (slotType == stringType || (slotType instanceof TypeImpl_string)) {
  // slots.add(Slot_StrRef);
  // strRefsTemp.add(i + 1); // first feature is offset 1 from fs addr
  // } else if (slotType == intType) {
  // slots.add(Slot_Int);
  // } else if (slotType == booleanType) {
  // slots.add(Slot_Boolean);
  // } else if (slotType == byteType) {
  // slots.add(Slot_Byte);
  // } else if (slotType == shortType) {
  // slots.add(Slot_Short);
  // } else if (slotType == floatType) {
  // slots.add(Slot_Float);
  // } else if (slotType == longType) {
  // slots.add(Slot_LongRef);
  // } else if (slotType == doubleType) {
  // slots.add(Slot_DoubleRef);
  // } else {
  // slots.add(Slot_HeapRef);
  // }
  // } // end of for loop
  // slotKinds = slots.toArray(new SlotKind[slots.size()]);
  // // convert to int []
  // final int srlength = strRefsTemp.size();
  // if (srlength > 0) {
  // strRefOffsets = new int[srlength];
  // for (int j = 0; j < srlength; j++) {
  // strRefOffsets[j] = strRefsTemp.get(j);
  // }
  // } else {
  // strRefOffsets = INT0;
  // }
  // }
  // }
  //
  // /**
  // * @param offset 0 = typeCode, 1 = first feature, ...
  // * @return
  // */
  // SlotKind getSlotKind(int offset) {
  // if (0 == offset) {
  // return Slot_TypeCode;
  // }
  // return slotKinds[offset - 1];
  // }
  //
  // @Override
  // public String toString() {
  // return type.toString();
  // }
  // }
  //
  // TypeInfo getTypeInfo(int typeCode) {
  // if (null == typeInfoArray[typeCode]) {
  // TypeImpl type = (TypeImpl) ll_getTypeForCode(typeCode);
  //// if (null == type) {
  //// diagnoseBadTypeCode(typeCode);
  //// }
  // typeInfoArray[typeCode] = new TypeInfo(type);
  // }
  // return typeInfoArray[typeCode];
  // }

  // // debugging
  // private void diagnoseBadTypeCode(int typeCode) {
  // System.err.format("Bad type code %,d passed to TypeSystem.getTypeInfo, largest type code is
  // %,d, size of types list is %,d%n",
  // typeCode, getLargestTypeCode(), types.size());
  // System.err.println(this.toString());
  // System.err.format("Type in types: %s%n", types.get(typeCode));
  // throw new RuntimeException();
  // }

  /*********************************************************
   * Type Mapping Objects used in compressed binary (de)serialization These are in an identity map,
   * key = target type system
   * 
   * Threading: this map is used by multiple threads
   * 
   * Key = target type system, via a weak reference. Automatically cleared via WeakHashMap
   * 
   * The map itself is not synchronized, because all accesses to it are from the synchronized
   * getTypeSystemMapper method
   *********************************************************/
  public final Map<TypeSystemImpl, CasTypeSystemMapper> typeSystemMappers = new WeakHashMap<>();

  synchronized CasTypeSystemMapper getTypeSystemMapper(TypeSystemImpl tgtTs) {
    CasTypeSystemMapper ctsm = getTypeSystemMapperInner(tgtTs);
    if (null == ctsm || ctsm.isEqual()) { // if the mapper is for this type system
      return null;
    }
    return ctsm;
  }

  synchronized CasTypeSystemMapper getTypeSystemMapperInner(TypeSystemImpl tgtTs) {
    if ((null == tgtTs) || (this == tgtTs)) {
      return null; // conventions for no type mapping
    }
    CasTypeSystemMapper m = typeSystemMappers.computeIfAbsent(tgtTs,
            key -> new CasTypeSystemMapper(this, tgtTs));

    return m;
  }

  // /**
  // * @param otherTs type system to compare to this one
  // * @return true if one or more identically named features have differently named ranges
  // */
  // public boolean isRangeCheckNeeded(TypeSystemImpl otherTs) {
  // if (this == otherTs) {
  // return false;
  // }
  // final List<Feature> smallerList;
  // if (features.size() > otherTs.features.size()) {
  // smallerList = otherTs.features;
  // otherTs = this;
  // } else {
  // smallerList = features;
  // }
  //
  // for (int i = smallerList.size() - 1; i > 0; i--) { // position 0 is null
  // final Feature f1 = smallerList.get(i);
  // final Feature f2 = otherTs.getFeatureByFullName(f1.getName());
  // if (f2 == null) {
  // continue;
  // }
  // if (!f1.getRange().getName().equals(f2.getRange().getName())) {
  // return true;
  // }
  // }
  // return false;
  // }
  //

//@formatter:off
  /****************************************************************
   * Low Level APIs that use type codes and feature codes         *
   * These are less efficient in V3, and should be replaced where *
   * possible with standard non-low-level access                  *
   * **************************************************************
   */
//@formatter:on

  /**
   * 
   * @param typeCode
   *          of some type
   * @return the type code of the parent type
   */
  @Override
  public int ll_getParentType(int typeCode) {
    if (typeCode == 1) {
      return 0;
    }
    return types.get(typeCode).getSuperType().getCode();
  }

  /**
   * Get an array of the appropriate features for this type.
   */
  @Override
  public int[] ll_getAppropriateFeatures(int typecode) {
    if (!isType(typecode)) {
      return null;
    }
    return types.get(typecode).getFeaturesAsStream().mapToInt(FeatureImpl::getCode).toArray();
  }

  @Override
  public boolean ll_subsumes(int superType, int type) {
    return subsumes(types.get(superType), types.get(type));
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.cas.impl.LowLevelTypeSystem#ll_getCodeForTypeName(java.lang.String)
   */
  @Override
  public int ll_getCodeForTypeName(String typeName) {
    if (typeName == null) {
      throw new NullPointerException();
    }
    TypeImpl ti = getType(typeName);
    return (ti == null) ? LowLevelTypeSystem.UNKNOWN_TYPE_CODE : ti.getCode();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.cas.impl.LowLevelTypeSystem#ll_getCodeForType(org.apache.uima.cas.Type)
   */
  @Override
  public int ll_getCodeForType(Type type) {
    return ((TypeImpl) type).getCode();
  }

  @Override
  public int ll_getCodeForFeatureName(String featureName) {
    if (featureName == null) {
      throw new NullPointerException();
    }
    FeatureImpl fi = (FeatureImpl) getFeatureByFullName(featureName);
    if (fi != null) {
      return fi.getCode();
    }
    return UNKNOWN_FEATURE_CODE;
  }

  @Override
  public int ll_getCodeForFeature(Feature feature) {
    return ((FeatureImpl) feature).getCode();
  }

  @Override
  public Type ll_getTypeForCode(int typeCode) {
    return getTypeForCode(typeCode);
  }

  public TypeImpl getTypeForCode(int typeCode) {
    if (isType(typeCode)) {
      return this.types.get(typeCode);
    }
    return null;
  }

  public TypeImpl getTypeForCode_checked(int typeCode) {
    TypeImpl r = getTypeForCode(typeCode);
    if (r == null) {
      throw new LowLevelException(LowLevelException.INVALID_TYPECODE, typeCode);
    }
    return r;
  }

  /**
   * Check if feature is appropriate for type (i.e., type is subsumed by domain type of feature).
   * 
   * @param typecode
   *          -
   * @param featcode
   *          -
   * @return true if feature is appropriate for type (i.e., type is subsumed by domain type of
   *         feature).
   */
  public boolean isApprop(int typecode, int featcode) {
    return ((TypeImpl) ll_getTypeForCode(typecode))
            .isAppropriateFeature(ll_getFeatureForCode(featcode));
  }

  /**
   * Feature Code to Offset Offset has no clear meaning in V3
   * 
   * @return An offset <code>&gt;0</code> if <code>feat</code> exists; <code>0</code>, else.
   */
  int getFeatureOffset(int feat) {
    throw new UIMARuntimeException(UIMARuntimeException.NOT_SUPPORTED_NO_HEAP_IN_UIMA_V3);
  }

  private final int getLargestFeatureCode() {
    return this.features.size();
  }

  final boolean isFeature(int featureCode) {
    return ((featureCode > UNKNOWN_FEATURE_CODE) && (featureCode <= getLargestFeatureCode()));
  }

  @Override
  public Feature ll_getFeatureForCode(int featureCode) {
    if (isFeature(featureCode)) {
      return this.features.get(featureCode);
    }
    return null;
  }

  FeatureImpl getFeatureForCode(int featureCode) {
    return this.features.get(featureCode);
  }

  FeatureImpl getFeatureForCode_checked(int featureCode) {
    FeatureImpl f = getFeatureForCode(featureCode);
    if (null == f) {
      throw new LowLevelException(LowLevelException.INVALID_FEATURE_CODE, featureCode);
    }
    return f;
  }

  @Override
  public int ll_getDomainType(int featureCode) {
    return intro(featureCode);
  }

  @Override
  public int ll_getRangeType(int featureCode) {
    return range(featureCode);
  }

  @Override
  public LowLevelTypeSystem getLowLevelTypeSystem() {
    return this;
  }

  @Override
  public boolean ll_isStringSubtype(int typecode) {
    return types.get(typecode).isStringSubtype();
  }

  /**
   * The range type of features may include special uima types that are not creatable, such as the
   * primitive ones like integer, or string, or subtypes of string. Other types are reference types
   */
  boolean classifyAsRefType(String name, TypeImpl superType) {
    switch (name) {
      case CAS.TYPE_NAME_BOOLEAN:
      case CAS.TYPE_NAME_BYTE:
      case CAS.TYPE_NAME_SHORT:
      case CAS.TYPE_NAME_INTEGER:
      case CAS.TYPE_NAME_LONG:
      case CAS.TYPE_NAME_FLOAT:
      case CAS.TYPE_NAME_DOUBLE:
      case CAS.TYPE_NAME_STRING:
        // case CAS.TYPE_NAME_JAVA_OBJECT:
        // case CAS.TYPE_NAME_MAP:
        // case CAS.TYPE_NAME_LIST:
        return false;
    }
    // superType is null for TOP, which is a Ref type
    if (superType != null && superType.getName().equals(CAS.TYPE_NAME_STRING)) { // can't compare to
                                                                                 // stringType - may
                                                                                 // not be set yet
      return false;
    }
    return true;
  }

  /**
   * @param type
   *          the type to test
   * @return true if it's a reference type - one that can be created as a FeatureStructure
   */
  public boolean isRefType(TypeImpl type) {
    return type.isRefType;
  }

  @Override
  public boolean ll_isRefType(int typeCode) {
    return isRefType(getTypeForCode(typeCode));
  }

  @Override
  public final int ll_getTypeClass(int typeCode) {
    if (typeCode == TypeSystemConstants.booleanTypeCode) {
      return LowLevelCAS.TYPE_CLASS_BOOLEAN;
    }
    if (typeCode == TypeSystemConstants.byteTypeCode) {
      return LowLevelCAS.TYPE_CLASS_BYTE;
    }
    if (typeCode == TypeSystemConstants.shortTypeCode) {
      return LowLevelCAS.TYPE_CLASS_SHORT;
    }
    if (typeCode == TypeSystemConstants.intTypeCode) {
      return LowLevelCAS.TYPE_CLASS_INT;
    }
    if (typeCode == TypeSystemConstants.floatTypeCode) {
      return LowLevelCAS.TYPE_CLASS_FLOAT;
    }
    if (typeCode == TypeSystemConstants.longTypeCode) {
      return LowLevelCAS.TYPE_CLASS_LONG;
    }
    if (typeCode == TypeSystemConstants.doubleTypeCode) {
      return LowLevelCAS.TYPE_CLASS_DOUBLE;
    }
    // false if string type code not yet set up (during initialization)
    // need this to avoid NPE in subsumes
    if ((TypeSystemConstants.stringTypeCode != LowLevelTypeSystem.UNKNOWN_TYPE_CODE)
            && ll_subsumes(TypeSystemConstants.stringTypeCode, typeCode)) {
      return LowLevelCAS.TYPE_CLASS_STRING;
    }
    if (typeCode == TypeSystemConstants.booleanArrayTypeCode) {
      return LowLevelCAS.TYPE_CLASS_BOOLEANARRAY;
    }
    if (typeCode == TypeSystemConstants.byteArrayTypeCode) {
      return LowLevelCAS.TYPE_CLASS_BYTEARRAY;
    }
    if (typeCode == TypeSystemConstants.shortArrayTypeCode) {
      return LowLevelCAS.TYPE_CLASS_SHORTARRAY;
    }
    if (typeCode == TypeSystemConstants.intArrayTypeCode) {
      return LowLevelCAS.TYPE_CLASS_INTARRAY;
    }
    if (typeCode == TypeSystemConstants.floatArrayTypeCode) {
      return LowLevelCAS.TYPE_CLASS_FLOATARRAY;
    }
    if (typeCode == TypeSystemConstants.longArrayTypeCode) {
      return LowLevelCAS.TYPE_CLASS_LONGARRAY;
    }
    if (typeCode == TypeSystemConstants.doubleArrayTypeCode) {
      return LowLevelCAS.TYPE_CLASS_DOUBLEARRAY;
    }
    if (typeCode == TypeSystemConstants.stringArrayTypeCode) {
      return LowLevelCAS.TYPE_CLASS_STRINGARRAY;
    }
    if (ll_isArrayType(typeCode)) {
      return LowLevelCAS.TYPE_CLASS_FSARRAY;
    }
    return LowLevelCAS.TYPE_CLASS_FS;
  }

  /**
   * @see LowLevelTypeSystem#ll_getArrayType(int)
   * @param componentTypeCode
   *          the type code of the components of the array
   * @return the type code of the requested Array type having components of componentTypeCode, or 0
   *         if the componentTypeCode is invalid.
   */
  @Override
  public int ll_getArrayType(int componentTypeCode) {
    if (!ll_isValidTypeCode(componentTypeCode)) {
      return 0;
    }

    return ((TypeImpl) (getArrayType(types.get(componentTypeCode)))).getCode();
  }

  /**
   * @see LowLevelTypeSystem#ll_isValidTypeCode(int)
   */
  @Override
  public boolean ll_isValidTypeCode(int typeCode) {
    return isType(typeCode);
    // return (this.typeNameST.getSymbol(typeCode) != null)
    // || this.arrayToComponentTypeMap.containsKey(typeCode);
  }

  @Override
  public boolean ll_isArrayType(int typeCode) {
    if (!ll_isValidTypeCode(typeCode)) {
      return false;
    }
    return types.get(typeCode).isArray();
  }

  @Override
  public int ll_getComponentType(int arrayTypeCode) {
    final TypeImpl type = types.get(arrayTypeCode);
    if (type.isArray()) {
      return ((TypeImpl) ((TypeImpl_array) type).getComponentType()).getCode();
    }
    return UNKNOWN_TYPE_CODE;
  }

  /* note that subtypes of String are considered primitive */
  @Override
  public boolean ll_isPrimitiveType(int typeCode) {
    return !ll_isRefType(typeCode);
  }

  @Override
  public String[] ll_getStringSet(int typeCode) {
    TypeImpl ti = types.get(typeCode);
    if (!ti.isStringSubtype()) {
      return null;
    }
    Set<String> allowedValues = ((TypeImpl_stringSubtype) ti).getAllowedValues();
    return allowedValues.stream().toArray(i -> new String[i]);
  }

  @Override
  public Iterator<Feature> getFeatures() {
    List<Feature> lf = Collections.unmodifiableList(this.features);
    Iterator<Feature> it = lf.iterator();
    // The first element is null, so skip it.
    it.next();
    return it;
  }

  // public List<FeatureImpl> getFeatureImpls() {
  // return this.features;
  // }

  /**
   * 
   * @param feat
   *          -
   * @return the domain type for a feature.
   */
  public int intro(int feat) {
    return ((TypeImpl) (this.features.get(feat).getDomain())).getCode();
  }

  /**
   * Get the range type for a feature.
   * 
   * @param feat
   *          -
   * @return -
   */
  public int range(int feat) {
    return ((TypeImpl) (this.features.get(feat).getRange())).getCode();
  }

//@formatter:off
  /**
   * Given a component type, return the parent type of the corresponding array type,
   * without needing the corresponding array type to exist (yet).
   *    component type ->  (member of) array type  -> UIMA parent type of that array type.
   *    
   * The UIMA Type parent of an array is either
   *   ArrayBase (for primitive arrays, plus String[] and TOP[] (see below) 
   *   FsArray - for XYZ[] reference kinds of arrays
   *   
   * The UIMA Type parent chain goes like this:
   *   primitive_array -> ArrayBase -> TOP (primitive: boolean, byte, short, int, long, float, double, String)
   *   String[]        -> ArrayBase -> TOP
   *   TOP[]           -> ArrayBase -> TOP
   *    
   *   XYZ[]           -> FSArray    -> TOP  where XYZ is not a primitive, not String[], not TOP[]
   *   
   *   Arrays of TOP are handled differently from XYZ[] because 
   *     - the creation of the FSArray type requires
   *       -- the creation of TOP[] type, which requires (unless this is done)
   *       -- the recursive creation of FSArray type - which causes a null pointer exception
   *     
   *   Note that the UIMA super chain is not used very much (mainly for subsumption,
   *   and for this there is special case code anyways), so this doesn't really matter. (2015 Sept)
   *    
   * Note: the super type chain of the Java impl classes varies from the UIMA super type chain.
   *   It is used to factor out common behavior among classes of arrays.
   *   
   *   *********** NOTE: TBD update the rest of this comment for V3 **************
   *   
   *   For non-JCas (in V3, this is only the XYZ[] and TOP[] kinds of arrays)
   *     
   *     CommonArrayFSImpl  [ for arrays stored on the main heap ]
   *       ArrayFSImpl  (for arrays of FS)
   *       FloatArrayFSImpl
   *       IntArrayFSImpl
   *       StringArrayFSImpl
   *     CommonAuxArrayFSImpl  [ for arrays stored in Aux heaps ]
   *       BooleanArrayFSImpl
   *       ByteArrayFSImpl
   *       ShortArrayFSImpl
   *       LongArrayFSImpl
   *       DoubleArrayFSImpl
   *   
   *   For JCas: The corresponding types have only TOP as their supertypes
   *     but they implement the nonJCas interfaces for each subtype.
   *       Those interfaces implement CommonArrayFS interface
   *          
   * @param componentType the UIMA type of the component of the array
   * @return the parent type of the corresponding array type 
   */
//@formatter:on
  private TypeImpl computeArrayParentFromComponentType(Type componentType) {
    if (componentType.isPrimitive() || (componentType == topType)) {
      return arrayBaseType;
    }
    return fsArrayType;
  }

  // /**
  // * Generate the class for the given name. If it refers to super classes not yet loaded, then the
  // act of
  // * defining the class will cause these to be loaded too.
  // *
  // * Called from UIMATypeSystemClassLoader for lazy loading, or
  // * from commit via UIMATypeSystemClassLoaderInjector -> generateAndLoadClass, for batch loading
  // *
  // * @param name the x.y.z.Foo style name of the class to generate
  // * @return the generated Class
  // */
  // public byte[] jcasGenerate(String name) {
  // TypeImpl type = getType(name);
  // return featureStructureClassGen.createJCasCoverClass(type);
  // }
  //
  // /**
  // * Generate the class for the given name. If it refers to super classes not yet loaded, then the
  // act of
  // * defining the class will cause these to be loaded too.
  // * @param name the x.y.z.Foo style name of the class to generate
  // * @return the generated Class
  // */
  // public byte[] jcas_TypeGenerate(String name) {
  // TypeImpl type = getType(name); // name doesn't have the _Type suffix
  // return featureStructureClassGen.createJCas_TypeCoverClass(type);
  // }

  // /**
  // *
  // * @param name
  // * @return true if name is a JCasGen name (_Type suffix removed outside of this routine)
  // * need to exclude those names that are built-in because otherwise, a sub class loader will
  // regenerate them
  // * and the built-ins need to be shared and not generated
  // */
  // public boolean isJCasGenerateOnLoad(String name) {
  // return !JCasImpl.builtInsWithAltNames.contains(name) && typeName2TypeImpl.containsKey(name);
  // }

  // /**
  // * FunctionalInterface for creating instances of a type.
  // * Acts like a Function, taking a JCas arg, and returning an instance of the JCas type.
  // * @param jcasClass the class of the JCas type to construct
  // * @return a Functional Interface whose apply method takes a JCas and returns a new instance of
  // the class
  // */
  // private Function<JCas, TOP> getCreator(Class<?> jcasClass) {
  // try {
  // MethodHandle mh = lookup.findConstructor(jcasClass, MethodType.methodType(void.class,
  // JCas.class));
  // MethodType mtConstructor = MethodType.methodType(jcasClass, JCas.class);
  //
  // return (Function<JCas, TOP>)LambdaMetafactory.metafactory(
  // lookup, // lookup context for the constructor
  // "apply", // name of the method in the Function Interface
  // MethodType.methodType(Function.class), // signature of callsite, return type is functional
  // interface, args are captured args if any
  // mtConstructor.generic(), // samMethodType signature and return type of method impl by function
  // object
  // mh, // method handle to constructor
  // mtConstructor).getTarget().invokeExact();
  // } catch (Throwable e) {
  // throw new UIMARuntimeException(UIMARuntimeException.INTERNAL_ERROR);
  // }
  // }

  /**
   * Convert between fixed JCas class int (set when it is loaded) and this type system's TypeImpl.
   * 
   * @param i
   *          the index from the typeIndexId of the JCas cover class
   * @return - the type impl associated with that JCas cover class
   */
  public TypeImpl getJCasRegisteredType(int i) {
    // first try without sync lock https://issues.apache.org/jira/browse/UIMA-6249
    TypeImpl ti = (i >= jcasRegisteredTypes.size()) ? null : jcasRegisteredTypes.get(i);
    if (ti != null) {
      return ti;
    }
    synchronized (jcasRegisteredTypes) {
      ti = (i >= jcasRegisteredTypes.size()) ? null : jcasRegisteredTypes.get(i);
    }
    if (null == ti) {
      throwMissingUIMAtype(i);
    }
    return ti;
  }

  /**
   * For a given JCasRegistry index, that doesn't have a corresponding UIMA type, throw an
   * appropriate exception
   * 
   * @param typeIndex
   *          the index in the JCasRegistry
   */
  private void throwMissingUIMAtype(int typeIndex) {
    Class<? extends TOP> cls = JCasRegistry.getClassForIndex(typeIndex);
    if (cls == null) {
      throw new CASRuntimeException(CASRuntimeException.JCAS_UNKNOWN_TYPE_NOT_IN_CAS);
    }

    String className = cls.getName();
    UIMAFramework.getLogger().error(
            "Missing UIMA type, JCas Class name: {}, index: {}, jcasRegisteredTypes size: {}",
            className, typeIndex, jcasRegisteredTypes.size());
    dumpTypeSystem();

    throw new CASRuntimeException(CASRuntimeException.JCAS_TYPE_NOT_IN_CAS, className);
  }

  // for debugging
  public void dumpTypeSystem() {
    StringBuilder sb = new StringBuilder();
    sb.append("TypeSystem committed?: ").append(isCommitted()).append('\n');
    sb.append("jcasRegisteredTypes:\n");
    // dump 5 at a time, use temp stringbuilder to avoid issues with users that wrap System.err
    synchronized (jcasRegisteredTypes) {
      for (int i = 0; i < jcasRegisteredTypes.size(); i++) {
        TypeImpl ti = jcasRegisteredTypes.get(i);
        sb.append(String.format("%4d: ", i));
        sb.append((ti == null) ? "null" : ti.getName());
        if (i % 5 == 0) {
          sb.append('\n');
        }
      }
    }
    UIMAFramework.getLogger().error(sb::toString);

    /** debug dump all types **/
    sb.setLength(0);
    sb.append("Dumping all type names\n");
    List<TypeImpl> allTypes = getAllTypes();
    int sz = allTypes.size();

    for (int i = 0; i < sz;) {
      sb.append(String.format("%4d: %-20s ", i, allTypes.get(i++).getName()));
      if (i == sz) {
        break;
      }
      if (i % 5 == 0) {
        sb.append('\n');
      }
    }
    UIMAFramework.getLogger().error(sb::toString);
  }

  // public FSClassRegistry getFSClassRegistry() {
  // return fsClassRegistry;
  // }

  /**
   * 
   * @param typeIndexID
   *          the JCasTypeID for the JCas cover class that will be used when creating instances of
   *          ti - may be a supertype of ti, if ti has no JCas class itself - may be a subtype of
   *          the supertype of ti, if ti has no JCas class itself
   * @param ti
   *          the UIMA type
   */
  void setJCasRegisteredType(int typeIndexID, TypeImpl ti) {
    synchronized (jcasRegisteredTypes) {
      TypeImpl existing = Misc.getWithExpand(jcasRegisteredTypes, typeIndexID);
      if (existing != null) {
        if (ti != existing) {
          // allowed
          if (ti == null) {
            Misc.internalError();
          }
          if (!existing.subsumes(ti)) {
            Misc.internalError();
          }
        }
      } else {
        jcasRegisteredTypes.set(typeIndexID, ti);
      }
    }
  }

  public static final int getTypeClass(TypeImpl ti) {
    switch (ti.getCode()) {
      case TypeSystemConstants.intTypeCode:
        return CASImpl.TYPE_CLASS_INT;
      case TypeSystemConstants.floatTypeCode:
        return CASImpl.TYPE_CLASS_FLOAT;
      case TypeSystemConstants.stringTypeCode:
        return CASImpl.TYPE_CLASS_STRING;
      case TypeSystemConstants.intArrayTypeCode:
        return CASImpl.TYPE_CLASS_INTARRAY;
      case TypeSystemConstants.floatArrayTypeCode:
        return CASImpl.TYPE_CLASS_FLOATARRAY;
      case TypeSystemConstants.stringArrayTypeCode:
        return CASImpl.TYPE_CLASS_STRINGARRAY;
      case TypeSystemConstants.fsArrayTypeCode:
        return CASImpl.TYPE_CLASS_FSARRAY;
      case TypeSystemConstants.booleanTypeCode:
        return CASImpl.TYPE_CLASS_BOOLEAN;
      case TypeSystemConstants.byteTypeCode:
        return CASImpl.TYPE_CLASS_BYTE;
      case TypeSystemConstants.shortTypeCode:
        return CASImpl.TYPE_CLASS_SHORT;
      case TypeSystemConstants.longTypeCode:
        return CASImpl.TYPE_CLASS_LONG;
      case TypeSystemConstants.doubleTypeCode:
        return CASImpl.TYPE_CLASS_DOUBLE;
      case TypeSystemConstants.booleanArrayTypeCode:
        return CASImpl.TYPE_CLASS_BOOLEANARRAY;
      case TypeSystemConstants.byteArrayTypeCode:
        return CASImpl.TYPE_CLASS_BYTEARRAY;
      case TypeSystemConstants.shortArrayTypeCode:
        return CASImpl.TYPE_CLASS_SHORTARRAY;
      case TypeSystemConstants.longArrayTypeCode:
        return CASImpl.TYPE_CLASS_LONGARRAY;
      case TypeSystemConstants.doubleArrayTypeCode:
        return CASImpl.TYPE_CLASS_DOUBLEARRAY;
    }

    if (ti.isStringOrStringSubtype()) {
      return CASImpl.TYPE_CLASS_STRING;
    }

    if (ti.isArray()) {
      return CASImpl.TYPE_CLASS_FSARRAY;
    }

    return CASImpl.TYPE_CLASS_FS;
  }

  public List<TypeImpl> getAllTypes() {
    return types.subList(1, types.size());
  }

  public static final TypeSystemImpl staticTsi = new TypeSystemImpl();
  static {
    TypeSystemImpl tsi = staticTsi.commit(); // needed to assign adjusted offsets to the builtins
    if (tsi != staticTsi) {
      Misc.internalError();
    }
  }

//@formatter:off
  /**
   * HashCode and Equals
   *   used to compare two type system.  
   *   Equal type systems are collapsed into one
   *   
   * Equal means 
   *   Same types
   *     Type are same if they have the same features and other flags
   *       Same Features  
   */
//@formatter:on
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((types == null) ? 0 : types.hashCode());
    return result;
  }

  // debug - compare two type systems, print first different type
  public static void compareTs(TypeSystem t1, TypeSystem t2) {
    TypeSystemImpl ts1 = (TypeSystemImpl) t1;
    TypeSystemImpl ts2 = (TypeSystemImpl) t2;
    if (ts1.types.size() != ts2.types.size()) {
      System.out.format("ts1 size: %,d ts2 size: %d%n", ts1.types.size(), ts2.types.size());
    }

    for (int i = 1; i < ts1.types.size(); i++) {
      if (ts1.types.get(i).hashCode() != ts2.types.get(i).hashCode()) {
        System.out.format("ts1 type: %s%n%nts2 type: %s%n", ts1.types.get(i), ts2.types.get(i));
      }
    }
    System.out.println("done");
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    TypeSystemImpl other = (TypeSystemImpl) obj;
    if (types == null) {
      if (other.types != null) {
        return false;
      }
    } else if (!types.equals(other.types)) {
      return false;
    }
    return true;
  }

  // public void installTypeCreator(Class<?> jcasClass) {
  // TypeImpl ti = typeName2TypeImpl.get(jcasClass.getName());
  // assert (ti != null);
  // ti.setCreator(getCreator(jcasClass), get_TypeCreator(jcasClass));
  // }
  //
  // public void install_TypeCreator(Class<?> jcasClass) {
  // Type
  // }

  // Methods to assist users who build type systems programatically and have
  // variables referring to Types and Features to update these after a commit
  // (in case the commit consolidated type systems).
  public TypeImpl refreshType(Type t) {
    return getType(t.getName());
  }

  public FeatureImpl refreshFeature(Feature f) {
    return getFeatureByFullName(f.getName());
  }

  // /**
  // * @param typecode the type code
  // * @return the generator for the type
  // */
  // Object getGenerator(int typecode) {
  // return jcasClassesInfo[typecode].generator;
  // }

  // /**
  // * @param typecode
  // * @return the associated JCas class (may be for a UIMA supertype, is never null)
  // */
  // Class<?> getJCasClass(int typecode) {
  // return jcasClassesInfo[typecode].jcasClass;
  // }
  /**
   * ******** OBSOLETE - only left in for supporting some jcas style in alpha level *************
   * This code is run when a JCas class is loaded and resolved, for the first time, as part of type
   * system commit, or as part of statically loading the FSClassRegister class (where this is done
   * for all the built-ins, once). It looks up the offset value in the type system (via a
   * thread-local)
   *
   * hidden parameter: threadLocal value: referencing this type
   * 
   * @param featName
   *          -
   * @return the offset in the int or ref data arrays for the named feature
   */
  // ******** OBSOLETE - only left in for supporting some jcas style in alpha level *************
  public static synchronized int getAdjustedFeatureOffset(String featName) {
    /*
     * The JCas class being loaded was generated for the "alpha" level of UIMA v3, and is not
     * supported for Beta and later levels.
     * 
     * This can be fixed by regenerating this using the current v3 version of UIMA tooling (JCasgen,
     * or the migration tooling to migrate from v2).
     */
    Logger logger = UIMAFramework.getLogger(TypeSystemImpl.class);
    logger.warn(() -> logger.rb_ue(CASRuntimeException.JCAS_ALPHA_LEVEL_NOT_SUPPORTED));
    return -1;
  }

  static int getAdjustedFeatureOffset(TypeImpl type, String featName) {
    FeatureImpl fi = type.getFeatureByBaseName(featName);
    return (fi == null) ? -1 : fi.getAdjustedOffset();
    // if (fi == null) {
    // FeatureImpl_jcas_only fi_j = type.getJcasAddedFeature(featName);
    // return (fi_j == null) ? -1 : fi_j.getAdjustedOffset();
    // }
    // return fi.getAdjustedOffset();
  }

//@formatter:off
  /**
   * When deserializing Xmi and XCAS, Arrays of Feature Structures are encoded as FSArray types, but
   * they may have a more restrictive typing, e.g. arrays of Annotation, with the type code of
   * Annotation[].
   * 
   * This is determined by the range type of the feature referring to the array instance.
   * 
   * A fixup is done when
   *   - the arrayFs is not already subtyped (could be for 0 length arrays) 
   *   - it's not disabled (for backward compatibility with v2)
   * 
   * If the feature is marked as multipleReferencesAllowed, the presumption is that all references
   * are to the same subtype.
   * 
   * 0-length arrays are created with the proper subtype already, and won't need fixing up
   * 
   * The fixup updates the type of the Feature Structure from FSArray to the array type declared; no
   * checking is done to insure the elements conform, however.
   */
//@formatter:on
  void fixupFSArrayTypes(TypeImpl featRange, TOP arrayFs) {
    if (CASImpl.IS_DISABLE_SUBTYPE_FSARRAY_CREATION) {
      return; // for compatibility with V2
    }
    if (arrayFs != null && featRange.isTypedFsArray()) { // https://issues.apache.org/jira/browse/UIMA-5446
      TypeImpl array_type = arrayFs._getTypeImpl();
      if (array_type.isTypedFsArray()) {
        // don't change an already subtyped FSArray
        return;
      }
      arrayFs._setTypeImpl(featRange); // replace more general type with more specific type
    }
  }

  /**
   * Called when switching or initializing CAS's shared-view-data instance of FsGenerator[]
   * generators are kept in a map, unique for each type system, keyed by classloader.
   * 
   * @param cl
   *          the class loader
   * @param isPear
   *          -
   * @return the generators
   */
  public FsGenerator3[] getGeneratorsForClassLoader(ClassLoader cl, boolean isPear) {
    Map<ClassLoader, FsGenerator3[]> gByC = isPear ? generators4pearsByClassLoader
            : generatorsByClassLoader;
    synchronized (gByC) {

      FsGenerator3[] g = gByC.get(cl); // a separate map per type system instance
      if (g == null && !skip_loading_user_jcas) {
        g = FSClassRegistry.getGeneratorsForClassLoader(cl, isPear, this);
        gByC.put(cl, g);
      }
      return g;
    }
  }

//@formatter:off
  /**
   * Creates and returns a new MutableCallSite, 
//   * recording it in list of all callsites for this type, in a map by typename
//   * 
//   * Done this way because 
//   *   - can't be a classloader-wide list of call sites - some might not be associated with this type system
//   *   - can't be a typesystem-wide list of call sites - the JCas class might be used by multiple type systems
//   *     and the first one to load it would set this value.
//   *   - has to be pairs of feature name, call-site, in order to get the value to set, later
//   *   --  doesn't need to be a hashmap, can be an arraylist of entry
//   *   Type being loaded may not be known at this point.
   * @param clazz the JCas class
   * @param featName the short name of the feature
   * @return the created callsite
   */
//@formatter:on
  public final static MutableCallSite createCallSite(Class<? extends TOP> clazz, String featName) {
    MutableCallSite callSite = new MutableCallSite(MethodType.methodType(int.class));
    callSite.setTarget(MHC_MINUS_1); // for error checking
    // ArrayList<Entry<String, MutableCallSite>> callSitesForType =
    // FSClassRegistry.callSites_all_JCasClasses.computeIfAbsent(clazz, k -> new ArrayList<>());
    // callSitesForType.add(new AbstractMap.SimpleEntry<String, MutableCallSite>(featName,
    // callSite));
    return callSite;
  }

  /**
   * <b>INTERNAL API - DO NOT USE, MAY CHANGE WITHOUT NOTICE!</b>
   * <p>
   * Creates and returns a new MutableCallSite for a built-in type. This handles the special case
   * where the {@link #staticTsi} is initialized as a result of a builtin type JCas cover class
   * being loaded in which case the callsite in the cover class is set after the {@link #staticTsi}
   * already has been committed and therefore won't automatically get its callsites updated during
   * the commit. So we need to create a proper callsite already pointing to the correct feature
   * offset here.
   * 
   * @param clazz
   *          the JCas class
   * @param featName
   *          the short name of the feature
   * @return the created callsite
   */
  public final static MutableCallSite createCallSiteForBuiltIn(Class<? extends TOP> clazz,
          String featName) {
    // If the static TSI has not yet been initialized, we assume that the initialization of the
    // static TSI was not triggered by the given JCas cover class. So we return a default callsite
    // and trust that it will be properly updated when the static TSI is committed.
    if (staticTsi == null) {
      return createCallSite(clazz, featName);
    }

    // If the given JCas cover class not registered yet, we also assume that the initialization of
    // the static TSI was not triggered by the given JCas cover class.
    TypeImpl type;
    try {
      int typeId = clazz.getField("typeIndexID").getInt(null);
      type = (typeId >= staticTsi.jcasRegisteredTypes.size()) ? null
              : staticTsi.jcasRegisteredTypes.get(typeId);
      if (type == null) {
        return createCallSite(clazz, featName);
      }
    } catch (Exception e) {
      throw new UIMARuntimeException(e, UIMARuntimeException.INTERNAL_ERROR, e);
    }

    // If the JCas class has already been registered and the static TSI is already there, we look
    // up the proper feature offset in the static TSI
    MutableCallSite callSite = new MutableCallSite(MethodType.methodType(int.class));
    int adjustedOffset = type.getAdjOffset(featName);
    callSite.setTarget(FSClassRegistry.getConstantIntMethodHandle(adjustedOffset));
    return callSite;
  }

  @Override
  public Iterator<Type> iterator() {
    return getTypeIterator();
  }

  public void set_skip_loading_user_jcas(boolean v) {
    this.skip_loading_user_jcas = v;
  }

  // private static boolean isBuiltIn(Class<? extends TOP> clazz) {
  // return BuiltinTypeKinds.creatableBuiltinJCasClassNames.contains(clazz.getName());
  // }
  // /**
  // * Get a list of types which have OID feature, filtered down to being just the top-most
  // * (in the type hierarchy)
  // */
  // public List<TypeImpl> getTopOidTypes() {
  // List<TypeImpl> r = new ArrayList<>();
  //
  // outer:
  // for (TypeImpl t : types) {
  // if (t.featUimaUID != null) {
  // for (TypeImpl prev : r) {
  // if (prev.subsumes(t)) {
  // continue outer;
  // }
  // }
  // r.add(t);
  // }
  // }
  // return r;
  // }

}
