package gw.internal.gosu.parser;

import gw.internal.gosu.compiler.GosuClassLoader;
import gw.internal.gosu.parser.expressions.TypeVariableDefinition;
import gw.internal.gosu.parser.expressions.TypeVariableDefinitionImpl;
import gw.internal.gosu.parser.statements.ClassStatement;
import gw.internal.gosu.parser.statements.DelegateStatement;
import gw.internal.gosu.parser.statements.StatementList;
import gw.internal.gosu.parser.statements.VarStatement;


import gw.lang.Returns;
import gw.lang.parser.*;
import gw.lang.parser.CaseInsensitiveCharSequence;
import gw.lang.parser.GosuParserFactory;
import gw.lang.parser.IManagedContext;
import gw.lang.parser.IScriptPartId;
import gw.lang.parser.ISymbol;
import gw.lang.parser.ISymbolTable;
import gw.lang.parser.ITypeUsesMap;
import gw.lang.parser.ScriptPartId;
import gw.lang.parser.ScriptabilityModifiers;
import gw.lang.parser.ICapturedSymbol;
import gw.lang.parser.IBlockClass;
import gw.lang.parser.PostCompilationAnalysis;
import gw.lang.parser.TypeVarToTypeMap;
import gw.lang.parser.exceptions.ErrantGosuClassException;
import gw.lang.parser.exceptions.ParseResultsException;
import gw.lang.parser.expressions.ITypeVariableDefinition;
import gw.lang.parser.expressions.IVarStatement;
import gw.lang.parser.statements.IFunctionStatement;
import gw.lang.parser.statements.IUsesStatement;
import gw.lang.reflect.*;
import gw.lang.reflect.gs.*;
import gw.lang.reflect.java.IJavaType;
import gw.lang.reflect.java.JavaTypes;
import gw.lang.reflect.module.Dependency;
import gw.lang.reflect.module.IModule;
import gw.lang.tidb.IInheritanceRecord;
import gw.util.GosuExceptionUtil;
import gw.util.GosuStringUtil;
import gw.util.Predicate;
import gw.util.concurrent.LockingLazyVar;
import gw.util.fingerprint.FP64;
import gw.util.perf.objectsize.IObjectSizeFilter;
import gw.util.perf.objectsize.ObjectSize;
import gw.util.perf.objectsize.ObjectSizeUtil;

import java.io.InvalidClassException;
import java.io.ObjectStreamException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 */
public class GosuClass implements IGosuClassInternal
{
  private static final long serialVersionUID = 5L;
  public static final String CLASS_ANNOTATION_SLOT = "class";
  public static final String INTERFACE_METHODS_INNER_CLASS = "$_methods_$";
  public static final String EVAL_ANNOTATIONS_METHOD = "$evalAnnotations";

  protected transient GosuClassParseInfo _parseInfo;

  private String _strFullName;
  private IType[] _typeParams;
  transient private String _strNamespace;
  transient private String _strRelativeName;
  transient private GosuClassTypeLoader _typeLoader;
  transient private boolean _bInterface;
  transient private boolean _bEnum;
  transient private Map<CharSequence, IGosuClassInternal> _mapInnerClasses;
  transient private volatile Set<IType> _setTypes;
  transient private List<IType> _interfaces;
  transient private IType _superType;
  transient private IType _enclosingType;
  transient private IJavaType _proxiedJavaClassInGosuProxy; // only relevant when this GosuClass is a _proxy_
  transient private volatile Class<?> _javaClass;
  transient private IGosuClassInternal _genericClass;
  transient private Map<String, IGosuClassInternal> _parameterizationByParamsName;
  transient private volatile GosuClassTypeInfo _typeInfo;
  transient private IType _gsArrayClass;
  transient private volatile Boolean _bHasSessionVarStatements;
  transient private int _iMdChecksum;
  transient private int _iTiChecksum;
  transient private CompilationState _compilationState;
  transient private IManagedContext _dbgContext;
  transient private boolean _bEditorParser;
  transient private ISourceFileHandle _sourceFileHandle;
  transient private List<IGosuClassInternal> _subtypes;
  transient private String _description;
  transient private CaseInsensitiveCharSequence _defaultConstructorName;
  transient private boolean _bCannotCaptureSymbols;
  transient private Boolean _hasError;
  transient private boolean _bDiscarded;
  transient private boolean _bInitializing;
  transient private List<IGosuClass> _blocks;
  transient private LockingLazyVar<Boolean> _valid;
  transient private ITypeRef _typeRef;
  transient private List<ITypeVariableDefinition> _typeVarDefs;
  transient private GenericTypeVariable[] _genTypeVar;
  transient private GosuParser _parser;
  transient private LockingLazyVar<Map<String, List>> _featureAnnotationMap;
  transient private ITypeUsesMap _typeUsesMap;
  transient private ModifierInfo _modifierInfo;
  transient private boolean _bCompiledToUberModule;


  public GosuClass( String strNamespace, String strRelativeName, GosuClassTypeLoader classTypeLoader,
                    ISourceFileHandle sourceFile, ITypeUsesMap typeUsesMap )
  {
//    System.out.println("NEW: "+ strNamespace + ": " + sourceFile.getClass().getSimpleName() + " :::: " + sourceFile.getSource().getSource().replace("\n", " "));
    initLazyVars();

    if( strNamespace == null )
    {
      throw new IllegalArgumentException( "Namespace must be non-null" );
    }
    if( strRelativeName == null )
    {
      throw new IllegalArgumentException( "Relative name must be non-null" );
    }

    _bInitializing = true;
    try
    {
      _compilationState = new CompilationState();
      _iMdChecksum = TypeSystem.getRefreshChecksum();
      _iTiChecksum = TypeSystem.getSingleRefreshChecksum();
      _strNamespace = strNamespace.intern();
      _strRelativeName = strRelativeName;
      _typeLoader = classTypeLoader;
      _sourceFileHandle = sourceFile;
      _strFullName = (GosuStringUtil.isEmpty( _strNamespace ) ? "" : (_strNamespace + '.')) + _strRelativeName;
      _mapInnerClasses = Collections.emptyMap();
      _interfaces = EMPTY_GSCLASS_LIST;
      _defaultConstructorName = CaseInsensitiveCharSequence.get( _strRelativeName + "()" );
      _blocks = Collections.emptyList();
      _typeUsesMap = typeUsesMap;
      _modifierInfo = new ModifierInfo( Modifier.PUBLIC );
    }
    finally
    {
      // Ok, this is really, really weird.
      //   Notice the code in the method isDiscarded below.  We do this so that we don't use this new type until after
      //   it has been fully initialized.
      //   The last call to getOrCreateTypeRef simply makes sure we replace the handle with this type
      _bInitializing = false;
    }
    getOrCreateTypeReference();
  }

  protected GosuClass( IGosuClass genericClass, IType[] typeParams )
  {
    this( genericClass, typeParams, true );
  }
  protected GosuClass( IGosuClass genericClass, IType[] typeParams, boolean bCopyState  )
  {
    initLazyVars();
    try
    {
      IGosuClassInternal pureGenericClass = (IGosuClassInternal)TypeLord.getPureGenericType( genericClass );
      pureGenericClass.compileHeaderIfNeeded();

      _iMdChecksum = TypeSystem.getRefreshChecksum();
      _iTiChecksum = TypeSystem.getSingleRefreshChecksum();
      _genericClass = pureGenericClass;
      _typeParams = typeParams;
      copyGenericState( bCopyState );
    }
    finally
    {
      // Ok, this is really, really weird.
      //   Notice the code in the method isDiscarded below.  We do this so that we don't use this new type until after
      //   it has been fully initialized.
      //   The last call to getOrCreateTypeRef simply makes sure we replace the handle with this type
      _bInitializing = false;
    }
    getOrCreateTypeReference();
  }

  public GosuClassParseInfo getParseInfo()
  {
    return _parseInfo;
  }

  @Override
  public void clearParseInfo()
  {
    _parseInfo = null;

    if( _sourceFileHandle != null )
    {
      _sourceFileHandle.cleanAfterCompile();
    }

    _typeUsesMap = null;
    
    for( IGosuAnnotation ga : getGosuAnnotations() )
    {
      ga.clearExpression();
    }
  }

  public Object dontEverCallThis()
  {
    return this;
  }

  public void copyGenericState( boolean bCopyHierarchy )
  {
    _iMdChecksum = TypeSystem.getRefreshChecksum();
    _iTiChecksum = TypeSystem.getSingleRefreshChecksum();

    GosuClass realGenericClass = (GosuClass)_genericClass.dontEverCallThis();
    _strNamespace = realGenericClass._strNamespace;
    _typeLoader = realGenericClass._typeLoader;
    _mapInnerClasses = realGenericClass._mapInnerClasses;
    // Must synchronize so that _staticScope is guaranteed to be non-null, see initialize()
    TypeSystem.lock();
    try
    {
      _strRelativeName = realGenericClass._strRelativeName + TypeLord.getNameOfParams( _typeParams, true, false );
      _strFullName = realGenericClass._strFullName + TypeLord.getNameOfParams( _typeParams, false, false );

      _defaultConstructorName = CaseInsensitiveCharSequence.get( realGenericClass.getRelativeName() + "()" );

      _bInterface = realGenericClass._bInterface;

      _compilationState = realGenericClass._compilationState;
      _hasError = realGenericClass._hasError;

      _enclosingType = realGenericClass._enclosingType;
      _sourceFileHandle = realGenericClass._sourceFileHandle;
      _bHasSessionVarStatements = realGenericClass._bHasSessionVarStatements;

      _dbgContext = realGenericClass._dbgContext;

      _bEnum = realGenericClass._bEnum;

      _parseInfo = realGenericClass._parseInfo;
      _typeUsesMap = realGenericClass._typeUsesMap;

      //transient private Set<IType> _setTypes; // lazy
      //transient private IType _javaStubType; // lazy
      //transient private GosuClassTypeInfo _typeInfo; // lazy
      //transient private GosuArrayClass _gsArrayClass; // lazy

      if( bCopyHierarchy )
      {
        copyHierarchyInfo();
      }
      _blocks = realGenericClass._blocks;
    }
    finally
    {
      TypeSystem.unlock();
    }
  }

  public void copyHierarchyInfo()
  {
    TypeSystem.lock();
    try
    {
      assignParameterizedInterfaces();
      assignParameterizedSuperType();
      assignParameterizedJavaTypeIfProxy();

      assignTypeVarsFromTypeParams( getTypeParameters() );
    }
    finally
    {
      TypeSystem.unlock();
    }
  }

  private void assignParameterizedJavaTypeIfProxy()
  {
    if( isProxy() )
    {
      IJavaTypeInternal javaType = (IJavaTypeInternal) _genericClass.getJavaType();
      if( javaType == null )
      {
        return;
      }

      if( isParameterizedType() )
      {
        getOrCreateTypeReference();
        javaType = (IJavaTypeInternal) javaType.getParameterizedType( getTypeParameters() );
      }

      setJavaType(javaType);
    }
  }

  private void assignParameterizedSuperType()
  {
    IType genSuperType = _genericClass.getSupertype();
    if( genSuperType == null )
    {
      return;
    }

    if( !genSuperType.isGenericType() )
    {
      setSuperType( genSuperType );
    }
    else if( genSuperType instanceof IJavaType )
    {
      IJavaTypeInternal javaGenSuperType = (IJavaTypeInternal)genSuperType;
      TypeVarToTypeMap actualParamByVarName = TypeLord.mapTypeByVarName( getOrCreateTypeReference(), getOrCreateTypeReference(), true );
      setSuperType( TypeLord.getActualType( javaGenSuperType, actualParamByVarName, true ) );
      ((IJavaTypeInternal)_superType).setAdapterClass( javaGenSuperType.getAdapterClass() );
    }
    else if( genSuperType instanceof IGosuClassInternal )
    {
      IGosuClassInternal gsGenSuperType = (IGosuClassInternal)genSuperType;
      TypeVarToTypeMap actualParamByVarName = TypeLord.mapTypeByVarName( getOrCreateTypeReference(), getOrCreateTypeReference(), true );
      _superType = TypeLord.getActualType( gsGenSuperType, actualParamByVarName, true );
    }
  }

  private void assignParameterizedInterfaces()
  {
    GosuClass realGenericClass = (GosuClass)_genericClass.dontEverCallThis();
    List<IType> interfaces = realGenericClass._interfaces;
    if( interfaces.isEmpty() )
    {
      _interfaces = EMPTY_GSCLASS_LIST;
      return;
    }
    if( _interfaces == null )
    {
      _interfaces = EMPTY_GSCLASS_LIST;
    }
    for( IType genInterface : interfaces )
    {
      if( genInterface.isGenericType() )
      {
        if( genInterface instanceof IJavaType)
        {
          IJavaTypeInternal javaGenInterface = (IJavaTypeInternal)genInterface;
          TypeVarToTypeMap actualParamByVarName = TypeLord.mapTypeByVarName(getOrCreateTypeReference(), getOrCreateTypeReference(), true );
          genInterface = TypeLord.getActualType( javaGenInterface, actualParamByVarName, true );
          ((IJavaTypeInternal)genInterface).setAdapterClass( javaGenInterface.getAdapterClass() );
        }
        else if( genInterface instanceof IGosuClassInternal )
        {
          IGosuClassInternal gsGenInterface = (IGosuClassInternal)genInterface;
          TypeVarToTypeMap actualParamByVarName = TypeLord.mapTypeByVarName(getOrCreateTypeReference(), getOrCreateTypeReference(), true );
          genInterface = TypeLord.getActualType( gsGenInterface, actualParamByVarName, true );
        }
      }
      addInterface( genInterface );
    }
  }

  public String getName()
  {
    return _strFullName;
  }

  public String getDisplayName()
  {
    return getName();
  }

  public String getRelativeName()
  {
    return _strRelativeName;
  }

  public String getNamespace()
  {
    return _strNamespace;
  }

  public void setNamespace( String namespace )
  {
    _strNamespace = namespace == null ? null : namespace.intern();
  }

  public GosuClassTypeLoader getTypeLoader()
  {
    return _typeLoader;
  }

  public IType getSupertype()
  {
    compileHeaderIfNeeded();
    return _superType;
  }

  public IGosuClassInternal getGenericType()
  {
    return isParameterizedType()
           ? _genericClass
           : isGenericType()
             ? (IGosuClassInternal) getOrCreateTypeReference()
             : null;
  }

  public boolean isFinal()
  {
    compileHeaderIfNeeded();
    return Modifier.isFinal( getModifiers() );
  }

  public boolean isInterface()
  {
    compileHeaderIfNeeded();

    return _bInterface;
  }

  public void setInterface( boolean bInterface )
  {
    _bInterface = bInterface;
  }

  public boolean isEnum()
  {
    compileHeaderIfNeeded();

    return _bEnum;
  }

  public void setEnum()
  {
    _bEnum = true;
    if( getEnclosingType() != null )
    {
      markStatic();
    }
    addInterface(TypeSystem.get(IEnumValue.class, TypeSystem.getGlobalModule()));
  }

  public List<IEnumValue> getEnumValues()
  {
    //noinspection unchecked
    return (List<IEnumValue>)getTypeInfo().getProperty("AllValues").getAccessor().getValue(null);
  }

  public IEnumValue getEnumValue( String strName )
  {
    return (IEnumValue) getTypeInfo().getMethod( "valueOf", JavaTypes.STRING() ).getCallHandler().handleCall( null, strName );
  }

  public List<IType> getInterfaces()
  {
    compileHeaderIfNeeded();

    return Collections.unmodifiableList( _interfaces );
  }

  public void addInterface( IType type )
  {
    if( _interfaces == EMPTY_GSCLASS_LIST )
    {
      _interfaces = new ArrayList<IType>( 2 );
    }
    for( int i = 0; i < _interfaces.size(); i++ )
    {
      IType iface =_interfaces.get( i );
      if( TypeLord.getPureGenericType( iface ).equals( TypeLord.getPureGenericType( type ) ) )
      {
        // Remove duplicate generic interface
        //
        // Normally this won't happen, but...
        // if the class is a recursive type (via generics), we have to short-circuit
        // the type when we first parse it, hence the pure generic type for the
        // interface is added during the first header parse. It's safe to remove it here
        // assuming we're adding a properly parameterized type e.g.,
        //   class Foo<T extends Bar<T>> implements Comparable<Foo<T>>
        //
        _interfaces.remove( i );
      }
    }
    if( !_interfaces.contains( type ) )
    {
      _interfaces.add(type);
    }
  }

  public IJavaType getJavaType()
  {
    return _proxiedJavaClassInGosuProxy;
  }

  public void setJavaType( IJavaType javaType )
  {
    _proxiedJavaClassInGosuProxy = javaType;
  }

  public IType findProxiedClassInHierarchy()
  {
    if( getSuperClass() != null )
    {
      return getSuperClass().findProxiedClassInHierarchy();
    }
    return null;
  }

  public boolean isParameterizedType()
  {
    //!! Do not call following method:
    // compileDeclarationsIfNeeded();

    return _typeParams != null && _typeParams.length > 0;
  }

  public boolean isGenericType()
  {
    compileHeaderIfNeeded();

    List<ITypeVariableDefinition> typeVarDefs = getTypeVarDefs();
    return typeVarDefs != null && !typeVarDefs.isEmpty();
  }

  public GenericTypeVariable[] getGenericTypeVariables()
  {
    compileHeaderIfNeeded();

    if( _genTypeVar == null )
    {
      if( _typeVarDefs == null || // Hack to short-circuit case where header compilation is reentrant e.g., parsing an inner class w/o closing brace as a field initializer value.
          _typeVarDefs.isEmpty() )
      {
        return _genTypeVar = GenericTypeVariable.EMPTY_TYPEVARS;
      }
      _genTypeVar = new GenericTypeVariable[_typeVarDefs.size()];
      for( int i = 0; i < _typeVarDefs.size(); i++ )
      {
        _genTypeVar[i] = (GenericTypeVariable) _typeVarDefs.get(i).getTypeVar();
      }
    }
    return _genTypeVar;
  }

  public IGosuClassInternal getParameterizedType( IType... paramTypes )
  {
    compileHeaderIfNeeded();

//    if( getGenericTypeVariables().length != paramTypes.length )
//    {
//      throw new IllegalStateException();
//    }
    for( IType t: paramTypes )
    {
      if( t == null )
      {
        throw new IllegalStateException( "Type parameters must be non-null" );
      }
    }

    if( isParameterizedType() )
    {
      return (IGosuClassInternal)TypeLord.getPureGenericType(getOrCreateTypeReference()).getParameterizedType( paramTypes );
    }

    if( paramTypes == null || paramTypes.length == 0 )
    {
      throw new IllegalArgumentException( "Parameter types required. Invoked on type: " + _strFullName);
    }

    if( _parameterizationByParamsName == null )
    {
      _parameterizationByParamsName = new ConcurrentHashMap<String, IGosuClassInternal>( 2 );
    }

    paramTypes = TypeSystem.boxPrimitiveTypeParams( paramTypes );
    String strNameOfParams = TypeLord.getNameOfParams( paramTypes, false, true );
    IGosuClassInternal parameterizedClass = _parameterizationByParamsName.get( strNameOfParams );
    if( parameterizedClass == null )
    {
      parameterizedClass = makeCopy( paramTypes );
      _parameterizationByParamsName.put( strNameOfParams, parameterizedClass );
      parameterizedClass.copyHierarchyInfo();

    }
    return parameterizedClass;
  }

  protected IGosuClassInternal makeCopy( IType... paramTypes )
  {
    return (IGosuClassInternal)getOrCreateTypeReference( new GosuClass( (IGosuClass)getOrCreateTypeReference(), paramTypes, false ) );
  }

  public IType[] getTypeParameters()
  {
    compileHeaderIfNeeded();

    return _typeParams;
  }

  public Set<IType> getAllTypesInHierarchy()
  {
    compileHeaderIfNeeded();

    if( !isHeaderCompiled() )
    {
      return Collections.emptySet();
    }

    if( _setTypes == null )
    {
      TypeSystem.lock();
      try
      {
        if( _setTypes == null )
        {
          _setTypes = createAllTypesInHierarchy();
        }
      }
      finally
      {
        TypeSystem.unlock();
      }
    }
    return _setTypes;
  }

  private Set<IType> createAllTypesInHierarchy()
  {
    Set<IType> types;
    if( isArray() )
    {
      types = TypeLord.getArrayVersionsOfEachType( getComponentType().getAllTypesInHierarchy() );
    }
    else
    {
      types = TypeLord.getAllClassesInClassHierarchyAsIntrinsicTypes(getOrCreateTypeReference());
      if( !isInterface() )
      {
        if( isParameterizedType() )
        {
          // Will indirectly call handleProxyClassAssignment() on generic type
          getGenericType().getAllTypesInHierarchy();
        }
        else if( ProxyUtil.isProxy( this ) )
        {
          types.addAll( TypeSystem.getByFullName( getName().substring( PROXY_PREFIX.length() + 1 ) ).getAllTypesInHierarchy() );
        }
      }
    }
    return types;
  }

  public boolean isArray()
  {
    return false;
  }

  public boolean isPrimitive()
  {
    return false;
  }

  public IType getArrayType()
  {
    if( _gsArrayClass == null )
    {
      // Only sync if it's null.  If so, then check again to avoid a race
      TypeSystem.lock();
      try
      {
        if( _gsArrayClass == null )
        {
          //noinspection UnnecessaryLocalVariable
          IGosuArrayClass gsArrayClass =
            (IGosuArrayClass)getOrCreateTypeReference( new GosuArrayClass( getOrCreateTypeReference(), getTypeLoader() ) );
          _gsArrayClass = gsArrayClass;
        }
      }
      finally
      {
        TypeSystem.unlock();
      }
    }
    return _gsArrayClass;
  }

  public Object makeArrayInstance( int iLength )
  {
    return Array.newInstance(getBackingClass(), iLength);
  }

  public Object getArrayComponent( Object array, int iIndex ) throws IllegalArgumentException, ArrayIndexOutOfBoundsException
  {
    return Array.get( array, iIndex );
  }

  public void setArrayComponent( Object array, int iIndex, Object value ) throws IllegalArgumentException, ArrayIndexOutOfBoundsException
  {
    Array.set( array, iIndex, value );
  }

  public int getArrayLength( Object array ) throws IllegalArgumentException
  {
    return Array.getLength( array );
  }

  public IType getComponentType()
  {
    return null;
  }

  public boolean isAssignableFrom( IType type )
  {
    IType pThis = getOrCreateTypeReference();

    // Short-circuit if the types are the same
    if( type == pThis )
    {
      return true;
    }

    // Short-circuit if the given type is null
    if( type == null )
    {
      return false;
    }

    if( isArray() && type.isArray() )
    {
      return getComponentType().isAssignableFrom( type.getComponentType() );
    }
    else if( isArray() || type.isArray() )
    {
      return false;
    }
    else
    {
      //noinspection SuspiciousMethodCalls
      return type.getAllTypesInHierarchy().contains( pThis ) ||
             TypeLord.areGenericOrParameterizedTypesAssignable( pThis, type );
    }
  }

  public boolean isMutable()
  {
    return true;
  }

  public boolean isValid()
  {
    try
    {
      compileDefinitionsIfNeeded();
    }
    catch( ErrantGosuClassException egce )
    {
      return false;
    }
    return _valid.get();
  }

  public boolean isStatic()
  {
    return Modifier.isStatic(getModifiers());
  }

  public ModifierInfo getModifierInfo()
  {
    if( isParameterizedType() )
    {
      return (ModifierInfo)getGenericType().getModifierInfo();
    }
    compileHeaderIfNeeded();
    return _modifierInfo;
  }
  public void setModifierInfo( ModifierInfo modifierInfo )
  {
    if( isParameterizedType() )
    {
      getGenericType().setModifierInfo( modifierInfo);
      return;
    }

    GosuClass.filterClassAnnotations( modifierInfo.getAnnotations() );
    _modifierInfo = modifierInfo;
  }

  public int getModifiers()
  {
    return getModifierInfo().getModifiers();
  }

  public void markStatic()
  {
    getModifierInfo().setModifiers( Modifier.setStatic( getModifiers(), true ) );
  }

  public boolean isAbstract()
  {
    return Modifier.isAbstract( getModifiers() );
  }

  public GosuClassTypeInfo getTypeInfo()
  {
    if( _typeInfo == null || hasAncestorBeenUpdated() )
    {
      TypeSystem.lock();
      try {
        if (_typeInfo == null || hasAncestorBeenUpdated() ) {
          IGosuClassInternal self = (IGosuClassInternal) getOrCreateTypeReference();
          _typeInfo = new GosuClassTypeInfo(self);
          if(self.getSuperClass() != null) {
            self.getSuperClass().getTypeInfo();
          }
          self.compileDeclarationsIfNeeded();
          _iTiChecksum = TypeSystem.getSingleRefreshChecksum();
        }
      } finally {
        TypeSystem.unlock();
      }
    }
    else
    {
      // To be certain we never return a stale typeinfo
      compileDeclarationsIfNeeded();
    }
    return _typeInfo;
  }

  public boolean hasAncestorBeenUpdated() {
    IGosuClassInternal genThis = (IGosuClassInternal) TypeLord.getPureGenericType( getOrCreateTypeReference() );
    return haveAncestorsBeenUpdated(this, genThis.getTypeInfoChecksum());
  }

  private boolean haveAncestorsBeenUpdated(IGosuClassInternal type, int tiCheckSum) {
    final IGosuClassInternal supertype = type.getSuperClass();
    if (supertype != null && hasBeenUpdated(supertype, tiCheckSum)) {
      return true;
    }
    for (IType anInterface : type.getInterfaces()) {
      if (anInterface instanceof IJavaTypeInternal) {
        final IJavaTypeInternal iFace = (IJavaTypeInternal) anInterface;
        if (JavaType.hasBeenUpdated(iFace, tiCheckSum, new HashSet<IType>())) {
          return true;
        };
      } else if (anInterface instanceof IGosuClassInternal) {
        if (hasBeenUpdated((IGosuClassInternal) anInterface, tiCheckSum)) {
          return true;
        };
      }
    }
    return false;
  }

  private boolean hasBeenUpdated(IGosuClassInternal type, int tiChecksum) {
    IGosuClassInternal genType = TypeLord.getPureGenericType( type );
    if (genType.isProxy()) {
      IJavaTypeInternal javaType = (IJavaTypeInternal)genType.getJavaType();
      return javaType.getTypeInfoChecksum() > tiChecksum || javaType.hasAncestorBeenUpdated();
    } else {
      return genType.getTypeInfoChecksum() > tiChecksum || haveAncestorsBeenUpdated(genType, tiChecksum);
    }
  }

  public void unloadTypeInfo()
  {
    if( _typeInfo != null )
    {
      _typeInfo.unload();
    }
    _valid.clear();
    if( _parameterizationByParamsName != null )
    {
      _parameterizationByParamsName.clear();
//## Note we can't just clear the tyep info because the type params could get stale e.g., _proxy_.Map<Foo, MyGosuClass>
//      for( GosuClass parameteredClass : _parameterizationByParamsName.values() )
//      {
//        parameteredClass.unloadTypeInfo();
//      }
    }
  }

  public Object readResolve() throws ObjectStreamException
  {
    try
    {
      String strProxyPrefix = PROXY_PREFIX + '.';
      if( getName().startsWith( strProxyPrefix ) )
      {
        String strJavaName = getName().substring( strProxyPrefix.length() );
        IJavaTypeInternal javaType = (IJavaTypeInternal)TypeSystem.getByFullName( strJavaName );
        return javaType.getAdapterClass();
      }
      int iIndex = getName().indexOf( '<' );
      if( iIndex > 0 )
      {
        String strGenName = getName().substring( 0, iIndex );
        IType type = TypeLoaderAccess.instance().getIntrinsicTypeByFullName( strGenName );
        return type.getParameterizedType( _typeParams );
      }
      else
      {
        return TypeLoaderAccess.instance().getIntrinsicTypeByFullName( getName() );
      }
    }
    catch( ClassNotFoundException e )
    {
      throw new InvalidClassException( e.getMessage() );
    }
  }

  public List<? extends ISourceRoot> getSourceRoots()
  {
    return Collections.singletonList( (ISourceRoot) getOrCreateTypeReference());
  }

  public String getId()
  {
    return getName();
  }

  public String getSource()
  {
    return _sourceFileHandle.getSource().getSource();
  }

  public FP64 getFingerprint()
  {
    FP64 fp64 = new FP64( getName().toLowerCase() );
    for( ISourceRoot sr : getSourceRoots() )
    {
      String source = sr.getSource();
      if( source != null )
      {
        fp64.extend( source );
      }
    }
    return fp64;
  }

  public boolean isStale()
  {
    // If this is a synthetic type off of a java proxy then don't refresh.
    return (_iMdChecksum != TypeSystem.getRefreshChecksum() && !isProxy());
  }

  public int getTypeInfoChecksum() {
    return _iTiChecksum;
  }

  public boolean shouldIndex()
  {
    return true;
  }

  public boolean isProxy()
  {
    return ProxyUtil.isProxy( this );
  }

  public void update( IScriptPartId partId, String strSource )
  {
    if( getSourceFileHandle() != null )
    {
      TypeSystem.refresh( getOrCreateTypeReference(), true);
    }
  }

  protected ITypeRef getOrCreateTypeReference()
  {
    if( _typeRef == null )
    {
      _typeRef = getOrCreateTypeReference( this );
    }
    ((AbstractTypeRef)_typeRef).verify(this);
    return _typeRef;
  }
  protected ITypeRef getOrCreateTypeReference( IType type )
  {
    return getTypeLoader().getModule().getModuleTypeLoader().getTypeRefFactory().create( type );
  }

  public boolean isSubClass( IType gsSubType )
  {
    if( gsSubType == null )
    {
      return false;
    }

    if( gsSubType instanceof IGosuClassInternal )
    {
      IGosuClassInternal gsSuperClass = TypeLord.getPureGenericType( ( (IGosuClassInternal)gsSubType).getSuperClass() );
      return gsSuperClass == TypeLord.getPureGenericType( getOrCreateTypeReference() ) ||
             isSubClass( gsSuperClass );
    }
    return false;
  }

  public boolean isCompiled()
  {
    return isDeclarationsCompiled() && isDefinitionsCompiled();
  }

  public List<DynamicFunctionSymbol> getConstructorFunctions()
  {
    compileDeclarationsIfNeeded();

    Map<CaseInsensitiveCharSequence, DynamicFunctionSymbol> ctors = getParseInfo().getConstructorFunctions();
    //noinspection unchecked
    return ctors.isEmpty() ? Collections.<VarStatement>emptyList() : getUnmodifiableValues( ctors );
  }

  private List getUnmodifiableValues( Map functionSymbolMap )
  {
    //noinspection unchecked
    return Collections.unmodifiableList(new ArrayList(functionSymbolMap.values()));
  }

  public DynamicFunctionSymbol getConstructorFunction( CaseInsensitiveCharSequence name )
  {
    compileDeclarationsIfNeeded();

    return getParseInfo().getConstructorFunctions().get(name);
  }

  public DynamicFunctionSymbol getDefaultConstructor()
  {
    compileDeclarationsIfNeeded();

    return getConstructorFunction(_defaultConstructorName);
  }

  public List<DynamicFunctionSymbol> getStaticFunctions()
  {
    compileDeclarationsIfNeeded();

    return Collections.unmodifiableList(getParseInfo().getStaticFunctions());
  }

  public Map<CharSequence, IGosuClassInternal> getInnerClassesMap()
  {
    compileHeaderIfNeeded();

    return _mapInnerClasses;
  }

  public List<IGosuClassInternal> getInnerClasses()
  {
    compileHeaderIfNeeded();

    return new ArrayList<IGosuClassInternal>(_mapInnerClasses.values());
  }

  public Map<CharSequence, ? extends IGosuClass> getKnownInnerClassesWithoutCompiling()
  {
    return _mapInnerClasses;
  }

  @Override
  public boolean isInstrumented()
  {
    return getBackingClass().isAnnotationPresent(Instrumented.class);
  }

  @Override
  public IGosuClass getBlock( int i )
  {
    return getBlocks().get(i);
  }

  public void addInnerClass( IGosuClassInternal innerGsClass )
  {
    innerGsClass.setCreateEditorParser(isCreateEditorParser());
    if( _mapInnerClasses == Collections.EMPTY_MAP )
    {
      _mapInnerClasses = new LinkedHashMap<CharSequence, IGosuClassInternal>( 2 );
    }
    // We put in types relative to ours.  I.e. if we are foo.SomeClass and we have an inner class named Bar, we put in Bar.
    _mapInnerClasses.put( innerGsClass.getName().substring( getName().length() + 1 ).intern(), innerGsClass );
  }

  public IType resolveRelativeInnerClass( String strRelativeInnerClassName, boolean bForce )
  {
    if( !bForce && (!getCompilationState().isHeaderCompiled() || getCompilationState().isReparsingHeader()) )
    {
      // A class' header cannot reference inner classes inside of itself
      ICompilableTypeInternal outerClass = getEnclosingType();
      // Force the outer class to resolve the name, since it must be header compiled (it may say its not, but only because it's still compiler its inner class' headers).
      return outerClass == null ? null : outerClass.resolveRelativeInnerClass( strRelativeInnerClassName, true );
    }

    // Now try to resolve the inner class name relative to this class and its enclosing class[s] and its hierarchy
    for( ICompilableTypeInternal outerClass = (ICompilableTypeInternal) getOrCreateTypeReference();
         outerClass != null;
         outerClass = outerClass.getEnclosingType() )
    {
      String strContainingType = outerClass.getName();
      if( !strRelativeInnerClassName.startsWith( strContainingType ) )
      {
        IType innerClass = outerClass.getInnerClass( strRelativeInnerClassName );
        if( innerClass != null )
        {
          return innerClass;
        }
        else
        {
          IType superType = outerClass.getSupertype();
          while( superType instanceof IGosuClass )
          {
            innerClass = ((IGosuClass)superType).resolveRelativeInnerClass( strRelativeInnerClassName, bForce );
            if( innerClass != null )
            {
              return innerClass;
            }
            superType = superType.getSupertype();
          }
        }
      }
    }
    return null;
  }

  @SuppressWarnings({"unchecked"})
  public List<DynamicFunctionSymbol> getMemberFunctions()
  {
    compileDeclarationsIfNeeded();

    Map<CaseInsensitiveCharSequence, DynamicFunctionSymbol> memberFunctions = getParseInfo().getMemberFunctions();
    return memberFunctions.isEmpty() ? Collections.<VarStatement>emptyList() : getUnmodifiableValues( memberFunctions );
  }

  public DynamicFunctionSymbol getMemberFunction( CaseInsensitiveCharSequence name )
  {
    return getParseInfo().getMemberFunctions().get(name);
  }

  public List<DynamicFunctionSymbol> getMemberFunctions( CaseInsensitiveCharSequence names )
  {
    Collection<DynamicFunctionSymbol> dfss = getParseInfo().getMemberFunctions().values();
    List<DynamicFunctionSymbol> returnDFSs = new ArrayList<DynamicFunctionSymbol>( );
    for( DynamicFunctionSymbol dfs : dfss )
    {
      if( names.equalsIgnoreCase( dfs.getDisplayName() ) )
      {
        returnDFSs.add( dfs );
      }
    }
    return returnDFSs;
  }

  public DynamicPropertySymbol getStaticProperty( CaseInsensitiveCharSequence name )
  {
    compileDeclarationsIfNeeded();

    for( DynamicPropertySymbol dps :getParseInfo().getStaticProperties() )
    {
      if( name.equalsIgnoreCase( dps.getName() ) )
      {
        return dps;
      }
    }
    return null;
  }

  public List<DynamicPropertySymbol> getStaticProperties()
  {
    compileDeclarationsIfNeeded();

    return Collections.unmodifiableList( getParseInfo().getStaticProperties() );
  }

  @SuppressWarnings({"unchecked"})
  public List<DynamicPropertySymbol> getMemberProperties()
  {
    compileDeclarationsIfNeeded();

    Map<CaseInsensitiveCharSequence, DynamicPropertySymbol> properties = getParseInfo().getMemberProperties();
    return properties.isEmpty() ? Collections.<VarStatement>emptyList() : getUnmodifiableValues( properties );
  }

  public DynamicPropertySymbol getMemberProperty( CaseInsensitiveCharSequence name )
  {
    return getParseInfo().getMemberProperties().get(name);
  }

  @SuppressWarnings({"unchecked"})
  public List<IVarStatement> getStaticFields()
  {
    compileDeclarationsIfNeeded();

    Map<CaseInsensitiveCharSequence, VarStatement> fields = getParseInfo().getStaticFields();
    return fields.isEmpty() ? Collections.<VarStatement>emptyList() : getUnmodifiableValues( fields );
  }

  public VarStatement getStaticField( CaseInsensitiveCharSequence name )
  {
    return getParseInfo().getStaticFields().get(name);
  }

  public Map<CharSequence, ISymbol> getMemberFieldIndexByName()
  {
    compileDeclarationsIfNeeded();

    return getParseInfo().getMemberFieldIndexByName();
  }

  @SuppressWarnings({"unchecked"})
  public List<IVarStatement> getMemberFields()
  {
    compileDeclarationsIfNeeded();

    Map<CaseInsensitiveCharSequence, VarStatement> fields = getParseInfo().getMemberFields();
    return fields.isEmpty() ? Collections.<VarStatement>emptyList() : getUnmodifiableValues( fields );
  }

  public Map<CaseInsensitiveCharSequence, VarStatement> getMemberFieldsMap()
  {
    compileDeclarationsIfNeeded();

    return getParseInfo().getMemberFields();
  }

  public Symbol getStaticThisSymbol()
  {
    compileDeclarationsIfNeeded();

    return getParseInfo().getStaticThisSymbol();
  }

  public Map<CaseInsensitiveCharSequence, ICapturedSymbol> getCapturedSymbols()
  {
    compileDefinitionsIfNeeded();

    return getParseInfo().getCapturedSymbols();
  }

  public ICapturedSymbol getCapturedSymbol( CaseInsensitiveCharSequence strName )
  {
    return getCapturedSymbols().get(strName);
  }

  public void addCapturedSymbol( ICapturedSymbol sym )
  {
    compileDefinitionsIfNeeded();

    getParseInfo().addCapturedSymbolSilent(sym);
  }

  @Override
  public Map<String, List> getRuntimeFeatureAnnotationMap()
  {
    try
    {
      return _featureAnnotationMap.get();
    }
    catch( Exception e )
    {
      throw GosuExceptionUtil.forceThrow( e );
    }
  }

  public void setManagedContext( IManagedContext managedCtx )
  {
    managedCtx.setParentManagedContext( _dbgContext );
    _dbgContext = managedCtx;
  }

  public boolean ensureDefaultConstructor( ISymbolTable symbolTable )
  {
    return !getConstructorFunctions().isEmpty() || getParseInfo().addDefaultConstructor(symbolTable);
  }

  /**
   * @return The ClassStatement (root ParsedElement) for this GosuClass<p>
   *         <b>NOTE:</b>  Because GosuClass is lazily compiled, the ClassStatement returned is
   *         NOT guaranteed to be fully compiled by this method, and may return only the
   *         declarations of the class.  If you wish to access the fully compiled ClassStatement
   *         you must call {@link #compileDefinitionsIfNeeded()}.
   */
  public ClassStatement getClassStatement()
  {
    compileDeclarationsIfNeeded();

    return getParseInfo().getClassStatement();
  }

  public ClassStatement getClassStatementWithoutCompile()
  {
    return getParseInfo().getClassStatement();
  }

  public String toString()
  {
    return getName();
  }

  public void setSuperType( IType superType )
  {
    _superType = superType;
  }

  public void setEnclosingType( IType enclosingType )
  {
    _enclosingType = enclosingType;
  }

  public IType getEnclosingTypeReference()
  {
    return _enclosingType;
  }

  public boolean isAnonymous()
  {
    return getRelativeName().startsWith( ANONYMOUS_PREFIX );
  }

  public int getDepth()
  {
    if( getEnclosingType() != null )
    {
      return getEnclosingType().getDepth() + 1;
    }
    return 0;
  }

  public void compileDefinitionsIfNeeded()
  {
    compileDefinitionsIfNeeded( false );
  }

  public void compileDefinitionsIfNeeded( boolean bForce )
  {
    boolean bHasError = false;
    
    if( !isDefinitionsCompiled() )
    {
      ((GosuClass)getPureGenericClass().dontEverCallThis())._hasError = null;

      if( isParameterizedType() )
      {
        getGenericType().compileDefinitionsIfNeeded();
        return;
      }

      if( getEnclosingType() != null )
      {
        //## todo: does this handle the case where an inner gosu class is embedded in a synthetic type (is that possible?)
        getEnclosingType().compileDefinitionsIfNeeded( bForce );
        return;
      }

      TypeSystem.lock();
      try
      {
        if( !isCompilingDefinitions() && !isDefinitionsCompiled() )
        {
          //noinspection CaughtExceptionImmediatelyRethrown
          try
          {
            compileDeclarationsIfNeeded();
            if( !isDeclarationsCompiled() ) {
            	return;
            }
          }
          catch( ErrantGosuClassException e )
          {
            if( !bForce )
            {
              throw e;
            }
            // Force = Continue even if there are decl errors; we want as much error info as possible
          }

          setCompilingDefinitions( true );
          try
          {
            if( shouldResolve() )
            {
              IGosuClassInternal gosuClass = (IGosuClassInternal) getOrCreateTypeReference();
              gosuClass.createNewParseInfo();
              GosuParser parser = makeParserForPhase();
              GosuClassParser classParser = new GosuClassParser( parser );
              classParser.parseDefinitions( gosuClass );
            }

            postAnalyze();

            if( getClassStatement().hasParseIssues() || hasParseIssuesInUsesStatements() )
            {
              updateParseResultsException();
            }
          }
          finally
          {
            setCompilingDefinitions( false );
          }
        }
      }
      finally
      {
        try
        {
          ((GosuClass)getPureGenericClass().dontEverCallThis())._hasError = null;
          bHasError = hasError();

//          _sourceFileHandle.cleanAfterCompile();
          if( getEditorParser() == null ) //|| (!hasError() && !hasWarnings()) )
          {
            getParseInfo().maybeClearDebugInfo();
//            IClassStatement classStmt = getClassStatementWithoutCompile();
//            dropParseInfo();
          }
        }
        finally
        {
          TypeSystem.unlock();
        }
      }
    }

    if( bHasError )
    {
      throw new ErrantGosuClassException( (IGosuClass) getOrCreateTypeReference() );
    }
  }

  private void postAnalyze()
  {
    if( !isAnonymous()  )
    {
      assert getEnclosingType() == null : "Should only analyze from top-level";

      PostCompilationAnalysis.maybeAnalyze( getClassStatement().getClassFileStatement(), getClassStatement() );
    }
  }

  private boolean hasParseIssuesInUsesStatements()
  {
    if( (getTypeUsesMap() != null) &&
        (getTypeUsesMap().getUsesStatements() != null) )
    {
      for( IUsesStatement usesStatement : getTypeUsesMap().getUsesStatements() )
      {
        if( usesStatement.hasParseIssues() )
        {
          return true;
        }
      }
    }
    return false;
  }

  @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"})
  private void updateParseResultsException()
  {
    if( getParseResultsException() == null )
    {
      //noinspection ThrowableInstanceNeverThrown
      setParseResultsException( new ParseResultsException( getClassStatement().getClassFileStatement() ) );
    }
    else
    {
      Statement classFileStmt = (Statement)getParseResultsException().getParsedElement();
      if( classFileStmt == null || !classFileStmt.hasParseExceptions() )
      {
        // Update the ClassFileStmt in with the definition-compiled version only if
        // the decl-compiled version does not have errors
        classFileStmt = getParseInfo().getClassFileStatement();
      }
      getParseResultsException().reset(classFileStmt);
    }
  }

  public void compileDeclarationsIfNeeded()
  {
    if( shouldCompileDeclarations() )
    {
      if( isParameterizedType() )
      {
        getGenericType().compileDeclarationsIfNeeded();
        return;
      }

      if( getEnclosingType() != null )
      {
        getEnclosingType().compileDeclarationsIfNeeded();
        return;
      }

      TypeSystem.lock();
      try
      {
        if( !isCompilingHeader() && !isCompilingDeclarations() && shouldCompileDeclarations() )
        {
          try
          {
            compileHeaderIfNeeded();
          }
          catch( ErrantGosuClassException e )
          {
            // Ignore
          }

          setCompilingDeclarations( true );
          try
          {
            if( !isDeclarationsCompiled() )
            {
              IGosuClassInternal gosuClass = (IGosuClassInternal) getOrCreateTypeReference();
              gosuClass.createNewParseInfo();
              GosuParser parser = makeParserForPhase();
              GosuClassParser classParser = new GosuClassParser( parser );
              classParser.parseDeclarations(gosuClass);
            }
            getParseInfo().maybeClearDebugInfo();

            forceTypeInfoInitialization();
          }
          finally
          {
            setCompilingDeclarations( false );
          }
        }
      }
      finally
      {
        TypeSystem.unlock();
      }
    }
  }

  public void forceTypeInfoInitialization()
  {
    if( !isDeclarationsCompiled() )
    {
      return;
    }

    if( !getClassStatement().hasParseExceptions() )
    {
      getTypeInfo().forceInit();

      for( IGosuClass innerGsClass : getKnownInnerClassesWithoutCompiling().values() )
      {
        if( innerGsClass.isDeclarationsCompiled() )
        {
          ((IGosuClassInternal)innerGsClass).forceTypeInfoInitialization();
        }
      }
    }
  }

  public void syncGenericAndParameterizedClasses()
  {
    if( isParameterizedType() )
    {
      copyGenericState( true );
    }
    else if( isGenericType() )
    {
      if( _parameterizationByParamsName != null )
      {
        // Make a copy to guard against concurrent modification
        Collection<IGosuClassInternal> values = _parameterizationByParamsName.values();
        IGosuClassInternal[] gosuClasses = values.toArray( new IGosuClassInternal[values.size()] );
        for( IGosuClassInternal gosuClass : gosuClasses )
        {
          gosuClass.copyGenericState( true );
        }
      }
    }
  }

  public Collection<IGosuClassInternal> getParameterizedTypes() {
    return _parameterizationByParamsName == null ? Collections.<IGosuClassInternal>emptyList() : _parameterizationByParamsName.values();
  }

  @Override
  public void setAnnotations(List<IGosuAnnotation> annotations) {
    filterClassAnnotations(annotations);
    getModifierInfo().setAnnotations(annotations);
  }

  public static void filterClassAnnotations( List<IGosuAnnotation> annotations )
  {
    for( Iterator<IGosuAnnotation> it = annotations.iterator(); it.hasNext(); )
    {
      IGosuAnnotation annotation = it.next();
      if( annotation instanceof GosuDocAnnotation)
      {
        if( JavaTypes.THROWS().equals( annotation.getType() ) ||
            JavaTypes.getGosuType( Returns.class ).equals( annotation.getType() ) ||
            JavaTypes.PARAM().equals( annotation.getType() ) )
        {
          it.remove();
        }
      }
    }
  }

  @Override
  public IType getEnclosingNonBlockType()
  {
    ICompilableTypeInternal type =  getEnclosingType();
    while( type instanceof IBlockClassInternal )
    {
      type = type.getEnclosingType();
    }
    return type;
  }

  public void compileHeaderIfNeeded()
  {
    if( !shouldCompileHeader() )
    {
      return;
    }

    if( isParameterizedType() )
    {
      getGenericType().compileHeaderIfNeeded();
      return;
    }

    if( getEnclosingType() != null )
    {
      getEnclosingType().compileHeaderIfNeeded();

      IGosuClassInternal gosuClass = (IGosuClassInternal) getOrCreateTypeReference();
      gosuClass.createNewParseInfo();

      return;
    }

    TypeSystem.lock();
    try
    {
      if( shouldCompileHeader() )
      {
        setCompilingHeader( true );
        try
        {
          GosuClassCompilingStack.pushCompilingType( getOrCreateTypeReference() );
          try
          {
            if( getSuperClass() != null )
            {
              try
              {
                getSuperClass().compileHeaderIfNeeded();
              }
              catch( ErrantGosuClassException e )
              {
                // Super type has an error, but we don't care so long as we can compile
              }
            }
          }
          finally
          {
            GosuClassCompilingStack.popCompilingType();
          }

          if( !isHeaderCompiled() )
          {
            IGosuClassInternal gosuClass = (IGosuClassInternal) getOrCreateTypeReference();
            gosuClass.createNewParseInfo();
            GosuParser parser = makeParserForPhase();
            GosuClassParser classParser = new GosuClassParser( parser );
            classParser.parseHeader( gosuClass, false, false, false );
          }
        }
        finally
        {
          setCompilingHeader( false );
        }
      }
    }
    finally
    {
      TypeSystem.unlock();
    }
  }

  private boolean shouldCompileDeclarations()
  {
    return !isDeclarationsCompiled() || !_compilationState.isInnerDeclarationsCompiled();
  }

  private boolean shouldCompileHeader()
  {
    return !isCompilingHeader() && !isHeaderCompiled();
  }

  public CompilationState getCompilationState()
  {
    return _compilationState;
  }

  public boolean isCompilingHeader()
  {
    return getCompilationState().isCompilingHeader();
  }

  public void setCompilingHeader( boolean bCompilingHeader )
  {
    getCompilationState().setCompilingHeader( bCompilingHeader );
  }

  public boolean isHeaderCompiled()
  {
    return getCompilationState().isHeaderCompiled();
  }

  public void setHeaderCompiled()
  {
    getCompilationState().setHeaderCompiled();
  }

  public boolean isCompilingDeclarations()
  {
    return getCompilationState().isCompilingDeclarations();
  }

  public void setCompilingDeclarations( boolean bCompilingDeclarations )
  {
    getCompilationState().setCompilingDeclarations( bCompilingDeclarations );
  }

  public boolean isDeclarationsCompiled()
  {
    return getCompilationState().isDeclarationsCompiled();
  }

  public boolean isInnerDeclarationsCompiled()
  {
    return getCompilationState().isInnerDeclarationsCompiled();
  }

  public void setDeclarationsCompiled()
  {
    getCompilationState().setDeclarationsCompiled();
  }

  public void setInnerDeclarationsCompiled()
  {
    getCompilationState().setInnerDeclarationsCompiled();
  }

  @Override
  public boolean isCompilingDefinitions()
  {
    return getCompilationState().isCompilingDefinitions();
  }

  @Override
  public boolean shouldFullyCompileAnnotations()
  {
    return getCompilationState().isCompilingDefinitions();
  }

  public void setCompilingDefinitions( boolean bCompilingDefinitions )
  {
    getCompilationState().setCompilingDefinitions( bCompilingDefinitions );
  }

  public boolean isDefinitionsCompiled()
  {
    return getCompilationState().isDefinitionsCompiled();
  }

  public void setDefinitionsCompiled()
  {
    getCompilationState().setDefinitionsCompiled();
  }

  public ISourceFileHandle getSourceFileHandle()
  {
    return _sourceFileHandle;
  }

  @Override
  public List<IType> getLoadedInnerClasses() {
    if( isDeclarationsCompiled() )
    {
      Collection<? extends IGosuClass> innerClasses = getKnownInnerClassesWithoutCompiling().values();
      return new ArrayList<IType>(innerClasses);
    }
    else
    {
      return Collections.EMPTY_LIST;
    }
  }

  @Override
  public ClassType getClassType() {
    return ClassType.Class;
  }

  @Override
  public List<StatementList> getAnnotationInitialization()
  {
    return getAnnotationInitialization( false );
  }
  public List<StatementList> getAnnotationInitialization( boolean bClearOnly )
  {
    return AnnotationBuilder.buildAnnotationInitMethodBody((ICompilableTypeInternal) getOrCreateTypeReference(), bClearOnly );
  }

  public List<? extends IGosuAnnotation> getGosuAnnotations()
  {
    return getModifierInfo().getAnnotations();
  }

  /**
   * When changing the places from which this method is called run pc's
   * gw.smoketest.pc.job.common.effectivetime.VisibleEffectiveTimeTest
   * cause it will break!
   */
  public GosuClassParseInfo createNewParseInfo() {
    if( _parseInfo == null ) {
      _parseInfo = new GosuClassParseInfo((IGosuClassInternal) getOrCreateTypeReference());
    }
    return _parseInfo;
  }

  @Override
  public void validateAndReturnParseTree() {
    _compilationState = new CompilationState();
    compileDeclarationsIfNeeded();
  }

  public boolean isTestClass()
  {
    return _sourceFileHandle.isTestClass();
  }

  public IGosuClassInternal getInnerClass( CharSequence relativeName )
  {
    String strRelativeName = relativeName == null ? null : relativeName.toString();
    if( strRelativeName != null && strRelativeName.startsWith( ANONYMOUS_PREFIX ) )
    {
      try
      {
        compileDefinitionsIfNeeded();
      }
      catch( ErrantGosuClassException e )
      {
        // gulp
      }
    }

    int dotIndex = strRelativeName == null ? -1 : strRelativeName.indexOf( '.' );
    IGosuClassInternal innerClass;
    if( dotIndex == -1 )
    {
      innerClass = getInnerClassesMap().get( strRelativeName );
    }
    else
    {
      innerClass = getInnerClassesMap().get( strRelativeName.subSequence( 0, dotIndex ).toString() );
      if( innerClass != null )
      {
        innerClass = (IGosuClassInternal)innerClass.getInnerClass( strRelativeName.subSequence( dotIndex + 1, strRelativeName.length() ).toString() );
      }
    }

    return innerClass;
  }

  public VarStatement getMemberField( CaseInsensitiveCharSequence charSequence )
  {
    return getParseInfo().getMemberFields().get(charSequence);
  }

  public boolean shouldKeepDebugInfo()
  {
    return !isProxy();
  }

  public IGosuClassInternal getSuperClass()
  {
    compileHeaderIfNeeded();

    IType superType = getSupertype();
    if( superType instanceof IGosuClassInternal )
    {
      return (IGosuClassInternal)superType;
    }
    else if( superType instanceof IJavaType)
    {
      return Util.getGosuClassFrom( superType );
    }
    return null;
  }

  public ICompilableTypeInternal getEnclosingType()
  {
    // Do NOT compile decls here. It's not needed and it'll break inner class compilation.
    return (_enclosingType == null || (_enclosingType instanceof ErrorType))
           ? null
           : (ICompilableTypeInternal)_enclosingType;
  }

  @Override
  public boolean hasBackingClass()
  {
    return _javaClass != null || (_proxiedJavaClassInGosuProxy != null && !_proxiedJavaClassInGosuProxy.getBackingClassInfo().isAnnotation());
  }
  @Override
  public void unloadBackingClass()
  {
    if( _javaClass != null )
    {
      //## todo: probably grab type sys lock here (double null check)
      ((IGosuClassLoader)_javaClass.getClassLoader()).dispose( _javaClass );
      _javaClass = null;
    }
  }
  @Override
  public Class<?> getBackingClass()
  {
    if( isParameterizedType() )
    {
      return getGenericType().getBackingClass();
    }

    if( _proxiedJavaClassInGosuProxy != null &&
        !_proxiedJavaClassInGosuProxy.getBackingClassInfo().isAnnotation() )
    {
      // If this is a Gosu proxy, return the underlying java class
      return _proxiedJavaClassInGosuProxy.getIntrinsicClass();
    }

    Class clazz = _javaClass;
    if( clazz == null )
    {
      TypeSystem.lock();
      try
      {
        clazz = _javaClass;
        if( clazz == null )
        {
          clazz = GosuClassLoader.instance().defineClass( (IGosuClassInternal)getOrCreateTypeReference(), defineInSingleServingClassLoader() );
          _javaClass = clazz;
        }
      }
      catch( ClassNotFoundException e )
      {
        throw GosuExceptionUtil.forceThrow( e );
      }
      finally
      {
        TypeSystem.unlock();
      }
    }
    return clazz;
  }

  /**
   * @return true if this class should be defined in a single-serving class loader (e.g. Programs)
   */
  protected boolean defineInSingleServingClassLoader()
  {
    return false;
  }

  @Override
  public ISymbol getExternalSymbol( String strName )
  {
    if( getEnclosingType() != null )
    {
      return getEnclosingType().getExternalSymbol( strName );
    }
    return null;
  }

  public void putClassMembers( GosuParser owner, ISymbolTable table, IGosuClassInternal gsContextClass, boolean bStatic )
  {
    putClassMembers( getTypeLoader(), owner, table, gsContextClass, bStatic );
  }
  public void putClassMembers( GosuClassTypeLoader loader, GosuParser owner, ISymbolTable table, IGosuClassInternal gsContextClass, boolean bStatic )
  {
    // Visit interfaces first so that concrete impls stamp over them,
    // then visit super classes so that subclass members stamp over super ones.

    //noinspection ForLoopReplaceableByForEach
    compileDeclarationsIfNeeded();
    for( int i = 0; i < _interfaces.size(); i++ )
    {
      IType type = _interfaces.get( i );
      if( !(type instanceof ErrorType) )
      {
        IGosuClassInternal gsClass = Util.getGosuClassFrom( type );
        if( gsClass != null )
        {
          gsClass.putClassMembers( owner, table, gsContextClass, bStatic );
        }
      }
    }
    if( getSuperClass() != null )
    {
      getSuperClass().putClassMembers( owner, table, gsContextClass, bStatic );
      if( getSuperClass().isProxy() )
      {
        addJavaEnhancements( owner, table, gsContextClass, bStatic, getSuperClass().getJavaType() );
      }
    }
    if( getEnclosingType() instanceof IGosuClassInternal &&
        ((IGosuClassInternal)getEnclosingType()).isHeaderCompiled() )
    {
      getEnclosingType().putClassMembers( loader, owner, table, gsContextClass, bStatic || isStatic() );
    }

    putEnhancements( owner, table, gsContextClass, bStatic, loader.getModule() );

    boolean bSuperClass = gsContextClass != getOrCreateTypeReference();

    putStaticFields( table, gsContextClass, bSuperClass );
    putStaticFunctions( owner, table, gsContextClass, bSuperClass );
    putStaticProperties( table, gsContextClass, bSuperClass );
    if( !bStatic )
    {
      putFields( table, gsContextClass, bSuperClass );
      putFunctions( owner, table, gsContextClass, bSuperClass );
      putProperties( table, gsContextClass, bSuperClass );
      putConstructors( owner, table, bSuperClass );
    }
  }

  //TODO-dp this method may be wrong due to an integration issue
  private void addJavaEnhancements( GosuParser owner, ISymbolTable table, IGosuClassInternal gsContextClass, boolean bStatic, IJavaType type )
  {
    IType superType = type.getSupertype();
    if( superType != null )
    {
      addJavaEnhancements( owner, table, gsContextClass, bStatic, (IJavaType)superType );
    }
    for( IType iface : type.getInterfaces() )
    {
      addJavaEnhancements( owner, table, gsContextClass, bStatic, (IJavaType)iface );
    }
    for( IGosuEnhancement enhancement : getTypeLoader().getEnhancementIndex().getEnhancementsForType(type) )
    {
      ((IGosuEnhancementInternal)enhancement).putClassMembers( owner, table, gsContextClass, bStatic );
    }
  }

  private void putEnhancements( GosuParser owner, ISymbolTable table, IGosuClassInternal gsContextClass, boolean bStatic, IModule module)
  {
    IModule[] moduleTraversalList = module.getModuleTraversalList();
    for (IModule m : moduleTraversalList) {
      GosuClassTypeLoader loader = GosuClassTypeLoader.getDefaultClassLoader(m);
      if( loader != null ) {
        for( IGosuEnhancement enhancement : loader.getEnhancementIndex().getEnhancementsForType(getOrCreateTypeReference()) ) {
          ((IGosuEnhancementInternal)enhancement).putClassMembers( loader, owner, table, gsContextClass, bStatic );
        }
      }
    }
  }
  
  private void putFunctions( GosuParser owner, ISymbolTable table, IGosuClassInternal gsContextClass, boolean bSuperClass )
  {
    for( DynamicFunctionSymbol dfs : getMemberFunctions() )
    {
      if( !bSuperClass || isAccessible( gsContextClass, dfs ) )
      {
        if( isParameterizedType() )
        {
          dfs = dfs.getParameterizedVersion( (IGosuClass) getOrCreateTypeReference());
        }
        table.putSymbol( dfs );
        if( owner != null )
        {
          owner.putDfsDeclInSetByName( dfs );
        }
      }
    }
  }

  private void putProperties( ISymbolTable table, IGosuClassInternal gsContextClass, boolean bSuperClass )
  {
    for( DynamicPropertySymbol dps : getMemberProperties() )
    {
      if( !bSuperClass || isAccessible( gsContextClass, dps ) )
      {
        if( isParameterizedType() )
        {
          dps = dps.getParameterizedVersion( (IGosuClass) getOrCreateTypeReference());
        }
        table.putSymbol( dps );
      }
    }
  }

  private void putConstructors( GosuParser owner, ISymbolTable table, boolean bSuperClass )
  {
    for( DynamicFunctionSymbol dfs : getConstructorFunctions() )
    {
      if( !bSuperClass )
      {
        if( isParameterizedType() )
        {
          dfs = dfs.getParameterizedVersion( (IGosuClass) getOrCreateTypeReference());
        }
        table.putSymbol( dfs );
        if( owner != null )
        {
          owner.putDfsDeclInSetByName( dfs );
        }
      }
    }
  }

  private void putStaticFunctions( GosuParser owner, ISymbolTable table, IGosuClassInternal gsContextClass, boolean bSuperClass )
  {
    List<DynamicFunctionSymbol> staticFunctions = getStaticFunctions();
    for (int i = 0; i < staticFunctions.size(); i++) {
      DynamicFunctionSymbol dfs = staticFunctions.get(i);
      if( !bSuperClass || isAccessible( gsContextClass, dfs ) )
      {
        if( isParameterizedType() )
        {
          dfs = dfs.getParameterizedVersion( (IGosuClass) getOrCreateTypeReference());
        }
        table.putSymbol( dfs );
        if( owner != null )
        {
          owner.putDfsDeclInSetByName( dfs );
        }
      }
    }
  }

  private void putStaticProperties( ISymbolTable table, IGosuClassInternal gsContextClass, boolean bSuperClass )
  {
    List<DynamicPropertySymbol> staticProperties = getStaticProperties();
    for (int i = 0; i < staticProperties.size(); i++) {
      DynamicPropertySymbol dps = staticProperties.get(i);
      if( !bSuperClass || isAccessible( gsContextClass, dps ) )
      {
        if( isParameterizedType() )
        {
          dps = dps.getParameterizedVersion( (IGosuClass) getOrCreateTypeReference());
        }
        table.putSymbol( dps );
      }
    }
  }

  private void putStaticFields( ISymbolTable table, IGosuClassInternal gsContextClass, boolean bSuperClass )
  {
    for( IVarStatement varStmt : getStaticFields() )
    {
      if( !bSuperClass || isAccessible( gsContextClass, varStmt ) )
      {
        table.putSymbol( varStmt.getSymbol() );
      }
    }
  }

  private void putFields( ISymbolTable table, IGosuClassInternal gsContextClass, boolean bSuperClass )
  {
    for( IVarStatement varStmt : getMemberFields() )
    {
      if( !bSuperClass || isAccessible( gsContextClass, varStmt ) )
      {
        ISymbol symbol = varStmt.getSymbol();
        if( isParameterizedType() && symbol instanceof AbstractDynamicSymbol )
        {
          symbol = ((AbstractDynamicSymbol)symbol).getParameterizedVersion( (IGosuClass) getOrCreateTypeReference());
        }
        table.putSymbol( symbol );
      }
    }
  }

  public boolean isAccessible( IGosuClassInternal compilingClass, AbstractDynamicSymbol ads )
  {
    return isInEnclosingTypeChain( compilingClass ) ||
           ads.isPublic() ||
           ads.isProtected() ||
           (ads.isInternal() && getNamespace().equalsIgnoreCase( compilingClass.getNamespace() ));
  }

  private boolean isAccessible( IGosuClassInternal compilingClass, IVarStatement varStmt )
  {
    return isInEnclosingTypeChain( compilingClass ) ||
           varStmt.isPublic() ||
           varStmt.isProtected() ||
           (varStmt.isInternal() && getNamespace().equalsIgnoreCase( compilingClass.getNamespace() ));
  }

  private boolean isInEnclosingTypeChain( ICompilableTypeInternal gosuClass )
  {
    ICompilableTypeInternal csr = gosuClass;
    IType thisRef = getOrCreateTypeReference();
    if( thisRef.getGenericType() != null )
    {
      thisRef = thisRef.getGenericType();
    }
    while( csr != thisRef && csr != null )
    {
      csr = csr.getEnclosingType();
    }
    return csr == thisRef;
  }

  public void setParseResultsException( ParseResultsException pe )
  {
    getParseInfo().setParseResultsException(pe);
    _valid.clear();
  }

  public boolean hasError()
  {
    Boolean hasError = _hasError != null ? _hasError : ((GosuClass)getPureGenericClass().dontEverCallThis())._hasError;
    if( !isCompilingDefinitions() && hasError != null )
    {
      return hasError;
    }

    TypeSystem.lock();

    try {
      compileDeclarationsIfNeeded();
    } finally {
      TypeSystem.unlock();
    }

    ParseResultsException pe = getParseResultsException();
    return ((GosuClass)getPureGenericClass().dontEverCallThis())._hasError = (pe != null && pe.hasParseExceptions()) ? Boolean.TRUE : Boolean.FALSE;
  }

  public boolean hasWarnings() {
    ParseResultsException pe = getParseResultsException();
    return pe != null && pe.hasParseWarnings();
  }

  private IGosuClassInternal getPureGenericClass()
  {
    return (IGosuClassInternal)TypeLord.getPureGenericType(getOrCreateTypeReference());
  }

  public ParseResultsException getParseResultsException()
  {
    return getParseInfo() == null ? null : getParseInfo().getParseResultsException();
  }

  public int compareTo( Object o )
  {
    return getName().compareTo(((IType) o).getName());
  }

  public boolean shouldResolve()
  {
    return !isCompiled() && isDeclarationsCompiled();
  }

  private GosuParser makeParserForPhase()
  {
    CompiledGosuClassSymbolTable symbolTable = CompiledGosuClassSymbolTable.instance();
    GosuParser parser = getParser( symbolTable );
    parser.setScript( _sourceFileHandle.getSource() );
    assignTypeUsesMap( parser );
    return parser;
  }

  public ITypeUsesMap getTypeUsesMap() {
    return _typeUsesMap;
  }

  public void setTypeUsesMap( ITypeUsesMap usesMap )
  {
    _typeUsesMap = usesMap;
  }

  public void assignTypeUsesMap( GosuParser parser )
  {
    if( _typeUsesMap != null )
    {
      parser.setTypeUsesMap(_typeUsesMap);
    }
    else
    {
      ICompilableTypeInternal gsOuter = getEnclosingType();
      if( gsOuter != null )
      {
        gsOuter.assignTypeUsesMap( parser );
      }
      else if( _strNamespace != null )
      {
        parser.getTypeUsesMap().addToTypeUses( _strNamespace + ".*" );
      }
    }
  }

  /**
   * !! Used only when compiling from an edtior !!
   */
  public void setEditorParser( GosuParser parser )
  {
    _parser = parser;
  }
  public void setCreateEditorParser(boolean bEditorParser) {
    _bEditorParser = bEditorParser;
  }
  public boolean isCreateEditorParser() {
    return _bEditorParser;
  }

  @Override
  public int getAnonymousInnerClassCount()
  {
    Collection<? extends IGosuClass> innerClasses = getKnownInnerClassesWithoutCompiling().values();
    int iCount = 0;
    for( IGosuClass innerClass : innerClasses )
    {
      if( innerClass.isAnonymous() )
      {
        iCount++;
      }
    }
    return iCount;
  }

  /**
   * !! The parser is only set when this class is parsed in an editor in an IDE !!
   */
  public GosuParser getEditorParser()
  {
    return _parser;
  }

  protected GosuParser getParser( CompiledGosuClassSymbolTable symbolTable )
  {
    if( _parser != null )
    {
      return _parser;
    }
    else
    {
      GosuParser parser = (GosuParser)GosuParserFactory.createParser( symbolTable, ScriptabilityModifiers.SCRIPTABLE );
      parser.pushScriptPart( new ScriptPartId( getOrCreateTypeReference(), null ) );
      ICompilableTypeInternal enclosingType = getEnclosingType();
      if( enclosingType != null ) {
        if (enclosingType.getEditorParser() != null) {
          parser.setThrowParseExceptionForWarnings( enclosingType.getEditorParser().isThrowParseResultsExceptionForWarnings() );
          parser.setWarnOnCaseIssue(enclosingType.getEditorParser().isThrowParseResultsExceptionForWarnings());
          parser.setEditorParser(enclosingType.getEditorParser().isThrowParseResultsExceptionForWarnings());
        } else {
          parser.setThrowParseExceptionForWarnings(enclosingType.isCreateEditorParser());
          parser.setWarnOnCaseIssue(enclosingType.isCreateEditorParser());
          parser.setEditorParser(enclosingType.isCreateEditorParser());
        }
      } else {
        parser.setThrowParseExceptionForWarnings(_bEditorParser);
        parser.setWarnOnCaseIssue(_bEditorParser);
        parser.setEditorParser(_bEditorParser);
      }
      return parser;
    }
  }

  public IManagedContext getManagedContext()
  {
    compileDefinitionsIfNeeded();
    return _dbgContext != null
           ? _dbgContext
           : getEnclosingType() != null && getEnclosingType() instanceof IGosuClass
             ? ((IGosuClass)getEnclosingType()).getManagedContext()
             : null;
  }

  public void addDelegateImpls( ISymbolTable symTable, GosuClassParser parser )
  {
    for( VarStatement varStmt : getMemberFieldsMap().values() )
    {
      if( varStmt instanceof DelegateStatement )
      {
        DelegateStatement delegateStmt = (DelegateStatement)varStmt;
        List<IFunctionType> unimpled = new ArrayList<IFunctionType>();
        IGosuClassInternal pThis = (IGosuClassInternal) getOrCreateTypeReference();
        for( IType iface : delegateStmt.getConstituents() )
        {
          IGosuClassInternal gsInterface = Util.getGosuClassFrom( iface );
          if( gsInterface != null )
          {
            unimpled = getUnimplementedMethods( gsInterface, pThis, unimpled, true );
            for( IMethodInfo mi : gsInterface.getTypeInfo().getMethods() )
            {
              GosuMethodInfo gmi = (GosuMethodInfo)mi;
              IFunctionType type = new FunctionType( gmi );
              if( unimpled.contains( type ) )
              {
                ReducedDynamicFunctionSymbol dfs = gmi.getDfs();

                mi = (IMethodInfo)dfs.getMethodOrConstructorInfo();
                if( !(iface instanceof IGosuClass) )
                {
                  String strMethodName = mi.getDisplayName();
                   mi = iface.getTypeInfo().getMethod( strMethodName, ((IFunctionType)dfs.getType()).getParameterTypes() );
                  if( mi == null )
                  {
                    if( dfs.getDisplayName().startsWith( "@" ) )
                    {
                      mi = iface.getTypeInfo().getMethod( "get" + strMethodName.substring( 1 ) );
                      if( mi == null )
                      {
                        mi = iface.getTypeInfo().getMethod( "is" + strMethodName.substring( 1 ) );
                      }
                    }
                    if( mi == null )
                    {
                      mi = iface.getTypeInfo().getCallableMethod( strMethodName, ((IFunctionType)dfs.getType()).getParameterTypes() );
                    }
                    if( mi == null )
                    {
                      throw new IllegalStateException( "Did not find method info for: " + dfs.getMethodOrConstructorInfo() );
                    }
                  }
                }
                DelegateFunctionSymbol delegateFs = new DelegateFunctionSymbol( pThis, symTable, dfs, mi, delegateStmt );
                if( delegateFs.getName().startsWith( "@" ) )
                {
                  // Handle case where the dfs is a property getter or setter
                  DynamicPropertySymbol dps = parser.getOrCreateDynamicPropertySymbol(
                    getParseInfo().getClassStatement(), pThis, delegateFs, delegateFs.getReturnType() != GosuParserTypes.NULL_TYPE() );
                  parser.processPropertySymbol( dps, pThis );
                }
                else
                {
                  parser.processFunctionSymbol( delegateFs, pThis );
                }
              }
            }
          }
        }
      }
    }
  }

  public List<IFunctionType> getUnimplementedMethods()
  {
    List<IFunctionType> emptyList = Collections.emptyList();
    if( isAbstract() )
    {
      return emptyList;
    }
    return getUnimplementedMethods( emptyList, (IGosuClassInternal) getOrCreateTypeReference());
  }

  public List<IFunctionType> getUnimplementedMethods( List<IFunctionType> unimpled, IGosuClassInternal implClass )
  {
    for( IType iface : _interfaces )
    {
      if( iface instanceof ErrorType )
      {
        continue;
      }

      // Ignore the IEnumValue interface on enums; all the associated functions are implemented
      // by the compiler
      if( isEnum() && iface == JavaTypes.getGosuType( IEnumValue.class ) )
      {
        continue;
      }

      if( iface == JavaTypes.IPROGRAM_INSTANCE() )
      {
        continue;
      }

      iface = Util.getGosuClassFrom( iface );
      if( iface != null )
      {
        unimpled = getUnimplementedMethods( (IGosuClassInternal)iface, implClass, unimpled, true );
      }
    }
    IType superType = getSupertype();
    if( superType != null && superType.isAbstract() )
    {
      IGosuClassInternal gsSuper = Util.getGosuClassFrom( superType );
      if (gsSuper != null) {
        unimpled = getUnimplementedMethods( gsSuper, implClass, unimpled, false );
        unimpled = gsSuper.getUnimplementedMethods( unimpled, implClass );
      }
    }
    return unimpled;
  }

  private List<IFunctionType> getUnimplementedMethods( IGosuClassInternal gsIface, IGosuClass implClass, List<IFunctionType> unimpled, boolean ensurePublic )
  {
    if( gsIface.isGenericType() && !gsIface.isParameterizedType() )
    {
      throw new IllegalStateException( "Expecting a regular or parameterized interface." );
    }

    for( IMethodInfo mi : gsIface.getTypeInfo().getMethods( gsIface ) )
    {
      if( !mi.isAbstract() )
      {
        continue;
      }
      if( mi.getOwnersType() == IGosuClassInternal.Util.getGosuClassFrom( JavaTypes.IGOSU_OBJECT() ) )
      {
        continue;
      }
      IFunctionType ifaceFuncType = new FunctionType( mi );
      CaseInsensitiveCharSequence signature = ifaceFuncType.getParamSignatureForCurrentModule();
      DynamicFunctionSymbol implDfs = getImplDfs( (IGosuClassInternal)implClass, signature );
      if( (implDfs == null || !isAssignable( implDfs, ifaceFuncType ) || (ensurePublic && !implDfs.isPublic()) ) && !isObjectMethod( mi ) )
      {
        if( !handleParameterizedDfs( implClass, mi, ifaceFuncType ) )
        {
          if( unimpled.isEmpty() )
          {
            unimpled = new ArrayList<IFunctionType>( 8 );
          }
          // System.out.println("UNIMPLED: " + ifaceFuncType.getParamSignature() + " from " + gsIface.getName() + " in impl class " + implClass.getName() );
          unimpled.add( ifaceFuncType );
        }
      }
    }
    return unimpled;
  }

  private boolean handleParameterizedDfs( IGosuClass implClass, IMethodInfo mi, IFunctionType ifaceFuncType )
  {
    CaseInsensitiveCharSequence signature;
    DynamicFunctionSymbol implDfs;
    if( mi instanceof GosuMethodInfo )
    {
      ReducedDynamicFunctionSymbol miDfs = ((GosuMethodInfo)mi).getDfs();
      if( miDfs != null )
      {
        while( miDfs instanceof ReducedParameterizedDynamicFunctionSymbol )
        {
          signature = ((FunctionType)miDfs.getBackingDfs().getType()).getParamSignatureForCurrentModule();
          implDfs = getImplDfs( (IGosuClassInternal)implClass, signature );
          if( implDfs == null || !isAssignable( implDfs, ifaceFuncType ) )
          {
            miDfs = (ReducedDynamicFunctionSymbol) miDfs.getBackingDfs();
          }
          else
          {
            return true;
          }
        }
      }
    }
    return false;
  }

  private boolean isAssignable( DynamicFunctionSymbol implDfs, IFunctionType ifaceFuncType )
  {
    if( ifaceFuncType.isAssignableFrom( implDfs.getType() ) )
    {
      return true;
    }
    DynamicFunctionSymbol superDfs = implDfs.getSuperDfs();
    if( superDfs != null && isAssignable( superDfs, ifaceFuncType ) )
    {
      return true;
    }
    DynamicFunctionSymbol delegateDfs = (DynamicFunctionSymbol) implDfs.getBackingDfs();
    //noinspection RedundantIfStatement
    if( delegateDfs != null && delegateDfs != implDfs && isAssignable( delegateDfs, ifaceFuncType ) )
    {
      return true;
    }
    return false;
  }

  static boolean isObjectMethod( IMethodInfo mi )
  {
    IParameterInfo[] params = mi.getParameters();
    IType[] paramTypes = new IType[params.length];
    for( int i = 0; i < params.length; i++ )
    {
      paramTypes[i] = params[i].getFeatureType();
    }
    IMethodInfo objMethod = JavaTypes.OBJECT().getTypeInfo().getMethod( mi.getDisplayName(), paramTypes );
    return objMethod != null;
  }

  private DynamicFunctionSymbol getImplDfs( IGosuClassInternal implClass, CaseInsensitiveCharSequence signature )
  {
    if( implClass == null )
    {
      return null;
    }

    DynamicFunctionSymbol implDfs = implClass.getMemberFunction( signature );
    if( implDfs != null && implClass.isParameterizedType() )
    {
      implDfs = implDfs.getParameterizedVersion( implClass );
    }

    if( implDfs == null && signature.charAt( 0 ) == '@' )
    {
      implDfs = findVarPropertyAccessorDfs( signature, implClass );
    }
    if( implDfs == null )
    {
      implDfs = getImplDfs( implClass.getSuperClass(), signature );
    }
    if( implDfs != null && implDfs.isAbstract() )
    {
      implDfs = null;
    }
    return implDfs;
  }

  private DynamicFunctionSymbol findVarPropertyAccessorDfs( CaseInsensitiveCharSequence signature, IGosuClassInternal implClass )
  {
    String strPropName = signature.toString();
    strPropName = strPropName.substring( 1, strPropName.indexOf( '(' ) );
    DynamicPropertySymbol dps = implClass.getMemberProperty( CaseInsensitiveCharSequence.get( strPropName ) );
    if( dps != null )
    {
      if( dps.getGetterDfs() != null && dps.getGetterDfs().getCaseInsensitiveName().equals(signature) )
      {
        return dps.getGetterDfs();
      }
      else if( dps.getSetterDfs() != null && dps.getSetterDfs().getCaseInsensitiveName().equals(signature) )
      {
        return dps.getSetterDfs();
      }
    }
    return null;
  }

  public void setFullDescription( String description )
  {
    _description = description;
  }

  public String getFullDescription()
  {
    return _description == null ? "" : _description;
  }

  public List<? extends IType> getSubtypes()
  {
    if( TypeInfoDatabaseInit.isInitialized() )
    {
      Set<? extends IInheritanceRecord> subtypeRecords = TypeInfoDatabaseInit.getInheritenceInfoRecordFinder().findSubtypes( getTypeInfo() );
      List<IType> subtypes = new ArrayList<IType>();
      if( subtypeRecords != null )
      {
        for( IInheritanceRecord subtypeRecord : subtypeRecords )
        {
          subtypes.add( TypeSystem.getByFullName( subtypeRecord.getSubtypeTypeInfoID().getFeatureName().toString() ) );
        }
      }
      return subtypes;
    }
    else
    {
      // WARNING:  This cache relies on the fact that we refresh the complete type system when something changes
      if( _subtypes == null )
      {
        _subtypes = new ArrayList<IGosuClassInternal>();
        Set<? extends CharSequence> typeNames = getTypeLoader().getAllTypeNames();
        for( CharSequence typeName : typeNames )
        {
          try
          {
            IGosuClassInternal type = (IGosuClassInternal)TypeSystem.getByFullName( typeName.toString() );
            if( getOrCreateTypeReference() != type &&
                isAssignableFrom( type ) )
            {
              _subtypes.add( type );
            }
          }
          catch( Throwable t )
          {
            // ignore
          }
        }
      }

      return _subtypes;
    }
  }

  public Set<FeatureInfoRecord> getFeatureInfoRecords( Predicate predicate )
  {
    // Force the type to be fully defined (compiled)
    isValid();
    return getClassStatement().getFeatureInfoRecords( predicate );
  }

  public boolean isDiscarded()
  {
    return _bDiscarded && !_bInitializing;
  }

  public void setDiscarded( boolean bDiscarded )
  {
    if( bDiscarded )
    {
      unloadBackingClass();
    }
    _bDiscarded = bDiscarded;
  }

  public boolean isCompoundType()
  {
    return false;
  }

  public Set<IType> getCompoundTypeComponents()
  {
    return null;
  }

  public boolean isCannotCaptureSymbols()
  {
    return _bCannotCaptureSymbols;
  }

  public void setCannotCaptureSymbols( boolean bCannotCaptureSymbols )
  {
    _bCannotCaptureSymbols = bCannotCaptureSymbols;
  }
  @Override
  public List<IGosuClass> getBlocks()
  {
    if( !isValid() )
    {
      return Collections.emptyList();
    }
    else
    {
      return _blocks;
    }
  }

  @Override
  public int getBlockCount() {
    return _blocks.size();
  }

  public void addBlock( IBlockClass block )
  {
    if( _blocks == Collections.EMPTY_LIST )
    {
      _blocks = new ArrayList<IGosuClass>();
    }
    addInnerClass( (IGosuClassInternal) block );
    _blocks.add( block );
  }

  @Override
  public void validateAncestry() {
    List<IType> visited = new ArrayList<IType>();
    validateAncestry(visited);
  }

  private void initLazyVars()
  {
    _valid =
      new LockingLazyVar<Boolean>()
      {
        @Override protected Boolean init()
        {
          return getClassStatement() != null && !getClassStatement().hasParseExceptions() ? Boolean.TRUE : Boolean.FALSE;
        }
      };

    _featureAnnotationMap = new LockingLazyVar<Map<String, List>>()
    {
      @Override
      protected Map<String, List> init()
      {
        if( isParameterizedType() )
        {
          return getGenericType().getRuntimeFeatureAnnotationMap();
        }
        else
        {
          try
          {
            Class classToLoadAnnotationsFrom;
            if( isInterface() )
            {
              classToLoadAnnotationsFrom = Class.forName( getInterfaceMethodsClassName(), false, getBackingClass().getClassLoader() );
            }
            else
            {
              classToLoadAnnotationsFrom = getBackingClass();
            }
            Method declaredMethod = classToLoadAnnotationsFrom.getDeclaredMethod( EVAL_ANNOTATIONS_METHOD );
            declaredMethod.setAccessible( true );
            return (Map) declaredMethod.invoke( null );
          }
          catch( InvocationTargetException e )
          {
            throw GosuExceptionUtil.forceThrow( e.getTargetException() );
          }
          catch( Exception e )
          {
            throw GosuExceptionUtil.forceThrow( e );
          }
        }
      }
    };
  }

  public String getInterfaceMethodsClassName()
  {
    return getName() + "." + INTERFACE_METHODS_INNER_CLASS;
  }

  public void validateAncestry(List<IType> visited) {
    if (visited.contains(getOrCreateTypeReference())) {
      return;
    }
    visited.add(getOrCreateTypeReference());

    isValid();

    Collection<IGosuClassInternal> innerClasses = getInnerClasses();
    for (IGosuClassInternal gosuClass : innerClasses) {
      gosuClass.validateAncestry(visited);
    }

    // validate supertype
    IType supertype = getSupertype();
    if (supertype instanceof IGosuClass) {
      ((IGosuClass)supertype).validateAncestry(visited);
    }

    // validate interfaces
    List<IType> interfaces = getInterfaces();
    for (IType iface : interfaces) {
      if (iface instanceof IGosuClass) {
        ((IGosuClass)iface).validateAncestry(visited);
      }
    }

    // validate enhancements
    IEnhancementIndex enhancementIndex = getTypeLoader().getEnhancementIndex();
    List<? extends IGosuEnhancement> enhancements = enhancementIndex.getEnhancementsForType(getOrCreateTypeReference());
    for (IGosuEnhancement enhancement : enhancements) {
      enhancement.isValid();
    }
  }

  @Override
  public ObjectSize getRetainedMemory() {
    return ObjectSizeUtil.deepSizeOf(this, new IObjectSizeFilter() {      
      @Override
      public boolean skipField(Field field) {
        return
            field.getType().equals(GosuClassParseInfo.class) ||
//            IFeatureInfo.class.isAssignableFrom(field.getType()) ||
            field.getType().equals(Class.class) ||
            field.getName().equals("_fileInfo") ||
            field.getName().equals("_typeLoader") ||
            (field.getDeclaringClass().equals(LockingLazyVar.class) && field.getName().equals("_lock"))
            ;
      }

      @Override
      public boolean skipObject(Object obj) {
        Class<? extends Object> objClass = obj.getClass();
        return
            objClass.equals(Class.class) ||
            (obj instanceof IModule) ||
            (obj instanceof ITypeInfo && obj != GosuClass.this._typeInfo) ||
            (IType.class.isAssignableFrom(objClass) && obj != GosuClass.this) ||
            objClass.getName().startsWith("java.lang.reflect") ||
            obj.getClass().getName().startsWith("org.eclipse")
            ;
      }
    }, 100000);
  }

  /**
   * Note a gosu class can be BOTH parameterzied AND generic. For example,
   * <p/>
   * class Bar<T> {
   * function blah() : T {...}
   * }
   * class Foo<T extends CharSequence> extends Bar<T> {}
   * <p/>
   * The class Bar<T> here is parameterized by the type var from Foo, yet it is
   * still a generic class. The blah() method in Foo's typeinfo must have a
   * return type consistent with Foo's type var upper bound, CharSequence.
   *
   * //## todo: maybe we don't need this concept any longer? i.e., parameterization should work correctly regardless.
   */
  public void assignTypeVarsFromTypeParams( IType[] typeParams )
  {
    _typeVarDefs = new ArrayList<ITypeVariableDefinition>( 2 );
    for( IType typeParam : typeParams )
    {
      if( typeParam instanceof TypeVariableType )
      {
        TypeVariableDefinitionImpl typeVarDef = getTypeVarDefImpl(((TypeVariableType)typeParam).getTypeVarDef());
        _typeVarDefs.add( typeVarDef );
      }
    }

    if( _typeVarDefs.isEmpty() )
    {
      _typeVarDefs = Collections.emptyList();
    }
  }

  public List<ITypeVariableDefinition> getTypeVarDefs() {
    return _typeVarDefs;
  }

  public void setGenericTypeVariables( List<ITypeVariableDefinition> typeVarExprList )
  {
    if( typeVarExprList.isEmpty() )
    {
      typeVarExprList = Collections.emptyList();
    }
    _typeVarDefs = new ArrayList<ITypeVariableDefinition>(typeVarExprList.size());
    for (int i = 0; i < typeVarExprList.size(); i++) {
      _typeVarDefs.add(getTypeVarDefImpl(typeVarExprList.get(i)));
    }
  }

  private TypeVariableDefinitionImpl getTypeVarDefImpl(ITypeVariableDefinition typeVarDef) {
    while (!(typeVarDef instanceof TypeVariableDefinitionImpl)) {
      typeVarDef = ((TypeVariableDefinition)typeVarDef).getTypeVarDef();
    }
    return (TypeVariableDefinitionImpl) typeVarDef;
  }

  public IFunctionStatement getFunctionStatement(IMethodInfo method) {
    for (IDynamicFunctionSymbol dfs : getMemberFunctions()) {
      if (method.getName().equals(dfs.getName()) && equalArgs(method.getParameters(), dfs.getArgs())) {
        return dfs.getDeclFunctionStmt();
      }
    }
    for (IDynamicFunctionSymbol dfs : getStaticFunctions()) {
      if (method.getName().equals(dfs.getName()) && equalArgs(method.getParameters(), dfs.getArgs())) {
        return dfs.getDeclFunctionStmt();
      }
    }
    return null;
  }

  public boolean isCompiledToUberModule()
  {
    return _bCompiledToUberModule;
  }
  public void setCompiledToUberModule( boolean bUber )
  {
    _bCompiledToUberModule = bUber;
  }

  private boolean equalArgs(IParameterInfo[] parameters, List<ISymbol> args) {
    if (parameters.length != args.size()) {
      return false;
    }
    for (int i = 0; i < parameters.length; i++) {
      if (!parameters[i].getFeatureType().equals(args.get(i).getType())) {
        return false;
      }
    }
    return true;
  }

  @Override
  public IType[] getLoaderParameterizedTypes() {
    if(isHeaderCompiled() && isGenericType() )
    {
      Collection<IGosuClassInternal> types = getParameterizedTypes();
      return types.toArray(new IType[types.size()]);
    } else {
      return new IType[0];
    }
  }
}
