Package com.redhat.ceylon.compiler.loader

Source Code of com.redhat.ceylon.compiler.loader.AbstractModelLoader

/*
* Copyright Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the authors tag. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License version 2.
*
* This particular file is subject to the "Classpath" exception as provided in the
* LICENSE file that accompanied this code.
*
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE.  See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License,
* along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA  02110-1301, USA.
*/
package com.redhat.ceylon.compiler.loader;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.lang.model.type.TypeKind;

import com.redhat.ceylon.cmr.api.ArtifactResult;
import com.redhat.ceylon.cmr.api.JDKUtils;
import com.redhat.ceylon.common.BooleanUtil;
import com.redhat.ceylon.common.Versions;
import com.redhat.ceylon.compiler.java.codegen.AbstractTransformer;
import com.redhat.ceylon.compiler.java.codegen.AnnotationArgument;
import com.redhat.ceylon.compiler.java.codegen.AnnotationConstructorParameter;
import com.redhat.ceylon.compiler.java.codegen.AnnotationFieldName;
import com.redhat.ceylon.compiler.java.codegen.AnnotationInvocation;
import com.redhat.ceylon.compiler.java.codegen.AnnotationTerm;
import com.redhat.ceylon.compiler.java.codegen.BooleanLiteralAnnotationTerm;
import com.redhat.ceylon.compiler.java.codegen.CharacterLiteralAnnotationTerm;
import com.redhat.ceylon.compiler.java.codegen.CodegenUtil;
import com.redhat.ceylon.compiler.java.codegen.CollectionLiteralAnnotationTerm;
import com.redhat.ceylon.compiler.java.codegen.Decl;
import com.redhat.ceylon.compiler.java.codegen.DeclarationLiteralAnnotationTerm;
import com.redhat.ceylon.compiler.java.codegen.FloatLiteralAnnotationTerm;
import com.redhat.ceylon.compiler.java.codegen.IntegerLiteralAnnotationTerm;
import com.redhat.ceylon.compiler.java.codegen.InvocationAnnotationTerm;
import com.redhat.ceylon.compiler.java.codegen.LiteralAnnotationTerm;
import com.redhat.ceylon.compiler.java.codegen.Naming;
import com.redhat.ceylon.compiler.java.codegen.ObjectLiteralAnnotationTerm;
import com.redhat.ceylon.compiler.java.codegen.ParameterAnnotationTerm;
import com.redhat.ceylon.compiler.java.codegen.StringLiteralAnnotationTerm;
import com.redhat.ceylon.compiler.java.util.Timer;
import com.redhat.ceylon.compiler.java.util.Util;
import com.redhat.ceylon.compiler.loader.mirror.AccessibleMirror;
import com.redhat.ceylon.compiler.loader.mirror.AnnotatedMirror;
import com.redhat.ceylon.compiler.loader.mirror.AnnotationMirror;
import com.redhat.ceylon.compiler.loader.mirror.ClassMirror;
import com.redhat.ceylon.compiler.loader.mirror.FieldMirror;
import com.redhat.ceylon.compiler.loader.mirror.MethodMirror;
import com.redhat.ceylon.compiler.loader.mirror.PackageMirror;
import com.redhat.ceylon.compiler.loader.mirror.TypeMirror;
import com.redhat.ceylon.compiler.loader.mirror.TypeParameterMirror;
import com.redhat.ceylon.compiler.loader.mirror.VariableMirror;
import com.redhat.ceylon.compiler.loader.model.FieldValue;
import com.redhat.ceylon.compiler.loader.model.JavaBeanValue;
import com.redhat.ceylon.compiler.loader.model.JavaMethod;
import com.redhat.ceylon.compiler.loader.model.LazyClass;
import com.redhat.ceylon.compiler.loader.model.LazyClassAlias;
import com.redhat.ceylon.compiler.loader.model.LazyContainer;
import com.redhat.ceylon.compiler.loader.model.LazyElement;
import com.redhat.ceylon.compiler.loader.model.LazyInterface;
import com.redhat.ceylon.compiler.loader.model.LazyInterfaceAlias;
import com.redhat.ceylon.compiler.loader.model.LazyMethod;
import com.redhat.ceylon.compiler.loader.model.LazyModule;
import com.redhat.ceylon.compiler.loader.model.LazyPackage;
import com.redhat.ceylon.compiler.loader.model.LazyTypeAlias;
import com.redhat.ceylon.compiler.loader.model.LazyValue;
import com.redhat.ceylon.compiler.loader.model.LocalDeclarationContainer;
import com.redhat.ceylon.compiler.loader.model.SetterWithLocalDeclarations;
import com.redhat.ceylon.compiler.typechecker.analyzer.DeclarationVisitor;
import com.redhat.ceylon.compiler.typechecker.analyzer.ModuleManager;
import com.redhat.ceylon.compiler.typechecker.model.Annotation;
import com.redhat.ceylon.compiler.typechecker.model.Class;
import com.redhat.ceylon.compiler.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.DeclarationCompleter;
import com.redhat.ceylon.compiler.typechecker.model.Element;
import com.redhat.ceylon.compiler.typechecker.model.Functional;
import com.redhat.ceylon.compiler.typechecker.model.Interface;
import com.redhat.ceylon.compiler.typechecker.model.IntersectionType;
import com.redhat.ceylon.compiler.typechecker.model.Method;
import com.redhat.ceylon.compiler.typechecker.model.MethodOrValue;
import com.redhat.ceylon.compiler.typechecker.model.Module;
import com.redhat.ceylon.compiler.typechecker.model.ModuleImport;
import com.redhat.ceylon.compiler.typechecker.model.Modules;
import com.redhat.ceylon.compiler.typechecker.model.Package;
import com.redhat.ceylon.compiler.typechecker.model.Parameter;
import com.redhat.ceylon.compiler.typechecker.model.ParameterList;
import com.redhat.ceylon.compiler.typechecker.model.ProducedType;
import com.redhat.ceylon.compiler.typechecker.model.Scope;
import com.redhat.ceylon.compiler.typechecker.model.Setter;
import com.redhat.ceylon.compiler.typechecker.model.SiteVariance;
import com.redhat.ceylon.compiler.typechecker.model.TypeAlias;
import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.TypeParameter;
import com.redhat.ceylon.compiler.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.UnionType;
import com.redhat.ceylon.compiler.typechecker.model.Unit;
import com.redhat.ceylon.compiler.typechecker.model.UnknownType;
import com.redhat.ceylon.compiler.typechecker.model.Value;

/**
* Abstract class of a model loader that can load a model from a compiled Java representation,
* while being agnostic of the reflection API used to load the compiled Java representation.
*
* @author Stéphane Épardaud <stef@epardaud.fr>
*/
public abstract class AbstractModelLoader implements ModelCompleter, ModelLoader, DeclarationCompleter {

    public static final String JAVA_BASE_MODULE_NAME = "java.base";
    public static final String CEYLON_LANGUAGE = "ceylon.language";
    public static final String CEYLON_LANGUAGE_MODEL = "ceylon.language.meta.model";
    public static final String CEYLON_LANGUAGE_MODEL_DECLARATION = "ceylon.language.meta.declaration";
    public static final String CEYLON_LANGUAGE_SERIALIZATION = "ceylon.language.serialization";
   
    private static final String TIMER_MODEL_LOADER_CATEGORY = "model loader";
   
    public static final String CEYLON_CEYLON_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Ceylon";
    private static final String CEYLON_MODULE_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Module";
    private static final String CEYLON_PACKAGE_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Package";
    public static final String CEYLON_IGNORE_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Ignore";
    private static final String CEYLON_CLASS_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Class";
    public static final String CEYLON_NAME_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Name";
    private static final String CEYLON_SEQUENCED_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Sequenced";
    private static final String CEYLON_FUNCTIONAL_PARAMETER_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.FunctionalParameter";
    private static final String CEYLON_DEFAULTED_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Defaulted";
    private static final String CEYLON_SATISFIED_TYPES_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.SatisfiedTypes";
    private static final String CEYLON_CASE_TYPES_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.CaseTypes";
    private static final String CEYLON_TYPE_PARAMETERS = "com.redhat.ceylon.compiler.java.metadata.TypeParameters";
    private static final String CEYLON_TYPE_INFO_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.TypeInfo";
    public static final String CEYLON_ATTRIBUTE_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Attribute";
    public static final String CEYLON_SETTER_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Setter";
    public static final String CEYLON_OBJECT_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Object";
    public static final String CEYLON_METHOD_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Method";
    public static final String CEYLON_CONTAINER_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Container";
    public static final String CEYLON_LOCAL_CONTAINER_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.LocalContainer";
    public static final String CEYLON_LOCAL_DECLARATION_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.LocalDeclaration";
    public static final String CEYLON_LOCAL_DECLARATIONS_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.LocalDeclarations";
    private static final String CEYLON_MEMBERS_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Members";
    private static final String CEYLON_ANNOTATIONS_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Annotations";
    public static final String CEYLON_VALUETYPE_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.ValueType";
    public static final String CEYLON_ALIAS_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Alias";
    public static final String CEYLON_TYPE_ALIAS_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.TypeAlias";
    private static final String CEYLON_ANNOTATION_INSTANTIATION_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.AnnotationInstantiation";
    private static final String CEYLON_ANNOTATION_INSTANTIATION_ARGUMENTS_MEMBER = "arguments";
    private static final String CEYLON_ANNOTATION_INSTANTIATION_ANNOTATION_MEMBER = "primary";
   
    private static final String CEYLON_ANNOTATION_INSTANTIATION_TREE_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.AnnotationInstantiationTree";
    private static final String CEYLON_STRING_VALUE_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.StringValue";
    private static final String CEYLON_STRING_EXPRS_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.StringExprs";
    private static final String CEYLON_BOOLEAN_VALUE_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.BooleanValue";
    private static final String CEYLON_BOOLEAN_EXPRS_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.BooleanExprs";
    private static final String CEYLON_INTEGER_VALUE_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.IntegerValue";
    private static final String CEYLON_INTEGER_EXPRS_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.IntegerExprs";
    private static final String CEYLON_CHARACTER_VALUE_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.CharacterValue";
    private static final String CEYLON_CHARACTER_EXPRS_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.CharacterExprs";
    private static final String CEYLON_FLOAT_VALUE_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.FloatValue";
    private static final String CEYLON_FLOAT_EXPRS_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.FloatExprs";
    private static final String CEYLON_OBJECT_VALUE_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.ObjectValue";
    private static final String CEYLON_OBJECT_EXPRS_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.ObjectExprs";
    private static final String CEYLON_DECLARATION_VALUE_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.DeclarationValue";
    private static final String CEYLON_DECLARATION_EXPRS_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.DeclarationExprs";
    private static final String CEYLON_TRANSIENT_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Transient";
    private static final String CEYLON_DYNAMIC_ANNOTATION = "com.redhat.ceylon.compiler.java.metadata.Dynamic";
    private static final String JAVA_DEPRECATED_ANNOTATION = "java.lang.Deprecated";
   
    private static final String CEYLON_LANGUAGE_ABSTRACT_ANNOTATION = "ceylon.language.AbstractAnnotation$annotation$";
    private static final String CEYLON_LANGUAGE_ACTUAL_ANNOTATION = "ceylon.language.ActualAnnotation$annotation$";
    private static final String CEYLON_LANGUAGE_ANNOTATION_ANNOTATION = "ceylon.language.AnnotationAnnotation$annotation$";
    private static final String CEYLON_LANGUAGE_DEFAULT_ANNOTATION = "ceylon.language.DefaultAnnotation$annotation$";
    private static final String CEYLON_LANGUAGE_FORMAL_ANNOTATION = "ceylon.language.FormalAnnotation$annotation$";
    private static final String CEYLON_LANGUAGE_LATE_ANNOTATION = "ceylon.language.LateAnnotation$annotation$";
    private static final String CEYLON_LANGUAGE_SEALED_ANNOTATION = "ceylon.language.SealedAnnotation$annotation$";

    // important that these are with ::
    private static final String CEYLON_LANGUAGE_CALLABLE_TYPE_NAME = "ceylon.language::Callable";
    private static final String CEYLON_LANGUAGE_TUPLE_TYPE_NAME = "ceylon.language::Tuple";
    private static final String CEYLON_LANGUAGE_SEQUENTIAL_TYPE_NAME = "ceylon.language::Sequential";
    private static final String CEYLON_LANGUAGE_SEQUENCE_TYPE_NAME = "ceylon.language::Sequence";
    private static final String CEYLON_LANGUAGE_EMPTY_TYPE_NAME = "ceylon.language::Empty";
   
    private static final TypeMirror OBJECT_TYPE = simpleCeylonObjectType("java.lang.Object");
    private static final TypeMirror CEYLON_OBJECT_TYPE = simpleCeylonObjectType("ceylon.language.Object");
    private static final TypeMirror CEYLON_BASIC_TYPE = simpleCeylonObjectType("ceylon.language.Basic");
    private static final TypeMirror CEYLON_REIFIED_TYPE_TYPE = simpleCeylonObjectType("com.redhat.ceylon.compiler.java.runtime.model.ReifiedType");
    private static final TypeMirror CEYLON_TYPE_DESCRIPTOR_TYPE = simpleCeylonObjectType("com.redhat.ceylon.compiler.java.runtime.model.TypeDescriptor");
   
    private static final TypeMirror THROWABLE_TYPE = simpleCeylonObjectType("java.lang.Throwable");
    private static final TypeMirror ERROR_TYPE = simpleCeylonObjectType("java.lang.Error");
    private static final TypeMirror EXCEPTION_TYPE = simpleCeylonObjectType("java.lang.Exception");
    private static final TypeMirror CEYLON_THROWABLE_TYPE = simpleCeylonObjectType("java.lang.Throwable");
    private static final TypeMirror CEYLON_EXCEPTION_TYPE = simpleCeylonObjectType("ceylon.language.Exception");
   
    private static final TypeMirror STRING_TYPE = simpleJDKObjectType("java.lang.String");
    private static final TypeMirror CEYLON_STRING_TYPE = simpleCeylonObjectType("ceylon.language.String");
   
    private static final TypeMirror PRIM_BOOLEAN_TYPE = simpleJDKObjectType("boolean");
    private static final TypeMirror CEYLON_BOOLEAN_TYPE = simpleCeylonObjectType("ceylon.language.Boolean");
   
    private static final TypeMirror PRIM_BYTE_TYPE = simpleJDKObjectType("byte");
    private static final TypeMirror CEYLON_BYTE_TYPE = simpleCeylonObjectType("ceylon.language.Byte");
   
    private static final TypeMirror PRIM_SHORT_TYPE = simpleJDKObjectType("short");

    private static final TypeMirror PRIM_INT_TYPE = simpleJDKObjectType("int");
    private static final TypeMirror PRIM_LONG_TYPE = simpleJDKObjectType("long");
    private static final TypeMirror CEYLON_INTEGER_TYPE = simpleCeylonObjectType("ceylon.language.Integer");
   
    private static final TypeMirror PRIM_FLOAT_TYPE = simpleJDKObjectType("float");
    private static final TypeMirror PRIM_DOUBLE_TYPE = simpleJDKObjectType("double");
    private static final TypeMirror CEYLON_FLOAT_TYPE = simpleCeylonObjectType("ceylon.language.Float");

    private static final TypeMirror PRIM_CHAR_TYPE = simpleJDKObjectType("char");
    private static final TypeMirror CEYLON_CHARACTER_TYPE = simpleCeylonObjectType("ceylon.language.Character");
   
    // this one has no "_" postfix because that's how we look it up
    protected static final String JAVA_LANG_BYTE_ARRAY = "java.lang.ByteArray";
    protected static final String JAVA_LANG_SHORT_ARRAY = "java.lang.ShortArray";
    protected static final String JAVA_LANG_INT_ARRAY = "java.lang.IntArray";
    protected static final String JAVA_LANG_LONG_ARRAY = "java.lang.LongArray";
    protected static final String JAVA_LANG_FLOAT_ARRAY = "java.lang.FloatArray";
    protected static final String JAVA_LANG_DOUBLE_ARRAY = "java.lang.DoubleArray";
    protected static final String JAVA_LANG_CHAR_ARRAY = "java.lang.CharArray";
    protected static final String JAVA_LANG_BOOLEAN_ARRAY = "java.lang.BooleanArray";
    protected static final String JAVA_LANG_OBJECT_ARRAY = "java.lang.ObjectArray";

    // this one has the "_" postfix because that's what we translate it to
    private static final String CEYLON_BYTE_ARRAY = "com.redhat.ceylon.compiler.java.language.ByteArray";
    private static final String CEYLON_SHORT_ARRAY = "com.redhat.ceylon.compiler.java.language.ShortArray";
    private static final String CEYLON_INT_ARRAY = "com.redhat.ceylon.compiler.java.language.IntArray";
    private static final String CEYLON_LONG_ARRAY = "com.redhat.ceylon.compiler.java.language.LongArray";
    private static final String CEYLON_FLOAT_ARRAY = "com.redhat.ceylon.compiler.java.language.FloatArray";
    private static final String CEYLON_DOUBLE_ARRAY = "com.redhat.ceylon.compiler.java.language.DoubleArray";
    private static final String CEYLON_CHAR_ARRAY = "com.redhat.ceylon.compiler.java.language.CharArray";
    private static final String CEYLON_BOOLEAN_ARRAY = "com.redhat.ceylon.compiler.java.language.BooleanArray";
    private static final String CEYLON_OBJECT_ARRAY = "com.redhat.ceylon.compiler.java.language.ObjectArray";

    private static final TypeMirror JAVA_BYTE_ARRAY_TYPE = simpleJDKObjectType("java.lang.ByteArray");
    private static final TypeMirror JAVA_SHORT_ARRAY_TYPE = simpleJDKObjectType("java.lang.ShortArray");
    private static final TypeMirror JAVA_INT_ARRAY_TYPE = simpleJDKObjectType("java.lang.IntArray");
    private static final TypeMirror JAVA_LONG_ARRAY_TYPE = simpleJDKObjectType("java.lang.LongArray");
    private static final TypeMirror JAVA_FLOAT_ARRAY_TYPE = simpleJDKObjectType("java.lang.FloatArray");
    private static final TypeMirror JAVA_DOUBLE_ARRAY_TYPE = simpleJDKObjectType("java.lang.DoubleArray");
    private static final TypeMirror JAVA_CHAR_ARRAY_TYPE = simpleJDKObjectType("java.lang.CharArray");
    private static final TypeMirror JAVA_BOOLEAN_ARRAY_TYPE = simpleJDKObjectType("java.lang.BooleanArray");
   
    private static TypeMirror simpleJDKObjectType(String name) {
        return new SimpleReflType(name, SimpleReflType.Module.JDK, TypeKind.DECLARED);
    }
    private static TypeMirror simpleCeylonObjectType(String name) {
        return new SimpleReflType(name, SimpleReflType.Module.CEYLON, TypeKind.DECLARED);
    }

    protected Map<String, Declaration> valueDeclarationsByName = new HashMap<String, Declaration>();
    protected Map<String, Declaration> typeDeclarationsByName = new HashMap<String, Declaration>();
    protected Map<Package, Unit> unitsByPackage = new HashMap<Package, Unit>();
    protected TypeParser typeParser;
    /**
     * The type factory
     * (<strong>should not be used while completing a declaration</strong>)
     */
    protected Unit typeFactory;
    protected final Set<String> loadedPackages = new HashSet<String>();
    protected final Map<String,LazyPackage> packagesByName = new HashMap<String,LazyPackage>();
    protected boolean packageDescriptorsNeedLoading = false;
    protected boolean isBootstrap;
    protected ModuleManager moduleManager;
    protected Modules modules;
    protected Map<String, ClassMirror> classMirrorCache = new HashMap<String, ClassMirror>();
    protected boolean binaryCompatibilityErrorRaised = false;
    protected Timer timer;
    private Map<String,LazyPackage> modulelessPackages = new HashMap<String,LazyPackage>();
    private ParameterNameParser parameterNameParser = new ParameterNameParser(this);
   
    /**
     * Loads a given package, if required. This is mostly useful for the javac reflection impl.
     *
     * @param the module to load the package from
     * @param packageName the package name to load
     * @param loadDeclarations true to load all the declarations in this package.
     * @return
     */
    public abstract boolean loadPackage(Module module, String packageName, boolean loadDeclarations);

    public final Object getLock(){
        return this;
    }

    /**
     * To be redefined by subclasses if they don't need local declarations.
     */
    protected boolean needsLocalDeclarations(){
        return true;
    }

    public boolean searchAgain(Module module, String name) {
        return false;
    }
   
    public boolean searchAgain(LazyPackage lazyPackage, String name) {
        return false;
    }
   
    /**
     * Looks up a ClassMirror by name. Uses cached results, and caches the result of calling lookupNewClassMirror
     * on cache misses.
     *
     * @param module the module in which we should find the class
     * @param name the name of the Class to load
     * @return a ClassMirror for the specified class, or null if not found.
     */
    public final ClassMirror lookupClassMirror(Module module, String name) {
        synchronized(getLock()){
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            try{
                // Java array classes are not where we expect them
                if (JAVA_LANG_OBJECT_ARRAY.equals(name)
                        || JAVA_LANG_BOOLEAN_ARRAY.equals(name)
                        || JAVA_LANG_BYTE_ARRAY.equals(name)
                        || JAVA_LANG_SHORT_ARRAY.equals(name)
                        || JAVA_LANG_INT_ARRAY.equals(name)
                        || JAVA_LANG_LONG_ARRAY.equals(name)
                        || JAVA_LANG_FLOAT_ARRAY.equals(name)
                        || JAVA_LANG_DOUBLE_ARRAY.equals(name)
                        || JAVA_LANG_CHAR_ARRAY.equals(name)) {
                    // turn them into their real class location (get rid of the "java.lang" prefix)
                    name = "com.redhat.ceylon.compiler.java.language" + name.substring(9);
                    module = getLanguageModule();
                }
                String cacheKey = cacheKeyByModule(module, name);
                // we use containsKey to be able to cache null results
                if(classMirrorCache.containsKey(cacheKey)) {
                    ClassMirror cachedMirror = classMirrorCache.get(cacheKey);
                    if (cachedMirror != null || ! searchAgain(module, name)) {
                        return cachedMirror;
                    }
                }
                ClassMirror mirror = lookupNewClassMirror(module, name);
                // we even cache null results
                classMirrorCache.put(cacheKey, mirror);
                return mirror;
            }finally{
                timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
            }
        }
    }

    protected String cacheKeyByModule(Module module, String name) {
        return getCacheKeyByModule(module, name);
    }

    public static String getCacheKeyByModule(Module module, String name){
        String moduleSignature = module.getSignature();
        StringBuilder buf = new StringBuilder(moduleSignature.length()+1+name.length());
        // '/' is allowed in module version but not in module or class name, so we're good
        return buf.append(moduleSignature).append('/').append(name).toString();
    }

    protected boolean lastPartHasLowerInitial(String name) {
        int index = name.lastIndexOf('.');
        if (index != -1){
            name = name.substring(index+1);
        }
        // remove any possibly quoting char
        name = Naming.stripLeadingDollar(name);
        if(!name.isEmpty()){
            char c = name.charAt(0);
            return Util.isLowerCase(c);
        }
        return false;
    }
   
    /**
     * Looks up a ClassMirror by name. Called by lookupClassMirror on cache misses.
     *
     * @param module the module in which we should find the given class
     * @param name the name of the Class to load
     * @return a ClassMirror for the specified class, or null if not found.
     */
    protected abstract ClassMirror lookupNewClassMirror(Module module, String name);

    /**
     * Adds the given module to the set of modules from which we can load classes.
     *
     * @param module the module
     * @param artifact the module's artifact, if any. Can be null.
     */
    public abstract void addModuleToClassPath(Module module, ArtifactResult artifact);

    /**
     * Returns true if the given module has been added to this model loader's classpath.
     * Defaults to true.
     */
    public boolean isModuleInClassPath(Module module){
        return true;
    }
   
    /**
     * Returns true if the given method is overriding an inherited method (from super class or interfaces).
     */
    protected abstract boolean isOverridingMethod(MethodMirror methodMirror);

    /**
     * Returns true if the given method is overloading an inherited method (from super class or interfaces).
     */
    protected abstract boolean isOverloadingMethod(MethodMirror methodMirror);

    /**
     * Logs a warning.
     */
    protected abstract void logWarning(String message);

    /**
     * Logs a debug message.
     */
    protected abstract void logVerbose(String message);
   
    /**
     * Logs an error
     */
    protected abstract void logError(String message);
   
    public void loadStandardModules(){
        // set up the type factory
        Timer nested = timer.nestedTimer();
        nested.startTask("load ceylon.language");
        Module languageModule = findOrCreateModule(CEYLON_LANGUAGE, null);
        addModuleToClassPath(languageModule, null);
        Package languagePackage = findOrCreatePackage(languageModule, CEYLON_LANGUAGE);
        typeFactory.setPackage(languagePackage);
       
        // make sure the language module has its real dependencies added, because we need them in the classpath
        // otherwise we will get errors on the Util and Metamodel calls we insert
        languageModule.addImport(new ModuleImport(findOrCreateModule("com.redhat.ceylon.compiler.java", Versions.CEYLON_VERSION_NUMBER), false, false));
        languageModule.addImport(new ModuleImport(findOrCreateModule("com.redhat.ceylon.compiler.js", Versions.CEYLON_VERSION_NUMBER), false, false));
        languageModule.addImport(new ModuleImport(findOrCreateModule("com.redhat.ceylon.common", Versions.CEYLON_VERSION_NUMBER), false, false));
        languageModule.addImport(new ModuleImport(findOrCreateModule("com.redhat.ceylon.module-resolver", Versions.CEYLON_VERSION_NUMBER), false, false));
        languageModule.addImport(new ModuleImport(findOrCreateModule("com.redhat.ceylon.typechecker", Versions.CEYLON_VERSION_NUMBER), false, false));
        languageModule.addImport(new ModuleImport(findOrCreateModule("org.jboss.modules", "1.3.3.Final"), false, false));
        languageModule.addImport(new ModuleImport(findOrCreateModule("org.jboss.jandex", "1.0.3.Final"), false, false));
       
        nested.endTask();
       
        nested.startTask("load JDK");
        // make sure the jdk modules are loaded
        for(String jdkModule : JDKUtils.getJDKModuleNames())
            findOrCreateModule(jdkModule, JDKUtils.jdk.version);
        for(String jdkOracleModule : JDKUtils.getOracleJDKModuleNames())
            findOrCreateModule(jdkOracleModule, JDKUtils.jdk.version);
        Module jdkModule = findOrCreateModule(JAVA_BASE_MODULE_NAME, JDKUtils.jdk.version);
        nested.endTask();
       
        /*
         * We start by loading java.lang and ceylon.language because we will need them no matter what.
         */
        nested.startTask("load standard packages");
        loadPackage(jdkModule, "java.lang", false);
        loadPackage(languageModule, "com.redhat.ceylon.compiler.java.metadata", false);
        loadPackage(languageModule, "com.redhat.ceylon.compiler.java.language", false);
        nested.endTask();
    }

    /**
     * This is meant to be called if your subclass doesn't call loadStandardModules for whatever reason
     */
    public void setupWithNoStandardModules() {
        Module languageModule = modules.getLanguageModule();
        if(languageModule == null)
            throw new RuntimeException("Assertion failed: language module is null");
        Package languagePackage = languageModule.getPackage(CEYLON_LANGUAGE);
        if(languagePackage == null)
            throw new RuntimeException("Assertion failed: language package is null");
        typeFactory.setPackage(languagePackage);
    }

    enum ClassType {
        ATTRIBUTE, METHOD, OBJECT, CLASS, INTERFACE;
    }
   
    private ClassMirror loadClass(Module module, String pkgName, String className) {
        ClassMirror moduleClass = null;
        try{
            loadPackage(module, pkgName, false);
            moduleClass = lookupClassMirror(module, className);
        }catch(Exception x){
            logVerbose("[Failed to complete class "+className+"]");
        }
        return moduleClass;
    }

    private Declaration convertNonPrimitiveTypeToDeclaration(Module moduleScope, TypeMirror type, Scope scope, DeclarationType declarationType) {
        switch(type.getKind()){
        case VOID:
            return typeFactory.getAnythingDeclaration();
        case ARRAY:
            return ((Class)convertToDeclaration(getLanguageModule(), JAVA_LANG_OBJECT_ARRAY, DeclarationType.TYPE));
        case DECLARED:
            return convertDeclaredTypeToDeclaration(moduleScope, type, declarationType);
        case TYPEVAR:
            return safeLookupTypeParameter(scope, type.getQualifiedName());
        case WILDCARD:
            return typeFactory.getNothingDeclaration();
        // those can't happen
        case BOOLEAN:
        case BYTE:
        case CHAR:
        case SHORT:
        case INT:
        case LONG:
        case FLOAT:
        case DOUBLE:
            // all the autoboxing should already have been done
            throw new RuntimeException("Expected non-primitive type: "+type);
        case ERROR:
            return null;
        default:
            throw new RuntimeException("Failed to handle type "+type);
        }
    }

    private Declaration convertDeclaredTypeToDeclaration(Module moduleScope, TypeMirror type, DeclarationType declarationType) {
        // SimpleReflType does not do declared class so we make an exception for it
        String typeName = type.getQualifiedName();
        if(type instanceof SimpleReflType){
            Module module = null;
            switch(((SimpleReflType) type).getModule()){
            case CEYLON: module = getLanguageModule(); break;
            case JDK : module = getJDKBaseModule(); break;
            }
            return convertToDeclaration(module, typeName, declarationType);
        }
        ClassMirror classMirror = type.getDeclaredClass();
        Module module = findModuleForClassMirror(classMirror);
        if(isImported(moduleScope, module)){
            return convertToDeclaration(module, typeName, declarationType);
        }else{
            logVerbose("Declaration is not from an imported module: "+typeName);
            return null;
        }
    }
    protected Declaration convertToDeclaration(Module module, ClassMirror classMirror, DeclarationType declarationType) {
        // avoid ignored classes
        if(classMirror.getAnnotation(CEYLON_IGNORE_ANNOTATION) != null)
            return null;
        // avoid local interfaces that were pulled to the toplevel if required
        if(classMirror.getAnnotation(CEYLON_LOCAL_CONTAINER_ANNOTATION) != null
                && !needsLocalDeclarations())
            return null;
        // avoid Ceylon annotations
        if(classMirror.getAnnotation(CEYLON_CEYLON_ANNOTATION) != null
                && classMirror.isAnnotationType())
            return null;
        // avoid module and package descriptors too
        if(classMirror.getAnnotation(CEYLON_MODULE_ANNOTATION) != null
                || classMirror.getAnnotation(CEYLON_PACKAGE_ANNOTATION) != null)
            return null;
       
        // find its package
        String pkgName = getPackageNameForQualifiedClassName(classMirror);
       
        if (pkgName.equals("java.lang")) {
            module = getJDKBaseModule();
        }
       
        List<Declaration> decls = new ArrayList<Declaration>();
        boolean[] alreadyExists = new boolean[1];
        Declaration decl = getOrCreateDeclaration(module, classMirror, declarationType,
                decls, alreadyExists);
       
        if (alreadyExists[0]) {
            return decl;
        }

        LazyPackage pkg = findOrCreatePackage(module, pkgName);

        // find/make its Unit
        Unit unit = getCompiledUnit(pkg, classMirror);

        // set all the containers
        for(Declaration d : decls){
       
            // add it to its Unit
            d.setUnit(unit);
            unit.addDeclaration(d);

            setContainer(classMirror, d, pkg);
        }

        return decl;
    }

    public String getPackageNameForQualifiedClassName(String pkg, String qualifiedName){
        // Java array classes we pretend come from java.lang
        if(qualifiedName.startsWith(CEYLON_OBJECT_ARRAY)
                || qualifiedName.startsWith(CEYLON_BOOLEAN_ARRAY)
                || qualifiedName.startsWith(CEYLON_BYTE_ARRAY)
                || qualifiedName.startsWith(CEYLON_SHORT_ARRAY)
                || qualifiedName.startsWith(CEYLON_INT_ARRAY)
                || qualifiedName.startsWith(CEYLON_LONG_ARRAY)
                || qualifiedName.startsWith(CEYLON_FLOAT_ARRAY)
                || qualifiedName.startsWith(CEYLON_DOUBLE_ARRAY)
                || qualifiedName.startsWith(CEYLON_CHAR_ARRAY))
            return "java.lang";
        else
            return unquotePackageName(pkg);
       
    }
   
    protected String getPackageNameForQualifiedClassName(ClassMirror classMirror) {
        return getPackageNameForQualifiedClassName(classMirror.getPackage().getQualifiedName(), classMirror.getQualifiedName());
    }
   
    private String unquotePackageName(PackageMirror pkg) {
        return unquotePackageName(pkg.getQualifiedName());
    }

    private String unquotePackageName(String pkg) {
        return Util.removeChar('$', pkg);
    }

    private void setContainer(ClassMirror classMirror, Declaration d, LazyPackage pkg) {
        // add it to its package if it's not an inner class
        if(!classMirror.isInnerClass() && !classMirror.isLocalClass()){
            d.setContainer(pkg);
            d.setScope(pkg);
            pkg.addCompiledMember(d);
            if(d instanceof LazyInterface && ((LazyInterface) d).isCeylon()){
                setInterfaceCompanionClass(d, null, pkg);
            }
            DeclarationVisitor.setVisibleScope(d);
        }else if(classMirror.isLocalClass()){
            // set its container to the package for now, but don't add it to the package as a member because it's not
            Scope localContainer = getLocalContainer(pkg, classMirror, d);
            if(localContainer != null){
                d.setContainer(localContainer);
                d.setScope(localContainer);
                // do not add it as member, it has already been registered by getLocalContainer
            }else{
                d.setContainer(pkg);
                d.setScope(pkg);
            }
            ((LazyElement)d).setLocal(true);
        }else if(d instanceof ClassOrInterface || d instanceof TypeAlias){
            // do overloads later, since their container is their abstract superclass's container and
            // we have to set that one first
            if(d instanceof Class == false || !((Class)d).isOverloaded()){
                ClassOrInterface container = getContainer(pkg.getModule(), classMirror);
                d.setContainer(container);
                d.setScope(container);
                if(d instanceof LazyInterface && ((LazyInterface) d).isCeylon()){
                    setInterfaceCompanionClass(d, container, pkg);
                }
                // let's not trigger lazy-loading
                ((LazyContainer)container).addMember(d);
                DeclarationVisitor.setVisibleScope(d);
                // now we can do overloads
                if(d instanceof Class && ((Class)d).getOverloads() != null){
                    for(Declaration overload : ((Class)d).getOverloads()){
                        overload.setContainer(container);
                        overload.setScope(container);
                        // let's not trigger lazy-loading
                        ((LazyContainer)container).addMember(overload);
                        DeclarationVisitor.setVisibleScope(overload);
                    }
                }
            }
        }
    }

    private void setInterfaceCompanionClass(Declaration d, ClassOrInterface container, LazyPackage pkg) {
        // find its companion class in its real container
        ClassMirror containerMirror = null;
        if(container instanceof LazyClass){
            containerMirror = ((LazyClass) container).classMirror;
        }else if(container instanceof LazyInterface){
            // container must be a LazyInterface, as TypeAlias doesn't contain anything
            containerMirror = ((LazyInterface)container).companionClass;
            if(containerMirror == null){
                throw new ModelResolutionException("Interface companion class for "+container.getQualifiedNameString()+" not set up");
            }
        }
        String companionName;
        if(containerMirror != null)
            companionName = containerMirror.getFlatName() + "$" + Naming.suffixName(Naming.Suffix.$impl, d.getName());
        else{
            // toplevel
            String p = pkg.getNameAsString();
            companionName = "";
            if(!p.isEmpty())
                companionName =  p + ".";
            companionName +=  Naming.suffixName(Naming.Suffix.$impl, d.getName());
        }
        ClassMirror companionClass = lookupClassMirror(pkg.getModule(), companionName);
        if(companionClass == null){
            ((Interface)d).setCompanionClassNeeded(false);
        }
        ((LazyInterface)d).companionClass = companionClass;
    }
   
    private Scope getLocalContainer(Package pkg, ClassMirror classMirror, Declaration declaration) {
        AnnotationMirror localContainerAnnotation = classMirror.getAnnotation(CEYLON_LOCAL_CONTAINER_ANNOTATION);
        String qualifier = getAnnotationStringValue(classMirror, CEYLON_LOCAL_DECLARATION_ANNOTATION, "qualifier");
       
        // deal with types local to functions in the body of toplevel non-lazy attributes, whose container is ultimately the package
        Boolean isPackageLocal = getAnnotationBooleanValue(classMirror, CEYLON_LOCAL_DECLARATION_ANNOTATION, "isPackageLocal");
        if(BooleanUtil.isTrue(isPackageLocal)){
            // make sure it still knows it's a local
            declaration.setQualifier(qualifier);
            return null;
        }

        LocalDeclarationContainer methodDecl = null;
        // we get a @LocalContainer annotation for local interfaces
        if(localContainerAnnotation != null){
            methodDecl = (LocalDeclarationContainer) findLocalContainerFromAnnotationAndSetCompanionClass(pkg, (Interface) declaration, localContainerAnnotation);
        }else{
            // all the other cases stay where they belong
            MethodMirror method = classMirror.getEnclosingMethod();
            if(method == null)
                return null;
           
            // see where that method belongs
            ClassMirror enclosingClass = method.getEnclosingClass();
            while(enclosingClass.isAnonymous()){
                // this gives us the method in which the anonymous class is, which should be the one we're looking for
                method = enclosingClass.getEnclosingMethod();
                if(method == null)
                    return null;
                // and the method's containing class
                enclosingClass = method.getEnclosingClass();
            }
           
            // if we are in a setter class, the attribute is declared in the getter class, so look for its declaration there
            TypeMirror getterClass = (TypeMirror) getAnnotationValue(enclosingClass, CEYLON_SETTER_ANNOTATION, "getterClass");
            boolean isSetter = false;
            // we use void.class as default value
            if(getterClass != null && !getterClass.isPrimitive()){
                enclosingClass = getterClass.getDeclaredClass();
                isSetter = true;
            }
           
            String javaClassName = enclosingClass.getQualifiedName();
           
            // make sure we don't go looking in companion classes
            if(javaClassName.endsWith(Naming.Suffix.$impl.name()))
                javaClassName = javaClassName.substring(0, javaClassName.length() - 5);
           
            // find the enclosing declaration
            Declaration enclosingClassDeclaration = convertToDeclaration(pkg.getModule(), javaClassName, DeclarationType.TYPE);
            if(enclosingClassDeclaration instanceof ClassOrInterface){
                ClassOrInterface containerDecl = (ClassOrInterface) enclosingClassDeclaration;
                // now find the method's declaration
                // FIXME: find the proper overload if any
                if(method.isConstructor()){
                    methodDecl = (LocalDeclarationContainer) containerDecl;
                }else{
                    String name = method.getName();
                    // this is only for error messages
                    String type;
                    // lots of special cases
                    if(isStringAttribute(method)){
                        name = "string";
                        type = "attribute";
                    }else if(isHashAttribute(method)){
                        name = "hash";
                        type = "attribute";
                    }else if(isGetter(method)) {
                        // simple attribute
                        name = getJavaAttributeName(name);
                        type = "attribute";
                    }else if(isSetter(method)) {
                        // simple attribute
                        name = getJavaAttributeName(name);
                        type = "attribute setter";
                        isSetter = true;
                    }else{
                        type = "method";
                    }
                    // strip any escaping or private suffix
                    // it can be foo$priv$canonical so get rid of that one first
                    if (name.endsWith(Naming.Suffix.$canonical$.toString())) {
                        name = name.substring(0, name.length()-11);
                    }
                    name = Util.strip(name, true, method.isPublic() || method.isProtected() || method.isDefaultAccess());

                    methodDecl = (LocalDeclarationContainer) containerDecl.getDirectMember(name, null, false);

                    if(methodDecl == null)
                        throw new ModelResolutionException("Failed to load outer "+type+" " + name
                                + " for local type " + classMirror.getQualifiedName().toString());

                    // if it's a setter we wanted, let's get it
                    if(isSetter){
                        LocalDeclarationContainer setter = (LocalDeclarationContainer) ((Value)methodDecl).getSetter();
                        if(setter == null)
                            throw new ModelResolutionException("Failed to load outer "+type+" " + name
                                    + " for local type " + classMirror.getQualifiedName().toString());
                        methodDecl = setter;
                    }
                }
            }else if(enclosingClassDeclaration instanceof LazyMethod){
                // local and toplevel methods
                methodDecl = (LazyMethod)enclosingClassDeclaration;
            }else if(enclosingClassDeclaration instanceof LazyValue){
                // local and toplevel attributes
                if(enclosingClassDeclaration.isToplevel() && method.getName().equals(Naming.Unfix.set_.name()))
                    isSetter = true;
                if(isSetter){
                    LocalDeclarationContainer setter = (LocalDeclarationContainer) ((LazyValue)enclosingClassDeclaration).getSetter();
                    if(setter == null)
                        throw new ModelResolutionException("Failed to toplevel attribute setter " + enclosingClassDeclaration.getName()
                                + " for local type " + classMirror.getQualifiedName().toString());
                    methodDecl = setter;
                }else
                    methodDecl = (LazyValue)enclosingClassDeclaration;
            }else{
                throw new ModelResolutionException("Unknown container type " + enclosingClassDeclaration
                        + " for local type " + classMirror.getQualifiedName().toString());
            }
        }

        // we have the method, now find the proper local qualifier if any
        if(qualifier == null)
            return null;
        declaration.setQualifier(qualifier);
        methodDecl.addLocalDeclaration(declaration);
        return methodDecl;
    }
   
    private Scope findLocalContainerFromAnnotationAndSetCompanionClass(Package pkg, Interface declaration, AnnotationMirror localContainerAnnotation) {
        @SuppressWarnings("unchecked")
        List<String> path = (List<String>) localContainerAnnotation.getValue("path");
        // we start at the package
        Scope scope = pkg;
        for(String name : path){
            scope = (Scope) getDirectMember(scope, name);
        }
        String companionClassName = (String) localContainerAnnotation.getValue("companionClassName");
        if(companionClassName == null || companionClassName.isEmpty()){
            declaration.setCompanionClassNeeded(false);
            return scope;
        }
        ClassMirror container;
        Scope javaClassScope;
        if(scope instanceof TypedDeclaration && ((TypedDeclaration) scope).isMember())
            javaClassScope = scope.getContainer();
        else
            javaClassScope = scope;
       
        if(javaClassScope instanceof LazyInterface){
            container = ((LazyInterface)javaClassScope).companionClass;
        }else if(javaClassScope instanceof LazyClass){
            container = ((LazyClass) javaClassScope).classMirror;
        }else if(javaClassScope instanceof LazyValue){
            container = ((LazyValue) javaClassScope).classMirror;
        }else if(javaClassScope instanceof LazyMethod){
            container = ((LazyMethod) javaClassScope).classMirror;
        }else if(javaClassScope instanceof SetterWithLocalDeclarations){
            container = ((SetterWithLocalDeclarations) javaClassScope).classMirror;
        }else{
            throw new ModelResolutionException("Unknown scope class: "+javaClassScope);
        }
        String qualifiedCompanionClassName = container.getQualifiedName() + "$" + companionClassName;
        ClassMirror companionClassMirror = lookupClassMirror(pkg.getModule(), qualifiedCompanionClassName);
        if(companionClassMirror == null)
            throw new ModelResolutionException("Could not find companion class mirror: "+qualifiedCompanionClassName);
        ((LazyInterface)declaration).companionClass = companionClassMirror;
        return scope;
    }
   
    /**
     * Looks for a direct member of type ClassOrInterface. We're not using Class.getDirectMember()
     * because it skips object types and we want them.
     */
    public static Declaration getDirectMember(Scope container, String name) {
        if(name.isEmpty())
            return null;
        boolean wantsSetter = false;
        if(name.startsWith(Naming.Suffix.$setter$.name())){
            wantsSetter = true;
            name = name.substring(8);
        }
           
        if(Character.isDigit(name.charAt(0))){
            // this is a local type we have a different accessor for it
            return ((LocalDeclarationContainer)container).getLocalDeclaration(name);
        }
        // don't even try using getDirectMember except on Package,
        // because it will fail miserably during completion, since we
        // will for instance have only anonymous types first, before we load their anonymous values, and
        // when we go looking for them we won't be able to find them until we add their anonymous values,
        // which is too late
        if(container instanceof Package){
            // don't use Package.getMembers() as it loads the whole package
            Declaration result = container.getDirectMember(name, null, false);
            return selectTypeOrSetter(result, wantsSetter);
        }else{
            // must be a Declaration
            for(Declaration member : container.getMembers()){
                if(!member.getName().equals(name))
                    continue;
                Declaration result = selectTypeOrSetter(member, wantsSetter);
                if(result != null)
                    return result;
            }
        }
        // not found
        return null;
    }
   
    private static Declaration selectTypeOrSetter(Declaration member, boolean wantsSetter) {
        // if we found a type or a method/value we're good to go
        if (member instanceof ClassOrInterface
                || member instanceof Method) {
            return member;
        }
        // if it's a Value return its object type by preference, the member otherwise
        if (member instanceof Value){
            TypeDeclaration typeDeclaration = ((Value) member).getTypeDeclaration();
            if(typeDeclaration != null && typeDeclaration.isAnonymous())
                return typeDeclaration;
            // did we want the setter?
            if(wantsSetter)
                return ((Value)member).getSetter();
            // must be a non-object value
            return member;
        }
        return null;
    }
   
    private ClassOrInterface getContainer(Module module, ClassMirror classMirror) {
        AnnotationMirror containerAnnotation = classMirror.getAnnotation(CEYLON_CONTAINER_ANNOTATION);
        if(containerAnnotation != null){
            TypeMirror javaClassMirror = (TypeMirror)containerAnnotation.getValue("klass");
            String javaClassName = javaClassMirror.getQualifiedName();
            ClassOrInterface containerDecl = (ClassOrInterface) convertToDeclaration(module, javaClassName, DeclarationType.TYPE);
            if(containerDecl == null)
                throw new ModelResolutionException("Failed to load outer type " + javaClassName
                        + " for inner type " + classMirror.getQualifiedName().toString());
            return containerDecl;
        }else{
            return (ClassOrInterface) convertToDeclaration(module, classMirror.getEnclosingClass(), DeclarationType.TYPE);
        }
    }

    protected Declaration getOrCreateDeclaration(Module module, ClassMirror classMirror,
            DeclarationType declarationType, List<Declaration> decls, boolean[] alreadyExists) {
        alreadyExists[0] = false;
        Declaration decl = null;
        ClassType type;
        if(classMirror.isCeylonToplevelAttribute()){
            type = ClassType.ATTRIBUTE;
        }else if(classMirror.isCeylonToplevelMethod()){
            type = ClassType.METHOD;
        }else if(classMirror.isCeylonToplevelObject()){
            type = ClassType.OBJECT;
        }else if(classMirror.isInterface()){
            type = ClassType.INTERFACE;
        }else{
            type = ClassType.CLASS;
        }

        String key = classMirror.getCacheKey(module);
        // see if we already have it
        Map<String, Declaration> declarationCache = null;
        switch(type){
        case OBJECT:
            if(declarationType == DeclarationType.TYPE){
                declarationCache = typeDeclarationsByName;
                break;
            }
            // else fall-through to value
        case ATTRIBUTE:
        case METHOD:
            declarationCache = valueDeclarationsByName;
            break;
        case CLASS:
        case INTERFACE:
            declarationCache = typeDeclarationsByName;
        }
        if(declarationCache.containsKey(key)){
            alreadyExists[0] = true;
            return declarationCache.get(key);
        }
       

        checkBinaryCompatibility(classMirror);
       
        boolean isCeylon = classMirror.getAnnotation(CEYLON_CEYLON_ANNOTATION) != null;
       
        try{
            // make it
            switch(type){
            case ATTRIBUTE:
                decl = makeToplevelAttribute(classMirror);
                setDeclarationVisibility(decl, classMirror, classMirror, true);
                break;
            case METHOD:
                decl = makeToplevelMethod(classMirror);
                setDeclarationVisibility(decl, classMirror, classMirror, true);
                break;
            case OBJECT:
                // we first make a class
                Declaration objectClassDecl = makeLazyClass(classMirror, null, null);
                typeDeclarationsByName.put(key, objectClassDecl);
                decls.add(objectClassDecl);
                // then we make a value for it
                Declaration objectDecl = makeToplevelAttribute(classMirror);
                valueDeclarationsByName.put(key, objectDecl);
                decls.add(objectDecl);
                // which one did we want?
                decl = declarationType == DeclarationType.TYPE ? objectClassDecl : objectDecl;
                setDeclarationVisibility(objectClassDecl, classMirror, classMirror, true);
                setDeclarationVisibility(objectDecl, classMirror, classMirror, true);
                break;
            case CLASS:
                if(classMirror.getAnnotation(CEYLON_ALIAS_ANNOTATION) != null){
                    decl = makeClassAlias(classMirror);
                    setDeclarationVisibility(decl, classMirror, classMirror, true);
                }else if(classMirror.getAnnotation(CEYLON_TYPE_ALIAS_ANNOTATION) != null){
                    decl = makeTypeAlias(classMirror);
                    setDeclarationVisibility(decl, classMirror, classMirror, true);
                }else{
                    final List<MethodMirror> constructors = getClassConstructors(classMirror);
                    if (!constructors.isEmpty()) {
                        if (constructors.size() > 1) {
                            decl = makeOverloadedConstructor(constructors, classMirror, decls, isCeylon);
                        } else {
                            // single constructor
                            MethodMirror constructor = constructors.get(0);
                            // if the class and constructor have different visibility, we pretend there's an overload of one
                            // if it's a ceylon class we don't care that they don't match sometimes, like for inner classes
                            // where the constructor is protected because we want to use an accessor, in this case the class
                            // visibility is to be used
                            if(isCeylon || getJavaVisibility(classMirror) == getJavaVisibility(constructor)){
                                decl = makeLazyClass(classMirror, null, constructor);
                                setDeclarationVisibility(decl, classMirror, classMirror, isCeylon);
                            }else{
                                decl = makeOverloadedConstructor(constructors, classMirror, decls, isCeylon);
                            }
                        }
                    } else if(isCeylon && classMirror.getAnnotation(CEYLON_OBJECT_ANNOTATION) != null) {
                        // objects don't need overloading stuff
                        decl = makeLazyClass(classMirror, null, null);
                        setDeclarationVisibility(decl, classMirror, classMirror, isCeylon);
                    } else if(getJavaVisibility(classMirror) != JavaVisibility.PRIVATE){
                        Class klass = (Class)makeOverloadedConstructor(constructors, classMirror, decls, isCeylon);
                        decl = klass;
                        LazyClass subdecl = makeLazyClass(classMirror, klass, null);
                        // no visibility for subdecl (private)
                        subdecl.setOverloaded(true);
                        klass.getOverloads().add(subdecl);
                        decls.add(subdecl);
                    } else {
                        // private class does not need a constructor
                        decl = makeLazyClass(classMirror, null, null);
                    }
                    if (!isCeylon) {
                        setSealedFromConstructorMods(decl, constructors);
                    }
                }
                break;
            case INTERFACE:
                if(classMirror.getAnnotation(CEYLON_ALIAS_ANNOTATION) != null){
                    decl = makeInterfaceAlias(classMirror);
                }else{
                    decl = makeLazyInterface(classMirror);
                }
                setDeclarationVisibility(decl, classMirror, classMirror, isCeylon);
                break;
            }
        }catch(ModelResolutionException x){
            // FIXME: this may not be the best thing to do, perhaps we should have an erroneous Class,Interface,Method
            // etc, like javac's model does?
            decl = logModelResolutionException(x, null, "Failed to load declaration "+classMirror).getDeclaration();
        }

        // objects have special handling above
        if(type != ClassType.OBJECT){
            declarationCache.put(key, decl);
            decls.add(decl);
        }
       
        return decl;
    }
    private void setSealedFromConstructorMods(Declaration decl,
            final List<MethodMirror> constructors) {
        boolean effectivelySealed = true;
        for (MethodMirror ctor : constructors) {
            if (ctor.isPublic() || ctor.isProtected()) {
                effectivelySealed = false;
                break;
            }
        }
        if (effectivelySealed && decl instanceof Class) {
            Class type = (Class)decl;
            type.setSealed(effectivelySealed);
            if (type.getOverloads() != null) {
                for (Declaration oload : type.getOverloads()) {
                    ((Class)oload).setSealed(effectivelySealed);
                }
            }
        }
    }

    private Declaration makeOverloadedConstructor(List<MethodMirror> constructors, ClassMirror classMirror, List<Declaration> decls, boolean isCeylon) {
        // If the class has multiple constructors we make a copy of the class
        // for each one (each with it's own single constructor) and make them
        // a subclass of the original
        Class supercls = makeLazyClass(classMirror, null, null);
        // the abstraction class gets the class modifiers
        setDeclarationVisibility(supercls, classMirror, classMirror, isCeylon);
        supercls.setAbstraction(true);
        List<Declaration> overloads = new ArrayList<Declaration>(constructors.size());
        // all filtering is done in getClassConstructors
        for (MethodMirror constructor : constructors) {
            LazyClass subdecl = makeLazyClass(classMirror, supercls, constructor);
            // the subclasses class get the constructor modifiers
            setDeclarationVisibility(subdecl, constructor, classMirror, isCeylon);
            subdecl.setOverloaded(true);
            overloads.add(subdecl);
            decls.add(subdecl);
        }
        supercls.setOverloads(overloads);
        return supercls;
    }

    private void setDeclarationVisibility(Declaration decl, AccessibleMirror mirror, ClassMirror classMirror, boolean isCeylon) {
        if(isCeylon){
            decl.setShared(mirror.isPublic());
        }else{
            decl.setShared(mirror.isPublic() || (mirror.isDefaultAccess() && classMirror.isInnerClass()) || mirror.isProtected());
            decl.setPackageVisibility(mirror.isDefaultAccess());
            decl.setProtectedVisibility(mirror.isProtected());
        }
    }

    private enum JavaVisibility {
        PRIVATE, PACKAGE, PROTECTED, PUBLIC;
    }
   
    private JavaVisibility getJavaVisibility(AccessibleMirror mirror) {
        if(mirror.isPublic())
            return JavaVisibility.PUBLIC;
        if(mirror.isProtected())
            return JavaVisibility.PROTECTED;
        if(mirror.isDefaultAccess())
            return JavaVisibility.PACKAGE;
        return JavaVisibility.PRIVATE;
    }

    private Declaration makeClassAlias(ClassMirror classMirror) {
        return new LazyClassAlias(classMirror, this);
    }

    private Declaration makeTypeAlias(ClassMirror classMirror) {
        return new LazyTypeAlias(classMirror, this);
    }

    private Declaration makeInterfaceAlias(ClassMirror classMirror) {
        return new LazyInterfaceAlias(classMirror, this);
    }

    private void checkBinaryCompatibility(ClassMirror classMirror) {
        // let's not report it twice
        if(binaryCompatibilityErrorRaised)
            return;
        AnnotationMirror annotation = classMirror.getAnnotation(CEYLON_CEYLON_ANNOTATION);
        if(annotation == null)
            return; // Java class, no check
        Integer major = (Integer) annotation.getValue("major");
        if(major == null)
            major = 0;
        Integer minor = (Integer) annotation.getValue("minor");
        if(minor == null)
            minor = 0;
        if(major != Versions.JVM_BINARY_MAJOR_VERSION
                || minor != Versions.JVM_BINARY_MINOR_VERSION){
            logError("Ceylon class " + classMirror.getQualifiedName() + " was compiled by an incompatible version of the Ceylon compiler"
                    +"\nThe class was compiled using "+major+"."+minor+"."
                    +"\nThis compiler supports "+Versions.JVM_BINARY_MAJOR_VERSION+"."+Versions.JVM_BINARY_MINOR_VERSION+"."
                    +"\nPlease try to recompile your module using a compatible compiler."
                    +"\nBinary compatibility will only be supported after Ceylon 1.2.");
            binaryCompatibilityErrorRaised = true;
        }
    }

    private List<MethodMirror> getClassConstructors(ClassMirror classMirror) {
        LinkedList<MethodMirror> constructors = new LinkedList<MethodMirror>();
        boolean isFromJDK = isFromJDK(classMirror);
        for(MethodMirror methodMirror : classMirror.getDirectMethods()){
            // We skip members marked with @Ignore
            if(methodMirror.getAnnotation(CEYLON_IGNORE_ANNOTATION) != null)
                continue;
            if(!methodMirror.isConstructor())
                continue;
            // FIXME: tmp hack to skip constructors that have type params as we don't handle them yet
            if(!methodMirror.getTypeParameters().isEmpty())
                continue;
            // FIXME: temporary, because some private classes from the jdk are
            // referenced in private methods but not available
            if(isFromJDK
                    && !methodMirror.isPublic()
                    // allow protected because we can subclass them, but not package-private because we can't define
                    // classes in the jdk packages
                    && !methodMirror.isProtected())
                continue;

            // if we are expecting Ceylon code, check that we have enough reified type parameters
            if(classMirror.getAnnotation(AbstractModelLoader.CEYLON_CEYLON_ANNOTATION) != null){
                List<AnnotationMirror> tpAnnotations = getTypeParametersFromAnnotations(classMirror);
                int tpCount = tpAnnotations != null ? tpAnnotations.size() : classMirror.getTypeParameters().size();
                if(!checkReifiedTypeDescriptors(tpCount, classMirror.getQualifiedName(), methodMirror, true))
                    continue;
            }
           
            constructors.add(methodMirror);
        }
        return constructors;
    }

    private boolean checkReifiedTypeDescriptors(int tpCount, String containerName, MethodMirror methodMirror, boolean isConstructor) {
        List<VariableMirror> params = methodMirror.getParameters();
        int actualTypeDescriptorParameters = 0;
        for(VariableMirror param : params){
            if(param.getAnnotation(CEYLON_IGNORE_ANNOTATION) != null && sameType(CEYLON_TYPE_DESCRIPTOR_TYPE, param.getType())){
                actualTypeDescriptorParameters++;
            }else
                break;
        }
        if(tpCount != actualTypeDescriptorParameters){
            if(isConstructor)
                logError("Constructor for '"+containerName+"' should take "+tpCount
                        +" reified type arguments (TypeDescriptor) but has '"+actualTypeDescriptorParameters+"': skipping constructor.");
            else
                logError("Method '"+containerName+"."+methodMirror.getName()+"' should take "+tpCount
                    +" reified type arguments (TypeDescriptor) but has '"+actualTypeDescriptorParameters+"': method is invalid.");
            return false;
        }
        return true;
    }

    protected Unit getCompiledUnit(LazyPackage pkg, ClassMirror classMirror) {
        Unit unit = unitsByPackage.get(pkg);
        if(unit == null){
            unit = new Unit();
            unit.setPackage(pkg);
            unitsByPackage.put(pkg, unit);
        }
        return unit;
    }

    protected LazyValue makeToplevelAttribute(ClassMirror classMirror) {
        LazyValue value = new LazyValue(classMirror, this);
        return value;
    }

    protected LazyMethod makeToplevelMethod(ClassMirror classMirror) {
        LazyMethod method = new LazyMethod(classMirror, this);
        return method;
    }
   
    protected LazyClass makeLazyClass(ClassMirror classMirror, Class superClass, MethodMirror constructor) {
        LazyClass klass = new LazyClass(classMirror, this, superClass, constructor);
        klass.setAnonymous(classMirror.getAnnotation(CEYLON_OBJECT_ANNOTATION) != null);
        klass.setAnnotation(classMirror.getAnnotation(CEYLON_LANGUAGE_ANNOTATION_ANNOTATION) != null);
        if(klass.isCeylon())
            klass.setAbstract(classMirror.getAnnotation(CEYLON_LANGUAGE_ABSTRACT_ANNOTATION) != null
                              // for toplevel classes if the annotation is missing we respect the java abstract modifier
                              // for member classes that would be ambiguous between formal and abstract so we don't and require
                              // the model annotation
                              || (!classMirror.isInnerClass() && !classMirror.isLocalClass() && classMirror.isAbstract()));
        else
            klass.setAbstract(classMirror.isAbstract());
        klass.setFormal(classMirror.getAnnotation(CEYLON_LANGUAGE_FORMAL_ANNOTATION) != null);
        klass.setDefault(classMirror.getAnnotation(CEYLON_LANGUAGE_DEFAULT_ANNOTATION) != null);
        // hack to make Throwable sealed until ceylon/ceylon.language#408 is fixed
        klass.setSealed(classMirror.getAnnotation(CEYLON_LANGUAGE_SEALED_ANNOTATION) != null
                || "ceylon.language".equals(classMirror.getPackage().getQualifiedName()) && "Throwable".equals(classMirror.getName()));
        boolean actual = classMirror.getAnnotation(CEYLON_LANGUAGE_ACTUAL_ANNOTATION) != null;
        klass.setActual(actual);
        klass.setActualCompleter(this);
        klass.setFinal(classMirror.isFinal());
        klass.setStaticallyImportable(!klass.isCeylon() && classMirror.isStatic());
        return klass;
    }

    protected LazyInterface makeLazyInterface(ClassMirror classMirror) {
        LazyInterface iface = new LazyInterface(classMirror, this);
        iface.setSealed(classMirror.getAnnotation(CEYLON_LANGUAGE_SEALED_ANNOTATION) != null);
        iface.setDynamic(classMirror.getAnnotation(CEYLON_DYNAMIC_ANNOTATION) != null);
        iface.setStaticallyImportable(!iface.isCeylon());
        return iface;
    }

    public Declaration convertToDeclaration(Module module, String typeName, DeclarationType declarationType)  {
        synchronized(getLock()){
            // FIXME: this needs to move to the type parser and report warnings
            //This should be done where the TypeInfo annotation is parsed
            //to avoid retarded errors because of a space after a comma
            typeName = typeName.trim();
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            try{
                if ("ceylon.language.Nothing".equals(typeName)) {
                    return typeFactory.getNothingDeclaration();
                } else if ("java.lang.Throwable".equals(typeName)) {
                    // FIXME: this being here is highly dubious
                    return convertToDeclaration(modules.getLanguageModule(), "ceylon.language.Throwable", declarationType);
                } else if ("java.lang.Exception".equals(typeName)) {
                    // FIXME: this being here is highly dubious
                    return convertToDeclaration(modules.getLanguageModule(), "ceylon.language.Exception", declarationType);
                }
                ClassMirror classMirror;
                try{
                    classMirror = lookupClassMirror(module, typeName);
                }catch(NoClassDefFoundError x){
                    // FIXME: this may not be the best thing to do. If the class is not there we don't know what type of declaration
                    // to return, but perhaps if we use annotation scanner rather than reflection we can figure it out, at least
                    // in cases where the supertype is missing, which throws in reflection at class load.
                    return logModelResolutionException(x.getMessage(), null, "Unable to load type "+typeName).getDeclaration();
                }
                if (classMirror == null) {
                    // special case when bootstrapping because we may need to pull the decl from the typechecked model
                    if(isBootstrap && typeName.startsWith(CEYLON_LANGUAGE+".")){
                        Declaration languageDeclaration = findLanguageModuleDeclarationForBootstrap(typeName);
                        if(languageDeclaration != null)
                            return languageDeclaration;
                    }

                    throw new ModelResolutionException("Failed to resolve "+typeName);
                }
                // we only allow source loading when it's java code we're compiling in the same go
                // (well, technically before the ceylon code)
                if(classMirror.isLoadedFromSource() && !classMirror.isJavaSource())
                    return null;
                return convertToDeclaration(module, classMirror, declarationType);
            }finally{
                timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
            }
        }
    }

    private ProducedType newUnknownType() {
        return new UnknownType(typeFactory).getType();
    }

    protected TypeParameter safeLookupTypeParameter(Scope scope, String name) {
        TypeParameter param = lookupTypeParameter(scope, name);
        if(param == null)
            throw new ModelResolutionException("Type param "+name+" not found in "+scope);
        return param;
    }
   
    private TypeParameter lookupTypeParameter(Scope scope, String name) {
        if(scope instanceof Method){
            Method m = (Method) scope;
            for(TypeParameter param : m.getTypeParameters()){
                if(param.getName().equals(name))
                    return param;
            }
            if (!m.isToplevel()) {
                // look it up in its container
                return lookupTypeParameter(scope.getContainer(), name);
            } else {
                // not found
                return null;
            }
        }else if(scope instanceof ClassOrInterface
                || scope instanceof TypeAlias){
            TypeDeclaration decl = (TypeDeclaration) scope;
            for(TypeParameter param : decl.getTypeParameters()){
                if(param.getName().equals(name))
                    return param;
            }
            if (!decl.isToplevel()) {
                // look it up in its container
                return lookupTypeParameter(scope.getContainer(), name);
            } else {
                // not found
                return null;
            }
        }else if(scope instanceof Value
                || scope instanceof Setter){
            Declaration decl = (Declaration) scope;
            if (!decl.isToplevel()) {
                // look it up in its container
                return lookupTypeParameter(scope.getContainer(), name);
            } else {
                // not found
                return null;
            }
        }else
            throw new ModelResolutionException("Type param "+name+" lookup not supported for scope "+scope);
    }
   
    //
    // Packages
   
    public LazyPackage findExistingPackage(Module module, String pkgName) {
        synchronized(getLock()){
            String quotedPkgName = Util.quoteJavaKeywords(pkgName);
            LazyPackage pkg = findCachedPackage(module, quotedPkgName);
            if(pkg != null)
                return pkg;
            // special case for the jdk module
            String moduleName = module.getNameAsString();
            if(AbstractModelLoader.isJDKModule(moduleName)){
                if(JDKUtils.isJDKPackage(moduleName, pkgName) || JDKUtils.isOracleJDKPackage(moduleName, pkgName)){
                    return findOrCreatePackage(module, pkgName);
                }
                return null;
            }
            // only create it if it exists
            if(((LazyModule)module).containsPackage(pkgName) && loadPackage(module, pkgName, false)){
                return findOrCreatePackage(module, pkgName);
            }
            return null;
        }
    }
   
    private LazyPackage findCachedPackage(Module module, String quotedPkgName) {
        LazyPackage pkg = packagesByName.get(cacheKeyByModule(module, quotedPkgName));
        if(pkg != null){
            // only return it if it matches the module we're looking for, because if it doesn't we have an issue already logged
            // for a direct dependency on same module different versions logged, so no need to confuse this further
            if(module != null && pkg.getModule() != null && !module.equals(pkg.getModule()))
                return null;
            return pkg;
        }
        return null;
    }

    public LazyPackage findOrCreatePackage(Module module, final String pkgName)  {
        synchronized(getLock()){
            String quotedPkgName = Util.quoteJavaKeywords(pkgName);
            LazyPackage pkg = findCachedPackage(module, quotedPkgName);
            if(pkg != null)
                return pkg;
            // try to find it from the module, perhaps it already got created and we didn't catch it
            if(module instanceof LazyModule){
                pkg = (LazyPackage) ((LazyModule) module).findPackageNoLazyLoading(pkgName);
            }else if(module != null){
                pkg = (LazyPackage) module.getDirectPackage(pkgName);
            }
            boolean isNew = pkg == null;
            if(pkg == null){
                pkg = new LazyPackage(this);
                // FIXME: some refactoring needed
                pkg.setName(Arrays.asList(pkgName.split("\\.")));
            }
            packagesByName.put(cacheKeyByModule(module, quotedPkgName), pkg);

            // only bind it if we already have a module
            if(isNew && module != null){
                pkg.setModule(module);
                if(module instanceof LazyModule)
                    ((LazyModule) module).addPackage(pkg);
                else
                    module.getPackages().add(pkg);
            }

            // only load package descriptors for new packages after a certain phase
            if(packageDescriptorsNeedLoading)
                loadPackageDescriptor(pkg);

            return pkg;
        }
    }

    public void loadPackageDescriptors()  {
        synchronized(getLock()){
            for(Package pkg : packagesByName.values()){
                loadPackageDescriptor(pkg);
            }
            packageDescriptorsNeedLoading  = true;
        }
    }

    private void loadPackageDescriptor(Package pkg) {
        // Don't try to load a package descriptor for ceylon.language
        // if we're bootstrapping
        if (isBootstrap
                && pkg.getQualifiedNameString().startsWith(CEYLON_LANGUAGE)) {
            return;
        }
       
        // let's not load package descriptors for Java modules
        if(pkg.getModule() != null
                && ((LazyModule)pkg.getModule()).isJava()){
            pkg.setShared(true);
            return;
        }
        String quotedQualifiedName = Util.quoteJavaKeywords(pkg.getQualifiedNameString());
        // FIXME: not sure the toplevel package can have a package declaration
        String className = quotedQualifiedName.isEmpty()
                ? Naming.PACKAGE_DESCRIPTOR_CLASS_NAME
                : quotedQualifiedName + "." + Naming.PACKAGE_DESCRIPTOR_CLASS_NAME;
        logVerbose("[Trying to look up package from "+className+"]");
        Module module = pkg.getModule();
        if(module == null)
            throw new RuntimeException("Assertion failed: module is null for package "+pkg.getNameAsString());
        ClassMirror packageClass = loadClass(module, quotedQualifiedName, className);
        if(packageClass == null){
            logVerbose("[Failed to complete "+className+"]");
            // missing: leave it private
            return;
        }
        // did we compile it from source or class?
        if(packageClass.isLoadedFromSource()){
            // must have come from source, in which case we walked it and
            // loaded its values already
            logVerbose("[We are compiling the package "+className+"]");
            return;
        }
        loadCompiledPackage(packageClass, pkg);
    }

    private void loadCompiledPackage(ClassMirror packageClass, Package pkg) {
        String name = getAnnotationStringValue(packageClass, CEYLON_PACKAGE_ANNOTATION, "name");
        Boolean shared = getAnnotationBooleanValue(packageClass, CEYLON_PACKAGE_ANNOTATION, "shared");
        // FIXME: validate the name?
        if(name == null || name.isEmpty()){
            logWarning("Package class "+pkg.getQualifiedNameString()+" contains no name, ignoring it");
            return;
        }
        if(shared == null){
            logWarning("Package class "+pkg.getQualifiedNameString()+" contains no shared, ignoring it");
            return;
        }
        pkg.setShared(shared);
    }

    public Module lookupModuleByPackageName(String packageName) {
        for(Module module : modules.getListOfModules()){
            // don't try the default module because it will always say yes
            if(module.isDefault())
                continue;
            // skip modules we're not loading things from
            if(!Decl.equalModules(module,getLanguageModule())
                    && !isModuleInClassPath(module))
                continue;
            if(module instanceof LazyModule){
                if(((LazyModule)module).containsPackage(packageName))
                    return module;
            }else if(isSubPackage(module.getNameAsString(), packageName)){
                return module;
            }
        }
        if(JDKUtils.isJDKAnyPackage(packageName)
                || JDKUtils.isOracleJDKAnyPackage(packageName)){
            String moduleName = JDKUtils.getJDKModuleNameForPackage(packageName);
            return findModule(moduleName, JDKUtils.jdk.version);
        }
        if(packageName.startsWith("com.redhat.ceylon.compiler.java.runtime")
                || packageName.startsWith("com.redhat.ceylon.compiler.java.language")
                || packageName.startsWith("com.redhat.ceylon.compiler.java.metadata")){
            return getLanguageModule();
        }
        return modules.getDefaultModule();
    }

    private boolean isSubPackage(String moduleName, String pkgName) {
        return pkgName.equals(moduleName)
                || pkgName.startsWith(moduleName+".");
    }

    //
    // Modules
   
    /**
     * Finds or creates a new module. This is mostly useful to force creation of modules such as jdk
     * or ceylon.language modules.
     */
    protected Module findOrCreateModule(String moduleName, String version)  {
        synchronized(getLock()){
            boolean isJdk = false;
            boolean defaultModule = false;

            // make sure it isn't loaded
            Module module = getLoadedModule(moduleName);
            if(module != null)
                return module;

            if(JDKUtils.isJDKModule(moduleName) || JDKUtils.isOracleJDKModule(moduleName)){
                isJdk = true;
            }

            java.util.List<String> moduleNameList = Arrays.asList(moduleName.split("\\."));
            module = moduleManager.getOrCreateModule(moduleNameList, version);
            // make sure that when we load the ceylon language module we set it to where
            // the typechecker will look for it
            if(moduleName.equals(CEYLON_LANGUAGE)
                    && modules.getLanguageModule() == null){
                modules.setLanguageModule(module);
            }

            // TRICKY We do this only when isJava is true to prevent resetting
            // the value to false by mistake. LazyModule get's created with
            // this attribute to false by default, so it should work
            if (isJdk && module instanceof LazyModule) {
                ((LazyModule)module).setJava(true);
            }

            // FIXME: this can't be that easy.
            if(isJdk)
                module.setAvailable(true);
            module.setDefault(defaultModule);
            return module;
        }
    }

    public boolean loadCompiledModule(Module module)  {
        synchronized(getLock()){
            if(module.isDefault())
                return false;
            String pkgName = module.getNameAsString();
            if(pkgName.isEmpty())
                return false;
            String moduleClassName = pkgName + "." + Naming.MODULE_DESCRIPTOR_CLASS_NAME;
            logVerbose("[Trying to look up module from "+moduleClassName+"]");
            ClassMirror moduleClass = loadClass(module, pkgName, moduleClassName);
            if(moduleClass == null){
                // perhaps we have an old module?
                String oldModuleClassName = pkgName + "." + Naming.OLD_MODULE_DESCRIPTOR_CLASS_NAME;
                logVerbose("[Trying to look up older module descriptor from "+oldModuleClassName+"]");
                ClassMirror oldModuleClass = loadClass(module, pkgName, oldModuleClassName);
                // keep it only if it has a module annotation, otherwise it could be a normal value
                if(oldModuleClass != null && oldModuleClass.getAnnotation(CEYLON_MODULE_ANNOTATION) != null)
                    moduleClass = oldModuleClass;
            }
            if(moduleClass != null){
                // load its module annotation
                return loadCompiledModule(module, moduleClass, moduleClassName);
            }
            // give up
            return false;
        }
    }

    private boolean loadCompiledModule(Module module, ClassMirror moduleClass, String moduleClassName) {
        String name = getAnnotationStringValue(moduleClass, CEYLON_MODULE_ANNOTATION, "name");
        String version = getAnnotationStringValue(moduleClass, CEYLON_MODULE_ANNOTATION, "version");
        if(name == null || name.isEmpty()){
            logWarning("Module class "+moduleClassName+" contains no name, ignoring it");
            return false;
        }
        if(!name.equals(module.getNameAsString())){
            logWarning("Module class "+moduleClassName+" declares an invalid name: "+name+". It should be: "+module.getNameAsString());
            return false;
        }
        if(version == null || version.isEmpty()){
            logWarning("Module class "+moduleClassName+" contains no version, ignoring it");
            return false;
        }
        if(!version.equals(module.getVersion())){
            logWarning("Module class "+moduleClassName+" declares an invalid version: "+version+". It should be: "+module.getVersion());
            return false;
        }
        int major = getAnnotationIntegerValue(moduleClass, CEYLON_CEYLON_ANNOTATION, "major", 0);
        int minor = getAnnotationIntegerValue(moduleClass, CEYLON_CEYLON_ANNOTATION, "minor", 0);
        module.setMajor(major);
        module.setMinor(minor);

        List<AnnotationMirror> imports = getAnnotationArrayValue(moduleClass, CEYLON_MODULE_ANNOTATION, "dependencies");
        if(imports != null){
            for (AnnotationMirror importAttribute : imports) {
                String dependencyName = (String) importAttribute.getValue("name");
                if (dependencyName != null) {
                    String dependencyVersion = (String) importAttribute.getValue("version");

                    Module dependency = moduleManager.getOrCreateModule(ModuleManager.splitModuleName(dependencyName), dependencyVersion);

                    Boolean optionalVal = (Boolean) importAttribute.getValue("optional");

                    Boolean exportVal = (Boolean) importAttribute.getValue("export");

                    ModuleImport moduleImport = moduleManager.findImport(module, dependency);
                    if (moduleImport == null) {
                        boolean optional = optionalVal != null && optionalVal;
                        boolean export = exportVal != null && exportVal;
                        moduleImport = new ModuleImport(dependency, optional, export);
                        module.addImport(moduleImport);
                    }
                }
            }
        }
       
        module.setAvailable(true);
       
        modules.getListOfModules().add(module);
        Module languageModule = modules.getLanguageModule();
        module.setLanguageModule(languageModule);
        if(!Decl.equalModules(module, languageModule)){
            ModuleImport moduleImport = moduleManager.findImport(module, languageModule);
            if (moduleImport == null) {
                moduleImport = new ModuleImport(languageModule, false, false);
                module.addImport(moduleImport);
            }
        }
       
        return true;
   
   
    //
    // Utils for loading type info from the model
   
    @SuppressWarnings("unchecked")
    private <T> List<T> getAnnotationArrayValue(AnnotatedMirror mirror, String type, String field) {
        return (List<T>) getAnnotationValue(mirror, type, field);
    }

    @SuppressWarnings("unchecked")
    private <T> List<T> getAnnotationArrayValue(AnnotatedMirror mirror, String type) {
        return (List<T>) getAnnotationValue(mirror, type);
    }

    private String getAnnotationStringValue(AnnotatedMirror mirror, String type) {
        return getAnnotationStringValue(mirror, type, "value");
    }
   
    private String getAnnotationStringValue(AnnotatedMirror mirror, String type, String field) {
        return (String) getAnnotationValue(mirror, type, field);
    }
   
    private Boolean getAnnotationBooleanValue(AnnotatedMirror mirror, String type, String field) {
        return (Boolean) getAnnotationValue(mirror, type, field);
    }

    private int getAnnotationIntegerValue(AnnotatedMirror mirror, String type, String field, int defaultValue) {
        Integer val = (Integer) getAnnotationValue(mirror, type, field);
        return val != null ? val : defaultValue;
    }
   
    @SuppressWarnings("unchecked")
    private List<TypeMirror> getAnnotationClassValues(AnnotationMirror annotation, String field) {
        return (List<TypeMirror>)annotation.getValue(field);
    }
   
    @SuppressWarnings("unchecked")
    private List<String> getAnnotationStringValues(AnnotationMirror annotation, String field) {
        return (List<String>)annotation.getValue(field);
    }
   
    @SuppressWarnings("unchecked")
    private List<Integer> getAnnotationIntegerValues(AnnotationMirror annotation, String field) {
        return (List<Integer>)annotation.getValue(field);
    }
   
    @SuppressWarnings("unchecked")
    private List<Boolean> getAnnotationBooleanValues(AnnotationMirror annotation, String field) {
        return (List<Boolean>)annotation.getValue(field);
    }
   
    @SuppressWarnings("unchecked")
    private List<Long> getAnnotationLongValues(AnnotationMirror annotation, String field) {
        return (List<Long>)annotation.getValue(field);
    }
   
    @SuppressWarnings("unchecked")
    private List<Double> getAnnotationDoubleValues(AnnotationMirror annotation, String field) {
        return (List<Double>)annotation.getValue(field);
    }
    @SuppressWarnings("unchecked")
    private List<AnnotationMirror> getAnnotationAnnoValues(AnnotationMirror annotation, String field) {
        return (List<AnnotationMirror>)annotation.getValue(field);
    }

    private Object getAnnotationValue(AnnotatedMirror mirror, String type) {
        return getAnnotationValue(mirror, type, "value");
    }
   
    private Object getAnnotationValue(AnnotatedMirror mirror, String type, String fieldName) {
        AnnotationMirror annotation = mirror.getAnnotation(type);
        if(annotation != null){
            return annotation.getValue(fieldName);
        }
        return null;
    }

    //
    // ModelCompleter
   
    @Override
    public void complete(LazyInterface iface)  {
        synchronized(getLock()){
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            complete(iface, iface.classMirror);
            timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
        }
    }

    @Override
    public void completeTypeParameters(LazyInterface iface)  {
        synchronized(getLock()){
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            completeTypeParameters(iface, iface.classMirror);
            timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
        }
    }

    @Override
    public void complete(LazyClass klass)  {
        synchronized(getLock()){
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            complete(klass, klass.classMirror);
            timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
        }
    }

    @Override
    public void completeTypeParameters(LazyClass klass)  {
        synchronized(getLock()){
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            completeTypeParameters(klass, klass.classMirror);
            timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
        }
    }

    @Override
    public void completeTypeParameters(LazyClassAlias lazyClassAlias)  {
        synchronized(getLock()){
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            completeLazyAliasTypeParameters(lazyClassAlias, lazyClassAlias.classMirror);
            timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
        }
    }
   
    @Override
    public void completeTypeParameters(LazyInterfaceAlias lazyInterfaceAlias)  {
        synchronized(getLock()){
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            completeLazyAliasTypeParameters(lazyInterfaceAlias, lazyInterfaceAlias.classMirror);
            timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
        }
    }

    @Override
    public void completeTypeParameters(LazyTypeAlias lazyTypeAlias)  {
        synchronized(getLock()){
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            completeLazyAliasTypeParameters(lazyTypeAlias, lazyTypeAlias.classMirror);
            timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
        }
    }

    @Override
    public void complete(LazyInterfaceAlias alias)  {
        synchronized(getLock()){
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            completeLazyAlias(alias, alias.classMirror, CEYLON_ALIAS_ANNOTATION);
            timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
        }
    }
   
    @Override
    public void complete(LazyClassAlias alias)  {
        synchronized(getLock()){
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            completeLazyAlias(alias, alias.classMirror, CEYLON_ALIAS_ANNOTATION);

            // Find the instantiator method
            MethodMirror instantiator = null;
            ClassMirror instantiatorClass = alias.isToplevel() ? alias.classMirror : alias.classMirror.getEnclosingClass();
            for (MethodMirror method : instantiatorClass.getDirectMethods()) {
                // If we're finding things based on their name, shouldn't we
                // we using Naming to do it?
                if (method.getName().equals(alias.getName() + "$aliased$")) {
                    instantiator = method;
                    break;
                }
            }
            // Read the parameters from the instantiator, rather than the aliased class
            if (instantiator != null) {
                setParameters(alias, instantiator, true, alias);
            }
            timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
        }
    }

    @Override
    public void complete(LazyTypeAlias alias)  {
        synchronized(getLock()){
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            completeLazyAlias(alias, alias.classMirror, CEYLON_TYPE_ALIAS_ANNOTATION);
            timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
        }
    }

    private void completeLazyAliasTypeParameters(TypeDeclaration alias, ClassMirror mirror) {
        // type parameters
        setTypeParameters(alias, mirror, true);
    }

    private void completeLazyAlias(TypeDeclaration alias, ClassMirror mirror, String aliasAnnotationName) {
        // now resolve the extended type
        AnnotationMirror aliasAnnotation = mirror.getAnnotation(aliasAnnotationName);
        String extendedTypeString = (String) aliasAnnotation.getValue();
       
        ProducedType extendedType = decodeType(extendedTypeString, alias, Decl.getModuleContainer(alias), "alias target");
        alias.setExtendedType(extendedType);
    }

    private void completeTypeParameters(ClassOrInterface klass, ClassMirror classMirror) {
        boolean isCeylon = classMirror.getAnnotation(CEYLON_CEYLON_ANNOTATION) != null;
        setTypeParameters(klass, classMirror, isCeylon);
    }

    private void complete(ClassOrInterface klass, ClassMirror classMirror) {
        Map<MethodMirror, List<MethodMirror>> variables = new HashMap<MethodMirror, List<MethodMirror>>();
        boolean isFromJDK = isFromJDK(classMirror);
        boolean isCeylon = (classMirror.getAnnotation(CEYLON_CEYLON_ANNOTATION) != null);
       
        // now that everything has containers, do the inner classes
        if(klass instanceof Class == false || !((Class)klass).isOverloaded()){
            // this will not load inner classes of overloads, but that's fine since we want them in the
            // abstracted super class (the real one)
            addInnerClasses(klass, classMirror);
        }

        // Java classes with multiple constructors get turned into multiple Ceylon classes
        // Here we get the specific constructor that was assigned to us (if any)
        MethodMirror constructor = null;
        if (klass instanceof LazyClass) {
            constructor = ((LazyClass)klass).getConstructor();
        }
           
        // Turn a list of possibly overloaded methods into a map
        // of lists that contain methods with the same name
        Map<String, List<MethodMirror>> methods = new LinkedHashMap<String, List<MethodMirror>>();
        collectMethods(classMirror.getDirectMethods(), methods, isCeylon, isFromJDK);

        if(isCeylon && klass instanceof LazyInterface && CodegenUtil.isCompanionClassNeeded(klass)){
            ClassMirror companionClass = ((LazyInterface)klass).companionClass;
            if(companionClass != null)
                collectMethods(companionClass.getDirectMethods(), methods, isCeylon, isFromJDK);
            else
                logWarning("CompanionClass missing for "+klass);
        }

        // Add the methods
        for(List<MethodMirror> methodMirrors : methods.values()){
            boolean isOverloaded = isMethodOverloaded(methodMirrors);
           
            List<Declaration> overloads = (isOverloaded) ? new ArrayList<Declaration>(methodMirrors.size()) : null;
            for (MethodMirror methodMirror : methodMirrors) {
                String methodName = methodMirror.getName();
                // same tests as in isMethodOverloaded()
                if(methodMirror.isConstructor() || isInstantiator(methodMirror)) {
                    break;
                } else if(isGetter(methodMirror)) {
                    // simple attribute
                    addValue(klass, methodMirror, getJavaAttributeName(methodName), isCeylon);
                } else if(isSetter(methodMirror)) {
                    // We skip setters for now and handle them later
                    variables.put(methodMirror, methodMirrors);
                } else if(isHashAttribute(methodMirror)) {
                    // ERASURE
                    // Un-erasing 'hash' attribute from 'hashCode' method
                    addValue(klass, methodMirror, "hash", isCeylon);
                } else if(isStringAttribute(methodMirror)) {
                    // ERASURE
                    // Un-erasing 'string' attribute from 'toString' method
                    addValue(klass, methodMirror, "string", isCeylon);
                } else if(!methodMirror.getName().equals("hash")
                        && !methodMirror.getName().equals("string")){
                    // normal method
                    Method m = addMethod(klass, methodMirror, classMirror, isCeylon, isOverloaded);
                    if (isOverloaded) {
                        overloads.add(m);
                    }
                }
            }
           
            if (overloads != null && !overloads.isEmpty()) {
                // We create an extra "abstraction" method for overloaded methods
                Method abstractionMethod = addMethod(klass, methodMirrors.get(0), classMirror, false, false);
                abstractionMethod.setAbstraction(true);
                abstractionMethod.setOverloads(overloads);
                abstractionMethod.setType(newUnknownType());
            }
        }

        for(FieldMirror fieldMirror : classMirror.getDirectFields()){
            // We skip members marked with @Ignore
            if(fieldMirror.getAnnotation(CEYLON_IGNORE_ANNOTATION) != null)
                continue;
            if(isCeylon && fieldMirror.isStatic())
                continue;
            // FIXME: temporary, because some private classes from the jdk are
            // referenced in private methods but not available
            if(isFromJDK && !fieldMirror.isPublic())
                continue;
            String name = fieldMirror.getName();
            // skip the field if "we've already got one"
            boolean conflicts = klass.getDirectMember(name, null, false) != null
                    || "equals".equals(name)
                    || "string".equals(name)
                    || "hash".equals(name);
            if (!conflicts) {
                addValue(klass, fieldMirror.getName(), fieldMirror, isCeylon);
            }
        }

        // Having loaded methods and values, we can now set the constructor parameters
        if(constructor != null
                && (!(klass instanceof LazyClass) || !((LazyClass)klass).isAnonymous()))
            setParameters((Class)klass, constructor, isCeylon, klass);
        // Now marry-up attributes and parameters)
        if (klass instanceof Class) {
            for (Declaration m : klass.getMembers()) {
                if (Decl.isValue(m)) {
                    Value v = (Value)m;
                    Parameter p = ((Class)klass).getParameter(v.getName());
                    if (p != null) {
                        p.setHidden(true);
                    }
                }
            }
        }

        // Now mark all Values for which Setters exist as variable
        for(Entry<MethodMirror, List<MethodMirror>> setterEntry : variables.entrySet()){
            MethodMirror setter = setterEntry.getKey();
            String name = getJavaAttributeName(setter.getName());
            // make sure we handle private postfixes
            name = Util.strip(name, isCeylon, setter.isPublic());
            Declaration decl = klass.getMember(name, null, false);
            boolean foundGetter = false;
            // skip Java fields, which we only get if there is no getter method, in that case just add the setter method
            if (decl instanceof Value && decl instanceof FieldValue == false) {
                Value value = (Value)decl;
                VariableMirror setterParam = setter.getParameters().get(0);
                ProducedType paramType = obtainType(setterParam.getType(), setterParam, klass, Decl.getModuleContainer(klass), VarianceLocation.INVARIANT,
                        "setter '"+setter.getName()+"'", klass);
                // only add the setter if it has exactly the same type as the getter
                if(paramType.isExactly(value.getType())){
                    foundGetter = true;
                    value.setVariable(true);
                    if(decl instanceof JavaBeanValue)
                        ((JavaBeanValue)decl).setSetterName(setter.getName());
                    if(value.isTransient()){
                        // must be a real setter
                        makeSetter(value, null);
                    }
                }else
                    logVerbose("Setter parameter type for "+name+" does not match corresponding getter type, adding setter as a method");
            }
           
            if(!foundGetter){
                // it was not a setter, it was a method, let's add it as such
                addMethod(klass, setter, classMirror, isCeylon, false);
            }
        }

        // In some cases, where all constructors are ignored, we can end up with no constructor, so
        // pretend we have one which takes no parameters (eg. ceylon.language.String).
        if(klass instanceof Class
                && !((Class) klass).isAbstraction()
                && !klass.isAnonymous()
                && ((Class) klass).getParameterList() == null){
            ((Class) klass).setParameterList(new ParameterList());
        }
       
        setExtendedType(klass, classMirror);
        setSatisfiedTypes(klass, classMirror);
        setCaseTypes(klass, classMirror);
        setAnnotations(klass, classMirror);
       
        // local declarations come last, because they need all members to be completed first
        if(!klass.isAlias()){
            ClassMirror containerMirror = classMirror;
            if(klass instanceof LazyInterface){
                ClassMirror companionClass = ((LazyInterface) klass).companionClass;
                if(companionClass != null)
                    containerMirror = companionClass;
            }
            addLocalDeclarations((LazyContainer) klass, containerMirror, classMirror);
        }
       
        if (!isCeylon) {
            // In java, a class can inherit a public member from a non-public supertype
            for (Declaration d : klass.getMembers()) {
                if (d.isShared()) {
                    d.setVisibleScope(null);
                }
            }
        }
    }

    private boolean isMethodOverloaded(List<MethodMirror> methodMirrors) {
        // it's overloaded if we have more than one method (non constructor/value)
        boolean one = false;
        for (MethodMirror methodMirror : methodMirrors) {
            // same tests as in complete(ClassOrInterface klass, ClassMirror classMirror)
            if(methodMirror.isConstructor()
                    || isInstantiator(methodMirror)
                    || isGetter(methodMirror)
                    || isSetter(methodMirror)
                    || isHashAttribute(methodMirror)
                    || isStringAttribute(methodMirror)
                    || methodMirror.getName().equals("hash")
                    || methodMirror.getName().equals("string")){
                break;
            }
            if(one)
                return true;
            one = true;
        }
        return false;
    }

    private void collectMethods(List<MethodMirror> methodMirrors, Map<String,List<MethodMirror>> methods,
                                boolean isCeylon, boolean isFromJDK) {
        for(MethodMirror methodMirror : methodMirrors){
            // We skip members marked with @Ignore
            if(methodMirror.getAnnotation(CEYLON_IGNORE_ANNOTATION) != null)
                continue;
            if(methodMirror.isStaticInit())
                continue;
            if(isCeylon && methodMirror.isStatic())
                continue;
            // FIXME: temporary, because some private classes from the jdk are
            // referenced in private methods but not available
            if(isFromJDK && !methodMirror.isPublic())
                continue;
            String methodName = methodMirror.getName();
            List<MethodMirror> homonyms = methods.get(methodName);
            if (homonyms == null) {
                homonyms = new LinkedList<MethodMirror>();
                methods.put(methodName, homonyms);
            }
            homonyms.add(methodMirror);
        }
    }
   
    private void addLocalDeclarations(LocalDeclarationContainer container, ClassMirror classContainerMirror, AnnotatedMirror annotatedMirror) {
        if(!needsLocalDeclarations())
            return;
        AnnotationMirror annotation = annotatedMirror.getAnnotation(CEYLON_LOCAL_DECLARATIONS_ANNOTATION);
        if(annotation == null)
            return;
        List<String> values = getAnnotationStringValues(annotation, "value");
        String parentClassName = classContainerMirror.getQualifiedName();
        Package pkg = Decl.getPackageContainer(container);
        Module module = pkg.getModule();
        for(String scope : values){
            // assemble the name with the parent
            String name;
            if(scope.startsWith("::")){
                // interface pulled to toplevel
                name = pkg.getNameAsString() + "." + scope.substring(2);
            }else{
                name = parentClassName;
                name += "$" + scope;
            }
            Declaration innerDecl = convertToDeclaration(module, name, DeclarationType.TYPE);
            if(innerDecl == null)
                throw new ModelResolutionException("Failed to load local type " + name
                        + " for outer type " + container.getQualifiedNameString());
        }
    }

    private boolean isInstantiator(MethodMirror methodMirror) {
        return methodMirror.getName().endsWith("$aliased$");
    }
   
    private boolean isFromJDK(ClassMirror classMirror) {
        String pkgName = unquotePackageName(classMirror.getPackage());
        return JDKUtils.isJDKAnyPackage(pkgName) || JDKUtils.isOracleJDKAnyPackage(pkgName);
    }

    private void setAnnotations(Declaration decl, AnnotatedMirror classMirror) {
        List<AnnotationMirror> annotations = getAnnotationArrayValue(classMirror, CEYLON_ANNOTATIONS_ANNOTATION);
        if(annotations != null) {
            for(AnnotationMirror annotation : annotations){
                decl.getAnnotations().add(readModelAnnotation(annotation));
            }
        }
        // Add a ceylon deprecated("") if it's annotated with java.lang.Deprecated
        // and doesn't already have the ceylon annotation
        if (classMirror.getAnnotation(JAVA_DEPRECATED_ANNOTATION) != null) {
            boolean hasCeylonDeprecated = false;
            for(Annotation a : decl.getAnnotations()) {
                if (a.getName().equals("deprecated")) {
                    hasCeylonDeprecated = true;
                    break;
                }
            }
            if (!hasCeylonDeprecated) {
                Annotation modelAnnotation = new Annotation();
                modelAnnotation.setName("deprecated");
                modelAnnotation.getPositionalArguments().add("");
                decl.getAnnotations().add(modelAnnotation);
            }
        }
    }

    private Annotation readModelAnnotation(AnnotationMirror annotation) {
        Annotation modelAnnotation = new Annotation();
        modelAnnotation.setName((String) annotation.getValue());
        @SuppressWarnings("unchecked")
        List<String> arguments = (List<String>) annotation.getValue("arguments");
        if(arguments != null){
            modelAnnotation.getPositionalArguments().addAll(arguments);
        }else{
            @SuppressWarnings("unchecked")
            List<AnnotationMirror> namedArguments = (List<AnnotationMirror>) annotation.getValue("namedArguments");
            if(namedArguments != null){
                for(AnnotationMirror namedArgument : namedArguments){
                    String argName = (String) namedArgument.getValue("name");
                    String argValue = (String) namedArgument.getValue("value");
                    modelAnnotation.getNamedArguments().put(argName, argValue);
                }
            }
        }
        return modelAnnotation;
    }

    private void addInnerClasses(ClassOrInterface klass, ClassMirror classMirror) {
        AnnotationMirror membersAnnotation = classMirror.getAnnotation(CEYLON_MEMBERS_ANNOTATION);
        if(membersAnnotation == null)
            addInnerClassesFromMirror(klass, classMirror);
        else
            addInnerClassesFromAnnotation(klass, membersAnnotation);
    }

    private void addInnerClassesFromAnnotation(ClassOrInterface klass, AnnotationMirror membersAnnotation) {
        @SuppressWarnings("unchecked")
        List<AnnotationMirror> members = (List<AnnotationMirror>) membersAnnotation.getValue();
        for(AnnotationMirror member : members){
            TypeMirror javaClassMirror = (TypeMirror)member.getValue("klass");
            String javaClassName = javaClassMirror.getQualifiedName();
            Declaration innerDecl = convertToDeclaration(Decl.getModuleContainer(klass), javaClassName, DeclarationType.TYPE);
            if(innerDecl == null)
                throw new ModelResolutionException("Failed to load inner type " + javaClassName
                        + " for outer type " + klass.getQualifiedNameString());
        }
    }

    /**
     * Allows subclasses to do something to the class name
     */
    protected String assembleJavaClass(String javaClass, String packageName) {
        return javaClass;
    }

    private void addInnerClassesFromMirror(ClassOrInterface klass, ClassMirror classMirror) {
        boolean isJDK = isFromJDK(classMirror);
        Module module = Decl.getModule(klass);
        for(ClassMirror innerClass : classMirror.getDirectInnerClasses()){
            // We skip members marked with @Ignore
            if(innerClass.getAnnotation(CEYLON_IGNORE_ANNOTATION) != null)
                continue;
            // We skip anonymous inner classes
            if(innerClass.isAnonymous())
                continue;
            // We skip private classes, otherwise the JDK has a ton of unresolved things
            if(isJDK && !innerClass.isPublic())
                continue;
            // convert it
            convertToDeclaration(module, innerClass, DeclarationType.TYPE);
            // no need to set its container as that's now handled by convertToDeclaration
        }
    }

    private Method addMethod(ClassOrInterface klass, MethodMirror methodMirror, ClassMirror classMirror,
                             boolean isCeylon, boolean isOverloaded) {
       
        JavaMethod method = new JavaMethod(methodMirror);
        String methodName = methodMirror.getName();
       
        method.setContainer(klass);
        method.setScope(klass);
        method.setRealName(methodName);
        method.setUnit(klass.getUnit());
        method.setOverloaded(isOverloaded || isOverloadingMethod(methodMirror));
        ProducedType type = null;
        try{
            setMethodOrValueFlags(klass, methodMirror, method, isCeylon);
        }catch(ModelResolutionException x){
            // collect an error in its type
            type = logModelResolutionException(x, klass, "method '"+methodMirror.getName()+"' (checking if it is an overriding method");
        }
        if(methodName.equals("hash")
                || methodName.equals("string"))
            method.setName(methodName+"_method");
        else
            method.setName(Util.strip(methodName, isCeylon, method.isShared()));
        method.setDefaultedAnnotation(methodMirror.isDefault());

        // type params first
        setTypeParameters(method, methodMirror, isCeylon);

        // and its return type
        // do not log an additional error if we had one from checking if it was overriding
        if(type == null)
            type = obtainType(methodMirror.getReturnType(), methodMirror, method, Decl.getModuleContainer(method), VarianceLocation.COVARIANT,
                              "method '"+methodMirror.getName()+"'", klass);
        method.setType(type);
       
        // now its parameters
        if(isEqualsMethod(methodMirror))
            setEqualsParameters(method, methodMirror);
        else
            setParameters(method, methodMirror, isCeylon, klass);
       
        method.setUncheckedNullType((!isCeylon && !methodMirror.getReturnType().isPrimitive()) || isUncheckedNull(methodMirror));
        type.setRaw(isRaw(Decl.getModuleContainer(klass), methodMirror.getReturnType()));
        markDeclaredVoid(method, methodMirror);
        markUnboxed(method, methodMirror, methodMirror.getReturnType());
        markTypeErased(method, methodMirror, methodMirror.getReturnType());
        markUntrustedType(method, methodMirror, methodMirror.getReturnType());
        setAnnotations(method, methodMirror);
       
        klass.getMembers().add(method);
        DeclarationVisitor.setVisibleScope(method);
       
        addLocalDeclarations(method, classMirror, methodMirror);

        return method;
    }

    private List<ProducedType> getSignature(Declaration decl) {
        List<ProducedType> result = null;
        if (decl instanceof Functional) {
            Functional func = (Functional)decl;
            if (func.getParameterLists().size() > 0) {
                List<Parameter> params = func.getParameterLists().get(0).getParameters();
                result = new ArrayList<ProducedType>(params.size());
                for (Parameter p : params) {
                    result.add(p.getType());
                }
            }
        }
        return result;
    }
   
    private boolean isStartOfJavaBeanPropertyName(char c){
        return Character.isUpperCase(c) || c == '_';
    }

    private boolean isNonGenericMethod(MethodMirror methodMirror){
        return !methodMirror.isConstructor()
                && methodMirror.getTypeParameters().isEmpty();
    }
   
    private boolean isGetter(MethodMirror methodMirror) {
        if(!isNonGenericMethod(methodMirror))
            return false;
        String name = methodMirror.getName();
        boolean matchesGet = name.length() > 3 && name.startsWith("get")
                && isStartOfJavaBeanPropertyName(name.charAt(3))
                && !"getString".equals(name) && !"getHash".equals(name) && !"getEquals".equals(name);
        boolean matchesIs = name.length() > 2 && name.startsWith("is")
                && isStartOfJavaBeanPropertyName(name.charAt(2))
                && !"isString".equals(name) && !"isHash".equals(name) && !"isEquals".equals(name);
        boolean hasNoParams = methodMirror.getParameters().size() == 0;
        boolean hasNonVoidReturn = (methodMirror.getReturnType().getKind() != TypeKind.VOID);
        boolean hasBooleanReturn = (methodMirror.getReturnType().getKind() == TypeKind.BOOLEAN);
        return (matchesGet && hasNonVoidReturn || matchesIs && hasBooleanReturn) && hasNoParams;
    }
   
    private boolean isSetter(MethodMirror methodMirror) {
        if(!isNonGenericMethod(methodMirror))
            return false;
        String name = methodMirror.getName();
        boolean matchesSet = name.length() > 3 && name.startsWith("set")
                && isStartOfJavaBeanPropertyName(name.charAt(3))
                && !"setString".equals(name) && !"setHash".equals(name) && !"setEquals".equals(name);
        boolean hasOneParam = methodMirror.getParameters().size() == 1;
        boolean hasVoidReturn = (methodMirror.getReturnType().getKind() == TypeKind.VOID);
        return matchesSet && hasOneParam && hasVoidReturn;
    }

    private boolean isHashAttribute(MethodMirror methodMirror) {
        if(!isNonGenericMethod(methodMirror)
                || methodMirror.isStatic())
            return false;
        String name = methodMirror.getName();
        boolean matchesName = "hashCode".equals(name);
        boolean hasNoParams = methodMirror.getParameters().size() == 0;
        return matchesName && hasNoParams;
    }
   
    private boolean isStringAttribute(MethodMirror methodMirror) {
        if(!isNonGenericMethod(methodMirror)
                || methodMirror.isStatic())
            return false;
        String name = methodMirror.getName();
        boolean matchesName = "toString".equals(name);
        boolean hasNoParams = methodMirror.getParameters().size() == 0;
        return matchesName && hasNoParams;
    }

    private boolean isEqualsMethod(MethodMirror methodMirror) {
        if(!isNonGenericMethod(methodMirror)
                || methodMirror.isStatic())
            return false;
        String name = methodMirror.getName();
        if(!"equals".equals(name)
                || methodMirror.getParameters().size() != 1)
            return false;
        VariableMirror param = methodMirror.getParameters().get(0);
        return sameType(param.getType(), OBJECT_TYPE);
    }

    private void setEqualsParameters(Method decl, MethodMirror methodMirror) {
        ParameterList parameters = new ParameterList();
        decl.addParameterList(parameters);
        Parameter parameter = new Parameter();
        Value value = new Value();
        parameter.setModel(value);
        value.setInitializerParameter(parameter);
        value.setUnit(decl.getUnit());
        value.setContainer((Scope) decl);
        value.setScope((Scope) decl);
        parameter.setName("that");
        value.setName("that");
        value.setType(getNonPrimitiveType(getLanguageModule(), CEYLON_OBJECT_TYPE, decl, VarianceLocation.INVARIANT));
        parameter.setDeclaration((Declaration) decl);
        parameters.getParameters().add(parameter);
        decl.addMember(value);
    }

    private String getJavaAttributeName(String getterName) {
        if (getterName.startsWith("get") || getterName.startsWith("set")) {
            return getJavaBeanName(getterName.substring(3));
        } else if (getterName.startsWith("is")) {
            // Starts with "is"
            return getJavaBeanName(getterName.substring(2));
        } else {
            throw new RuntimeException("Illegal java getter/setter name");
        }
    }

    private String getJavaBeanName(String name) {
        // See https://github.com/ceylon/ceylon-compiler/issues/340
        // make it lowercase until the first non-uppercase
        char[] newName = name.toCharArray();
        for(int i=0;i<newName.length;i++){
            char c = newName[i];
            if(Character.isLowerCase(c)){
                // if we had more than one upper-case, we leave the last uppercase: getURLDecoder -> urlDecoder
                if(i > 1){
                    newName[i-1] = Character.toUpperCase(newName[i-1]);
                }
                break;
            }
            newName[i] = Character.toLowerCase(c);
        }
        return new String(newName);
    }

    private void addValue(ClassOrInterface klass, String ceylonName, FieldMirror fieldMirror, boolean isCeylon) {
        // make sure it's a FieldValue so we can figure it out in the backend
        Value value = new FieldValue(fieldMirror.getName());
        value.setContainer(klass);
        value.setScope(klass);
        // use the name annotation if present (used by Java arrays)
        String nameAnnotation = getAnnotationStringValue(fieldMirror, CEYLON_NAME_ANNOTATION);
        value.setName(nameAnnotation != null ? nameAnnotation : ceylonName);
        value.setUnit(klass.getUnit());
        value.setShared(fieldMirror.isPublic() || fieldMirror.isProtected() || fieldMirror.isDefaultAccess());
        value.setProtectedVisibility(fieldMirror.isProtected());
        value.setPackageVisibility(fieldMirror.isDefaultAccess());
        value.setStaticallyImportable(fieldMirror.isStatic());
        // field can't be abstract or interface, so not formal
        // can we override fields? good question. Not really, but from an external point of view?
        // FIXME: figure this out: (default)
        // FIXME: for the same reason, can it be an overriding field? (actual)
        value.setVariable(!fieldMirror.isFinal());
        // figure out if it's an enum subtype in a final static field
        if(fieldMirror.getType().getKind() == TypeKind.DECLARED
                && fieldMirror.getType().getDeclaredClass() != null
                && fieldMirror.getType().getDeclaredClass().isEnum()
                && fieldMirror.isFinal()
                && fieldMirror.isStatic())
            value.setEnumValue(true);
       
        ProducedType type = obtainType(fieldMirror.getType(), fieldMirror, klass, Decl.getModuleContainer(klass), VarianceLocation.INVARIANT,
                "field '"+value.getName()+"'", klass);
        if (value.isEnumValue()) {
            Class enumValueType = new Class();
            enumValueType.setAnonymous(true);
            enumValueType.setExtendedType(type);
            enumValueType.setContainer(value.getContainer());
            enumValueType.setScope(value.getContainer());
            enumValueType.setDeprecated(value.isDeprecated());
            enumValueType.setName(value.getName());
            enumValueType.setFinal(true);
            enumValueType.setUnit(value.getUnit());
            enumValueType.setStaticallyImportable(value.isStaticallyImportable());
            value.setType(enumValueType.getType());
            value.setUncheckedNullType(false);
        } else {
            value.setType(type);
            value.setUncheckedNullType((!isCeylon && !fieldMirror.getType().isPrimitive()) || isUncheckedNull(fieldMirror));
        }
        type.setRaw(isRaw(Decl.getModuleContainer(klass), fieldMirror.getType()));

        markUnboxed(value, null, fieldMirror.getType());
        markTypeErased(value, fieldMirror, fieldMirror.getType());
        markUntrustedType(value, fieldMirror, fieldMirror.getType());
        setAnnotations(value, fieldMirror);
        klass.getMembers().add(value);
        DeclarationVisitor.setVisibleScope(value);
    }
   
    private boolean isRaw(Module module, TypeMirror type) {
        // dirty hack to get rid of bug where calling type.isRaw() on a ceylon type we are going to compile would complete() it, which
        // would try to parse its file. For ceylon types we don't need the class file info we can query it
        // See https://github.com/ceylon/ceylon-compiler/issues/1085
        switch(type.getKind()){
        case ARRAY: // arrays are never raw
        case BOOLEAN:
        case BYTE:
        case CHAR:
        case DOUBLE:
        case ERROR:
        case FLOAT:
        case INT:
        case LONG:
        case NULL:
        case SHORT:
        case TYPEVAR:
        case VOID:
        case WILDCARD:
            return false;
        case DECLARED:
            ClassMirror klass = type.getDeclaredClass();
            if(klass.isJavaSource()){
                // I suppose this should work
                return type.isRaw();
            }
            List<String> path = new LinkedList<String>();
            String pkgName = klass.getPackage().getQualifiedName();
            String unquotedPkgName = unquotePackageName(klass.getPackage());
            String qualifiedName = klass.getQualifiedName();
            String relativeName = pkgName.isEmpty() ? qualifiedName : qualifiedName.substring(pkgName.length()+1);
            for(String name : relativeName.split("[\\$\\.]")){
                if(!name.isEmpty()){
                    path.add(0, klass.getName());
                }
            }
            if(path.size() > 1){
                // find the proper class mirror for the container
                klass = loadClass(module, pkgName, path.get(0));
                if(klass == null)
                    return false;
            }
            if(!path.isEmpty() && klass.isLoadedFromSource()){
                // we need to find its model
                Scope scope = packagesByName.get(cacheKeyByModule(module, unquotedPkgName));
                if(scope == null)
                    return false;
                for(String name : path){
                    Declaration decl = scope.getDirectMember(name, null, false);
                    if(decl == null)
                        return false;
                    // if we get a value, we want its type
                    if(Decl.isValue(decl)
                            && ((Value)decl).getTypeDeclaration().getName().equals(name))
                        decl = ((Value)decl).getTypeDeclaration();
                    if(decl instanceof TypeDeclaration == false)
                        return false;
                    scope = (TypeDeclaration)decl;
                }
                TypeDeclaration typeDecl = (TypeDeclaration) scope;
                return !typeDecl.getTypeParameters().isEmpty() && type.getTypeArguments().isEmpty();
            }
            try{
                return type.isRaw();
            }catch(Exception x){
                // ignore this exception, it's likely to be due to missing module imports and an unknown type and
                // it will be logged somewhere else
                return false;
            }
        default:
            return false;
        }
    }

    private void addValue(ClassOrInterface klass, MethodMirror methodMirror, String methodName, boolean isCeylon) {
        JavaBeanValue value = new JavaBeanValue(methodMirror);
        value.setGetterName(methodMirror.getName());
        value.setContainer(klass);
        value.setScope(klass);
        value.setUnit(klass.getUnit());
        ProducedType type = null;
        try{
            setMethodOrValueFlags(klass, methodMirror, value, isCeylon);
        }catch(ModelResolutionException x){
            // collect an error in its type
            type = logModelResolutionException(x, klass, "getter '"+methodName+"' (checking if it is an overriding method");
        }
        value.setName(Util.strip(methodName, isCeylon, value.isShared()));

        // do not log an additional error if we had one from checking if it was overriding
        if(type == null)
            type = obtainType(methodMirror.getReturnType(), methodMirror, klass, Decl.getModuleContainer(klass), VarianceLocation.INVARIANT,
                              "getter '"+methodName+"'", klass);
        value.setType(type);
        // special case for hash attributes which we want to pretend are of type long internally
        if(value.isShared() && methodName.equals("hash"))
            type.setUnderlyingType("long");
        value.setUncheckedNullType((!isCeylon && !methodMirror.getReturnType().isPrimitive()) || isUncheckedNull(methodMirror));
        type.setRaw(isRaw(Decl.getModuleContainer(klass), methodMirror.getReturnType()));

        markUnboxed(value, methodMirror, methodMirror.getReturnType());
        markTypeErased(value, methodMirror, methodMirror.getReturnType());
        markUntrustedType(value, methodMirror, methodMirror.getReturnType());
        setAnnotations(value, methodMirror);
        klass.getMembers().add(value);
        DeclarationVisitor.setVisibleScope(value);
    }

    private boolean isUncheckedNull(AnnotatedMirror methodMirror) {
        Boolean unchecked = getAnnotationBooleanValue(methodMirror, CEYLON_TYPE_INFO_ANNOTATION, "uncheckedNull");
        return unchecked != null && unchecked.booleanValue();
    }

    private void setMethodOrValueFlags(final ClassOrInterface klass, final MethodMirror methodMirror, final MethodOrValue decl, boolean isCeylon) {
        decl.setShared(methodMirror.isPublic() || methodMirror.isProtected() || methodMirror.isDefaultAccess());
        decl.setProtectedVisibility(methodMirror.isProtected());
        decl.setPackageVisibility(methodMirror.isDefaultAccess());
        if(decl instanceof Value){
            setValueTransientLateFlags((Value)decl, methodMirror, isCeylon);
        }
        if(// for class members we rely on abstract bit
           (klass instanceof Class
                   && methodMirror.isAbstract())
           // Trust the abstract bit for Java interfaces, but not for Ceylon ones
           || (klass instanceof Interface
                   && !((LazyInterface)klass).isCeylon()
                   && methodMirror.isAbstract())
           // For Ceylon interfaces we rely on annotation
           || methodMirror.getAnnotation(CEYLON_LANGUAGE_FORMAL_ANNOTATION) != null) {
            decl.setFormal(true);
        } else {
            if (// for class members we rely on final/static bits
                (klass instanceof Class
                        && !klass.isFinal() // a final class necessarily has final members
                        && !methodMirror.isFinal()
                        && !methodMirror.isStatic())
                // Java interfaces are never final
                || (klass instanceof Interface
                        && !((LazyInterface)klass).isCeylon())
                // For Ceylon interfaces we rely on annotation
                || methodMirror.getAnnotation(CEYLON_LANGUAGE_DEFAULT_ANNOTATION) != null){
                decl.setDefault(true);
            }
        }
        decl.setStaticallyImportable(methodMirror.isStatic());

        decl.setActualCompleter(this);
    }
   
    @Override
    public void completeActual(Declaration decl){
        Scope container = decl.getContainer();

        if(container instanceof ClassOrInterface){
            ClassOrInterface klass = (ClassOrInterface) container;
           
            decl.setRefinedDeclaration(decl);
            // we never consider Interface and other stuff, since we never register the actualCompleter for them
            if(decl instanceof Class){
                // Java member classes are never actual
                if(!Decl.isCeylon((Class)decl))
                    return;
                // we already set the actual bit for member classes, we just need the refined decl
                if(decl.isActual()){
                    Declaration refined = klass.getRefinedMember(decl.getName(), getSignature(decl), false);
                    decl.setRefinedDeclaration(refined);
                }
            }else{ // Method or Value
                MethodMirror methodMirror;
                if(decl instanceof JavaBeanValue)
                    methodMirror = ((JavaBeanValue) decl).mirror;
                else if(decl instanceof JavaMethod)
                    methodMirror = ((JavaMethod) decl).mirror;
                else
                    throw new ModelResolutionException("Unknown type of declaration: "+decl+": "+decl.getClass().getName());
               
                decl.setRefinedDeclaration(decl);
                // For Ceylon interfaces we rely on annotation
                if(klass instanceof LazyInterface
                        && ((LazyInterface)klass).isCeylon()){
                    boolean actual = methodMirror.getAnnotation(CEYLON_LANGUAGE_ACTUAL_ANNOTATION) != null;
                    decl.setActual(actual);
                    if(actual){
                        Declaration refined = klass.getRefinedMember(decl.getName(), getSignature(decl), false);
                        decl.setRefinedDeclaration(refined);
                    }
                }else{
                    if(isOverridingMethod(methodMirror)){
                        decl.setActual(true);
                        Declaration refined = klass.getRefinedMember(decl.getName(), getSignature(decl), false);
                        decl.setRefinedDeclaration(refined);
                    }
                }
               
                // now that we know the refined declaration, we can check for reified type param support
                // for Ceylon methods
                if(decl instanceof JavaMethod && Decl.isCeylon(klass)){
                    if(!methodMirror.getTypeParameters().isEmpty()
                            // because this requires the refined decl, we defer this check until we've set it, to not trigger
                            // lazy loading just to check.
                            && AbstractTransformer.supportsReified(decl)){
                        checkReifiedTypeDescriptors(methodMirror.getTypeParameters().size(),
                                container.getQualifiedNameString(), methodMirror, false);
                    }
                }
            }
        }
    }
   
    private void setValueTransientLateFlags(Value decl, MethodMirror methodMirror, boolean isCeylon) {
        if(isCeylon)
            decl.setTransient(methodMirror.getAnnotation(CEYLON_TRANSIENT_ANNOTATION) != null);
        else
            // all Java getters are transient, fields are not
            decl.setTransient(decl instanceof FieldValue == false);
        decl.setLate(methodMirror.getAnnotation(CEYLON_LANGUAGE_LATE_ANNOTATION) != null);
    }

    private void setExtendedType(ClassOrInterface klass, ClassMirror classMirror) {
        // look at its super type
        TypeMirror superClass = classMirror.getSuperclass();
        ProducedType extendedType;
       
        if(klass instanceof Interface){
            // interfaces need to have their superclass set to Object
            if(superClass == null || superClass.getKind() == TypeKind.NONE)
                extendedType = getNonPrimitiveType(getLanguageModule(), CEYLON_OBJECT_TYPE, klass, VarianceLocation.INVARIANT);
            else
                extendedType = getNonPrimitiveType(Decl.getModule(klass), superClass, klass, VarianceLocation.INVARIANT);
        }else if(klass instanceof Class && ((Class) klass).isOverloaded()){
            // if the class is overloaded we already have it stored
            extendedType = klass.getExtendedType();
        }else{
            String className = classMirror.getQualifiedName();
            String superClassName = superClass == null ? null : superClass.getQualifiedName();
            if(className.equals("ceylon.language.Anything")){
                // ceylon.language.Anything has no super type
                extendedType = null;
            }else if(className.equals("java.lang.Object")){
                // we pretend its superclass is something else, but note that in theory we shouldn't
                // be seeing j.l.Object at all due to unerasure
                extendedType = getNonPrimitiveType(getLanguageModule(), CEYLON_BASIC_TYPE, klass, VarianceLocation.INVARIANT);
            }else{
                // read it from annotation first
                String annotationSuperClassName = getAnnotationStringValue(classMirror, CEYLON_CLASS_ANNOTATION, "extendsType");
                if(annotationSuperClassName != null && !annotationSuperClassName.isEmpty()){
                    extendedType = decodeType(annotationSuperClassName, klass, Decl.getModuleContainer(klass),
                            "extended type");
                }else{
                    // read it from the Java super type
                    // now deal with type erasure, avoid having Object as superclass
                    if("java.lang.Object".equals(superClassName)){
                        extendedType = getNonPrimitiveType(getLanguageModule(), CEYLON_BASIC_TYPE, klass, VarianceLocation.INVARIANT);
                    } else if(superClass != null){
                        try{
                            extendedType = getNonPrimitiveType(Decl.getModule(klass), superClass, klass, VarianceLocation.INVARIANT);
                        }catch(ModelResolutionException x){
                            extendedType = logModelResolutionException(x, klass, "Error while resolving extended type of "+klass.getQualifiedNameString());
                        }
                    }else{
                        // FIXME: should this be UnknownType?
                        extendedType = null;
                    }
                }
            }
        }
        if(extendedType != null)
            klass.setExtendedType(extendedType);
    }

    private void setParameters(Functional decl, MethodMirror methodMirror, boolean isCeylon, Scope container) {
        ParameterList parameters = new ParameterList();
        parameters.setNamedParametersSupported(isCeylon);
        decl.addParameterList(parameters);
        int parameterCount = methodMirror.getParameters().size();
        int parameterIndex = 0;
       
        for(VariableMirror paramMirror : methodMirror.getParameters()){
            // ignore some parameters
            if(paramMirror.getAnnotation(CEYLON_IGNORE_ANNOTATION) != null)
                continue;
           
            boolean isLastParameter = parameterIndex == parameterCount - 1;
            boolean isVariadic = isLastParameter && methodMirror.isVariadic();
           
            String paramName = getAnnotationStringValue(paramMirror, CEYLON_NAME_ANNOTATION);
            // use whatever param name we find as default
            if(paramName == null)
                paramName = paramMirror.getName();
           
            Parameter parameter = new Parameter();
            parameter.setName(paramName);
           
            TypeMirror typeMirror = paramMirror.getType();
            Module module = Decl.getModuleContainer((Scope) decl);

            ProducedType type;
            if(isVariadic){
                // possibly make it optional
                TypeMirror variadicType = typeMirror.getComponentType();
                // we pretend it's toplevel because we want to get magic string conversion for variadic methods
                type = obtainType(Decl.getModuleContainer((Scope)decl), variadicType, (Scope)decl, TypeLocation.TOPLEVEL, VarianceLocation.CONTRAVARIANT);
                if(!isCeylon && !variadicType.isPrimitive()){
                    // Java parameters are all optional unless primitives
                    ProducedType optionalType = getOptionalType(type, module);
                    optionalType.setUnderlyingType(type.getUnderlyingType());
                    type = optionalType;
                }
                // turn it into a Sequential<T>
                type = typeFactory.getSequentialType(type);
            }else{
                type = obtainType(typeMirror, paramMirror, (Scope) decl, module, VarianceLocation.CONTRAVARIANT,
                        "parameter '"+paramName+"' of method '"+methodMirror.getName()+"'", (Declaration)decl);
                // variadic params may technically be null in Java, but it Ceylon sequenced params may not
                // so it breaks the typechecker logic for handling them, and it will always be a case of bugs
                // in the java side so let's not allow this
                if(!isCeylon && !typeMirror.isPrimitive()){
                    // Java parameters are all optional unless primitives
                    ProducedType optionalType = getOptionalType(type, module);
                    optionalType.setUnderlyingType(type.getUnderlyingType());
                    type = optionalType;
                }
            }
           
            MethodOrValue value = null;
            if (isCeylon && decl instanceof Class){
                // For a functional parameter to a class, we can just lookup the member
                value = (MethodOrValue)((Class)decl).getDirectMember(paramName, null, false);
            }
            if (value == null) {
                // So either decl is not a Class,
                // or the method or value member of decl is not shared
                AnnotationMirror functionalParameterAnnotation = paramMirror.getAnnotation(CEYLON_FUNCTIONAL_PARAMETER_ANNOTATION);
                if (functionalParameterAnnotation != null) {
                    // A functional parameter to a method
                    Method method = loadFunctionalParameter((Declaration)decl, paramName, type, (String)functionalParameterAnnotation.getValue());
                    value = method;
                    parameter.setDeclaredAnything(method.isDeclaredVoid());
                } else {
                    // A value parameter to a method
                    value = new Value();
                    value.setType(type);
                }
               
                value.setContainer((Scope) decl);
                value.setScope((Scope) decl);
                DeclarationVisitor.setVisibleScope(value);
                value.setUnit(((Element)decl).getUnit());
                value.setName(paramName);
            }
            value.setInitializerParameter(parameter);
            parameter.setModel(value);

            if(paramMirror.getAnnotation(CEYLON_SEQUENCED_ANNOTATION) != null
                    || isVariadic)
                parameter.setSequenced(true);
            if(paramMirror.getAnnotation(CEYLON_DEFAULTED_ANNOTATION) != null)
                parameter.setDefaulted(true);
            if (parameter.isSequenced() &&
                    // FIXME: store info in Sequenced
                    typeFactory.isNonemptyIterableType(parameter.getType())) {
                parameter.setAtLeastOne(true);
            }
            // if it's variadic, consider the array element type (T[] == T...) for boxing rules
            markUnboxed(value, null, isVariadic ?
                    paramMirror.getType().getComponentType()
                    : paramMirror.getType());
            parameter.setDeclaration((Declaration) decl);
            setAnnotations(value, paramMirror);
            parameters.getParameters().add(parameter);
            parameter.getDeclaration().getMembers().add(parameter.getModel());
           
            parameterIndex++;
        }
        if (decl instanceof Method) {
            // Multiple parameter lists
            AnnotationMirror functionalParameterAnnotation = methodMirror.getAnnotation(CEYLON_FUNCTIONAL_PARAMETER_ANNOTATION);
            if (functionalParameterAnnotation != null) {
                parameterNameParser.parseMpl((String)functionalParameterAnnotation.getValue(), ((Method)decl).getType().getFullType(), (Method)decl);
            }
        }
    }
    private Method loadFunctionalParameter(Declaration decl, String paramName, ProducedType type, String parameterNames) {
        Method method = new Method();
        method.setName(paramName);
        method.setUnit(decl.getUnit());
        if (parameterNames == null || parameterNames.isEmpty()) {
            // This branch is broken, but it deals with old code which lacked
            // the encoding of parameter names of functional parameters, so we'll keep it until 1.2
            method.setType(getSimpleCallableReturnType(type));
            ParameterList pl = new ParameterList();
            int count = 0;
            for (ProducedType pt : getSimpleCallableArgumentTypes(type)) {
                Parameter p = new Parameter();
                Value v = new Value();
                String name = "arg" + count++;
                p.setName(name);
                v.setName(name);
                v.setType(pt);
                v.setContainer(method);
                v.setScope(method);
                p.setModel(v);
                pl.getParameters().add(p);
                method.addMember(v);
            }
            method.addParameterList(pl);
        } else {
            try {
                parameterNameParser.parse(parameterNames, type, method);
            } catch(Exception x){
                logError(x.getClass().getSimpleName() + " while parsing parameter names of "+decl+": " + x.getMessage());
                return method;
            }
        }
        return method;
    }

    List<ProducedType> getSimpleCallableArgumentTypes(ProducedType type) {
        if(type != null
                && type.getDeclaration() instanceof ClassOrInterface
                && type.getDeclaration().getQualifiedNameString().equals(CEYLON_LANGUAGE_CALLABLE_TYPE_NAME)
                && type.getTypeArgumentList().size() >= 2)
            return flattenCallableTupleType(type.getTypeArgumentList().get(1));
        return Collections.emptyList();
    }

    List<ProducedType> flattenCallableTupleType(ProducedType tupleType) {
        if(tupleType != null
                && tupleType.getDeclaration() instanceof ClassOrInterface){
            String declName = tupleType.getDeclaration().getQualifiedNameString();
            if(declName.equals(CEYLON_LANGUAGE_TUPLE_TYPE_NAME)){
                List<ProducedType> tal = tupleType.getTypeArgumentList();
                if(tal.size() >= 3){
                    List<ProducedType> ret = flattenCallableTupleType(tal.get(2));
                    ret.add(0, tal.get(1));
                    return ret;
                }
            }else if(declName.equals(CEYLON_LANGUAGE_EMPTY_TYPE_NAME)){
                return new LinkedList<ProducedType>();
            }else if(declName.equals(CEYLON_LANGUAGE_SEQUENTIAL_TYPE_NAME)){
                LinkedList<ProducedType> ret = new LinkedList<ProducedType>();
                ret.add(tupleType);
                return ret;
            }else if(declName.equals(CEYLON_LANGUAGE_SEQUENCE_TYPE_NAME)){
                LinkedList<ProducedType> ret = new LinkedList<ProducedType>();
                ret.add(tupleType);
                return ret;
            }
        }
        return Collections.emptyList();
    }
   
    ProducedType getSimpleCallableReturnType(ProducedType type) {
        if(type != null
                && type.getDeclaration() instanceof ClassOrInterface
                && type.getDeclaration().getQualifiedNameString().equals(CEYLON_LANGUAGE_CALLABLE_TYPE_NAME)
                && !type.getTypeArgumentList().isEmpty())
            return type.getTypeArgumentList().get(0);
        return newUnknownType();
    }
   
    private ProducedType getOptionalType(ProducedType type, Module moduleScope) {
        // we do not use Unit.getOptionalType because it causes lots of lazy loading that ultimately triggers the typechecker's
        // infinite recursion loop
        List<ProducedType> list = new ArrayList<ProducedType>(2);
        list.add(typeFactory.getNullDeclaration().getType());
        list.add(type);
        UnionType ut = new UnionType(getUnitForModule(moduleScope));
        ut.setCaseTypes(list);
        return ut.getType();
    }
   
    private ProducedType logModelResolutionError(Scope container, String message) {
        return logModelResolutionException((String)null, container, message);
    }

    private ProducedType logModelResolutionException(ModelResolutionException x, Scope container, String message) {
        return logModelResolutionException(x.getMessage(), container, message);
    }
   
    private ProducedType logModelResolutionException(final String exceptionMessage, Scope container, final String message) {
        final Module module = Decl.getModuleContainer(container);
        Runnable errorReporter;
        if(module != null && !module.isDefault()){
            final StringBuilder sb = new StringBuilder();
            sb.append("Error while loading the ").append(module.getNameAsString()).append("/").append(module.getVersion());
            sb.append(" module:\n ");
            sb.append(message);
           
            if(exceptionMessage != null)
                sb.append(":\n ").append(exceptionMessage);
            errorReporter = makeModelErrorReporter(module, sb.toString());
        }else if(exceptionMessage == null){
            errorReporter = makeModelErrorReporter(message);
        }else{
            errorReporter = makeModelErrorReporter(message+": "+exceptionMessage);
        }
        UnknownType ret = new UnknownType(typeFactory);
        ret.setErrorReporter(errorReporter);
        return ret.getType();
    }

    /**
     * To be overridden by subclasses
     */
    protected Runnable makeModelErrorReporter(String message) {
        return new LogErrorRunnable(this, message);
    }
   
    /**
     * To be overridden by subclasses
     */
    protected Runnable makeModelErrorReporter(Module module, String message) {
        return new ModuleErrorAttacherRunnable(moduleManager, module, message);
    }

    private static class ModuleErrorAttacherRunnable implements Runnable {

        private Module module;
        private String message;
        private ModuleManager moduleManager;

        public ModuleErrorAttacherRunnable(ModuleManager moduleManager, Module module, String message) {
            this.moduleManager = moduleManager;
            this.module = module;
            this.message = message;
        }

        @Override
        public void run() {
            moduleManager.attachErrorToOriginalModuleImport(module, message);
        }
    }

    private static class LogErrorRunnable implements Runnable {

        private String message;
        private AbstractModelLoader modelLoader;

        public LogErrorRunnable(AbstractModelLoader modelLoader, String message) {
            this.modelLoader = modelLoader;
            this.message = message;
        }

        @Override
        public void run() {
            modelLoader.logError(message);
        }
    }

    private void markTypeErased(TypedDeclaration decl, AnnotatedMirror typedMirror, TypeMirror type) {
        if (BooleanUtil.isTrue(getAnnotationBooleanValue(typedMirror, CEYLON_TYPE_INFO_ANNOTATION, "erased"))) {
            decl.setTypeErased(true);
        } else {
            decl.setTypeErased(sameType(type, OBJECT_TYPE));
        }
    }
   
    private void markUntrustedType(TypedDeclaration decl, AnnotatedMirror typedMirror, TypeMirror type) {
        if (BooleanUtil.isTrue(getAnnotationBooleanValue(typedMirror, CEYLON_TYPE_INFO_ANNOTATION, "untrusted"))) {
            decl.setUntrustedType(true);
        }
    }
   
    private void markDeclaredVoid(Method decl, MethodMirror methodMirror) {
        if (methodMirror.isDeclaredVoid() ||
                BooleanUtil.isTrue(getAnnotationBooleanValue(methodMirror, CEYLON_TYPE_INFO_ANNOTATION, "declaredVoid"))) {
            decl.setDeclaredVoid(true);
        }
    }

    private boolean hasTypeParameterWithConstraints(TypeMirror type) {
        switch(type.getKind()){
        case BOOLEAN:
        case BYTE:
        case CHAR:
        case DOUBLE:
        case FLOAT:
        case INT:
        case LONG:
        case SHORT:
        case VOID:
        case WILDCARD:
            return false;
        case ARRAY:
            return hasTypeParameterWithConstraints(type.getComponentType());
        case DECLARED:
            for(TypeMirror ta : type.getTypeArguments()){
                if(hasTypeParameterWithConstraints(ta))
                    return true;
            }
            return false;
        case TYPEVAR:
            TypeParameterMirror typeParameter = type.getTypeParameter();
            return typeParameter != null && hasNonErasedBounds(typeParameter);
        default:
            return false;
        }
    }
   
    private void markUnboxed(TypedDeclaration decl, MethodMirror methodMirror, TypeMirror type) {
        boolean unboxed = false;
        if(type.isPrimitive()
                || type.getKind() == TypeKind.ARRAY
                || sameType(type, STRING_TYPE)
                || (methodMirror != null && methodMirror.isDeclaredVoid())) {
            unboxed = true;
        }
        decl.setUnboxed(unboxed);
    }

    @Override
    public void complete(LazyValue value)  {
        synchronized(getLock()){
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            try{
                MethodMirror meth = null;
                String getterName = Naming.getGetterName(value);
                String setterName = Naming.getSetterName(value);
                boolean toplevel = value.isToplevel();
                for (MethodMirror m : value.classMirror.getDirectMethods()) {
                    // Do not skip members marked with @Ignore, because the getter is supposed to be ignored

                    if (m.getName().equals(getterName)
                            && (!toplevel || m.isStatic())
                            && m.getParameters().size() == 0) {
                        meth = m;
                    }
                    if (m.getName().equals(setterName)
                            && (!toplevel || m.isStatic())
                            && m.getParameters().size() == 1) {
                        value.setVariable(true);
                    }
                }
                if(meth == null || meth.getReturnType() == null){
                    value.setType(logModelResolutionError(value.getContainer(), "Error while resolving toplevel attribute "+value.getQualifiedNameString()+": getter method missing"));
                    return;
                }

                value.setType(obtainType(meth.getReturnType(), meth, null, Decl.getModuleContainer(value.getContainer()), VarianceLocation.INVARIANT,
                        "toplevel attribute", value));

                setValueTransientLateFlags(value, meth, true);
                setAnnotations(value, meth);
                markUnboxed(value, meth, meth.getReturnType());

                TypeMirror setterClass = (TypeMirror) getAnnotationValue(value.classMirror, CEYLON_ATTRIBUTE_ANNOTATION, "setterClass");
                // void.class is the default value, I guess it's a primitive?
                if(setterClass != null && !setterClass.isPrimitive()){
                    ClassMirror setterClassMirror = setterClass.getDeclaredClass();
                    value.setVariable(true);
                    SetterWithLocalDeclarations setter = makeSetter(value, setterClassMirror);
                    // adding local scopes should be done last, when we have the setter, because it may be needed by container chain
                    addLocalDeclarations(value, value.classMirror, value.classMirror);
                    addLocalDeclarations(setter, setterClassMirror, setterClassMirror);
                }else if(value.isToplevel() && value.isTransient() && value.isVariable()){
                    makeSetter(value, value.classMirror);
                    // all local scopes for getter/setter are declared in the same class
                    // adding local scopes should be done last, when we have the setter, because it may be needed by container chain
                    addLocalDeclarations(value, value.classMirror, value.classMirror);
                }else{
                    // adding local scopes should be done last, when we have the setter, because it may be needed by container chain
                    addLocalDeclarations(value, value.classMirror, value.classMirror);
                }
            }finally{
                timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
            }
        }
    }

    private SetterWithLocalDeclarations makeSetter(Value value, ClassMirror classMirror) {
        SetterWithLocalDeclarations setter = new SetterWithLocalDeclarations(classMirror);
        setter.setContainer(value.getContainer());
        setter.setScope(value.getContainer());
        setter.setType(value.getType());
        setter.setName(value.getName());
        value.setSetter(setter);
        setter.setGetter(value);
        return setter;
    }

    @Override
    public void complete(LazyMethod method)  {
        synchronized(getLock()){
            timer.startIgnore(TIMER_MODEL_LOADER_CATEGORY);
            try{
                MethodMirror meth = null;
                String lookupName = method.getName();
                for(MethodMirror m : method.classMirror.getDirectMethods()){
                    // We skip members marked with @Ignore
                    if(m.getAnnotation(CEYLON_IGNORE_ANNOTATION) != null)
                        continue;

                    if(Util.strip(m.getName()).equals(lookupName)){
                        meth = m;
                        break;
                    }
                }
                if(meth == null || meth.getReturnType() == null){
                    method.setType(logModelResolutionError(method.getContainer(), "Error while resolving toplevel method "+method.getQualifiedNameString()+": static method missing"));
                    return;
                }
                // only check the static mod for toplevel classes
                if(!method.classMirror.isLocalClass() && !meth.isStatic()){
                    method.setType(logModelResolutionError(method.getContainer(), "Error while resolving toplevel method "+method.getQualifiedNameString()+": method is not static"));
                    return;
                }

                // save the method name
                method.setRealMethodName(meth.getName());

                // save the method
                method.setMethodMirror(meth);

                // type params first
                setTypeParameters(method, meth, true);

                method.setType(obtainType(meth.getReturnType(), meth, method, Decl.getModuleContainer(method), VarianceLocation.COVARIANT,
                        "toplevel method", method));
                method.setDeclaredVoid(meth.isDeclaredVoid());
                markDeclaredVoid(method, meth);
                markUnboxed(method, meth, meth.getReturnType());
                markTypeErased(method, meth, meth.getReturnType());
                markUntrustedType(method, meth, meth.getReturnType());

             // now its parameters
                setParameters(method, meth, true /* toplevel methods are always Ceylon */, method);
               
                method.setAnnotation(meth.getAnnotation(CEYLON_LANGUAGE_ANNOTATION_ANNOTATION) != null);
                setAnnotations(method, meth);

                setAnnotationConstructor(method, meth);

                addLocalDeclarations(method, method.classMirror, method.classMirror);
            }finally{
                timer.stopIgnore(TIMER_MODEL_LOADER_CATEGORY);
            }
        }
     }

    private void setAnnotationConstructor(LazyMethod method, MethodMirror meth) {
        AnnotationInvocation ai = loadAnnotationInvocation(method, method.classMirror, meth);
        if (ai != null) {
            loadAnnotationConstructorDefaultedParameters(method, meth, ai);
            ai.setConstructorDeclaration(method);
            method.setAnnotationConstructor(ai);
        }
    }
   
    private AnnotationInvocation loadAnnotationInvocation(
            LazyMethod method,
            AnnotatedMirror annoInstMirror, MethodMirror meth) {
        AnnotationInvocation ai = null;
        AnnotationMirror annotationInvocationAnnotation = null;
       
        List<AnnotationMirror> annotationTree = getAnnotationArrayValue(annoInstMirror, CEYLON_ANNOTATION_INSTANTIATION_TREE_ANNOTATION, "value");
        if (annotationTree != null
                && !annotationTree.isEmpty()) {
            annotationInvocationAnnotation = annotationTree.get(0);
        } else {
            annotationInvocationAnnotation = annoInstMirror.getAnnotation(CEYLON_ANNOTATION_INSTANTIATION_ANNOTATION);
        }
        //stringValueAnnotation = annoInstMirror.getAnnotation(CEYLON_STRING_VALUE_ANNOTATION);
        if (annotationInvocationAnnotation != null) {
            ai = new AnnotationInvocation();
            setPrimaryFromAnnotationInvocationAnnotation(annotationInvocationAnnotation, ai);
            loadAnnotationInvocationArguments(new ArrayList<AnnotationFieldName>(2), method, ai, annotationInvocationAnnotation, annotationTree, annoInstMirror);
        }
        return ai;
    }
   
    private void loadAnnotationInvocationArguments(
            List<AnnotationFieldName> path,
            LazyMethod method,
            AnnotationInvocation ai, AnnotationMirror annotationInvocationAnnotation,
            List<AnnotationMirror> annotationTree,
            AnnotatedMirror dpm) {
        @SuppressWarnings("unchecked")
        List<Short> argumentCodes = (List<Short>)annotationInvocationAnnotation.getValue(CEYLON_ANNOTATION_INSTANTIATION_ARGUMENTS_MEMBER);
        if(argumentCodes != null){
            for (int ii = 0; ii < argumentCodes.size(); ii++) {
                short code = argumentCodes.get(ii);
                AnnotationArgument argument = new AnnotationArgument();
                Parameter classParameter = ai.getParameters().get(ii);
                argument.setParameter(classParameter);
                path.add(argument);
                argument.setTerm(loadAnnotationArgumentTerm(path, method, ai, classParameter, annotationTree, dpm, code));
                path.remove(path.size()-1);
                ai.getAnnotationArguments().add(argument);
            }
        }
    }
   
    public AnnotationTerm decode(Module moduleScope, List<Parameter> sourceParameters, AnnotationInvocation info,
            Parameter parameter,
            AnnotatedMirror dpm, List<AnnotationFieldName> path, int code) {
        AnnotationTerm result;
        if (code == Short.MIN_VALUE) {
            return findLiteralAnnotationTerm(moduleScope, path, parameter, dpm);
        } else if (code < 0) {
            InvocationAnnotationTerm invocation = new InvocationAnnotationTerm();
            result = invocation;
        } else if (code >= 0 && code < 512) {
            ParameterAnnotationTerm parameterArgument = new ParameterAnnotationTerm();
            boolean spread = false;
            if (code >= 256) {
                spread = true;
                code-=256;
            }
           
            parameterArgument.setSpread(spread);
            Parameter sourceParameter = sourceParameters.get(code);
            parameterArgument.setSourceParameter(sourceParameter);
            //result.setTargetParameter(sourceParameter);
            result = parameterArgument;
        } else {
            throw new RuntimeException();
        }
        return result;
    }
   
    private AnnotationTerm loadAnnotationArgumentTerm(
            List<AnnotationFieldName> path,
            LazyMethod method,
            AnnotationInvocation ai, Parameter parameter,
            List<AnnotationMirror> annotationTree,
            AnnotatedMirror dpm,
            short code) {
        if (code < 0 && code != Short.MIN_VALUE) {
            AnnotationMirror i = annotationTree.get(-code);
            AnnotationInvocation nested = new AnnotationInvocation();
            setPrimaryFromAnnotationInvocationAnnotation(i, nested);
            loadAnnotationInvocationArguments(path, method, nested, i, annotationTree, dpm);
            InvocationAnnotationTerm term = new InvocationAnnotationTerm();
            term.setInstantiation(nested);
            return term;
        } else {
            AnnotationTerm term = decode(Decl.getModuleContainer(method), method.getParameterLists().get(0).getParameters(), ai, parameter, dpm, path, code);
            return term;
        }
    }
    private void setPrimaryFromAnnotationInvocationAnnotation(AnnotationMirror annotationInvocationAnnotation,
            AnnotationInvocation ai) {
        TypeMirror annotationType = (TypeMirror)annotationInvocationAnnotation.getValue(CEYLON_ANNOTATION_INSTANTIATION_ANNOTATION_MEMBER);
        ClassMirror annotationClassMirror = annotationType.getDeclaredClass();
        Module module = findModuleForClassMirror(annotationClassMirror);
        if (annotationClassMirror.getAnnotation(CEYLON_METHOD_ANNOTATION) != null) {
            ai.setPrimary((Method)convertToDeclaration(module, annotationClassMirror, DeclarationType.VALUE));
        } else {
            ai.setPrimary((Class)convertToDeclaration(module, annotationClassMirror, DeclarationType.TYPE));
        }
    }
   
    private void loadAnnotationConstructorDefaultedParameters(
            LazyMethod method, MethodMirror meth, AnnotationInvocation ai) {
        for (Parameter ctorParam : method.getParameterLists().get(0).getParameters()) {
            AnnotationConstructorParameter acp = new AnnotationConstructorParameter();
            acp.setParameter(ctorParam);
            if (ctorParam.isDefaulted()) {
                acp.setDefaultArgument(
                        loadAnnotationConstructorDefaultedParameter(method, meth, ctorParam, acp));
            }
            ai.getConstructorParameters().add(acp);
        }
    }
    private AnnotationTerm loadAnnotationConstructorDefaultedParameter(
            LazyMethod method,
            MethodMirror meth,
            Parameter ctorParam, AnnotationConstructorParameter acp) {
        // Find the method mirror for the DPM
        for (MethodMirror mm : method.classMirror.getDirectMethods()) {
            if (mm.getName().equals(Naming.getDefaultedParamMethodName(method, ctorParam))) {
                // Create the appropriate AnnotationTerm
                if (mm.getAnnotation(CEYLON_ANNOTATION_INSTANTIATION_ANNOTATION) != null) {
                    // If the DPM has a @AnnotationInstantiation
                    // then it must be an invocation term so recurse
                    InvocationAnnotationTerm invocationTerm = new InvocationAnnotationTerm();
                    invocationTerm.setInstantiation(loadAnnotationInvocation(method, mm, meth));
                    return invocationTerm;
                } else {
                    return loadLiteralAnnotationTerm(method, ctorParam, mm);
                }
            }
        }
        return null;
    }
    /**
     * Loads a LiteralAnnotationTerm according to the presence of
     * <ul>
     * <li>{@code @StringValue} <li>{@code @IntegerValue} <li>etc
     * </ul>
     * @param ctorParam
     * 
     */
    private LiteralAnnotationTerm loadLiteralAnnotationTerm(LazyMethod method, Parameter parameter, AnnotatedMirror mm) {
        // FIXME: store iterable info somewhere else
        boolean singleValue = !typeFactory.isIterableType(parameter.getType())
            || typeFactory.getStringDeclaration().equals(parameter.getType().getDeclaration());
        AnnotationMirror valueAnnotation = mm.getAnnotation(CEYLON_STRING_VALUE_ANNOTATION);
        if (valueAnnotation != null) {
            return readStringValuesAnnotation(valueAnnotation, singleValue);
        }
        valueAnnotation = mm.getAnnotation(CEYLON_INTEGER_VALUE_ANNOTATION);
        if (valueAnnotation != null) {
            return readIntegerValuesAnnotation(valueAnnotation, singleValue);
        }
        valueAnnotation = mm.getAnnotation(CEYLON_BOOLEAN_VALUE_ANNOTATION);
        if (valueAnnotation != null) {
            return readBooleanValuesAnnotation(valueAnnotation, singleValue);
        }
        valueAnnotation = mm.getAnnotation(CEYLON_DECLARATION_VALUE_ANNOTATION);
        if (valueAnnotation != null) {
            return readDeclarationValuesAnnotation(valueAnnotation, singleValue);
        }
        valueAnnotation = mm.getAnnotation(CEYLON_OBJECT_VALUE_ANNOTATION);
        if (valueAnnotation != null) {
            return readObjectValuesAnnotation(Decl.getModuleContainer(method), valueAnnotation, singleValue);
        }
        valueAnnotation = mm.getAnnotation(CEYLON_CHARACTER_VALUE_ANNOTATION);
        if (valueAnnotation != null) {
            return readCharacterValuesAnnotation(valueAnnotation, singleValue);
        }
        valueAnnotation = mm.getAnnotation(CEYLON_FLOAT_VALUE_ANNOTATION);
        if (valueAnnotation != null) {
            return readFloatValuesAnnotation(valueAnnotation, singleValue);
        }
        return null;
    }
    /**
     * Searches the {@code @*Exprs} for one containing a {@code @*Value}
     * whose {@code name} matches the given namePath returning the first
     * match, or null.
     */
    private LiteralAnnotationTerm findLiteralAnnotationTerm(Module moduleScope, List<AnnotationFieldName> namePath, Parameter parameter, AnnotatedMirror mm) {
        // FIXME: store info somewhere else
        boolean singeValue = !typeFactory.isIterableType(parameter.getType())
            || typeFactory.getStringDeclaration().equals(parameter.getType().getDeclaration());
        final String name = Naming.getAnnotationFieldName(namePath);
        AnnotationMirror exprsAnnotation = mm.getAnnotation(CEYLON_STRING_EXPRS_ANNOTATION);
        if (exprsAnnotation != null) {
            for (AnnotationMirror valueAnnotation : getAnnotationAnnoValues(exprsAnnotation, "value")) {
                String path = (String)valueAnnotation.getValue("name");
                if (name.equals(path)) {
                    return readStringValuesAnnotation(valueAnnotation, singeValue);
                }
            }
        }
        exprsAnnotation = mm.getAnnotation(CEYLON_INTEGER_EXPRS_ANNOTATION);
        if (exprsAnnotation != null) {
            for (AnnotationMirror valueAnnotation : getAnnotationAnnoValues(exprsAnnotation, "value")) {
                String path = (String)valueAnnotation.getValue("name");
                if (name.equals(path)) {
                    return readIntegerValuesAnnotation(valueAnnotation, singeValue);
                }
            }
        }
        exprsAnnotation = mm.getAnnotation(CEYLON_BOOLEAN_EXPRS_ANNOTATION);
        if (exprsAnnotation != null) {
            for (AnnotationMirror valueAnnotation : getAnnotationAnnoValues(exprsAnnotation, "value")) {
                String path = (String)valueAnnotation.getValue("name");
                if (name.equals(path)) {
                    return readBooleanValuesAnnotation(valueAnnotation, singeValue);
                }
            }
        }
        exprsAnnotation = mm.getAnnotation(CEYLON_DECLARATION_EXPRS_ANNOTATION);
        if (exprsAnnotation != null) {
            for (AnnotationMirror valueAnnotation : getAnnotationAnnoValues(exprsAnnotation, "value")) {
                String path = (String)valueAnnotation.getValue("name");
                if (name.equals(path)) {
                    return readDeclarationValuesAnnotation(valueAnnotation, singeValue);
                }
            }
        }
        exprsAnnotation = mm.getAnnotation(CEYLON_OBJECT_EXPRS_ANNOTATION);
        if (exprsAnnotation != null) {
            for (AnnotationMirror valueAnnotation : getAnnotationAnnoValues(exprsAnnotation, "value")) {
                String path = (String)valueAnnotation.getValue("name");
                if (name.equals(path)) {
                    return readObjectValuesAnnotation(moduleScope, valueAnnotation, singeValue);
                }
            }
        }
        exprsAnnotation = mm.getAnnotation(CEYLON_CHARACTER_EXPRS_ANNOTATION);
        if (exprsAnnotation != null) {
            for (AnnotationMirror valueAnnotation : getAnnotationAnnoValues(exprsAnnotation, "value")) {
                String path = (String)valueAnnotation.getValue("name");
                if (name.equals(path)) {
                    return readCharacterValuesAnnotation(valueAnnotation, singeValue);
                }
            }
        }
        exprsAnnotation = mm.getAnnotation(CEYLON_FLOAT_EXPRS_ANNOTATION);
        if (exprsAnnotation != null) {
            for (AnnotationMirror valueAnnotation : getAnnotationAnnoValues(exprsAnnotation, "value")) {
                String path = (String)valueAnnotation.getValue("name");
                if (name.equals(path)) {
                    return readFloatValuesAnnotation(valueAnnotation, singeValue);
                }
            }
        }
       
        return null;
    }
    private LiteralAnnotationTerm readObjectValuesAnnotation(
            Module moduleScope,
            AnnotationMirror valueAnnotation, boolean singleValue) {
        if (singleValue) {
            TypeMirror klass = getAnnotationClassValues(valueAnnotation, "value").get(0);
            ProducedType type = obtainType(moduleScope, klass, null, null, null);
            ObjectLiteralAnnotationTerm term = new ObjectLiteralAnnotationTerm(type);
            return term;
        } else {
            CollectionLiteralAnnotationTerm result = new CollectionLiteralAnnotationTerm(ObjectLiteralAnnotationTerm.FACTORY);
            for (TypeMirror klass : getAnnotationClassValues(valueAnnotation, "value")) {
                ProducedType type = obtainType(moduleScope, klass, null, null, null);
                result.addElement(new ObjectLiteralAnnotationTerm(type));
            }
            return result;
        }
    }
    private LiteralAnnotationTerm readStringValuesAnnotation(
            AnnotationMirror valueAnnotation, boolean singleValue) {
        if (singleValue) {
            String value = getAnnotationStringValues(valueAnnotation, "value").get(0);
            StringLiteralAnnotationTerm term = new StringLiteralAnnotationTerm(value);
            return term;
        } else {
            CollectionLiteralAnnotationTerm result = new CollectionLiteralAnnotationTerm(StringLiteralAnnotationTerm.FACTORY);
            for (String value : getAnnotationStringValues(valueAnnotation, "value")) {
                result.addElement(new StringLiteralAnnotationTerm(value));
            }
            return result;
        }
    }
    private LiteralAnnotationTerm readIntegerValuesAnnotation(
            AnnotationMirror valueAnnotation, boolean singleValue) {
        if (singleValue) {
            Long value = getAnnotationLongValues(valueAnnotation, "value").get(0);
            IntegerLiteralAnnotationTerm term = new IntegerLiteralAnnotationTerm(value);
            return term;
        } else {
            CollectionLiteralAnnotationTerm result = new CollectionLiteralAnnotationTerm(IntegerLiteralAnnotationTerm.FACTORY);
            for (Long value : getAnnotationLongValues(valueAnnotation, "value")) {
                result.addElement(new IntegerLiteralAnnotationTerm(value));
            }
            return result;
        }
    }
    private LiteralAnnotationTerm readCharacterValuesAnnotation(
            AnnotationMirror valueAnnotation, boolean singleValue) {
        if (singleValue) {
            Integer value = getAnnotationIntegerValues(valueAnnotation, "value").get(0);
            CharacterLiteralAnnotationTerm term = new CharacterLiteralAnnotationTerm(value);
            return term;
        } else {
            CollectionLiteralAnnotationTerm result = new CollectionLiteralAnnotationTerm(CharacterLiteralAnnotationTerm.FACTORY);
            for (Integer value : getAnnotationIntegerValues(valueAnnotation, "value")) {
                result.addElement(new CharacterLiteralAnnotationTerm(value));
            }
            return result;
        }
    }
    private LiteralAnnotationTerm readFloatValuesAnnotation(
            AnnotationMirror valueAnnotation, boolean singleValue) {
        if (singleValue) {
            Double value = getAnnotationDoubleValues(valueAnnotation, "value").get(0);
            FloatLiteralAnnotationTerm term = new FloatLiteralAnnotationTerm(value);
            return term;
        } else {
            CollectionLiteralAnnotationTerm result = new CollectionLiteralAnnotationTerm(FloatLiteralAnnotationTerm.FACTORY);
            for (Double value : getAnnotationDoubleValues(valueAnnotation, "value")) {
                result.addElement(new FloatLiteralAnnotationTerm(value));
            }
            return result;
        }
    }
    private LiteralAnnotationTerm readBooleanValuesAnnotation(
            AnnotationMirror valueAnnotation, boolean singleValue) {
        if (singleValue) {
            boolean value = getAnnotationBooleanValues(valueAnnotation, "value").get(0);
            BooleanLiteralAnnotationTerm term = new BooleanLiteralAnnotationTerm(value);
            return term;
        } else {
            CollectionLiteralAnnotationTerm result = new CollectionLiteralAnnotationTerm(BooleanLiteralAnnotationTerm.FACTORY);
            for (Boolean value : getAnnotationBooleanValues(valueAnnotation, "value")) {
                result.addElement(new BooleanLiteralAnnotationTerm(value));
            }
            return result;
        }
    }
    private LiteralAnnotationTerm readDeclarationValuesAnnotation(
            AnnotationMirror valueAnnotation, boolean singleValue) {
        if (singleValue) {
            String value = getAnnotationStringValues(valueAnnotation, "value").get(0);
            DeclarationLiteralAnnotationTerm term = new DeclarationLiteralAnnotationTerm(value);
            return term;
        } else {
            CollectionLiteralAnnotationTerm result = new CollectionLiteralAnnotationTerm(DeclarationLiteralAnnotationTerm.FACTORY);
            for (String value : getAnnotationStringValues(valueAnnotation, "value")) {
                result.addElement(new DeclarationLiteralAnnotationTerm(value));
            }
            return result;
        }
    }
   
    //
    // Satisfied Types
   
    private List<String> getSatisfiedTypesFromAnnotations(AnnotatedMirror symbol) {
        return getAnnotationArrayValue(symbol, CEYLON_SATISFIED_TYPES_ANNOTATION);
    }
   
    private void setSatisfiedTypes(ClassOrInterface klass, ClassMirror classMirror) {
        List<String> satisfiedTypes = getSatisfiedTypesFromAnnotations(classMirror);
        if(satisfiedTypes != null){
            klass.getSatisfiedTypes().addAll(getTypesList(satisfiedTypes, klass, Decl.getModuleContainer(klass), "satisfied types", klass.getQualifiedNameString()));
        }else{
            for(TypeMirror iface : classMirror.getInterfaces()){
                // ignore ReifiedType interfaces
                if(sameType(iface, CEYLON_REIFIED_TYPE_TYPE))
                    continue;
                try{
                    klass.getSatisfiedTypes().add(getNonPrimitiveType(Decl.getModule(klass), iface, klass, VarianceLocation.INVARIANT));
                }catch(ModelResolutionException x){
                    String classPackageName = unquotePackageName(classMirror.getPackage());
                    if(JDKUtils.isJDKAnyPackage(classPackageName)){
                        if(iface.getKind() == TypeKind.DECLARED){
                            // check if it's a JDK thing
                            ClassMirror ifaceClass = iface.getDeclaredClass();
                            String ifacePackageName = unquotePackageName(ifaceClass.getPackage());
                            if(JDKUtils.isOracleJDKAnyPackage(ifacePackageName)){
                                // just log and ignore it
                                logMissingOracleType(iface.getQualifiedName());
                                continue;
                            }
                        }
                    }
                }
            }
        }
    }

    //
    // Case Types
   
    private List<String> getCaseTypesFromAnnotations(AnnotatedMirror symbol) {
        return getAnnotationArrayValue(symbol, CEYLON_CASE_TYPES_ANNOTATION);
    }
   
    private String getSelfTypeFromAnnotations(AnnotatedMirror symbol) {
        return getAnnotationStringValue(symbol, CEYLON_CASE_TYPES_ANNOTATION, "of");
    }

    private void setCaseTypes(ClassOrInterface klass, ClassMirror classMirror) {
        if (classMirror.isEnum()) {
            ArrayList<ProducedType> caseTypes = new ArrayList<ProducedType>();
            for (Declaration member : klass.getMembers()) {
                if (member instanceof FieldValue
                        && ((FieldValue) member).isEnumValue()) {
                    caseTypes.add(((FieldValue)member).getType());
                }
            }
            klass.setCaseTypes(caseTypes);
        } else {
            String selfType = getSelfTypeFromAnnotations(classMirror);
            Module moduleScope = Decl.getModuleContainer(klass);
            if(selfType != null && !selfType.isEmpty()){
                ProducedType type = decodeType(selfType, klass, moduleScope, "self type");
                if(!(type.getDeclaration() instanceof TypeParameter)){
                    logError("Invalid type signature for self type of "+klass.getQualifiedNameString()+": "+selfType+" is not a type parameter");
                }else{
                    klass.setSelfType(type);
                    List<ProducedType> caseTypes = new LinkedList<ProducedType>();
                    caseTypes.add(type);
                    klass.setCaseTypes(caseTypes);
                }
            } else {
                List<String> caseTypes = getCaseTypesFromAnnotations(classMirror);
                if(caseTypes != null && !caseTypes.isEmpty()){
                    klass.setCaseTypes(getTypesList(caseTypes, klass, moduleScope, "case types", klass.getQualifiedNameString()));
                }
            }
        }
    }

    private List<ProducedType> getTypesList(List<String> caseTypes, Scope scope, Module moduleScope, String targetType, String targetName) {
        List<ProducedType> producedTypes = new LinkedList<ProducedType>();
        for(String type : caseTypes){
            producedTypes.add(decodeType(type, scope, moduleScope, targetType));
        }
        return producedTypes;
    }

    //
    // Type parameters loading

    @SuppressWarnings("unchecked")
    private List<AnnotationMirror> getTypeParametersFromAnnotations(AnnotatedMirror symbol) {
        return (List<AnnotationMirror>) getAnnotationValue(symbol, CEYLON_TYPE_PARAMETERS);
    }

    // from our annotation
    @SuppressWarnings("deprecation")
    private void setTypeParametersFromAnnotations(Scope scope, List<TypeParameter> params, AnnotatedMirror mirror,
            List<AnnotationMirror> typeParameterAnnotations, List<TypeParameterMirror> typeParameterMirrors) {
        // We must first add every type param, before we resolve the bounds, which can
        // refer to type params.
        String selfTypeName = getSelfTypeFromAnnotations(mirror);
        int i=0;
        for(AnnotationMirror typeParamAnnotation : typeParameterAnnotations){
            TypeParameter param = new TypeParameter();
            param.setUnit(((Element)scope).getUnit());
            param.setContainer(scope);
            param.setScope(scope);
            DeclarationVisitor.setVisibleScope(param);
            param.setDeclaration((Declaration) scope);
            // let's not trigger the lazy-loading if we're completing a LazyClass/LazyInterface
            if(scope instanceof LazyContainer)
                ((LazyContainer)scope).addMember(param);
            else // must be a method
                scope.getMembers().add(param);
            param.setName((String)typeParamAnnotation.getValue("value"));
            param.setExtendedType(typeFactory.getAnythingDeclaration().getType());
            if(i < typeParameterMirrors.size()){
                TypeParameterMirror typeParameterMirror = typeParameterMirrors.get(i);
                param.setNonErasedBounds(hasNonErasedBounds(typeParameterMirror));
            }
           
            String varianceName = (String) typeParamAnnotation.getValue("variance");
            if(varianceName != null){
                if(varianceName.equals("IN")){
                    param.setContravariant(true);
                }else if(varianceName.equals("OUT"))
                    param.setCovariant(true);
            }
           
            // If this is a self type param then link it to its type's declaration
            if (param.getName().equals(selfTypeName)) {
                param.setSelfTypedDeclaration((TypeDeclaration)scope);
            }
           
            params.add(param);
            i++;
        }

        Module moduleScope = Decl.getModuleContainer(scope);
        // Now all type params have been set, we can resolve the references parts
        Iterator<TypeParameter> paramsIterator = params.iterator();
        for(AnnotationMirror typeParamAnnotation : typeParameterAnnotations){
            TypeParameter param = paramsIterator.next();
           
            @SuppressWarnings("unchecked")
            List<String> satisfiesAttribute = (List<String>)typeParamAnnotation.getValue("satisfies");
            setListOfTypes(param.getSatisfiedTypes(), satisfiesAttribute, scope, moduleScope,
                    "type parameter '"+param.getName()+"' satisfied types");

            @SuppressWarnings("unchecked")
            List<String> caseTypesAttribute = (List<String>)typeParamAnnotation.getValue("caseTypes");
            if(caseTypesAttribute != null && !caseTypesAttribute.isEmpty())
                param.setCaseTypes(new LinkedList<ProducedType>());
            setListOfTypes(param.getCaseTypes(), caseTypesAttribute, scope, moduleScope,
                    "type parameter '"+param.getName()+"' case types");

            String defaultValueAttribute = (String)typeParamAnnotation.getValue("defaultValue");
            if(defaultValueAttribute != null && !defaultValueAttribute.isEmpty()){
                ProducedType decodedType = decodeType(defaultValueAttribute, scope, moduleScope,
                        "type parameter '"+param.getName()+"' defaultValue");
                param.setDefaultTypeArgument(decodedType);
                param.setDefaulted(true);
            }
        }
    }

    private boolean hasNonErasedBounds(TypeParameterMirror typeParameterMirror) {
        List<TypeMirror> bounds = typeParameterMirror.getBounds();
        // if we have at least one bound and not a single Object one
        return bounds.size() > 0
                && (bounds.size() != 1
                   || !sameType(bounds.get(0), OBJECT_TYPE));
    }

    private void setListOfTypes(List<ProducedType> destinationTypeList, List<String> serialisedTypes, Scope scope, Module moduleScope,
                                String targetType) {
        if(serialisedTypes != null){
            for (String serialisedType : serialisedTypes) {
                ProducedType decodedType = decodeType(serialisedType, scope, moduleScope, targetType);
                destinationTypeList.add(decodedType);
            }
        }
    }

    // from java type info
    @SuppressWarnings("deprecation")
    private void setTypeParameters(Scope scope, List<TypeParameter> params, List<TypeParameterMirror> typeParameters, boolean isCeylon) {
        // We must first add every type param, before we resolve the bounds, which can
        // refer to type params.
        for(TypeParameterMirror typeParam : typeParameters){
            TypeParameter param = new TypeParameter();
            param.setUnit(((Element)scope).getUnit());
            param.setContainer(scope);
            param.setScope(scope);
            DeclarationVisitor.setVisibleScope(param);
            param.setDeclaration((Declaration) scope);
            // let's not trigger the lazy-loading if we're completing a LazyClass/LazyInterface
            if(scope instanceof LazyContainer)
                ((LazyContainer)scope).addMember(param);
            else // must be a method
                scope.getMembers().add(param);
            param.setName(typeParam.getName());
            param.setExtendedType(typeFactory.getAnythingDeclaration().getType());
            params.add(param);
        }
        boolean needsObjectBounds = !isCeylon && scope instanceof Method;
        // Now all type params have been set, we can resolve the references parts
        Iterator<TypeParameter> paramsIterator = params.iterator();
        for(TypeParameterMirror typeParam : typeParameters){
            TypeParameter param = paramsIterator.next();
            List<TypeMirror> bounds = typeParam.getBounds();
            for(TypeMirror bound : bounds){
                ProducedType boundType;
                // we turn java's default upper bound java.lang.Object into ceylon.language.Object
                if(sameType(bound, OBJECT_TYPE)){
                    // avoid adding java's default upper bound if it's just there with no meaning,
                    // especially since we do not want it for types
                    if(bounds.size() == 1)
                        break;
                    boundType = getNonPrimitiveType(getLanguageModule(), CEYLON_OBJECT_TYPE, scope, VarianceLocation.INVARIANT);
                }else
                    boundType = getNonPrimitiveType(Decl.getModuleContainer(scope), bound, scope, VarianceLocation.INVARIANT);
                param.getSatisfiedTypes().add(boundType);
            }
            if(needsObjectBounds && param.getSatisfiedTypes().isEmpty()){
                ProducedType boundType = getNonPrimitiveType(getLanguageModule(), CEYLON_OBJECT_TYPE, scope, VarianceLocation.INVARIANT);
                param.getSatisfiedTypes().add(boundType);
            }
        }
    }

    // method
    private void setTypeParameters(Method method, MethodMirror methodMirror, boolean isCeylon) {
        List<TypeParameter> params = new LinkedList<TypeParameter>();
        method.setTypeParameters(params);
        List<AnnotationMirror> typeParameters = getTypeParametersFromAnnotations(methodMirror);
        if(typeParameters != null) {
            setTypeParametersFromAnnotations(method, params, methodMirror, typeParameters, methodMirror.getTypeParameters());
        } else {
            setTypeParameters(method, params, methodMirror.getTypeParameters(), isCeylon);
        }
    }

    // class
    private void setTypeParameters(TypeDeclaration klass, ClassMirror classMirror, boolean isCeylon) {
        List<AnnotationMirror> typeParameters = getTypeParametersFromAnnotations(classMirror);
        List<TypeParameterMirror> mirrorTypeParameters = classMirror.getTypeParameters();
        if(typeParameters != null) {
            if(typeParameters.isEmpty())
                return;
            List<TypeParameter> params = new ArrayList<TypeParameter>(typeParameters.size());
            klass.setTypeParameters(params);
            setTypeParametersFromAnnotations(klass, params, classMirror, typeParameters, mirrorTypeParameters);
        } else {
            if(mirrorTypeParameters.isEmpty())
                return;
            List<TypeParameter> params = new ArrayList<TypeParameter>(mirrorTypeParameters.size());
            klass.setTypeParameters(params);
            setTypeParameters(klass, params, mirrorTypeParameters, isCeylon);
        }
    }       

    //
    // TypeParsing and ModelLoader

    private ProducedType decodeType(String value, Scope scope, Module moduleScope, String targetType) {
        return decodeType(value, scope, moduleScope, targetType, null);
    }
   
    private ProducedType decodeType(String value, Scope scope, Module moduleScope, String targetType, Declaration target) {
        try{
            return typeParser.decodeType(value, scope, moduleScope, getUnitForModule(moduleScope));
        }catch(TypeParserException x){
            String text = formatTypeErrorMessage("Error while parsing type of", targetType, target, scope);
            return logModelResolutionException(x.getMessage(), scope, text);
        }catch(ModelResolutionException x){
            String text = formatTypeErrorMessage("Error while resolving type of", targetType, target, scope);
            return logModelResolutionException(x, scope, text);
        }
    }
   
    private Unit getUnitForModule(Module module) {
        List<Package> packages = module.getPackages();
        if(packages.isEmpty()){
            System.err.println("No package for module "+module.getNameAsString());
            return null;
        }
        Package pkg = packages.get(0);
        if(pkg instanceof LazyPackage == false){
            System.err.println("No lazy package for module "+module.getNameAsString());
            return null;
        }
        Unit unit = getCompiledUnit((LazyPackage) pkg, null);
        if(unit == null){
            System.err.println("No unit for module "+module.getNameAsString());
            return null;
        }
        return unit;
    }
   
    private String formatTypeErrorMessage(String prefix, String targetType, Declaration target, Scope scope) {
        String forTarget;
        if(target != null)
            forTarget = " for "+target.getQualifiedNameString();
        else if(scope != null)
            forTarget = " for "+scope.getQualifiedNameString();
        else
            forTarget = "";
        return prefix+" "+targetType+forTarget;
    }

    /** Warning: only valid for toplevel types, not for type parameters */
    private ProducedType obtainType(TypeMirror type, AnnotatedMirror symbol, Scope scope, Module moduleScope, VarianceLocation variance,
                                    String targetType, Declaration target) {
        String typeName = getAnnotationStringValue(symbol, CEYLON_TYPE_INFO_ANNOTATION);
        if (typeName != null) {
            ProducedType ret = decodeType(typeName, scope, moduleScope, targetType, target);
            // even decoded types need to fit with the reality of the underlying type
            ret.setUnderlyingType(getUnderlyingType(type, TypeLocation.TOPLEVEL));
            return ret;
        } else {
            try{
                return obtainType(moduleScope, type, scope, TypeLocation.TOPLEVEL, variance);
            }catch(ModelResolutionException x){
                String text = formatTypeErrorMessage("Error while resolving type of", targetType, target, scope);
                return logModelResolutionException(x, scope, text);
            }
        }
    }
   
    private enum TypeLocation {
        TOPLEVEL, TYPE_PARAM;
    }
   
    private enum VarianceLocation {
        /**
         * Used in parameter
         */
        CONTRAVARIANT,
        /**
         * Used in method return value
         */
        COVARIANT,
        /**
         * For field
         */
        INVARIANT;
    }

    private String getUnderlyingType(TypeMirror type, TypeLocation location){
        // don't erase to c.l.String if in a type param location
        if ((sameType(type, STRING_TYPE) && location != TypeLocation.TYPE_PARAM)
            || sameType(type, PRIM_BYTE_TYPE)
            || sameType(type, PRIM_SHORT_TYPE)
            || sameType(type, PRIM_INT_TYPE)
            || sameType(type, PRIM_FLOAT_TYPE)
            || sameType(type, PRIM_CHAR_TYPE)) {
            return type.getQualifiedName();
        }
        return null;
    }
   
    private ProducedType obtainType(Module moduleScope, TypeMirror type, Scope scope, TypeLocation location, VarianceLocation variance) {
        TypeMirror originalType = type;
        // ERASURE
        type = applyTypeMapping(type, location);
       
        ProducedType ret = getNonPrimitiveType(moduleScope, type, scope, variance);
        if (ret.getUnderlyingType() == null) {
            ret.setUnderlyingType(getUnderlyingType(originalType, location));
        }
        return ret;
    }
   
    private TypeMirror applyTypeMapping(TypeMirror type, TypeLocation location) {
        // don't erase to c.l.String if in a type param location
        if (sameType(type, STRING_TYPE) && location != TypeLocation.TYPE_PARAM) {
            return CEYLON_STRING_TYPE;
           
        } else if (sameType(type, PRIM_BOOLEAN_TYPE)) {
            return CEYLON_BOOLEAN_TYPE;
           
        } else if (sameType(type, PRIM_BYTE_TYPE)) {
            return CEYLON_BYTE_TYPE;
           
        } else if (sameType(type, PRIM_SHORT_TYPE)) {
            return CEYLON_INTEGER_TYPE;
           
        } else if (sameType(type, PRIM_INT_TYPE)) {
            return CEYLON_INTEGER_TYPE;
           
        } else if (sameType(type, PRIM_LONG_TYPE)) {
            return CEYLON_INTEGER_TYPE;
           
        } else if (sameType(type, PRIM_FLOAT_TYPE)) {
            return CEYLON_FLOAT_TYPE;
           
        } else if (sameType(type, PRIM_DOUBLE_TYPE)) {
            return CEYLON_FLOAT_TYPE;
           
        } else if (sameType(type, PRIM_CHAR_TYPE)) {
            return CEYLON_CHARACTER_TYPE;
           
        } else if (sameType(type, OBJECT_TYPE)) {
            return CEYLON_OBJECT_TYPE;
           
        } else if (sameType(type, THROWABLE_TYPE)) {
            return CEYLON_THROWABLE_TYPE;
           
        } else if (sameType(type, EXCEPTION_TYPE)) {
            return CEYLON_EXCEPTION_TYPE;
           
        } else if (type.getKind() == TypeKind.ARRAY) {

            TypeMirror ct = type.getComponentType();
           
            if (sameType(ct, PRIM_BOOLEAN_TYPE)) {
                return JAVA_BOOLEAN_ARRAY_TYPE;
            } else if (sameType(ct, PRIM_BYTE_TYPE)) {
                return JAVA_BYTE_ARRAY_TYPE;
            } else if (sameType(ct, PRIM_SHORT_TYPE)) {
                return JAVA_SHORT_ARRAY_TYPE;
            } else if (sameType(ct, PRIM_INT_TYPE)) {
                return JAVA_INT_ARRAY_TYPE;
            } else if (sameType(ct, PRIM_LONG_TYPE)) {
                return JAVA_LONG_ARRAY_TYPE;
            } else if (sameType(ct, PRIM_FLOAT_TYPE)) {
                return JAVA_FLOAT_ARRAY_TYPE;
            } else if (sameType(ct, PRIM_DOUBLE_TYPE)) {
                return JAVA_DOUBLE_ARRAY_TYPE;
            } else if (sameType(ct, PRIM_CHAR_TYPE)) {
                return JAVA_CHAR_ARRAY_TYPE;
            } else {
                // object array
                return new SimpleReflType(JAVA_LANG_OBJECT_ARRAY, SimpleReflType.Module.JDK, TypeKind.DECLARED, ct);
            }
        }
        return type;
    }
   
    private boolean sameType(TypeMirror t1, TypeMirror t2) {
        // make sure we deal with arrays which can't have a qualified name
        if(t1.getKind() == TypeKind.ARRAY){
            if(t2.getKind() != TypeKind.ARRAY)
                return false;
            return sameType(t1.getComponentType(), t2.getComponentType());
        }
        if(t2.getKind() == TypeKind.ARRAY)
            return false;
        // the rest should be OK
        return t1.getQualifiedName().equals(t2.getQualifiedName());
    }
   
    @Override
    public Declaration getDeclaration(Module module, String typeName, DeclarationType declarationType) {
        return convertToDeclaration(module, typeName, declarationType);
    }

    private ProducedType getNonPrimitiveType(Module moduleScope, TypeMirror type, Scope scope, VarianceLocation variance) {
        TypeDeclaration declaration = (TypeDeclaration) convertNonPrimitiveTypeToDeclaration(moduleScope, type, scope, DeclarationType.TYPE);
        if(declaration == null){
            throw new ModelResolutionException("Failed to find declaration for "+type.getQualifiedName());
        }
        return applyTypeArguments(moduleScope, declaration, type, scope, variance, TypeMappingMode.NORMAL, null);
    }

    private enum TypeMappingMode {
        NORMAL, GENERATOR
    }
   
    @SuppressWarnings("serial")
    private static class RecursiveTypeParameterBoundException extends RuntimeException {}
   
    private ProducedType applyTypeArguments(Module moduleScope, TypeDeclaration declaration,
                                            TypeMirror type, Scope scope, VarianceLocation variance,
                                            TypeMappingMode mode, Set<TypeDeclaration> rawDeclarationsSeen) {
        List<TypeMirror> javacTypeArguments = type.getTypeArguments();
        boolean hasTypeParameters = !declaration.getTypeParameters().isEmpty();
        boolean hasTypeArguments = !javacTypeArguments.isEmpty();
        boolean isRaw = !hasTypeArguments && hasTypeParameters;
        // if we have type arguments or type parameters (raw)
        if(hasTypeArguments || isRaw){
            // if it's raw we will need the map anyways
            if(rawDeclarationsSeen == null)
                rawDeclarationsSeen = new HashSet<TypeDeclaration>();
            // detect recursive bounds that we can't possibly satisfy, such as Foo<T extends Foo<T>>
            if(rawDeclarationsSeen != null && !rawDeclarationsSeen.add(declaration))
                throw new RecursiveTypeParameterBoundException();
            try{
                List<ProducedType> typeArguments = new ArrayList<ProducedType>(javacTypeArguments.size());
                List<TypeParameter> typeParameters = declaration.getTypeParameters();
                List<TypeParameterMirror> typeParameterMirrors = null;
                // SimpleReflType for Object and friends don't have a type, but don't need one
                if(type.getDeclaredClass() != null)
                    typeParameterMirrors = type.getDeclaredClass().getTypeParameters();
                Map<TypeParameter,SiteVariance> siteVarianceMap = null;
                int len = hasTypeArguments ? javacTypeArguments.size() : typeParameters.size();
                for(int i=0 ; i<len ; i++){
                    TypeParameter typeParameter = null;
                    if(i < typeParameters.size())
                        typeParameter = typeParameters.get(i);
                    ProducedType producedTypeArgument = null;
                    // do we have a type argument?
                    TypeMirror typeArgument = null;
                    SiteVariance siteVariance = null;
                    if(hasTypeArguments){
                        typeArgument = javacTypeArguments.get(i);
                        // if a single type argument is a wildcard and we are in a covariant location, we erase to Object
                        if(typeArgument.getKind() == TypeKind.WILDCARD){
                           
                            TypeMirror bound = typeArgument.getUpperBound();
                            if(bound != null){
                                siteVariance = SiteVariance.OUT;
                            } else {
                                bound = typeArgument.getLowerBound();
                                if(bound != null){
                                    // it has a lower bound
                                    siteVariance = SiteVariance.IN;
                                }
                            }
                            // use the bound in any case
                            typeArgument = bound;
                        }
                    }
                    // if we have no type argument, or if it's a wildcard with no bound, use the type parameter bounds if we can
                    if(typeArgument == null && typeParameterMirrors != null && i < typeParameterMirrors.size()){
                        TypeParameterMirror typeParameterMirror = typeParameterMirrors.get(i);
                        // FIXME: multiple bounds?
                        if(typeParameterMirror.getBounds().size() == 1){
                            // make sure we don't go overboard
                            if(rawDeclarationsSeen == null){
                                rawDeclarationsSeen = new HashSet<TypeDeclaration>();
                                // detect recursive bounds that we can't possibly satisfy, such as Foo<T extends Foo<T>>
                                if(!rawDeclarationsSeen.add(declaration))
                                    throw new RecursiveTypeParameterBoundException();
                            }
                            TypeMirror bound = typeParameterMirror.getBounds().get(0);
                            try{
                                producedTypeArgument = obtainTypeParameterBound(moduleScope, bound, declaration, rawDeclarationsSeen);
                                siteVariance = SiteVariance.OUT;
                            }catch(RecursiveTypeParameterBoundException x){
                                // damnit, go for Object later
                            }
                        }                                       
                    }

                    // if we have no type argument, or it was a wildcard with no bounds and we could not use the type parameter bounds,
                    // let's fall back to "out Object"
                    if(typeArgument == null && producedTypeArgument == null){
                        producedTypeArgument = typeFactory.getObjectDeclaration().getType();
                        siteVariance = SiteVariance.OUT;
                    }

                    // record use-site variance if required
                    if(!Decl.isCeylon(declaration) && siteVariance != null){
                        // lazy alloc
                        if(siteVarianceMap == null)
                            siteVarianceMap = new HashMap<TypeParameter,SiteVariance>();
                        siteVarianceMap.put(typeParameter, siteVariance);
                    }
                   
                    // in some cases we may already have a produced type argument we can use. if not let's fetch it
                    if(producedTypeArgument == null){
                        if(mode == TypeMappingMode.NORMAL)
                            producedTypeArgument = obtainType(moduleScope, typeArgument, scope, TypeLocation.TYPE_PARAM, variance);
                        else
                            producedTypeArgument = obtainTypeParameterBound(moduleScope, typeArgument, scope, rawDeclarationsSeen);
                    }
                    typeArguments.add(producedTypeArgument);
                }
                ProducedType ret = declaration.getProducedType(getQualifyingType(declaration), typeArguments);
                if(siteVarianceMap != null){
                    ret.setVarianceOverrides(siteVarianceMap);
                }
                ret.setUnderlyingType(type.getQualifiedName());
                ret.setRaw(isRaw);

                return ret;
            }finally{
                if(rawDeclarationsSeen != null)
                    rawDeclarationsSeen.remove(declaration);
            }
        }
        return declaration.getType();
    }

    private ProducedType obtainTypeParameterBound(Module moduleScope, TypeMirror type, Scope scope, Set<TypeDeclaration> rawDeclarationsSeen) {
        // type variables are never mapped
        if(type.getKind() == TypeKind.TYPEVAR){
            TypeParameterMirror typeParameter = type.getTypeParameter();
            if(!typeParameter.getBounds().isEmpty()){
                IntersectionType it = new IntersectionType(getUnitForModule(moduleScope));
                for(TypeMirror bound : typeParameter.getBounds()){
                    ProducedType boundModel = obtainTypeParameterBound(moduleScope, bound, scope, rawDeclarationsSeen);
                    it.getSatisfiedTypes().add(boundModel);
                }
                return it.getType();
            }else
                // no bound is Object
                return typeFactory.getObjectDeclaration().getType();
        }else{
            TypeMirror mappedType = applyTypeMapping(type, TypeLocation.TYPE_PARAM);

            TypeDeclaration declaration = (TypeDeclaration) convertNonPrimitiveTypeToDeclaration(moduleScope, mappedType, scope, DeclarationType.TYPE);
            if(declaration == null){
                throw new RuntimeException("Failed to find declaration for "+type);
            }

            ProducedType ret = applyTypeArguments(moduleScope, declaration, type, scope, VarianceLocation.CONTRAVARIANT, TypeMappingMode.GENERATOR, rawDeclarationsSeen);
           
            if (ret.getUnderlyingType() == null) {
                ret.setUnderlyingType(getUnderlyingType(type, TypeLocation.TYPE_PARAM));
            }
            return ret;
        }
    }
   
    private ProducedType getQualifyingType(TypeDeclaration declaration) {
        // As taken from ProducedType.getType():
        if (declaration.isMember()) {
            return((ClassOrInterface) declaration.getContainer()).getType();
        }
        return null;
    }

    @Override
    public ProducedType getType(Module module, String pkgName, String name, Scope scope)  {
        Declaration decl = getDeclaration(module, pkgName, name, scope);
        if(decl == null)
            return null;
        if(decl instanceof TypeDeclaration)
            return ((TypeDeclaration) decl).getType();
        // it's a method or non-object value, but it's not a type
        return null;
    }

    @Override
    public Declaration getDeclaration(Module module, String pkgName, String name, Scope scope)  {
        synchronized(getLock()){
            if(scope != null){
                TypeParameter typeParameter = lookupTypeParameter(scope, name);
                if(typeParameter != null)
                    return typeParameter;
            }
            if(!isBootstrap || !name.startsWith(CEYLON_LANGUAGE)) {
                if(scope != null && pkgName != null){
                    Package containingPackage = Decl.getPackageContainer(scope);
                    Package pkg = containingPackage.getModule().getPackage(pkgName);
                    String relativeName = null;
                    String unquotedName = name.replace("$", "");
                    if(!pkgName.isEmpty()){
                        if(unquotedName.startsWith(pkgName+"."))
                            relativeName = unquotedName.substring(pkgName.length()+1);
                        // else we don't try it's not in this package
                    }else
                        relativeName = unquotedName;
                    if(relativeName != null && pkg != null){
                        Declaration declaration = pkg.getDirectMember(relativeName, null, false);
                        // if we get a value, we want its type
                        if(Decl.isValue(declaration)
                                && ((Value)declaration).getTypeDeclaration().getName().equals(relativeName))
                            declaration = ((Value)declaration).getTypeDeclaration();
                        if(declaration != null)
                            return declaration;
                    }
                }
                return convertToDeclaration(module, name, DeclarationType.TYPE);
            }

            return findLanguageModuleDeclarationForBootstrap(name);
        }
    }

    private Declaration findLanguageModuleDeclarationForBootstrap(String name) {
        // make sure we don't return anything for ceylon.language
        if(name.equals(CEYLON_LANGUAGE))
            return null;
       
        // we're bootstrapping ceylon.language so we need to return the ProducedTypes straight from the model we're compiling
        Module languageModule = modules.getLanguageModule();
       
        int lastDot = name.lastIndexOf(".");
        if(lastDot == -1)
            return null;
        String pkgName = name.substring(0, lastDot);
        String simpleName = name.substring(lastDot+1);
        // Nothing is a special case with no real decl
        if(name.equals("ceylon.language.Nothing"))
            return typeFactory.getNothingDeclaration();

        // find the right package
        Package pkg = languageModule.getDirectPackage(pkgName);
        if(pkg != null){
            Declaration member = pkg.getDirectMember(simpleName, null, false);
            // if we get a value, we want its type
            if(Decl.isValue(member)
                    && ((Value)member).getTypeDeclaration().getName().equals(simpleName)){
                member = ((Value)member).getTypeDeclaration();
            }
            if(member != null)
                return member;
        }
        throw new ModelResolutionException("Failed to look up given type in language module while bootstrapping: "+name);
    }

    public void removeDeclarations(List<Declaration> declarations)  {
        synchronized(getLock()){
            // keep in sync with getOrCreateDeclaration
            for (Declaration decl : declarations) {
                String fqn = decl.getQualifiedNameString().replace("::", ".");
                Module module = Decl.getModuleContainer(decl.getContainer());
                Map<String, Declaration> firstCache = null;
                Map<String, Declaration> secondCache = null;
                if(Decl.isToplevel(decl)){
                    if(Decl.isValue(decl)){
                        firstCache = valueDeclarationsByName;
                        if(((Value)decl).getTypeDeclaration().isAnonymous())
                            secondCache = typeDeclarationsByName;
                    }else if(Decl.isMethod(decl))
                        firstCache = valueDeclarationsByName;
                }
                if(decl instanceof ClassOrInterface){
                    firstCache = typeDeclarationsByName;
                }
                // ignore declarations which we do not cache, like member method/attributes
                String key = cacheKeyByModule(module, fqn);
                if(firstCache != null) {
                    firstCache.remove(key);
                    firstCache.remove(key + "_");

                    if(secondCache != null) {
                        secondCache.remove(key);
                        secondCache.remove(key + "_");
                    }
                }

                classMirrorCache.remove(key);
                classMirrorCache.remove(key + "_");
            }
        }
    }

    private static class Stats{
        int loaded, total;
    }

    private int inspectForStats(Map<String,Declaration> cache, Map<Package, Stats> loadedByPackage){
        int loaded = 0;
        for(Declaration decl : cache.values()){
            if(decl instanceof LazyElement){
                Package pkg = getPackage(decl);
                if(pkg == null){
                    logVerbose("[Model loader stats: declaration "+decl.getName()+" has no package. Skipping.]");
                    continue;
                }
                Stats stats = loadedByPackage.get(pkg);
                if(stats == null){
                    stats = new Stats();
                    loadedByPackage.put(pkg, stats);
                }
                stats.total++;
                if(((LazyElement)decl).isLoaded()){
                    loaded++;
                    stats.loaded++;
                }
            }
        }
        return loaded;
    }

    public void printStats() {
        synchronized(getLock()){
            Map<Package, Stats> loadedByPackage = new HashMap<Package, Stats>();
            int loaded = inspectForStats(typeDeclarationsByName, loadedByPackage)
                    + inspectForStats(valueDeclarationsByName, loadedByPackage);
            logVerbose("[Model loader: "+loaded+"(loaded)/"+(typeDeclarationsByName.size()+valueDeclarationsByName.size())+"(total) declarations]");
            for(Entry<Package, Stats> packageEntry : loadedByPackage.entrySet()){
                logVerbose("[ Package "+packageEntry.getKey().getNameAsString()+": "
                        +packageEntry.getValue().loaded+"(loaded)/"+packageEntry.getValue().total+"(total) declarations]");
            }
        }
    }

    private static Package getPackage(Object decl) {
        if(decl == null)
            return null;
        if(decl instanceof Package)
            return (Package) decl;
        return getPackage(((Declaration)decl).getContainer());
    }
   
    protected void logMissingOracleType(String type) {
        logVerbose("Hopefully harmless completion failure in model loader: "+type
                +". This is most likely when the JDK depends on Oracle private classes that we can't find."
                +" As a result some model information will be incomplete.");
    }

    public void setupSourceFileObjects(List<?> treeHolders) {
    }

    public static boolean isJDKModule(String name) {
        return JDKUtils.isJDKModule(name)
                || JDKUtils.isOracleJDKModule(name);
    }
   
    @Override
    public Module getLoadedModule(String moduleName) {
        // FIXME: version?
        for(Module mod : modules.getListOfModules()){
            if(mod.getNameAsString().equals(moduleName))
                return mod;
        }
        return null;
    }

    public Module getLanguageModule() {
        return modules.getLanguageModule();
    }

    public Module findModule(String name, String version){
        if(name.equals(Module.DEFAULT_MODULE_NAME))
            return modules.getDefaultModule();
        for(Module module : modules.getListOfModules()){
            if(module.getNameAsString().equals(name)
                    && (version == null || module.getVersion() == null || version.equals(module.getVersion())))
                return module;
        }
        return null;
    }
   
    public Module getJDKBaseModule() {
        return findModule(JAVA_BASE_MODULE_NAME, JDKUtils.jdk.version);
    }

    public Module findModuleForFile(File file){
        File path = file.getParentFile();
        while (path != null) {
            String name = path.getPath().replaceAll("[\\\\/]", ".");
            Module m = getLoadedModule(name);
            if (m != null) {
                return m;
            }
            path = path.getParentFile();
        }
        return modules.getDefaultModule();
    }

    protected abstract Module findModuleForClassMirror(ClassMirror classMirror);
   
    protected boolean isTypeHidden(Module module, String qualifiedName){
        return module.getNameAsString().equals(JAVA_BASE_MODULE_NAME)
                && qualifiedName.equals("java.lang.Object");
    }
   
    public Package findPackage(String quotedPkgName)  {
        synchronized(getLock()){
            String pkgName = quotedPkgName.replace("$", "");
            // in theory we only have one package with the same name per module in javac
            for(Package pkg : packagesByName.values()){
                if(pkg.getNameAsString().equals(pkgName))
                    return pkg;
            }
            return null;
        }
    }

    /**
     * See explanation in cacheModulelessPackages() below. This is called by LanguageCompiler during loadCompiledModules().
     */
    public LazyPackage findOrCreateModulelessPackage(String pkgName)  {
        synchronized(getLock()){
            LazyPackage pkg = modulelessPackages.get(pkgName);
            if(pkg != null)
                return pkg;
            pkg = new LazyPackage(this);
            // FIXME: some refactoring needed
            pkg.setName(pkgName == null ? Collections.<String>emptyList() : Arrays.asList(pkgName.split("\\.")));
            modulelessPackages.put(pkgName, pkg);
            return pkg;
        }
    }
   
    /**
     * Stef: this sucks balls, but the typechecker wants Packages created before we have any Module set up, including for parsing a module
     * file, and because the model loader looks up packages and caches them using their modules, we can't really have packages before we
     * have modules. Rather than rewrite the typechecker, we create moduleless packages during parsing, which means they are not cached with
     * their modules, and after the loadCompiledModules step above, we fix the package modules. Remains to be done is to move the packages
     * created from their cache to the right per-module cache.
     */
    public void cacheModulelessPackages() {
        synchronized(getLock()){
            for(LazyPackage pkg : modulelessPackages.values()){
                String quotedPkgName = Util.quoteJavaKeywords(pkg.getQualifiedNameString());
                packagesByName.put(cacheKeyByModule(pkg.getModule(), quotedPkgName), pkg);
            }
            modulelessPackages.clear();
        }
    }

    /**
     * Stef: after a lot of attempting, I failed to make the CompilerModuleManager produce a LazyPackage when the ModuleManager.initCoreModules
     * is called for the default package. Because it is called by the PhasedUnits constructor, which is called by the ModelLoader constructor,
     * which means the model loader is not yet in the context, so the CompilerModuleManager can't obtain it to pass it to the LazyPackage
     * constructor. A rewrite of the logic of the typechecker scanning would fix this, but at this point it's just faster to let it create
     * the wrong default package and fix it before we start parsing anything.
     */
    public void fixDefaultPackage()  {
        synchronized(getLock()){
            Module defaultModule = modules.getDefaultModule();
            Package defaultPackage = defaultModule.getDirectPackage("");
            if(defaultPackage instanceof LazyPackage == false){
                LazyPackage newPkg = findOrCreateModulelessPackage("");
                List<Package> defaultModulePackages = defaultModule.getPackages();
                if(defaultModulePackages.size() != 1)
                    throw new RuntimeException("Assertion failed: default module has more than the default package: "+defaultModulePackages.size());
                defaultModulePackages.clear();
                defaultModulePackages.add(newPkg);
                newPkg.setModule(defaultModule);
                defaultPackage.setModule(null);
            }
        }
    }

    public boolean isImported(Module moduleScope, Module importedModule) {
        if(Decl.equalModules(moduleScope, importedModule))
            return true;
        if(isImportedSpecialRules(moduleScope, importedModule))
            return true;
        Set<Module> visited = new HashSet<Module>();
        visited.add(moduleScope);
        for(ModuleImport imp : moduleScope.getImports()){
            if(Decl.equalModules(imp.getModule(), importedModule))
                return true;
            if(imp.isExport() && isImportedTransitively(imp.getModule(), importedModule, visited))
                return true;
        }
        return false;
    }

    private boolean isImportedSpecialRules(Module moduleScope, Module importedModule) {
        String importedModuleName = importedModule.getNameAsString();
        // every Java module imports the JDK
        // ceylon.language imports the JDK
        if((moduleScope.isJava()
                || Decl.equalModules(moduleScope, getLanguageModule()))
                && (JDKUtils.isJDKModule(importedModuleName)
                   || JDKUtils.isOracleJDKModule(importedModuleName)))
            return true;
        // everyone imports the language module
        if(Decl.equalModules(importedModule, getLanguageModule()))
            return true;
        if(Decl.equalModules(moduleScope, getLanguageModule())){
            // this really sucks, I suppose we should set that up better somewhere else
            if((importedModuleName.equals("com.redhat.ceylon.compiler.java")
                || importedModuleName.equals("com.redhat.ceylon.typechecker")
                || importedModuleName.equals("com.redhat.ceylon.common")
                || importedModuleName.equals("com.redhat.ceylon.module-resolver"))
                && importedModule.getVersion().equals(Versions.CEYLON_VERSION_NUMBER))
                return true;
            if(importedModuleName.equals("org.jboss.modules")
                    && importedModule.getVersion().equals("1.3.3.Final"))
                return true;
        }
        return false;
    }
   
    private boolean isImportedTransitively(Module moduleScope, Module importedModule, Set<Module> visited) {
        if(!visited.add(moduleScope))
            return false;
        for(ModuleImport imp : moduleScope.getImports()){
            // only consider exported transitive deps
            if(!imp.isExport())
                continue;
            if(Decl.equalModules(imp.getModule(), importedModule))
                return true;
            if(isImportedSpecialRules(imp.getModule(), importedModule))
                return true;
            if(isImportedTransitively(imp.getModule(), importedModule, visited))
                return true;
        }
        return false;
    }

    protected boolean isModuleOrPackageDescriptorName(String name) {
        return name.equals(Naming.MODULE_DESCRIPTOR_CLASS_NAME) || name.equals(Naming.PACKAGE_DESCRIPTOR_CLASS_NAME);
    }
   
    protected void loadJavaBaseArrays(){
        convertToDeclaration(getJDKBaseModule(), JAVA_LANG_OBJECT_ARRAY, DeclarationType.TYPE);
        convertToDeclaration(getJDKBaseModule(), JAVA_LANG_BOOLEAN_ARRAY, DeclarationType.TYPE);
        convertToDeclaration(getJDKBaseModule(), JAVA_LANG_BYTE_ARRAY, DeclarationType.TYPE);
        convertToDeclaration(getJDKBaseModule(), JAVA_LANG_SHORT_ARRAY, DeclarationType.TYPE);
        convertToDeclaration(getJDKBaseModule(), JAVA_LANG_INT_ARRAY, DeclarationType.TYPE);
        convertToDeclaration(getJDKBaseModule(), JAVA_LANG_LONG_ARRAY, DeclarationType.TYPE);
        convertToDeclaration(getJDKBaseModule(), JAVA_LANG_FLOAT_ARRAY, DeclarationType.TYPE);
        convertToDeclaration(getJDKBaseModule(), JAVA_LANG_DOUBLE_ARRAY, DeclarationType.TYPE);
        convertToDeclaration(getJDKBaseModule(), JAVA_LANG_CHAR_ARRAY, DeclarationType.TYPE);
    }
}
TOP

Related Classes of com.redhat.ceylon.compiler.loader.AbstractModelLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.