TypeFactory.java

package com.mackenziehigh.autumn.lang.compiler.typesystem;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IArrayType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IElementType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.INullType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IPrimitiveType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.ITypeFactory;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IVoidType;
import com.mackenziehigh.autumn.resources.Finished;
import java.util.Collection;
import java.util.Map;

/**
 * An instance of this class is a concrete implementation of the ITypeFactory interface.
 *
 * @author Mackenzie High
 */
@Finished("2014/07/12")
public final class TypeFactory
        implements ITypeFactory
{
    private final IPrimitiveType BOOLEAN = new PrimitiveType(this, boolean.class);

    private final IPrimitiveType CHAR = new PrimitiveType(this, char.class);

    private final IPrimitiveType BYTE = new PrimitiveType(this, byte.class);

    private final IPrimitiveType SHORT = new PrimitiveType(this, short.class);

    private final IPrimitiveType INT = new PrimitiveType(this, int.class);

    private final IPrimitiveType LONG = new PrimitiveType(this, long.class);

    private final IPrimitiveType FLOAT = new PrimitiveType(this, float.class);

    private final IPrimitiveType DOUBLE = new PrimitiveType(this, double.class);

    private final IVoidType VOID = new VoidType(this);

    private final INullType NULL = new NullType(this);

    private final Map<String, IType> names_to_types = Maps.newTreeMap();

    private final Map<Class, IType> classes_to_types = Maps.newHashMap();

    private final ClassLoader class_loader;

    /**
     * Constructor.
     *
     * <p>
     * The new factory will use the bootstrap class-loader to resolve previously loaded classes.
     * </p>
     */
    public TypeFactory()
    {
        this(String.class.getClassLoader());
    }

    /**
     * Constructor.
     *
     * @param class_loader is the class-loader that will be used to resolve loaded classes.
     */
    public TypeFactory(final ClassLoader class_loader)
    {
        this.class_loader = class_loader == null
                ? ClassLoader.getSystemClassLoader()
                : class_loader;

        names_to_types.put("Z", getBoolean());
        names_to_types.put("C", getChar());
        names_to_types.put("B", getByte());
        names_to_types.put("S", getShort());
        names_to_types.put("I", getInt());
        names_to_types.put("J", getLong());
        names_to_types.put("F", getFloat());
        names_to_types.put("D", getDouble());
        names_to_types.put("V", getVoid());
        names_to_types.put("Lnull;", getNull());

        classes_to_types.put(boolean.class, getBoolean());
        classes_to_types.put(char.class, getChar());
        classes_to_types.put(byte.class, getByte());
        classes_to_types.put(short.class, getShort());
        classes_to_types.put(int.class, getInt());
        classes_to_types.put(long.class, getLong());
        classes_to_types.put(float.class, getFloat());
        classes_to_types.put(double.class, getDouble());
        classes_to_types.put(void.class, getVoid());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IPrimitiveType getBoolean()
    {
        return BOOLEAN;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IPrimitiveType getChar()
    {
        return CHAR;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IPrimitiveType getByte()
    {
        return BYTE;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IPrimitiveType getShort()
    {
        return SHORT;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IPrimitiveType getInt()
    {
        return INT;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IPrimitiveType getLong()
    {
        return LONG;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IPrimitiveType getFloat()
    {
        return FLOAT;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IPrimitiveType getDouble()
    {
        return DOUBLE;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public INullType getNull()
    {
        return NULL;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IVoidType getVoid()
    {
        return VOID;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IArrayType getArrayType(final IElementType element,
                                   final int dimensions)
    {
        Preconditions.checkNotNull(element);
        Preconditions.checkArgument(dimensions >= 1);

        return new ArrayType(this, element, dimensions, null);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IType fromClass(final Class<?> clazz)
    {
        if (classes_to_types.containsKey(clazz))
        {
            return classes_to_types.get(clazz);
        }

        final IType result;

        if (clazz.isArray())
        {
            final Class element_class = getElementType(clazz);

            final IElementType element = (IElementType) fromClass(element_class);

            final int dimensions = getDimensions(clazz);

            result = new ArrayType(this, element, dimensions, clazz);
        }
        else if (clazz.isAnnotation())
        {
            result = new ReflectiveDeclaredType(this, clazz);
        }
        else if (clazz.isInterface())
        {
            result = new ReflectiveDeclaredType(this, clazz);
        }
        else if (clazz.isEnum())
        {
            result = new ReflectiveDeclaredType(this, clazz);
        }
        else // clazz.isClass()
        {
            result = new ReflectiveDeclaredType(this, clazz);
        }

        classes_to_types.put(clazz, result);

        names_to_types.put(result.getDescriptor(), result);

        return result;
    }

    /**
     * This method extracts the element-type from an array-type.
     *
     * <p>
     * Example: int[][][] returns int
     * </p>
     *
     * @param array is the array-type.
     * @return the type of the elements in an array of the array-type.
     */
    private Class getElementType(final Class array)
    {
        assert array.isArray();

        Class p = array;

        while (p.getComponentType() != null)
        {
            p = p.getComponentType();
        }

        return p;
    }

    /**
     * This method counts the number of dimensions in an array-type.
     *
     * @param array is the array-type.
     * @return the number of dimensions in the array-type.
     */
    private int getDimensions(final Class array)
    {
        assert array.isArray();

        int dimensions = 0;

        Class p = array;

        while (p.getComponentType() != null)
        {
            ++dimensions;

            p = p.getComponentType();
        }

        return dimensions;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IType findType(final String descriptor)
    {
        Preconditions.checkNotNull(descriptor);

        /**
         * If object that represents the type was already created, then simply return it.
         */
        if (names_to_types.containsKey(descriptor))
        {
            return names_to_types.get(descriptor);
        }

        /**
         * Since the object that represents the type was not already created,
         * there are only two possibilities left. First, entity that the type represents
         * may already have been loaded into the class-loader, but its types has not been
         * encountered here before. Second, the the type simply does not exist.
         */
        try
        {
            // Get the name of the type as it appears in source-code.
            final String name = descriptor.substring(1).replace(";", "").replace("/", ".");

            // If there is a class object for the aforesaid name,
            // then the type exists but has not been encountered here before.
            final Class clazz = Class.forName(name, true, class_loader);

            // Create an object that represents the type that is described by the class object.
            return fromClass(clazz);
        }
        catch (ClassNotFoundException ex)
        {
            /* Do Nothing */
        }

        /**
         * This type-factory does not know of any type associated with the given descriptor.
         */
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<IType> getTypes()
    {
        return ImmutableSet.copyOf(names_to_types.values());
    }

    /**
     * This method declares a new annotation-type.
     *
     * @param descriptor is the type-descriptor of the new type.
     * @return the object that represents the new type.
     */
    public CustomDeclaredType newAnnotationType(final String descriptor)
    {
        Preconditions.checkNotNull(descriptor);
        Preconditions.checkState(!names_to_types.containsKey(descriptor));

        final CustomDeclaredType result = CustomDeclaredType.newAnnotationType(this,
                                                                               descriptor);

        names_to_types.put(descriptor, result);

        return result;
    }

    /**
     * This method declares a new class-type.
     *
     * @param descriptor is the type-descriptor of the new type.
     * @return the object that represents the new type.
     */
    public CustomDeclaredType newClassType(final String descriptor)
    {
        Preconditions.checkNotNull(descriptor);
        Preconditions.checkState(!names_to_types.containsKey(descriptor));

        final CustomDeclaredType result = CustomDeclaredType.newClassType(this, descriptor);

        names_to_types.put(descriptor, result);

        return result;
    }

    /**
     * This method declares a new enum-type.
     *
     * @param descriptor is the type-descriptor of the new type.
     * @return the object that represents the new type.
     */
    public CustomDeclaredType newEnumType(final String descriptor)
    {
        Preconditions.checkNotNull(descriptor);
        Preconditions.checkState(!names_to_types.containsKey(descriptor));

        final CustomDeclaredType result = CustomDeclaredType.newEnumType(this, descriptor);

        names_to_types.put(descriptor, result);

        return result;
    }

    /**
     * This method declares a new interface-type.
     *
     * @param descriptor is the type-descriptor of the new type.
     * @return the object that represents the new type.
     */
    public CustomDeclaredType newInterfaceType(final String descriptor)
    {
        Preconditions.checkNotNull(descriptor);
        Preconditions.checkState(!names_to_types.containsKey(descriptor));

        final CustomDeclaredType result = CustomDeclaredType.newInterfaceType(this,
                                                                              descriptor);

        names_to_types.put(descriptor, result);

        return result;
    }
}