org.aspectj.weaver.loadtime.ClassLoaderWeavingAdaptor.java Source code

Java tutorial

Introduction

Here is the source code for org.aspectj.weaver.loadtime.ClassLoaderWeavingAdaptor.java

Source

/*******************************************************************************
 * Copyright (c) 2005, 2017 Contributors.
 * All rights reserved.
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution and is available at
 * http://eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.aspectj.weaver.loadtime;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

import org.aspectj.bridge.AbortException;
import org.aspectj.bridge.Constants;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.util.LangUtil;
import org.aspectj.weaver.IUnwovenClassFile;
import org.aspectj.weaver.Lint;
import org.aspectj.weaver.Lint.Kind;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.World;
import org.aspectj.weaver.bcel.BcelWeakClassLoaderReference;
import org.aspectj.weaver.bcel.BcelWeaver;
import org.aspectj.weaver.bcel.BcelWorld;
import org.aspectj.weaver.bcel.Utility;
import org.aspectj.weaver.loadtime.definition.Definition;
import org.aspectj.weaver.loadtime.definition.DocumentParser;
import org.aspectj.weaver.ltw.LTWWorld;
import org.aspectj.weaver.patterns.PatternParser;
import org.aspectj.weaver.patterns.TypePattern;
import org.aspectj.weaver.tools.GeneratedClassHandler;
import org.aspectj.weaver.tools.Trace;
import org.aspectj.weaver.tools.TraceFactory;
import org.aspectj.weaver.tools.WeavingAdaptor;
import org.aspectj.weaver.tools.cache.WeavedClassCache;

import sun.misc.Unsafe;

/**
 * @author Alexandre Vasseur
 * @author Andy Clement
 * @author Abraham Nevado
 * @author David Knibb
 * @author John Kew
 */
public class ClassLoaderWeavingAdaptor extends WeavingAdaptor {

    private final static String AOP_XML = Constants.AOP_USER_XML + ";" + Constants.AOP_AJC_XML + ";"
            + Constants.AOP_OSGI_XML;

    private boolean initialized;

    private List<TypePattern> dumpTypePattern = new ArrayList<TypePattern>();
    private boolean dumpBefore = false;
    private boolean dumpDirPerClassloader = false;

    private boolean hasExcludes = false;
    private List<TypePattern> excludeTypePattern = new ArrayList<TypePattern>(); // anything
    private List<String> excludeStartsWith = new ArrayList<String>(); // com.foo..*
    private List<String> excludeStarDotDotStar = new ArrayList<String>(); // *..*CGLIB*
    private List<String> excludeExactName = new ArrayList<String>(); // com.foo.Bar
    private List<String> excludeEndsWith = new ArrayList<String>(); // com.foo.Bar
    private List<String[]> excludeSpecial = new ArrayList<String[]>();

    private boolean hasIncludes = false;
    private List<TypePattern> includeTypePattern = new ArrayList<TypePattern>();
    private List<String> includeStartsWith = new ArrayList<String>();
    private List<String> includeExactName = new ArrayList<String>();
    private boolean includeStar = false;

    private List<TypePattern> aspectExcludeTypePattern = new ArrayList<TypePattern>();
    private List<String> aspectExcludeStartsWith = new ArrayList<String>();
    private List<TypePattern> aspectIncludeTypePattern = new ArrayList<TypePattern>();
    private List<String> aspectIncludeStartsWith = new ArrayList<String>();

    private StringBuffer namespace;
    private IWeavingContext weavingContext;

    private List<ConcreteAspectCodeGen> concreteAspects = new ArrayList<ConcreteAspectCodeGen>();

    private static Trace trace = TraceFactory.getTraceFactory().getTrace(ClassLoaderWeavingAdaptor.class);

    public ClassLoaderWeavingAdaptor() {
        super();
        if (trace.isTraceEnabled()) {
            trace.enter("<init>", this);
        }
        if (trace.isTraceEnabled()) {
            trace.exit("<init>");
        }
    }

    /**
     * We don't need a reference to the class loader and using it during construction can cause problems with recursion. It also
     * makes sense to supply the weaving context during initialization to.
     *
     * @deprecated
     */
    @Deprecated
    public ClassLoaderWeavingAdaptor(final ClassLoader deprecatedLoader, final IWeavingContext deprecatedContext) {
        super();
        if (trace.isTraceEnabled()) {
            trace.enter("<init>", this, new Object[] { deprecatedLoader, deprecatedContext });
        }
        if (trace.isTraceEnabled()) {
            trace.exit("<init>");
        }
    }

    class SimpleGeneratedClassHandler implements GeneratedClassHandler {
        private BcelWeakClassLoaderReference loaderRef;

        SimpleGeneratedClassHandler(ClassLoader loader) {
            loaderRef = new BcelWeakClassLoaderReference(loader);
        }

        /**
         * Callback when we need to define a Closure in the JVM
         *
         */
        @Override
        public void acceptClass(String name, byte[] originalBytes, byte[] wovenBytes) {
            try {
                if (shouldDump(name.replace('/', '.'), false)) {
                    dump(name, wovenBytes, false);
                }
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            if (activeProtectionDomain != null) {
                defineClass(loaderRef.getClassLoader(), name, wovenBytes, activeProtectionDomain);
            } else {
                defineClass(loaderRef.getClassLoader(), name, wovenBytes); // could be done lazily using the hook
            }
        }
    }

    public void initialize(final ClassLoader classLoader, IWeavingContext context) {
        if (initialized) {
            return;
        }

        boolean success = true;

        this.weavingContext = context;
        if (weavingContext == null) {
            weavingContext = new DefaultWeavingContext(classLoader);
        }

        createMessageHandler();

        this.generatedClassHandler = new SimpleGeneratedClassHandler(classLoader);

        List<Definition> definitions = weavingContext.getDefinitions(classLoader, this);
        if (definitions.isEmpty()) {
            disable(); // TODO maw Needed to ensure messages are flushed
            if (trace.isTraceEnabled()) {
                trace.exit("initialize", definitions);
            }
            return;
        }

        // TODO when the world works in terms of the context, we can remove the loader
        bcelWorld = new LTWWorld(classLoader, weavingContext, getMessageHandler(), null);

        weaver = new BcelWeaver(bcelWorld);

        // register the definitions
        success = registerDefinitions(weaver, classLoader, definitions);
        if (success) {

            // after adding aspects
            weaver.prepareForWeave();

            enable(); // TODO maw Needed to ensure messages are flushed
            success = weaveAndDefineConceteAspects();
        }

        if (success) {
            enable();
        } else {
            disable();
            bcelWorld = null;
            weaver = null;
        }
        if (WeavedClassCache.isEnabled()) {
            initializeCache(classLoader, getAspectClassNames(definitions), generatedClassHandler,
                    getMessageHandler());
        }

        initialized = true;
        if (trace.isTraceEnabled()) {
            trace.exit("initialize", isEnabled());
        }
    }

    /**
     * Get the list of all aspects from the defintion list
     * @param definitions
     * @return
     */
    List<String> getAspectClassNames(List<Definition> definitions) {
        List<String> aspects = new LinkedList<String>();
        for (Iterator<Definition> it = definitions.iterator(); it.hasNext();) {
            Definition def = it.next();
            List<String> defAspects = def.getAspectClassNames();
            if (defAspects != null) {
                aspects.addAll(defAspects);
            }
        }
        return aspects;
    }

    /**
     * Load and cache the aop.xml/properties according to the classloader visibility rules
     *
     * @param loader
     */
    List<Definition> parseDefinitions(final ClassLoader loader) {
        if (trace.isTraceEnabled()) {
            trace.enter("parseDefinitions", this);
        }

        List<Definition> definitions = new ArrayList<Definition>();
        try {
            info("register classloader " + getClassLoaderName(loader));
            // TODO av underoptimized: we will parse each XML once per CL that see it

            // TODO av dev mode needed ? TBD -Daj5.def=...
            if (loader.equals(ClassLoader.getSystemClassLoader())) {
                String file = System.getProperty("aj5.def", null);
                if (file != null) {
                    info("using (-Daj5.def) " + file);
                    definitions.add(DocumentParser.parse((new File(file)).toURI().toURL()));
                }
            }

            String resourcePath = System.getProperty("org.aspectj.weaver.loadtime.configuration", AOP_XML);
            if (trace.isTraceEnabled()) {
                trace.event("parseDefinitions", this, resourcePath);
            }

            StringTokenizer st = new StringTokenizer(resourcePath, ";");

            while (st.hasMoreTokens()) {
                String nextDefinition = st.nextToken();
                if (nextDefinition.startsWith("file:")) {
                    try {
                        String fpath = new URL(nextDefinition).getFile();
                        File configFile = new File(fpath);
                        if (!configFile.exists()) {
                            warn("configuration does not exist: " + nextDefinition);
                        } else {
                            definitions.add(DocumentParser.parse(configFile.toURI().toURL()));
                        }
                    } catch (MalformedURLException mue) {
                        error("malformed definition url: " + nextDefinition);
                    }
                } else {
                    Enumeration<URL> xmls = weavingContext.getResources(nextDefinition);
                    // System.out.println("? registerDefinitions: found-aop.xml=" + xmls.hasMoreElements() + ", loader=" + loader);

                    Set<URL> seenBefore = new HashSet<URL>();
                    while (xmls.hasMoreElements()) {
                        URL xml = xmls.nextElement();
                        if (trace.isTraceEnabled()) {
                            trace.event("parseDefinitions", this, xml);
                        }
                        if (!seenBefore.contains(xml)) {
                            info("using configuration " + weavingContext.getFile(xml));
                            definitions.add(DocumentParser.parse(xml));
                            seenBefore.add(xml);
                        } else {
                            debug("ignoring duplicate definition: " + xml);
                        }
                    }
                }
            }
            if (definitions.isEmpty()) {
                info("no configuration found. Disabling weaver for class loader " + getClassLoaderName(loader));
            }
        } catch (Exception e) {
            definitions.clear();
            warn("parse definitions failed", e);
        }

        if (trace.isTraceEnabled()) {
            trace.exit("parseDefinitions", definitions);
        }
        return definitions;
    }

    private boolean registerDefinitions(final BcelWeaver weaver, final ClassLoader loader,
            List<Definition> definitions) {
        if (trace.isTraceEnabled()) {
            trace.enter("registerDefinitions", this, definitions);
        }
        boolean success = true;

        try {
            registerOptions(weaver, loader, definitions);
            registerAspectExclude(weaver, loader, definitions);
            registerAspectInclude(weaver, loader, definitions);
            success = registerAspects(weaver, loader, definitions);
            registerIncludeExclude(weaver, loader, definitions);
            registerDump(weaver, loader, definitions);
        } catch (Exception ex) {
            trace.error("register definition failed", ex);
            success = false;
            warn("register definition failed", (ex instanceof AbortException) ? null : ex);
        }

        if (trace.isTraceEnabled()) {
            trace.exit("registerDefinitions", success);
        }
        return success;
    }

    private String getClassLoaderName(ClassLoader loader) {
        return weavingContext.getClassLoaderName();
    }

    /**
     * Configure the weaver according to the option directives TODO av - don't know if it is that good to reuse, since we only allow
     * a small subset of options in LTW
     *
     * @param weaver
     * @param loader
     * @param definitions
     */
    private void registerOptions(final BcelWeaver weaver, final ClassLoader loader,
            final List<Definition> definitions) {
        StringBuffer allOptions = new StringBuffer();
        for (Definition definition : definitions) {
            allOptions.append(definition.getWeaverOptions()).append(' ');
        }

        Options.WeaverOption weaverOption = Options.parse(allOptions.toString(), loader, getMessageHandler());

        // configure the weaver and world
        // AV - code duplicates AspectJBuilder.initWorldAndWeaver()
        World world = weaver.getWorld();
        setMessageHandler(weaverOption.messageHandler);
        world.setXlazyTjp(weaverOption.lazyTjp);
        world.setXHasMemberSupportEnabled(weaverOption.hasMember);
        world.setTiming(weaverOption.timers, true);
        world.setOptionalJoinpoints(weaverOption.optionalJoinpoints);
        world.setPinpointMode(weaverOption.pinpoint);
        weaver.setReweavableMode(weaverOption.notReWeavable);
        if (weaverOption.loadersToSkip != null && weaverOption.loadersToSkip.length() > 0) {
            Aj.loadersToSkip = LangUtil.anySplit(weaverOption.loadersToSkip, ",");
        }
        if (Aj.loadersToSkip != null) {
            MessageUtil.info(world.getMessageHandler(),
                    "no longer creating weavers for these classloaders: " + Aj.loadersToSkip);
        }
        world.performExtraConfiguration(weaverOption.xSet);
        world.setXnoInline(weaverOption.noInline);
        // AMC - autodetect as per line below, needed for AtAjLTWTests.testLTWUnweavable
        world.setBehaveInJava5Way(LangUtil.is15VMOrGreater());
        world.setAddSerialVerUID(weaverOption.addSerialVersionUID);

        /* First load defaults */
        bcelWorld.getLint().loadDefaultProperties();

        /* Second overlay LTW defaults */
        bcelWorld.getLint().adviceDidNotMatch.setKind(null);

        /* Third load user file using -Xlintfile so that -Xlint wins */
        if (weaverOption.lintFile != null) {
            InputStream resource = null;
            try {
                resource = loader.getResourceAsStream(weaverOption.lintFile);
                Exception failure = null;
                if (resource != null) {
                    try {
                        Properties properties = new Properties();
                        properties.load(resource);
                        world.getLint().setFromProperties(properties);
                    } catch (IOException e) {
                        failure = e;
                    }
                }
                if (failure != null || resource == null) {
                    warn("Cannot access resource for -Xlintfile:" + weaverOption.lintFile, failure);
                    // world.getMessageHandler().handleMessage(new Message(
                    // "Cannot access resource for -Xlintfile:"+weaverOption.lintFile,
                    // IMessage.WARNING,
                    // failure,
                    // null));
                }
            } finally {
                try {
                    resource.close();
                } catch (Throwable t) {
                }
            }
        }

        /* Fourth override with -Xlint */
        if (weaverOption.lint != null) {
            if (weaverOption.lint.equals("default")) {// FIXME should be AjBuildConfig.AJLINT_DEFAULT but yetanother deps..
                bcelWorld.getLint().loadDefaultProperties();
            } else {
                bcelWorld.getLint().setAll(weaverOption.lint);
                if (weaverOption.lint.equals("ignore")) {
                    bcelWorld.setAllLintIgnored();
                }
            }
        }
        // TODO proceedOnError option
    }

    private void registerAspectExclude(final BcelWeaver weaver, final ClassLoader loader,
            final List<Definition> definitions) {
        String fastMatchInfo = null;
        for (Definition definition : definitions) {
            for (String exclude : definition.getAspectExcludePatterns()) {
                TypePattern excludePattern = new PatternParser(exclude).parseTypePattern();
                aspectExcludeTypePattern.add(excludePattern);
                fastMatchInfo = looksLikeStartsWith(exclude);
                if (fastMatchInfo != null) {
                    aspectExcludeStartsWith.add(fastMatchInfo);
                }
            }
        }
    }

    private void registerAspectInclude(final BcelWeaver weaver, final ClassLoader loader,
            final List<Definition> definitions) {
        String fastMatchInfo = null;
        for (Definition definition : definitions) {
            for (String include : definition.getAspectIncludePatterns()) {
                TypePattern includePattern = new PatternParser(include).parseTypePattern();
                aspectIncludeTypePattern.add(includePattern);
                fastMatchInfo = looksLikeStartsWith(include);
                if (fastMatchInfo != null) {
                    aspectIncludeStartsWith.add(fastMatchInfo);
                }
            }
        }
    }

    protected void lint(String name, String[] infos) {
        Lint lint = bcelWorld.getLint();
        Kind kind = lint.getLintKind(name);
        kind.signal(infos, null, null);
    }

    @Override
    public String getContextId() {
        return weavingContext.getId();
    }

    /**
     * Register the aspect, following include / exclude rules
     *
     * @param weaver
     * @param loader
     * @param definitions
     */
    private boolean registerAspects(final BcelWeaver weaver, final ClassLoader loader,
            final List<Definition> definitions) {
        if (trace.isTraceEnabled()) {
            trace.enter("registerAspects", this, new Object[] { weaver, loader, definitions });
        }
        boolean success = true;

        // TODO: the exclude aspect allow to exclude aspect defined upper in the CL hierarchy - is it what we want ??
        // if not, review the getResource so that we track which resource is defined by which CL

        // iterate aspectClassNames
        // exclude if in any of the exclude list
        for (Definition definition : definitions) {
            for (String aspectClassName : definition.getAspectClassNames()) {
                if (acceptAspect(aspectClassName)) {
                    info("register aspect " + aspectClassName);
                    // System.err.println("? ClassLoaderWeavingAdaptor.registerAspects() aspectName=" + aspectClassName +
                    // ", loader=" + loader + ", bundle=" + weavingContext.getClassLoaderName());
                    String requiredType = definition.getAspectRequires(aspectClassName);
                    if (requiredType != null) {
                        // This aspect expresses that it requires a type to be around, otherwise it should 'switch off'
                        ((BcelWorld) weaver.getWorld()).addAspectRequires(aspectClassName, requiredType);
                    }
                    String definedScope = definition.getScopeForAspect(aspectClassName);
                    if (definedScope != null) {
                        ((BcelWorld) weaver.getWorld()).addScopedAspect(aspectClassName, definedScope);
                    }
                    // ResolvedType aspect =
                    weaver.addLibraryAspect(aspectClassName);

                    // generate key for SC
                    if (namespace == null) {
                        namespace = new StringBuffer(aspectClassName);
                    } else {
                        namespace = namespace.append(";").append(aspectClassName);
                    }

                } else {
                    // warn("aspect excluded: " + aspectClassName);
                    lint("aspectExcludedByConfiguration",
                            new String[] { aspectClassName, getClassLoaderName(loader) });
                }
            }
        }

        // iterate concreteAspects
        // exclude if in any of the exclude list - note that the user defined name matters for that to happen
        for (Definition definition : definitions) {
            for (Definition.ConcreteAspect concreteAspect : definition.getConcreteAspects()) {
                if (acceptAspect(concreteAspect.name)) {
                    info("define aspect " + concreteAspect.name);
                    ConcreteAspectCodeGen gen = new ConcreteAspectCodeGen(concreteAspect, weaver.getWorld());
                    if (!gen.validate()) {
                        error("Concrete-aspect '" + concreteAspect.name + "' could not be registered");
                        success = false;
                        break;
                    }

                    ((BcelWorld) weaver.getWorld())
                            .addSourceObjectType(Utility.makeJavaClass(concreteAspect.name, gen.getBytes()), true);

                    concreteAspects.add(gen);

                    weaver.addLibraryAspect(concreteAspect.name);

                    // generate key for SC
                    if (namespace == null) {
                        namespace = new StringBuffer(concreteAspect.name);
                    } else {
                        namespace = namespace.append(";" + concreteAspect.name);
                    }
                }
            }
        }

        /* We couldn't register one or more aspects so disable the adaptor */
        if (!success) {
            warn("failure(s) registering aspects. Disabling weaver for class loader " + getClassLoaderName(loader));
        }

        /* We didn't register any aspects so disable the adaptor */
        else if (namespace == null) {
            success = false;
            info("no aspects registered. Disabling weaver for class loader " + getClassLoaderName(loader));
        }

        if (trace.isTraceEnabled()) {
            trace.exit("registerAspects", success);
        }
        return success;
    }

    private boolean weaveAndDefineConceteAspects() {
        if (trace.isTraceEnabled()) {
            trace.enter("weaveAndDefineConceteAspects", this, concreteAspects);
        }
        boolean success = true;

        for (ConcreteAspectCodeGen gen : concreteAspects) {
            String name = gen.getClassName();
            byte[] bytes = gen.getBytes();

            try {
                byte[] newBytes = weaveClass(name, bytes, true);
                this.generatedClassHandler.acceptClass(name, bytes, newBytes);
            } catch (IOException ex) {
                trace.error("weaveAndDefineConceteAspects", ex);
                error("exception weaving aspect '" + name + "'", ex);
            }
        }

        if (trace.isTraceEnabled()) {
            trace.exit("weaveAndDefineConceteAspects", success);
        }
        return success;
    }

    /**
     * Register the include / exclude filters. We duplicate simple patterns in startWith filters that will allow faster matching
     * without ResolvedType
     *
     * @param weaver
     * @param loader
     * @param definitions
     */
    private void registerIncludeExclude(final BcelWeaver weaver, final ClassLoader loader,
            final List<Definition> definitions) {
        String fastMatchInfo = null;
        for (Definition definition : definitions) {
            for (Iterator<String> iterator1 = definition.getIncludePatterns().iterator(); iterator1.hasNext();) {
                hasIncludes = true;
                String include = iterator1.next();
                fastMatchInfo = looksLikeStartsWith(include);
                if (fastMatchInfo != null) {
                    includeStartsWith.add(fastMatchInfo);
                } else if (include.equals("*")) {
                    includeStar = true;
                } else if ((fastMatchInfo = looksLikeExactName(include)) != null) {
                    includeExactName.add(fastMatchInfo);
                } else {
                    TypePattern includePattern = new PatternParser(include).parseTypePattern();
                    includeTypePattern.add(includePattern);
                }
            }
            for (Iterator<String> iterator1 = definition.getExcludePatterns().iterator(); iterator1.hasNext();) {
                hasExcludes = true;
                String exclude = iterator1.next();
                fastMatchInfo = looksLikeStartsWith(exclude);
                if (fastMatchInfo != null) {
                    excludeStartsWith.add(fastMatchInfo);
                } else if ((fastMatchInfo = looksLikeStarDotDotStarExclude(exclude)) != null) {
                    excludeStarDotDotStar.add(fastMatchInfo);
                } else if ((fastMatchInfo = looksLikeExactName(exclude)) != null) {
                    excludeExactName.add(exclude);
                } else if ((fastMatchInfo = looksLikeEndsWith(exclude)) != null) {
                    excludeEndsWith.add(fastMatchInfo);
                } else if (exclude.equals(
                        "org.codehaus.groovy..* && !org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsController*")) {
                    // TODO need a more sophisticated analysis here, to allow for similar situations
                    excludeSpecial.add(new String[] { "org.codehaus.groovy.",
                            "org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsController" });
                    // for the related test:
                    // } else if (exclude.equals("testdata..* && !testdata.sub.Oran*")) {
                    // excludeSpecial.add(new String[] { "testdata.", "testdata.sub.Oran" });
                } else {
                    TypePattern excludePattern = new PatternParser(exclude).parseTypePattern();
                    excludeTypePattern.add(excludePattern);
                }
            }
        }
    }

    /**
     * Checks if the pattern looks like "*..*XXXX*" and if so returns XXXX. This will enable fast name matching of CGLIB exclusion
     *
     */
    private String looksLikeStarDotDotStarExclude(String typePattern) {
        if (!typePattern.startsWith("*..*")) {
            return null;
        }
        if (!typePattern.endsWith("*")) {
            return null;
        }
        String subPattern = typePattern.substring(4, typePattern.length() - 1);
        if (hasStarDot(subPattern, 0)) {
            return null;
        }
        return subPattern.replace('$', '.');
    }

    /**
     * Checks if the pattern looks like "com.foo.Bar" - an exact name
     */
    private String looksLikeExactName(String typePattern) {
        if (hasSpaceAnnotationPlus(typePattern, 0) || typePattern.indexOf("*") != -1) {
            return null;
        }
        return typePattern.replace('$', '.');
    }

    /**
     * Checks if the pattern looks like "*Exception"
     */
    private String looksLikeEndsWith(String typePattern) {
        if (typePattern.charAt(0) != '*') {
            return null;
        }
        if (hasSpaceAnnotationPlus(typePattern, 1) || hasStarDot(typePattern, 1)) {
            return null;
        }
        return typePattern.substring(1).replace('$', '.');
    }

    /**
     * Determine if something in the string is going to affect our ability to optimize. Checks for: ' ' '@' '+'
     */
    private boolean hasSpaceAnnotationPlus(String string, int pos) {
        for (int i = pos, max = string.length(); i < max; i++) {
            char ch = string.charAt(i);
            if (ch == ' ' || ch == '@' || ch == '+') {
                return true;
            }
        }
        return false;
    }

    /**
     * Determine if something in the string is going to affect our ability to optimize. Checks for: '*' '.'
     */
    private boolean hasStarDot(String string, int pos) {
        for (int i = pos, max = string.length(); i < max; i++) {
            char ch = string.charAt(i);
            if (ch == '*' || ch == '.') {
                return true;
            }
        }
        return false;
    }

    /**
     * Checks if the type pattern looks like "com.foo..*"
     */
    private String looksLikeStartsWith(String typePattern) {
        if (hasSpaceAnnotationPlus(typePattern, 0) || typePattern.charAt(typePattern.length() - 1) != '*') {
            return null;
        }
        // now must looks like with "charsss..*" or "cha.rss..*" etc
        // note that "*" and "*..*" won't be fast matched
        // and that "charsss.*" will not neither
        int length = typePattern.length();
        if (typePattern.endsWith("..*") && length > 3) {
            if (typePattern.indexOf("..") == length - 3 // no ".." before last sequence
                    && typePattern.indexOf('*') == length - 1) { // no earlier '*'
                return typePattern.substring(0, length - 2).replace('$', '.'); // "charsss." or "char.rss." etc
            }
        }
        return null;
    }

    /**
     * Register the dump filter
     *
     * @param weaver
     * @param loader
     * @param definitions
     */
    private void registerDump(final BcelWeaver weaver, final ClassLoader loader,
            final List<Definition> definitions) {
        for (Definition definition : definitions) {
            for (Iterator<String> iterator1 = definition.getDumpPatterns().iterator(); iterator1.hasNext();) {
                String dump = iterator1.next();
                TypePattern pattern = new PatternParser(dump).parseTypePattern();
                dumpTypePattern.add(pattern);
            }
            if (definition.shouldDumpBefore()) {
                dumpBefore = true;
            }
            if (definition.createDumpDirPerClassloader()) {
                dumpDirPerClassloader = true;
            }
        }
    }

    /**
     * Determine whether a type should be accepted for weaving, by checking it against any includes/excludes.
     *
     * @param className the name of the type to possibly accept
     * @param bytes the bytecode for the type (in case we need to look inside, eg. annotations)
     * @return true if it should be accepted for weaving
     */
    @Override
    protected boolean accept(String className, byte[] bytes) {

        if (!hasExcludes && !hasIncludes) {
            return true;
        }

        // still try to avoid ResolvedType if we have simple patterns
        String fastClassName = className.replace('/', '.');
        for (String excludeStartsWithString : excludeStartsWith) {
            if (fastClassName.startsWith(excludeStartsWithString)) {
                return false;
            }
        }

        // Fast exclusion of patterns like: "*..*CGLIB*"
        if (!excludeStarDotDotStar.isEmpty()) {
            for (String namePiece : excludeStarDotDotStar) {
                int index = fastClassName.lastIndexOf('.');
                if (fastClassName.indexOf(namePiece, index + 1) != -1) {
                    return false;
                }
            }
        }
        fastClassName = fastClassName.replace('$', '.');

        if (!excludeEndsWith.isEmpty()) {
            for (String lastPiece : excludeEndsWith) {
                if (fastClassName.endsWith(lastPiece)) {
                    return false;
                }
            }
        }

        // Fast exclusion of exact names
        if (!excludeExactName.isEmpty()) {
            for (String name : excludeExactName) {
                if (fastClassName.equals(name)) {
                    return false;
                }
            }
        }

        if (!excludeSpecial.isEmpty()) {
            for (String[] entry : excludeSpecial) {
                String excludeThese = entry[0];
                String exceptThese = entry[1];
                if (fastClassName.startsWith(excludeThese) && !fastClassName.startsWith(exceptThese)) {
                    return false;
                }
            }
        }

        /*
         * Bug 120363 If we have an exclude pattern that cannot be matched using "starts with" then we cannot fast accept
         */
        boolean didSomeIncludeMatching = false;
        if (excludeTypePattern.isEmpty()) {
            if (includeStar) {
                return true;
            }
            if (!includeExactName.isEmpty()) {
                didSomeIncludeMatching = true;
                for (String exactname : includeExactName) {
                    if (fastClassName.equals(exactname)) {
                        return true;
                    }
                }
            }
            boolean fastAccept = false;// defaults to false if no fast include
            for (int i = 0; i < includeStartsWith.size(); i++) {
                didSomeIncludeMatching = true;
                fastAccept = fastClassName.startsWith(includeStartsWith.get(i));
                if (fastAccept) {
                    return true;
                }
            }
            // We may have processed all patterns now... check that and return
            if (includeTypePattern.isEmpty()) {
                return !didSomeIncludeMatching;
            }
        }

        boolean accept;
        try {
            ensureDelegateInitialized(className, bytes);

            ResolvedType classInfo = delegateForCurrentClass.getResolvedTypeX();

            // exclude are "AND"ed
            for (TypePattern typePattern : excludeTypePattern) {
                if (typePattern.matchesStatically(classInfo)) {
                    // exclude match - skip
                    return false;
                }
            }
            // include are "OR"ed
            if (includeStar) {
                return true;
            }
            if (!includeExactName.isEmpty()) {
                didSomeIncludeMatching = true;
                for (String exactname : includeExactName) {
                    if (fastClassName.equals(exactname)) {
                        return true;
                    }
                }
            }
            for (int i = 0; i < includeStartsWith.size(); i++) {
                didSomeIncludeMatching = true;
                boolean fastaccept = fastClassName.startsWith(includeStartsWith.get(i));
                if (fastaccept) {
                    return true;
                }
            }
            accept = !didSomeIncludeMatching; // only true if no includes at all
            for (TypePattern typePattern : includeTypePattern) {
                accept = typePattern.matchesStatically(classInfo);
                if (accept) {
                    break;
                }
                // goes on if this include did not match ("OR"ed)
            }
        } finally {
            this.bcelWorld.demote();
        }
        return accept;
    }

    // FIXME we don't use include/exclude of others aop.xml
    // this can be nice but very dangerous as well to change that
    private boolean acceptAspect(String aspectClassName) {
        // avoid ResolvedType if not needed
        if (aspectExcludeTypePattern.isEmpty() && aspectIncludeTypePattern.isEmpty()) {
            return true;
        }

        // still try to avoid ResolvedType if we have simple patterns
        // EXCLUDE: if one match then reject
        String fastClassName = aspectClassName.replace('/', '.').replace('.', '$');
        for (int i = 0; i < aspectExcludeStartsWith.size(); i++) {
            if (fastClassName.startsWith(aspectExcludeStartsWith.get(i))) {
                return false;
            }
        }
        // INCLUDE: if one match then accept
        for (int i = 0; i < aspectIncludeStartsWith.size(); i++) {
            if (fastClassName.startsWith(aspectIncludeStartsWith.get(i))) {
                return true;
            }
        }

        // needs further analysis
        ResolvedType classInfo = weaver.getWorld().resolve(UnresolvedType.forName(aspectClassName), true);
        // exclude are "AND"ed
        for (TypePattern typePattern : aspectExcludeTypePattern) {
            if (typePattern.matchesStatically(classInfo)) {
                // exclude match - skip
                return false;
            }
        }
        // include are "OR"ed
        boolean accept = true;// defaults to true if no include
        for (TypePattern typePattern : aspectIncludeTypePattern) {
            accept = typePattern.matchesStatically(classInfo);
            if (accept) {
                break;
            }
            // goes on if this include did not match ("OR"ed)
        }
        return accept;
    }

    @Override
    protected boolean shouldDump(String className, boolean before) {
        // Don't dump before weaving unless asked to
        if (before && !dumpBefore) {
            return false;
        }

        // avoid ResolvedType if not needed
        if (dumpTypePattern.isEmpty()) {
            return false;
        }

        // TODO AV - optimize for className.startWith only
        ResolvedType classInfo = weaver.getWorld().resolve(UnresolvedType.forName(className), true);
        // dump
        for (Iterator<TypePattern> iterator = dumpTypePattern.iterator(); iterator.hasNext();) {
            TypePattern typePattern = iterator.next();
            if (typePattern.matchesStatically(classInfo)) {
                // dump match
                return true;
            }
        }
        return false;
    }

    @Override
    protected String getDumpDir() {
        if (dumpDirPerClassloader) {
            StringBuffer dir = new StringBuffer();
            dir.append("_ajdump").append(File.separator).append(weavingContext.getId());
            return dir.toString();
        } else {
            return super.getDumpDir();
        }
    }

    /*
     * shared classes methods
     */

    /**
     * @return Returns the key.
     */
    public String getNamespace() {
        // System.out.println("ClassLoaderWeavingAdaptor.getNamespace() classloader=" + weavingContext.getClassLoaderName() +
        // ", namespace=" + namespace);
        if (namespace == null) {
            return "";
        } else {
            return new String(namespace);
        }
    }

    /**
     * Check to see if any classes are stored in the generated classes cache. Then flush the cache if it is not empty
     *
     * @param className TODO
     * @return true if a class has been generated and is stored in the cache
     */
    public boolean generatedClassesExistFor(String className) {
        // System.err.println("? ClassLoaderWeavingAdaptor.generatedClassesExist() classname=" + className + ", size=" +
        // generatedClasses);
        if (className == null) {
            return !generatedClasses.isEmpty();
        } else {
            return generatedClasses.containsKey(className);
        }
    }

    /**
     * Flush the generated classes cache
     */
    public void flushGeneratedClasses() {
        // System.err.println("? ClassLoaderWeavingAdaptor.flushGeneratedClasses() generatedClasses=" + generatedClasses);
        generatedClasses = new HashMap<String, IUnwovenClassFile>();
    }

    /**
     * Remove generated classes based on the supplied className. This will
     * remove any entries related to this name - so the class itself plus
     * and inner classes.
     * @param className a slashed classname (e.g. com/foo/Bar)
     */
    public void flushGeneratedClassesFor(String className) {
        try {
            String dottedClassName = className.replace('/', '.');
            String dottedClassNameDollar = dottedClassName + "$"; // to pickup inner classes
            Iterator<Map.Entry<String, IUnwovenClassFile>> iter = generatedClasses.entrySet().iterator();
            while (iter.hasNext()) {
                Entry<String, IUnwovenClassFile> next = iter.next();
                String existingGeneratedName = next.getKey();
                if (existingGeneratedName.equals(dottedClassName)
                        || existingGeneratedName.startsWith(dottedClassNameDollar)) {
                    iter.remove();
                }
            }
        } catch (Throwable t) {
            new RuntimeException("Unexpected problem tidying up generated classes for " + className, t)
                    .printStackTrace();
        }
    }

    private Unsafe unsafe;

    private Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException {
        if (unsafe == null) {
            Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafeField.setAccessible(true);
            return (Unsafe) theUnsafeField.get(null);
        }
        return unsafe;
    }

    private static Method bindTo_Method, invokeWithArguments_Method = null;
    private static Object defineClassMethodHandle = null;

    private static Boolean initializedForJava11 = false;

    // In order to let this code compile on earlier versions of Java (8), use reflection to discover the elements
    // we need to define classes.
    private static synchronized void initializeForJava11() {
        if (initializedForJava11)
            return;
        try {
            // MethodType defineClassMethodType = MethodType.methodType(Class.class, new Class[]{String.class, byte[].class, int.class, int.class, ProtectionDomain.class});
            Class<?> methodType_Class = Class.forName("java.lang.invoke.MethodType");
            Method methodTypeMethodOnMethodTypeClass = methodType_Class.getDeclaredMethod("methodType", Class.class,
                    Class[].class);
            methodTypeMethodOnMethodTypeClass.setAccessible(true);
            Object defineClassMethodType = methodTypeMethodOnMethodTypeClass.invoke(null, Class.class,
                    new Class[] { String.class, byte[].class, int.class, int.class, ProtectionDomain.class });

            // MethodHandles.Lookup methodHandlesLookup = MethodHandles.lookup();
            Class<?> methodHandles_Class = Class.forName("java.lang.invoke.MethodHandles");
            Method lookupMethodOnMethodHandlesClass = methodHandles_Class.getDeclaredMethod("lookup");
            lookupMethodOnMethodHandlesClass.setAccessible(true);
            Object methodHandlesLookup = lookupMethodOnMethodHandlesClass.invoke(null);

            // MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(ClassLoader.class, methodHandlesLookup);
            Class<?> methodHandlesLookup_Class = Class.forName("java.lang.invoke.MethodHandles$Lookup");
            Method privateLookupMethodOnMethodHandlesClass = methodHandles_Class
                    .getDeclaredMethod("privateLookupIn", Class.class, methodHandlesLookup_Class);
            privateLookupMethodOnMethodHandlesClass.setAccessible(true);
            Object lookup = privateLookupMethodOnMethodHandlesClass.invoke(null, ClassLoader.class,
                    methodHandlesLookup);

            // MethodHandle defineClassMethodHandle = lookup.findVirtual(ClassLoader.class, "defineClass", defineClassMethodType);
            Method findVirtual_Method = methodHandlesLookup_Class.getDeclaredMethod("findVirtual", Class.class,
                    String.class, methodType_Class);
            findVirtual_Method.setAccessible(true);
            defineClassMethodHandle = findVirtual_Method.invoke(lookup, ClassLoader.class, "defineClass",
                    defineClassMethodType);

            // clazz = defineClassMethodHandle.bindTo(loader).invokeWithArguments(name, bytes, 0, bytes.length);
            Class<?> methodHandle_Class = Class.forName("java.lang.invoke.MethodHandle");
            bindTo_Method = methodHandle_Class.getDeclaredMethod("bindTo", Object.class);
            invokeWithArguments_Method = methodHandle_Class.getDeclaredMethod("invokeWithArguments",
                    Object[].class);

            initializedForJava11 = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void defineClass(ClassLoader loader, String name, byte[] bytes, ProtectionDomain protectionDomain) {
        if (trace.isTraceEnabled()) {
            trace.enter("defineClass", this, new Object[] { loader, name, bytes });
        }
        Object clazz = null;
        debug("generating class '" + name + "'");
        if (LangUtil.is11VMOrGreater()) {
            try {
                if (!initializedForJava11) {
                    initializeForJava11();
                }
                // Do this: clazz = defineClassMethodHandle.bindTo(loader).invokeWithArguments(name, bytes, 0, bytes.length, protectionDomain);
                Object o = bindTo_Method.invoke(defineClassMethodHandle, loader);
                clazz = invokeWithArguments_Method.invoke(o,
                        new Object[] { new Object[] { name, bytes, 0, bytes.length, protectionDomain } });

            } catch (Throwable t) {
                t.printStackTrace(System.err);
                warn("define generated class failed", t);
            }
        } else {
            try {
                if (defineClassMethod == null) {
                    synchronized (lock) {
                        getUnsafe();
                        defineClassMethod = Unsafe.class.getDeclaredMethod("defineClass", String.class,
                                byte[].class, Integer.TYPE, Integer.TYPE, ClassLoader.class,
                                ProtectionDomain.class);
                    }
                }
                defineClassMethod.setAccessible(true);
                clazz = defineClassMethod.invoke(getUnsafe(), name, bytes, 0, bytes.length, loader,
                        protectionDomain);
            } catch (LinkageError le) {
                le.printStackTrace();
                // likely thrown due to defining something that already exists?
                // Old comments from before moving to Unsafe.defineClass():
                // is already defined (happens for X$ajcMightHaveAspect interfaces since aspects are reweaved)
                // TODO maw I don't think this is OK and
            } catch (Exception e) {
                e.printStackTrace(System.err);
                warn("define generated class failed", e);
            }
        }

        if (trace.isTraceEnabled()) {
            trace.exit("defineClass", clazz);
        }
    }

    static Method defineClassMethod;
    private static String lock = "lock";

    //    /*
    //    This method is equivalent to the following code but use reflection to compile on Java 7:
    //     MethodHandles.Lookup baseLookup = MethodHandles.lookup();
    //    MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(ClassLoader.class, baseLookup);
    //    MethodHandle defineClassMethodHandle = lookup.findVirtual(ClassLoader.class, "defineClass", defineClassMethodType);
    //    handle.bindTo(classLoader).invokeWithArguments(className, classBytes, 0, classBytes.length));
    // */
    //@Override
    //@SuppressWarnings("unchecked")
    //public <T> Class<T> defineClass(ClassLoader classLoader, String className, byte[] classBytes) {
    //    Object baseLookup = methodHandlesLookup.invoke(null);
    //    Object lookup = methodHandlesPrivateLookupIn.invoke(null, ClassLoader.class, baseLookup);
    //    MethodHandle defineClassMethodHandle = (MethodHandle) lookupFindVirtual.invoke(lookup, ClassLoader.class, "defineClass", defineClassMethodType);
    //    try {
    //        return Cast.uncheckedCast(defineClassMethodHandle.bindTo(classLoader).invokeWithArguments(className, classBytes, 0, classBytes.length));
    //    } catch (Throwable throwable) {
    //        throw new RuntimeException(throwable);
    //        return (Class) defineClassMethodHandle.bindTo(classLoader).invokeWithArguments(className, classBytes, 0, classBytes.length);
    //    } catch (Throwable e) {
    //        throw new RuntimeException(e);
    //    }
    //}

    private void defineClass(ClassLoader loader, String name, byte[] bytes) {
        defineClass(loader, name, bytes, null);//, ProtectionDomain protectionDomain) {
    }
    //      if (trace.isTraceEnabled()) {
    //         trace.enter("defineClass", this, new Object[] { loader, name, bytes, protectionDomain });
    //      }
    //      Object clazz = null;
    //      debug("generating class '" + name + "'");
    //      try {
    //         getUnsafe().defineClass(name, bytes, 0, bytes.length, loader, protectionDomain);
    //      } catch (LinkageError le) {
    //         // likely thrown due to defining something that already exists?
    //         // Old comments from before moving to Unsafe.defineClass():
    //         // is already defined (happens for X$ajcMightHaveAspect interfaces since aspects are reweaved)
    //         // TODO maw I don't think this is OK and
    //      } catch (Exception e) {
    //         warn("define generated class failed", e);
    //      }
    //
    //      if (trace.isTraceEnabled()) {
    //         trace.exit("defineClass", clazz);
    //      }
    //   }

}