tools/java/src/stx/libjava/tools/environment/internal/ReflectiveClassLoader.java
author Jan Vrany <jan.vrany@fit.cvut.cz>
Thu, 27 Sep 2018 23:16:42 +0100
changeset 3855 bcaceb2ade06
parent 3411 ce03270017b7
permissions -rw-r--r--
Workaround compilation of Java code using JDK 9 and newer JDK 9 and newer removed class `sun.misc.Launcher` which is used by `ReflectiveClassLoader` to extract boot class path. In order to make the code at least compilable using `javac` from JRK 9 (and newer), use reflection rather than referencing the class directly. Naturally, this does not mean `stx:libjava` would run using JDK 9 (or newer), merely that it does compile. This makes building Smalltalk/X easier as there's no need to juggle with default `javac` versions or even to install (old) JRK 7 just to *compile* Smalltalk/X. For *running* Java inside Smalltalk/X, you'd still need JDK 7.

/*
 * COPYRIGHT (c) 2010-2015 by Jan Vrany, Jan Kurs and Marcel Hlopko
 * COPYRIGHT (c) 2014-2015 by Tomas Heger
 *                            SWING Research Group, Czech Technical University in Prague
 *
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice. This software may not
 * be provided or otherwise made available to, or used by, any
 * other person. No title to or ownership of the software is
 * hereby transferred.
 */
/**
 * 
 */
package stx.libjava.tools.environment.internal;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Vector;


/**
 * <code>ReflectiveClassLoader</code> is a simple wrapper on a {@link java.lang.ClassLoader} that provides
 * reflective features standard {@link java.lang.ClassLoader} doesn't.
 * In particular, it provides {@link #isPackage(String)} which is used
 * by {@link stx.libjava.tools.environment.ReflectiveEnvironment}.
 * 
 * The way {@link java.lang.ClassLoader} accesses and loads classes differ
 * so specialised subclasses exists for individual {@link java.lang.ClassLoader}
 * instances in order to provide more accurate answers. Therefore use method 
 * {@link #forClassLoader(ClassLoader)} to create an instance of a 
 * <code>ReflectiveClassLoader</code> 
 *  
 * @author Jan Vrany <jan.vrany [at] fit.cvut.cz>
 *
 */
@stx.libjava.annotation.Package("stx:libjava/tools")
public class ReflectiveClassLoader {
    /** 
     * Shared cached {@link ReflectiveClassLoader} representing primordial class loader. 
     * Primordial class loader is not to change during the life-span of a JVM, so it can be
     * safely cached.  
     * 
     */
    protected static ReflectiveClassLoader PRIMORDIAL;

    /**
     * Cached {@link Field} to access normally inaccessible field {@link java.lang.ClassLoader#classes}. 
     * On OpenJDK based libraries, this field contains a list of loaded classes. This list is used
     * to check for loaded classes as a short-circuit (or as the only way for generic class loaders) 
     */
    protected static Field FIELD_ClassLoader_Classes;
    
    /**
     * A real {@link ClassLoader} this {@link ReflectiveClassLoader} reflects on. 
     * For primordial class loader the value is <code>null</code>.
     */
    protected ClassLoader realClassLoader;
    
    /**
     * A cached {@link ReflectiveClassLoader} representing a parent class loader of
     * this object. For primordial class loader the value is <code>null</code>.
     */
    protected ReflectiveClassLoader parent;
    
    protected ReflectiveClassLoader(ClassLoader loader) {
        realClassLoader = loader;
    }
    
    static {
        try {
            FIELD_ClassLoader_Classes = ClassLoader.class.getDeclaredField("classes");
            FIELD_ClassLoader_Classes.setAccessible(true);
        } catch (NoSuchFieldException e) {
            FIELD_ClassLoader_Classes = null;
        } catch (SecurityException e) {
            FIELD_ClassLoader_Classes = null;
        }
        
        if (Double.parseDouble(System.getProperty("java.specification.version")) >= 9.0) {
        	throw new RuntimeException("Java 9 and newer is not yet supported");
        }
         
        try {
        	Class<?> launcher = Class.forName("sun.misc.Launcher");
        	Method getBootstrapClassPath = launcher.getMethod("getBootstrapClassPath");
        	Object classpath = getBootstrapClassPath.invoke(null);
        	Method getURLs = classpath.getClass().getMethod("getURLs");
        	URL[] urls = (URL[])getURLs.invoke(classpath);
        	PRIMORDIAL = new ReflectiveURLClassLoader(null, urls);
        } catch (ClassNotFoundException e) {
        	throw new RuntimeException("Cannot determine bootstrap class path", e);
        } catch (NoSuchMethodException e) {
        	throw new RuntimeException("Cannot determine bootstrap class path", e);
		} catch (SecurityException e) {
			throw new RuntimeException("Cannot determine bootstrap class path", e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException("Cannot determine bootstrap class path", e);
		} catch (InvocationTargetException e) {
			throw new RuntimeException("Cannot determine bootstrap class path", e);
		}
    }
        
    /**
     * Returns a suitable {@link ReflectiveClassLoader} for given (real) {@link ClassLoader}.
     * 
     * @param loader a {@link ClassLoader} on which to reflect.
     * @return a {@link ReflectiveClassLoader} for given @param loader. 
     */
    public static ReflectiveClassLoader forClassLoader(ClassLoader loader) {
        if (loader == null) {
            return PRIMORDIAL;
        }
        /* This is kind of ugly, we should provide a way to register custom subclasses,
         * but for now, keep it simple. We'll see if this machinery proves usable at all. 
         */
        if (loader instanceof java.net.URLClassLoader) {
            return new ReflectiveURLClassLoader((java.net.URLClassLoader)loader);
        }
        return new ReflectiveClassLoader(loader);
    }
    
    public ReflectiveClassLoader getParent() {
        if (realClassLoader == null) return null;
        if (parent == null) {
            parent = ReflectiveClassLoader.forClassLoader(realClassLoader.getParent());
        }
        return parent;
    }
    
    /** 
     * Return <code>true</code> is package with name @param packageNameDotted exists under
     * this class loader, <code>false</code> otherwise.  
     * 
     * A package is considered to exist if there's at least one class in that package. 
     * However, individual {@link ReflectiveClassLoader}s may extend the definition of
     * {@link #isPackage(String)} For example {@link ReflectiveURLClassLoader} may return
     * <code>true</code> if a directory exists along it's class path.  
     * 
     * @param packageNameDotted a name of the package (with dots as separator)
     * @return <code>true</code> if such package exists, <code>false</code> otherwise
     */
    public boolean isPackage(String packageNameDotted) {
    	String packageNameSlashed = packageNameDotted.replace('.', '/');
    	return isPackage(packageNameDotted, packageNameSlashed);
    }
    
    /**
     * Return <code>true</code> is package with name @param packageNameDotted exists under
     * this class loader, <code>false</code> otherwise.  
     * 
     * A package is considered to exist if there's at least one class in that package. 
     * However, individual {@link ReflectiveClassLoader}s may extend the definition of
     * {@link #isPackage(String)} For example {@link ReflectiveURLClassLoader} may return
     * <code>true</code> if a directory exists along it's class path.  
     * 
     * @param packageNameDotted a name of the package (with dots as separator)
     * @param packageNameSlashed a name of the package (with slashes as separator)
     * @return <code>true</code> if such package exists, <code>false</code> otherwise
     */
    public boolean isPackage(String packageNameDotted, String packageNameSlashed) {
        // First, check parent class loader
        if (realClassLoader != null) {
            if (realClassLoader.getParent() == null) {
                if (PRIMORDIAL.isPackage(packageNameDotted, packageNameSlashed)) {
                    return true;
                }
            } else {
                if (getParent().isPackage(packageNameDotted, packageNameSlashed)) {
                    return true;
                }
            }
        }
        // Second, consult list of already loaded classes.
        if (realClassLoader != null && FIELD_ClassLoader_Classes != null) {
            int packageNameLen = packageNameDotted.length();
            Vector<Class<?>> classes;
            try {
                classes = (Vector<Class<?>>)FIELD_ClassLoader_Classes.get(realClassLoader);
                if (classes.size() > 0) {
                	/* 
                	 * Iterate over all classes registered in a class loader,
                	 * if class in probed package is found, return true. 
                	 * If class with the same name as probed package, return false. 
                	 *	
                	 * If the vector of classes is modified during the search, restart. 
                	 * This may happen as other threads may load classes using the same
                	 * class loader and thus modify the classes vector. 
                	 */
                	do {
                		try {
		                    Iterator<Class<?>> i = classes.iterator();
		                    while (i.hasNext()) {                    
		                        String className = i.next().getName();
		                        int classNameLen = className.length();                  
		                        if ((classNameLen > packageNameLen) &&
		                            (className.charAt(packageNameLen) == '.') &&
		                            (className.startsWith(packageNameDotted))) {
		                            return true;
		                        } else if ((classNameLen == packageNameLen) &&
		                                    className.equals(packageNameDotted)) {
		                            return false;
		                        }
		                    }
                		} catch (ConcurrentModificationException cme) {
                			/* Oops, classes vector has ben modified by some other thread, 
                			 * restart.
                			 */
                			continue;
                		}
                	} while (false);
                }
            } catch (IllegalArgumentException e) {
                // If we're for whatever reason forbidden access to classes field,
                // well, just ignore it. 
            } catch (IllegalAccessException e) {
                // same as above. 
            }
        }
        // Last, we don't know. For generic ClassLoader, there's no other way
        // to figure out what packages are there or not. So be conservative...
        return false;
    }
    
    public ClassLoader getRealClassLoader() {
        return realClassLoader;
    }

}