Package org.hotswap.agent.plugin.jvm

Source Code of org.hotswap.agent.plugin.jvm.AnonymousClassInfos$AnonymousClassInfoMatcher

package org.hotswap.agent.plugin.jvm;

import org.hotswap.agent.javassist.ClassPool;
import org.hotswap.agent.javassist.CtClass;
import org.hotswap.agent.javassist.NotFoundException;
import org.hotswap.agent.logging.AgentLogger;

import java.io.File;
import java.lang.reflect.Method;
import java.util.*;

/**
* Info about anonymous classes.
* <p/>
* This class will on construction search for all anonymous classes of the main class and calculate
* superclass, interfaces, all methods signature and all fields signature. Depending on used constructor
* this is done via reflection from ClassLoader (current loaded state) or from ClassPool via javaassist.
* Note that ClassPool uses LoadClassPath on the ClassLoader and hence are resources resolved via the
* ClassLoader. Javaasist resolves the resource and returns bytcode as is in the resource file.
* <p/>
* Use mapPreviousState() to create compatible transition mapping between old state and new state. This mapping
* is then used by plugin to swap class bytecoded to retain hotswap changes compatible
*
* @author Jiri Bubnik
*/
public class AnonymousClassInfos {
    private static AgentLogger LOGGER = AgentLogger.getLogger(AnonymousClassInfos.class);

    // start indexing hotswap created synthetic anonymous classes from this index to avoid collision with existing
    public static final int UNIQUE_CLASS_START_INDEX = 10000;

    // how many milliseconds is delta is considered as same time in modification check
    private static final long ALLOWED_MODIFICATION_DELTA = 100;

    // counter to create uniqueue class name
    static int uniqueClass = UNIQUE_CLASS_START_INDEX;

    // previous state
    AnonymousClassInfos previous;

    // calculated transitions
    Map<AnonymousClassInfo, AnonymousClassInfo> compatibleTransitions;

    // mo
    long lastModifiedTimestamp = 0;

    // the main class
    String className;

    // zero based index list of anonymous classes
    List<AnonymousClassInfo> anonymousClassInfoList = new ArrayList<AnonymousClassInfo>();

    /**
     * Create info of the current state from the classloader via reflection.
     *
     * @param classLoader classloader to use
     * @param className   main class
     */
    public AnonymousClassInfos(ClassLoader classLoader, String className) {
        this.className = className;

        try {
            // reflective call to check already loaded class (not to load a new one)
            Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", new Class[]{String.class});
            m.setAccessible(true);

            int i = 1;
            while (true) {

                Class anonymous = (Class) m.invoke(classLoader, className + "$" + i);
                if (anonymous == null)
                    break;

                anonymousClassInfoList.add(i - 1, new AnonymousClassInfo(anonymous));
                i++;
            }
        } catch (Exception e) {
            throw new Error("Unexpected error in checking loaded classes", e);
        }
    }

    /**
     * Create info of the new state from the classPool via javassist.
     *
     * @param classPool classPool to resolve class files
     * @param className main class
     */
    public AnonymousClassInfos(ClassPool classPool, String className) {
        this.className = className;
        lastModifiedTimestamp = lastModified(classPool, className);

        // search for declared classes in new state to skip obsolete anonymous inner classes on filesystem
        List<CtClass> declaredClasses;
        try {
            CtClass ctClass = classPool.get(className);
            declaredClasses = Arrays.asList(ctClass.getNestedClasses());
        } catch (NotFoundException e) {
            throw new IllegalArgumentException("Class " + className + " not found.");
        }


        int i = 1;
        while (true) {
            try {
                CtClass anonymous = classPool.get(className + "$" + i);
                if (!declaredClasses.contains(anonymous))
                    break; // skip obsolete classes
                anonymousClassInfoList.add(i - 1, new AnonymousClassInfo(anonymous));
                i++;
            } catch (NotFoundException e) {
                // up to first not found class
                break;
            } catch (Exception e) {
                throw new Error("Unable to create AnonymousClassInfo definition for class " + className + "$i", e);
            }
        }
        LOGGER.trace("Anonymous class '{}' scan finished with {} classes found", className, i - 1);
    }

    /**
     * Search for a mapping between previous and nwe anonymous classes.
     *
     * @return map previous -> new. If no mapping to previous exists, synthetic class name is created.
     */
    private void calculateCompatibleTransitions() {
        compatibleTransitions = new HashMap<AnonymousClassInfo, AnonymousClassInfo>();

        // create a copy to remove resolved items
        List<AnonymousClassInfo> previousInfos = new ArrayList<AnonymousClassInfo>(previous.anonymousClassInfoList);
        List<AnonymousClassInfo> currentInfos = new ArrayList<AnonymousClassInfo>(anonymousClassInfoList);

        // previous classes are discarded and cannot be used
        if (previousInfos.size() > currentInfos.size()) {
            if (currentInfos.size() == 0)
                previousInfos.clear();
            else
                previousInfos = previousInfos.subList(0, currentInfos.size());
        }

        // try to match - exact, than signatures (enclosing method may change), than class signature
        searchForMappings(compatibleTransitions, previousInfos, currentInfos, new AnonymousClassInfoMatcher() {
            @Override
            public boolean match(AnonymousClassInfo previous, AnonymousClassInfo current) {
                return previous.matchExact(current);
            }
        });

        searchForMappings(compatibleTransitions, previousInfos, currentInfos, new AnonymousClassInfoMatcher() {
            @Override
            public boolean match(AnonymousClassInfo previous, AnonymousClassInfo current) {
                return previous.matchSignatures(current);
            }
        });

        searchForMappings(compatibleTransitions, previousInfos, currentInfos, new AnonymousClassInfoMatcher() {
            @Override
            public boolean match(AnonymousClassInfo previous, AnonymousClassInfo current) {
                return previous.matchClassSignature(current);
            }
        });


        // how many anonymous classes will be defined
        int newDefinitionCount = anonymousClassInfoList.size();
        // last myClass$index
        int lastAnonymousClassIndex = previous.anonymousClassInfoList.size();

        // not matched
        for (AnonymousClassInfo currentNotMatched : currentInfos) {
            if (lastAnonymousClassIndex < newDefinitionCount) {
                // free anonymous class available - this will be registered with onDefine event, +1 because one based index name
                compatibleTransitions.put(new AnonymousClassInfo(className + "$" + (lastAnonymousClassIndex + 1)), currentNotMatched);
                lastAnonymousClassIndex++;
            } else {
                compatibleTransitions.put(new AnonymousClassInfo(className + "$" + uniqueClass++), currentNotMatched);
            }
        }

        if (LOGGER.isLevelEnabled(AgentLogger.Level.TRACE)) {
            for (Map.Entry<AnonymousClassInfo, AnonymousClassInfo> mapping : compatibleTransitions.entrySet()) {
                LOGGER.trace("Transition {} => {}", mapping.getKey().getClassName(), mapping.getValue().getClassName());
            }
        }
    }

    /**
     * Iterate through both lists and find matching anonymous classes using matcher.
     * Found matches are removed from previous and current lists and added to transitions.
     */
    private void searchForMappings(Map<AnonymousClassInfo, AnonymousClassInfo> transitions, List<AnonymousClassInfo> previousInfos, List<AnonymousClassInfo> currentInfos,
                                   AnonymousClassInfoMatcher matcher) {
        for (ListIterator<AnonymousClassInfo> previousIt = previousInfos.listIterator(); previousIt.hasNext(); ) {
            AnonymousClassInfo previous = previousIt.next();

            for (ListIterator<AnonymousClassInfo> currentIt = currentInfos.listIterator(); currentIt.hasNext(); ) {
                AnonymousClassInfo current = currentIt.next();

                // found and resolved
                if (matcher.match(previous, current)) {
                    transitions.put(previous, current);
                    previousIt.remove();
                    currentIt.remove();
                    break;
                }
            }
        }
    }

    /**
     * Returns stored info of an anonymous class
     *
     * @param className class name of the anonymous class (Should be in the form of MyClass$3)
     * @return class info x null
     */
    public AnonymousClassInfo getAnonymousClassInfo(String className) {
        for (AnonymousClassInfo info : anonymousClassInfoList) {
            if (className.equals(info.getClassName())) {
                return info;
            }
        }
        return null;
    }

    /**
     * Set previous class info state and calculate compatible transitions.
     * Usually new state is created from classpool, while old state from classloader.
     *
     * @param previousAnonymousClassInfos previous state
     */
    public void mapPreviousState(AnonymousClassInfos previousAnonymousClassInfos) {
        this.previous = previousAnonymousClassInfos;

        // hold only one state back
        previousAnonymousClassInfos.previous = null;

        // calculate the mapping
        calculateCompatibleTransitions();
    }

    /**
     * Return true, if last modification timestamp is same as current timestamp of className.
     *
     * @param classPool classPool to check className file
     * @return true if is current
     */
    public boolean isCurrent(ClassPool classPool) {
        return lastModifiedTimestamp >= lastModified(classPool, className) - ALLOWED_MODIFICATION_DELTA;
    }

    // get timestamp on the main class file
    private long lastModified(ClassPool classPool, String className) {
        String file = classPool.find(className).getFile();
        return new File(file).lastModified();
    }

    // matcher helper
    private interface AnonymousClassInfoMatcher {
        public boolean match(AnonymousClassInfo previous, AnonymousClassInfo current);
    }

    /**
     * Returns calculated compatible transitions.
     *
     * @return calculated compatible transitions.
     */
    public Map<AnonymousClassInfo, AnonymousClassInfo> getCompatibleTransitions() {
        return compatibleTransitions;
    }

    /**
     * Find compatible transition class name.
     *
     * @param className name of existing class (old)
     * @return name of compatible new class that should replace old class. Can be null if no compatible class found.
     */
    public String getCompatibleTransition(String className) {
        for (Map.Entry<AnonymousClassInfo, AnonymousClassInfo> transition : compatibleTransitions.entrySet()) {
            if (transition.getKey().getClassName().equals(className))
                return transition.getValue().getClassName();
        }

        return null;
    }

}
TOP

Related Classes of org.hotswap.agent.plugin.jvm.AnonymousClassInfos$AnonymousClassInfoMatcher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.