All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.hotmoka.whitelisting.internal.ResolvingClassLoaderImpl Maven / Gradle / Ivy

/*
Copyright 2021 Fausto Spoto

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package io.hotmoka.whitelisting.internal;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import io.hotmoka.constants.Constants;
import io.hotmoka.whitelisting.ResolvingClassLoader;
import io.hotmoka.whitelisting.WhiteListingWizard;

/**
 * A sealed implementation of a {@link io.hotmoka.whitelisting.ResolvingClassLoader}.
 */
public class ResolvingClassLoaderImpl extends ClassLoader implements ResolvingClassLoader {

	/**
	 * The version of the verification module that must b e used; this affects the
	 * set of white-listing annotations used by the class loader.
	 */
	private final int verificationVersion;

	/**
	 * An object that knows about methods that can be called from Takamaka code and under which conditions.
	 */
	private final WhiteListingWizard whiteListingWizard;

	/**
	 * The jars of the classpath of this class loader.
	 */
	private final byte[][] jars;

	// getPackageName() not working under Android!
	private final static String WHITELISTING_PACKAGE_NAME = ResolvingClassLoader.class.getPackage().getName() + '.';

	private final static String DUMMY_NAME_WITH_SLASHES = Constants.DUMMY_NAME.replace('.', '/') + ".class";

	/**
	 * Builds a class loader with the given jars.
	 * 
	 * @param jars the jars, as arrays of bytes
	 * @param verificationVersion the version of the verification module that must b e used; this affects the
	 *                            set of white-listing annotations used by the class loader
	 */
	public ResolvingClassLoaderImpl(Stream jars, int verificationVersion) {
		super(null);

		this.verificationVersion = verificationVersion;
		this.jars = jars.toArray(byte[][]::new);
		this.whiteListingWizard = new WhiteListingWizardImpl(this);
	}

	@Override
	public final int getVerificationVersion() {
		return verificationVersion;
	}

	@Override
	protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
		Class clazz = loadClassFromCache(name)
			.or(() -> loadClassFromBootstrapClassloader(name))
			.or(() -> loadClassFromApplicationClassloader(name))
			.or(() -> loadClassFromJarsInNode(name))
			.orElseThrow(() -> new ClassNotFoundException(name));

        if (resolve)
			resolveClass(clazz);

        return clazz;
    }

	private Optional> loadClassFromCache(String name) {
		return Optional.ofNullable(findLoadedClass(name));
	}

	private Optional> loadClassFromApplicationClassloader(String name) {
		// there are some classes that need to be loaded from the running classpath of the node itself,
    	// since they are used by the instrumentation code or for checking white-listing annotations;
    	// for them, we use the application (aka system) class-loader, that takes into account
    	// the full classpath of the JVM running the node
		if (name.startsWith(WHITELISTING_PACKAGE_NAME) // to allow access to the white-listing database
				|| Constants.DUMMY_NAME.equals(name) // to allow instrumented methods
				|| Constants.RUNTIME_NAME.equals(name)) // to allow calls to Takamaka's runtime
			try {
				return Optional.of(ClassLoader.getSystemClassLoader().loadClass(name));
			}
			catch (ClassNotFoundException e) {
				// ignore it
			}

		return Optional.empty();
	}

	private Optional> loadClassFromBootstrapClassloader(String name) {
		try {
			return Optional.of(super.loadClass(name, false));
		}
		catch (ClassNotFoundException e) {
			return Optional.empty();
		}
	}

	private Optional> loadClassFromJarsInNode(String name) {
		try (InputStream in = getResourceAsStream(name.replace('.', '/') + ".class")) {
			if (in != null) {
				byte[] bytes = in.readAllBytes();
				Class clazz = defineClass(name, bytes, 0, bytes.length);
				return Optional.of(clazz);
			}
		}
		catch (IOException | ClassFormatError e) {
			throw new RuntimeException(e);
		}

		return Optional.empty();
	}

    @Override
    public InputStream getResourceAsStream(String name) {
    	return getResourceAsStreamFromBoostrapClassloader(name)
    		.or(() -> getResourceAsStreamFromApplicationClassloader(name))
    		.or(() -> getResourceAsStreamFromJarsInNode(name))
    		.orElse(null);
    }

    private Optional getResourceAsStreamFromJarsInNode(String name) {
		boolean found = false;
    	for (byte[] jar: jars) {
            ZipInputStream jis = null;

            try {
            	jis = new ZipInputStream(new ByteArrayInputStream(jar));
    			ZipEntry entry;
    			while ((entry = jis.getNextEntry()) != null)
    				if (entry.getName().equals(name)) {
    					found = true;
    					return Optional.of(jis);
    				}
            }
    		catch (IOException e) {
    			throw new UncheckedIOException(e);
    		}
            finally {
                // only close the stream if the entry could not be found
                if (jis != null && !found)
                    try {
                        jis.close();
                    }
                    catch (IOException e) {
                        // ignore me
                    }
            }
    	}

    	return Optional.empty();
	}

	private Optional getResourceAsStreamFromBoostrapClassloader(String name) {
		return Optional.ofNullable(super.getResourceAsStream(name));
	}

	private Optional getResourceAsStreamFromApplicationClassloader(String name) {
		// there are some classes that need to be loaded from the node itself,
    	// since they are used by the instrumentation code;
    	// for them, we use the application (aka system) class-loader, that takes into account
    	// the full classpath of the JVM running the node
		if (DUMMY_NAME_WITH_SLASHES.equals(name)) // to allow instrumented methods
    		return Optional.ofNullable(ClassLoader.getSystemClassLoader().getResourceAsStream(name));
    	else
    		return Optional.empty();
	}

    @Override
	public ClassLoader getJavaClassLoader() {
		return this;
	}

	@Override
	public WhiteListingWizard getWhiteListingWizard() {
		return whiteListingWizard;
	}

	@Override
	public final Optional resolveField(String className, String name, Class type) throws ClassNotFoundException {
		return resolveField(loadClass(className), name, type);
	}

	@Override
	public Optional resolveField(Class clazz, String name, Class type) {
		while (clazz != null) {
			Optional result = Stream.of(clazz.getDeclaredFields())
				.filter(field -> field.getType() == type && field.getName().equals(name))
				.findFirst();

			if (result.isPresent())
				return result;
	
			clazz = clazz.getSuperclass();
		}

		return Optional.empty();
	}

	@Override
	public final Optional> resolveConstructor(String className, Class[] args) throws ClassNotFoundException {
		return resolveConstructor(loadClass(className), args);
	}

	@Override
	public Optional> resolveConstructor(Class clazz, Class[] args) {
		try {
			return Optional.of(clazz.getDeclaredConstructor(args));
		}
		catch (NoSuchMethodException e) {
			return Optional.empty();
		}
	}

	@Override
	public final Optional resolveMethod(String className, String methodName, Class[] args, Class returnType) throws ClassNotFoundException {
		return resolveMethod(loadClass(className), methodName, args, returnType);
	}

	@Override
	public Optional resolveMethod(Class clazz, String methodName, Class[] args, Class returnType) {
		for (Class cursor = clazz; cursor != null; cursor = cursor.getSuperclass()) {
			Optional result = resolveMethodExact(cursor, methodName, args, returnType);
			if (result.isPresent())
				return result;
		}

		return resolveMethodInInterfacesOf(clazz, methodName, args, returnType);
	}

	@Override
	public final Optional resolveInterfaceMethod(String className, String methodName, Class[] args, Class returnType) throws ClassNotFoundException {
		return resolveInterfaceMethod(loadClass(className), methodName, args, returnType);
	}

	@Override
	public Optional resolveInterfaceMethod(Class clazz, String methodName, Class[] args, Class returnType) {
		Optional result = resolveMethodExact(clazz, methodName, args, returnType);
		return result.isPresent() ? result : resolveMethodInInterfacesOf(clazz, methodName, args, returnType);
	}

	/**
	 * Yields the method of the given class with the given signature. It does not look
	 * in superclasses nor in super-interfaces.
	 * 
	 * @param className the name of the class
	 * @param methodName the name of the method
	 * @param args the formal arguments of the method
	 * @param returnType the return type of the method
	 * @return the method, if any
	 * @throws ClassNotFoundException if some class could not be found during resolution
	 */
	Optional resolveMethodExact(String className, String methodName, Class[] args, Class returnType) throws ClassNotFoundException {
		return resolveMethodExact(loadClass(className), methodName, args, returnType);
	}

	/**
	 * Yields the method of an interface implemented by the given class with the given signature. It does not look in superclasses.
	 * 
	 * @param clazz the class
	 * @param methodName the name of the method
	 * @param args the formal arguments of the method
	 * @param returnType the return type of the method
	 * @return the method, if any
	 */
	private Optional resolveMethodInInterfacesOf(Class clazz, String methodName, Class[] args, Class returnType) {
		for (Class interf: clazz.getInterfaces()) {
			Optional result = resolveInterfaceMethod(interf, methodName, args, returnType);
			if (result.isPresent())
				return result;
		}
	
		return Optional.empty();
	}

	/**
	 * Yields the method of the given class with the given signature. It does not look
	 * in superclasses nor in super-interfaces.
	 * 
	 * @param clazz the class
	 * @param methodName the name of the method
	 * @param args the formal arguments of the method
	 * @param returnType the return type of the method
	 * @return the method, if any
	 */
	private static Optional resolveMethodExact(Class clazz, String methodName, Class[] args, Class returnType) {
		return Stream.of(clazz.getDeclaredMethods())
			.filter(method -> method.getReturnType() == returnType && method.getName().equals(methodName)
					&& Arrays.equals(method.getParameterTypes(), args))
			.findFirst();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy