co.paralleluniverse.actors.ActorModule.java Source code

Java tutorial

Introduction

Here is the source code for co.paralleluniverse.actors.ActorModule.java

Source

/*
 * Quasar: lightweight threads and actors for the JVM.
 * Copyright (c) 2013-2014, Parallel Universe Software Co. All rights reserved.
 * 
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *  
 *   or (per the licensee's choosing)
 *  
 * under the terms of the GNU Lesser General Public License version 3.0
 * as published by the Free Software Foundation.
 */
package co.paralleluniverse.actors;

import co.paralleluniverse.common.reflection.ASMUtil;
import co.paralleluniverse.common.reflection.AnnotationUtil;
import co.paralleluniverse.common.reflection.ClassLoaderUtil;
import static co.paralleluniverse.common.reflection.ClassLoaderUtil.classToResource;
import static co.paralleluniverse.common.reflection.ClassLoaderUtil.isClassFile;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Resources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A module of actor code-upgrades contained in a jar file.
 *
 * @author pron
 */
class ActorModule extends URLClassLoader {
    private static final Class<?>[] AUTOMATIC_UPGRADE_CLASSES = new Class[] { Actor.class,
            co.paralleluniverse.actors.behaviors.Initializer.class,
            co.paralleluniverse.actors.behaviors.ServerHandler.class,
            co.paralleluniverse.actors.behaviors.EventHandler.class, };

    static {
        ClassLoader.registerAsParallelCapable();
    }
    private static final Logger LOG = LoggerFactory.getLogger(ActorModule.class);
    private static final String UPGRADE_CLASSES_ATTR = "Upgrade-Classes";
    private final URL url;
    private final ClassLoader parent;
    private final Set<String> upgradeClasses;

    public ActorModule(URL jarUrl, ActorLoader parent) {
        super(new URL[] { jarUrl }, null);
        this.url = jarUrl;
        this.parent = parent;

        // determine upgrade classes
        try {
            JarFile jar = new JarFile(new File(jarUrl.toURI()));
            final ImmutableSet.Builder<String> builder = ImmutableSet.builder();

            Manifest manifest = jar.getManifest();
            Attributes attributes = manifest.getMainAttributes();
            String ucstr = attributes.getValue(UPGRADE_CLASSES_ATTR);
            if (ucstr != null && !ucstr.trim().isEmpty()) {
                if (ucstr.trim().equals("*")) {
                    ClassLoaderUtil.accept(this, new ClassLoaderUtil.Visitor() {
                        @Override
                        public void visit(String resource, URL url, ClassLoader cl) {
                            if (!isClassFile(resource))
                                return;
                            final String className = ClassLoaderUtil.resourceToClass(resource);
                            //                            System.out.println("className: " + className + " "
                            //                                    + ASMUtil.isAssignableFrom(Actor.class, className, ActorModule.this) + " "
                            //                                    + " " + url + " " + ActorModule.this.parent.getResource(resource) + " "
                            //                                    + equalContent(ActorModule.this.parent.getResource(resource), url));                  
                            if (isAutomaticUpgrade(className)
                                    && !equalContent(ActorModule.this.parent.getResource(resource), url))
                                builder.add(className);
                        }
                    });
                } else {
                    for (String className : ucstr.split("\\s")) {
                        LOG.debug("Upgrade of class {} (JAR manifest)", className);
                        builder.add(className);
                    }
                }
            }

            ClassLoaderUtil.accept(this, new ClassLoaderUtil.Visitor() {
                @Override
                public void visit(String resource, URL url, ClassLoader cl) {
                    if (!isClassFile(resource))
                        return;
                    final String className = ClassLoaderUtil.resourceToClass(resource);
                    try (InputStream is = cl.getResourceAsStream(resource)) {
                        if (AnnotationUtil.hasClassAnnotation(Upgrade.class, is)) {
                            LOG.debug("Upgrade of class {} (annotated)", className);
                            builder.add(className);
                        }
                    } catch (IOException e) {
                        throw new RuntimeException(
                                "Exception while scanning class " + className + " for Upgrade annotation", e);
                    }
                }
            });

            this.upgradeClasses = builder.build();
        } catch (IOException | URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    private boolean isAutomaticUpgrade(String className) {
        for (Class<?> c : AUTOMATIC_UPGRADE_CLASSES) {
            if (ASMUtil.isAssignableFrom(c, className, ActorModule.this)) {
                LOG.debug("Automatic upgrade of class {} (implements/extends {})", className, c);
                return true;
            }
        }
        return false;
    }

    public URL getURL() {
        return url;
    }

    public Set<String> getUpgradeClasses() {
        return upgradeClasses;
    }

    public Class<?> loadClassInModule(String name) throws ClassNotFoundException {
        Class<?> loaded = super.findLoadedClass(name);
        if (loaded != null)
            return loaded;
        return super.loadClass(name); // first try to use the URLClassLoader findClass
    }

    public Class<?> findLoadedClassInModule(String name) {
        return super.findLoadedClass(name);
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> loaded = super.findLoadedClass(name);
        if (loaded != null)
            return loaded;

        LOG.debug("findClass {} in module {}", name, this);
        boolean isUpgraded = upgradeClasses.contains(name);
        if (!isUpgraded && parent != null && !name.contains("$")) { // if class is possibly an inner class, don't use parent's, which will be in a different runtime package
            try {
                String resourceName = classToResource(name);
                URL parentUrl = parent.getResource(resourceName);
                LOG.debug("findClass {} in module {} - parent URL: {}", name, this, parentUrl);
                if (parentUrl != null) {
                    URL myUrl = super.getResource(resourceName);
                    if (myUrl == null || equalContent(parentUrl, myUrl)) {
                        // NOTE: classes may differ only in their Java source line information; considered a difference
                        if (myUrl != null)
                            LOG.debug("Class {} in module {} is identical to that in main module", name, this);
                        else
                            LOG.debug("findClass {} in module {} - not found in module", name, this);
                        return parent.loadClass(name);
                    }
                }
            } catch (ClassNotFoundException e) {
            }
        }

        try {
            Class<?> clazz = super.findClass(name); // first try to use the URLClassLoader findClass
            LOG.info("{} loaded {} class {}", this, isUpgraded ? "upgraded" : "internal", name);
            return clazz;
        } catch (ClassNotFoundException e) {
            if (parent != null)
                return parent.loadClass(name);
            throw e;
        }
    }

    @Override
    public URL getResource(String name) {
        URL url = super.getResource(name);
        if (url != null)
            LOG.debug("{} - getResource {}: {}", this, name, url);
        if (url == null && parent != null)
            url = parent.getResource(name);
        return url;
    }

    @Override
    public String toString() {
        return "ActorModule{" + "url=" + url + '}';
    }

    private static boolean equalContent(URL url1, URL url2) {
        try {
            return Resources.asByteSource(url1).contentEquals(Resources.asByteSource(url2));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}