SquirrelJME/SquirrelJME

View on GitHub
modules/cldc-compact/src/main/java/java/lang/Class.java

Summary

Maintainability
A
0 mins
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
//     Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------

package java.lang;

import cc.squirreljme.jvm.mle.JarPackageShelf;
import cc.squirreljme.jvm.mle.ObjectShelf;
import cc.squirreljme.jvm.mle.TypeShelf;
import cc.squirreljme.jvm.mle.brackets.JarPackageBracket;
import cc.squirreljme.jvm.mle.brackets.TypeBracket;
import cc.squirreljme.runtime.cldc.annotation.Api;
import cc.squirreljme.runtime.cldc.debug.Debugging;
import java.io.InputStream;

/**
 * This class is the in-language representation of a Java class, the CLDC
 * allows for minimal reflection via {@link Class#forName(String)} and
 * {@link Class#newInstance()}.
 *
 * @since 2018/12/08
 */
@Api
public final class Class<T>
{
    /** This is the prefix that is used for assertion checks. */
    private static final String _ASSERTION_PREFIX =
        "cc.squirreljme.runtime.noassert.";
    
    /** The type shelf for this class. */
    private final TypeBracket _type;
    
    /** Has the assertion status been checked already? */
    private volatile boolean _checkedAssert;
    
    /** Is this class being asserted? */
    private volatile boolean _useAssert;
    
    /**
     * Initializes the class reference holder.
     *
     * @param __type The type shelf to set this as.
     * @throws NullPointerException On null arguments.
     * @since 2020/06/07
     */
    private Class(TypeBracket __type)
        throws NullPointerException
    {
        if (__type == null)
            throw new NullPointerException("NARG");
        
        this._type = __type;
    }
    
    /**
     * This checks whether the specified input class extends this class or
     * implements an interface and then returns this class object which is
     * "cast" to the specified type. Note that this does not change the
     * returned value.
     *
     * @param <U> The sub-class to cast this class object to.
     * @param __cl A class which is checked to see if it extends or implements
     * this class.
     * @return {@code this} except cast to the specified sub-class
     * @throws ClassCastException If the specified class is not a sub-class of
     * this class type.
     * @throws NullPointerException On null arguments.
     * @see Class#isAssignableFrom(Class)
     * @since 2016/06/13
     */
    @Api
    @SuppressWarnings({"unchecked"})
    public <U> Class<? extends U> asSubclass(Class<U> __cl)
        throws ClassCastException, NullPointerException
    {
        if (__cl == null)
            throw new NullPointerException("NARG");
        
        /* {@squirreljme.error ZZ0v The specified class is not a sub-class
        of this class. (The class being checked; The current class)} */
        if (!this.isAssignableFrom(__cl))
            throw new ClassCastException("ZZ0v " + __cl + " " + this);
        
        return (Class<? extends U>)this;
    }
    
    /**
     * Casts the object to this class, checking it.
     *
     * @param __o The object to cast.
     * @return The casted object.
     * @throws ClassCastException If the type is not matched.
     * @since 2018/09/29
     */
    @Api
    @SuppressWarnings({"unchecked"})
    public T cast(Object __o)
        throws ClassCastException
    {
        // Null always casts OK
        if (__o == null)
            return null;
        
        /* {@squirreljme.error ZZ0w The other class cannot be casted to this
        class. (This class; The other class)} */
        Class<?> other = __o.getClass();
        if (!this.isAssignableFrom(other))
            throw new ClassCastException("ZZ0w " + this.getName() + " " +
                other.getName());
        
        return (T)__o;
    }
    
    /**
     * Returns whether or not assertions should be enabled in the specified
     * class, this is used internally by the virtual machine to determine if
     * assertions should fail or not.
     *
     * In SquirrelJME, this defaults to returning {@code true}. To disable
     * assertions for a class or an entire package then the following system
     * property may be specified to disable them:
     * {@code cc.squirreljme.noassert.(package)(.class)=true}.
     *
     * @return In SquirrelJME this returns by default {@code true}, otherwise
     * this may return {@code false} if they are disabled for a class.
     * @since 2016/06/13
     */
    @Api
    public boolean desiredAssertionStatus()
    {
        // If assertions have been checked, they do not have to be rechecked
        if (this._checkedAssert)
            return this._useAssert;
        
        // Otherwise check it
        return this.__checkAssertionStatus();
    }
    
    /**
     * Returns the name of this class.
     *
     * @return The name of this class.
     * @since 2018/09/22
     */
    @Api
    public String getName()
    {
        return TypeShelf.runtimeName(this._type);
    }
    
    /**
     * Obtains a resource from the classpath which exists within a JAR file,
     * inside of a directory, or in a prepacked resource. If a resource needs
     * to be obtain from another class which exists in another JAR file then
     * this method must be called from a class in that JAR.
     *
     * In the Java ME environment, one should not rely on getting resources
     * which are executable class files (files ending in .class). These class
     * files may be deleted during native compilation. This however should not
     * be relied upon.
     *
     * Using this method on the classes for primitive types ({@code int.class})
     * and using a relative name will always result in the path being treated
     * as absolute.
     *
     * Relative paths are converted to absolute paths by appending the name
     * to the binary name of the class package.
     *
     * @param __name The name of the resource to find, if this starts with a
     * forward slash {@code '/'} then it is treated as an absolute path.
     * Otherwise a resource will be derived from the calling class.
     * @return A stream to the given resource or {@code null} if one was not
     * found.
     * @throws NullPointerException On null arguments.
     * @since 2016/03/01
     */
    @Api
    public InputStream getResourceAsStream(String __name)
        throws NullPointerException
    {
        // Check
        if (__name == null)
            throw new NullPointerException("NARG");
        
        // Do not lookup blank resources
        if (__name.isEmpty())
            return null;
            
        // If a resource starts with slash then it is treated as being an
        // absolute reference, otherwise it will depend on the package the
        // class is in
        String want;
        if (__name.charAt(0) == '/')
            want = __name.substring(1);
        else
        {
            String binName = TypeShelf.binaryPackageName(this._type);
            
            if (binName.isEmpty())
                want = __name;
            else
                want = binName + "/" + __name;
        }
        
        // If our class is within a JAR try to search our own JAR first
        JarPackageBracket inJar = TypeShelf.inJar(this._type);
        if (inJar != null)
        {
            InputStream rv = JarPackageShelf.openResource(inJar, want);
            if (rv != null)
                return rv;
        }
        
        // Otherwise search every JAR in the classpath for the given resource
        for (JarPackageBracket jar : JarPackageShelf.classPath())
        {
            InputStream rv = JarPackageShelf.openResource(jar, want);
            if (rv != null)
                return rv;
        }
        
        // Not found
        return null;
    }
    
    /**
     * Returns the class which is the superclass of this class.
     *
     * @return The superclass or {@code null} if there is none.
     * @since 2017/03/29
     */
    @Api
    public Class<? super T> getSuperclass()
    {
        TypeBracket rv = TypeShelf.superClass(this._type);
        return (rv == null ? null : TypeShelf.typeToClass(rv));
    }
    
    /**
     * Returns {@code true} if this class represents an array type.
     *
     * @return {@code true} if this class represents an array type.
     * @since 2016/06/16
     */
    @Api
    public boolean isArray()
    {
        return TypeShelf.isArray(this._type);
    }
    
    /**
     * Checks if the given class can be assigned to this one, the check is
     * in the same order as {@code instanceof Object} that is
     * {@code b.getClass().isAssignableFrom(a.getClass()) == (a instanceof b)}
     * and {@code (Class<B>)a} does not throw {@link ClassCastException}.
     *
     * @param __cl The other class type.
     * @return If the other class can be assigned to this one.
     * @throws NullPointerException On null arguments.
     * @since 2018/09/27
     */
    @Api
    @SuppressWarnings("EqualsBetweenInconvertibleTypes")
    public boolean isAssignableFrom(Class<?> __cl)
        throws NullPointerException
    {
        if (__cl == null)
            throw new NullPointerException("NARG");
        
        // Quick determination if this is the same type
        if (this == __cl)
            return true;
            
        return TypeShelf.isAssignableFrom(this._type,
            TypeShelf.classToType(__cl));
    }
    
    /**
     * Is this class an interface?
     *
     * @return If this is an interface.
     * @since 2018/11/03
     */
    @Api
    public boolean isInterface()
    {
        return TypeShelf.isInterface(this._type);
    }
    
    /**
     * Checks if the given class is an instance of this class.
     *
     * @param __o The object to check.
     * @return If the given object is an instance of this class.
     * @since 2018/09/27
     */
    @Api
    public boolean isInstance(Object __o)
    {
        // Null will never be an instance
        if (__o == null)
            return false;
        
        // This is in the same form
        return this.isAssignableFrom(__o.getClass());
    }
    
    /**
     * Constructs a new instance of this class.
     *
     * @throws InstantiationException If the default constructor cannot be
     * accessed by the calling method.
     * @throws IllegalAccessException If the class or constructor could not
     * be accessed.
     * @return The newly created instance.
     * @since 2018/12/04
     */
    @Api
    @SuppressWarnings({"unchecked", "RedundantThrows"})
    public T newInstance()
        throws InstantiationException, IllegalAccessException
    {
        Debugging.todoNote("Implement newInstance() access checks.");
        
        Object rv = ObjectShelf.newInstance(this._type);
        if (rv == null)
            throw new OutOfMemoryError("OOME");
        
        return (T)rv;
    }
    
    /**
     * {@inheritDoc}
     * @since 2018/11/03
     */
    @Override
    public String toString()
    {
        // Arrays and primitive types essentially just use the binary name
        TypeBracket type = this._type;
        if (TypeShelf.isArray(type) || TypeShelf.isPrimitive(type))
            return TypeShelf.binaryName(type);
        
        return (this.isInterface() ? "interface " : "class ") + this.getName();
    }
    
    /**
     * This checks whether assertions should be **disabled** for this class (or
     * for the entire package).
     *
     * @return The assertions status to use.
     * @since 2016/10/09
     */
    private boolean __checkAssertionStatus()
    {
        // Default to true
        boolean rv = true;
        
        // Determine class name
        String cn = this.getName();
        String prop = Class._ASSERTION_PREFIX + cn;
        
        // Disabled for this class?
        if (Boolean.getBoolean(prop))
            rv = false;
        
        // Disabled for this package?
        else
        {
            // Find last dot, if there is none then this is just the default
            // package so never bother checking the package
            int ld = cn.lastIndexOf('.');
            if (ld > 0 && Boolean.getBoolean(prop.substring(0,
                prop.length() - (cn.length() - ld))))
                rv = false;
        }
        
        // Set as marked
        this._checkedAssert = true;
        this._useAssert = rv;
        return rv;
    }
    
    /**
     * Locates the class with the given name and returns it, otherwise an
     * exception is thrown.
     *
     * The expected form of the class is a name as is mostly used in the
     * Java language ({@code some.package.Foo}) and not one that is internal
     * to the virtual machine except in the case of an array. Inner classes do
     * not follow dot notation, an inner class is usually separated by a dollar
     * sign '$'. For example {@code Map.Entry} is {@code java.util.Map$Entry}.
     *
     * If an array is requested then it must only be of a primitive type using
     * a Java internal type descriptor.
     *
     * @param __n The name of the class to find.
     * @return The class with the given name.
     * @throws ClassNotFoundException If the given class was not found.
     * @throws NullPointerException If no name was specified.
     * @since 2016/03/01
     */
    @Api
    public static Class<?> forName(String __n)
        throws ClassNotFoundException
    {
        // No class specified
        if (__n == null)
            throw new NullPointerException();
        
        /* {@squirreljme.error ZZ0z Could not find the specified class. (The
        name of the class)} */
        TypeBracket found = TypeShelf.findType(
            __n.replace('.', '/'));
        if (found == null)
            throw new ClassNotFoundException("ZZ0z " + __n);
        
        // The name will have to be converted to binary form since that is
        // what is internally used
        return TypeShelf.typeToClass(found);
    }
}