Bringing up-to-date with default branch Instrumentation237919
authorJaroslav Tulach <jtulach@netbeans.org>
Wed, 13 Nov 2013 15:46:25 +0100
branchInstrumentation237919
changeset 276355b55a0cbcfa6c
parent 275822 7671d5f86e7d
parent 276354 e5a9a3fa6f3b
child 276356 6869874b14af
Bringing up-to-date with default branch
     1.1 --- a/core.netigso/manifest.mf	Tue Nov 12 21:57:59 2013 +0000
     1.2 +++ b/core.netigso/manifest.mf	Wed Nov 13 15:46:25 2013 +0100
     1.3 @@ -2,7 +2,7 @@
     1.4  OpenIDE-Module: org.netbeans.core.netigso
     1.5  OpenIDE-Module-Localizing-Bundle: org/netbeans/core/netigso/Bundle.properties
     1.6  OpenIDE-Module-Provides: org.netbeans.NetigsoFramework
     1.7 -OpenIDE-Module-Specification-Version: 1.24
     1.8 +OpenIDE-Module-Specification-Version: 1.25
     1.9  OpenIDE-Module-Needs: org.osgi.framework.launch.FrameworkFactory
    1.10  AutoUpdate-Essential-Module: true
    1.11  
     2.1 --- a/core.netigso/src/org/netbeans/core/netigso/Netigso.java	Tue Nov 12 21:57:59 2013 +0000
     2.2 +++ b/core.netigso/src/org/netbeans/core/netigso/Netigso.java	Wed Nov 13 15:46:25 2013 +0100
     2.3 @@ -49,6 +49,7 @@
     2.4  import java.io.IOException;
     2.5  import java.io.InputStream;
     2.6  import java.net.URL;
     2.7 +import java.security.ProtectionDomain;
     2.8  import java.util.Arrays;
     2.9  import java.util.Collection;
    2.10  import java.util.Collections;
    2.11 @@ -785,4 +786,8 @@
    2.12          }
    2.13          return pkgs;
    2.14      }
    2.15 +
    2.16 +    public final byte[] patchBC(ClassLoader l, String className, ProtectionDomain pd, byte[] arr) {
    2.17 +        return patchByteCode(l, className, pd, arr);
    2.18 +    }
    2.19  }
     3.1 --- a/core.netigso/src/org/netbeans/core/netigso/spi/NetigsoArchive.java	Tue Nov 12 21:57:59 2013 +0000
     3.2 +++ b/core.netigso/src/org/netbeans/core/netigso/spi/NetigsoArchive.java	Wed Nov 13 15:46:25 2013 +0100
     3.3 @@ -43,6 +43,9 @@
     3.4  package org.netbeans.core.netigso.spi;
     3.5  
     3.6  import java.io.IOException;
     3.7 +import java.lang.instrument.IllegalClassFormatException;
     3.8 +import java.lang.instrument.Instrumentation;
     3.9 +import java.security.ProtectionDomain;
    3.10  import org.netbeans.ArchiveResources;
    3.11  import org.netbeans.core.netigso.Netigso;
    3.12  import org.netbeans.core.netigso.NetigsoArchiveFactory;
    3.13 @@ -112,6 +115,25 @@
    3.14      public boolean isActive() {
    3.15          return netigso.isArchiveActive();
    3.16      }
    3.17 +    
    3.18 +    /** Gives OSGi containers that support bytecode patching a chance to
    3.19 +     * call into NetBeans internal patching system based on 
    3.20 +     * {@link Instrumentation}. 
    3.21 +     * 
    3.22 +     * @param l class loader loading the class
    3.23 +     * @param className the name of the class to define
    3.24 +     * @param pd its protection domain
    3.25 +     * @param arr bytecode (must not be modified)
    3.26 +     * @return the same or alternative bytecode to use for the defined class
    3.27 +     * @since 1.25
    3.28 +     */
    3.29 +    public final byte[] patchByteCode(ClassLoader l, 
    3.30 +        String className, 
    3.31 +        ProtectionDomain pd, 
    3.32 +        byte[] arr
    3.33 +    ) {
    3.34 +        return netigso.patchBC(l, className, pd, arr);
    3.35 +    }
    3.36  
    3.37      static {
    3.38          NetigsoArchiveFactory f = new NetigsoArchiveFactory() {
     4.1 --- a/netbinox/src/org/netbeans/modules/netbinox/NetbinoxHooks.java	Tue Nov 12 21:57:59 2013 +0000
     4.2 +++ b/netbinox/src/org/netbeans/modules/netbinox/NetbinoxHooks.java	Wed Nov 13 15:46:25 2013 +0100
     4.3 @@ -75,6 +75,7 @@
     4.4  import org.osgi.framework.BundleException;
     4.5  import org.osgi.framework.FrameworkEvent;
     4.6  import org.osgi.framework.FrameworkListener;
     4.7 +import org.osgi.framework.wiring.BundleWiring;
     4.8  
     4.9  /**
    4.10   *
    4.11 @@ -96,8 +97,10 @@
    4.12  
    4.13  
    4.14      @Override
    4.15 -    public byte[] processClass(String string, byte[] bytes, ClasspathEntry ce, BundleEntry be, ClasspathManager cm) {
    4.16 -        return bytes;
    4.17 +    public byte[] processClass(String className, byte[] bytes, ClasspathEntry ce, BundleEntry be, ClasspathManager cm) {
    4.18 +        BundleWiring w = ce.getBaseData().getBundle().adapt(org.osgi.framework.wiring.BundleWiring.class);
    4.19 +        ClassLoader loader = w.getClassLoader();
    4.20 +        return archive.patchByteCode(loader, className, ce.getDomain(), bytes);
    4.21      }
    4.22  
    4.23      @Override
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/netbinox/test/unit/data/jars/agent.mf	Wed Nov 13 15:46:25 2013 +0100
     5.3 @@ -0,0 +1,3 @@
     5.4 +Manifest-Version: 1.0
     5.5 +Bundle-SymbolicName: org.agent
     5.6 +Agent-Class: org.agent.HelloWorldAgent
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/netbinox/test/unit/data/jars/agent/org/agent/HelloWorld.java	Wed Nov 13 15:46:25 2013 +0100
     6.3 @@ -0,0 +1,56 @@
     6.4 +/*
     6.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     6.6 + *
     6.7 + * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
     6.8 + *
     6.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    6.10 + * Other names may be trademarks of their respective owners.
    6.11 + *
    6.12 + * The contents of this file are subject to the terms of either the GNU
    6.13 + * General Public License Version 2 only ("GPL") or the Common
    6.14 + * Development and Distribution License("CDDL") (collectively, the
    6.15 + * "License"). You may not use this file except in compliance with the
    6.16 + * License. You can obtain a copy of the License at
    6.17 + * http://www.netbeans.org/cddl-gplv2.html
    6.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    6.19 + * specific language governing permissions and limitations under the
    6.20 + * License.  When distributing the software, include this License Header
    6.21 + * Notice in each file and include the License file at
    6.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    6.23 + * particular file as subject to the "Classpath" exception as provided
    6.24 + * by Oracle in the GPL Version 2 section of the License file that
    6.25 + * accompanied this code. If applicable, add the following below the
    6.26 + * License Header, with the fields enclosed by brackets [] replaced by
    6.27 + * your own identifying information:
    6.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    6.29 + *
    6.30 + * If you wish your version of this file to be governed by only the CDDL
    6.31 + * or only the GPL Version 2, indicate your decision by adding
    6.32 + * "[Contributor] elects to include this software in this distribution
    6.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    6.34 + * single choice of license, a recipient has the option to distribute
    6.35 + * your version of this file under either the CDDL, the GPL Version 2 or
    6.36 + * to extend the choice of license to its licensees as provided above.
    6.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    6.38 + * Version 2 license, then the option applies only if the new code is
    6.39 + * made subject to such option by the copyright holder.
    6.40 + *
    6.41 + * Contributor(s):
    6.42 + *
    6.43 + * Portions Copyrighted 2013 Sun Microsystems, Inc.
    6.44 + */
    6.45 +
    6.46 +package org.agent;
    6.47 +
    6.48 +import java.util.concurrent.Callable;
    6.49 +
    6.50 +/** Sample application.
    6.51 + *
    6.52 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    6.53 + */
    6.54 +public class HelloWorld implements Callable<String> {
    6.55 +    @Override
    6.56 +    public String call() {
    6.57 +        return "Helo World!";
    6.58 +    }
    6.59 +}
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/netbinox/test/unit/data/jars/agent/org/agent/HelloWorldAgent.java	Wed Nov 13 15:46:25 2013 +0100
     7.3 @@ -0,0 +1,32 @@
     7.4 +package org.agent;
     7.5 +
     7.6 +import java.lang.instrument.ClassFileTransformer;
     7.7 +import java.lang.instrument.IllegalClassFormatException;
     7.8 +import java.lang.instrument.Instrumentation;
     7.9 +import java.security.ProtectionDomain;
    7.10 +
    7.11 +public class HelloWorldAgent implements ClassFileTransformer {
    7.12 +    public static void agentmain(String args, Instrumentation inst) {
    7.13 +        inst.addTransformer(new HelloWorldAgent());
    7.14 +    }
    7.15 +
    7.16 +    @Override
    7.17 +    public byte[] transform(
    7.18 +        ClassLoader loader, String className, Class<?> classBeingRedefined,
    7.19 +        ProtectionDomain protectionDomain, byte[] arr
    7.20 +    ) throws IllegalClassFormatException {
    7.21 +        byte[] ret = arr;
    7.22 +        for (int i = 0; i < arr.length - 4; i++) {
    7.23 +            if (arr[i] == 'H' && arr[i + 1] == 'e' && arr[i + 2] == 'l' && 
    7.24 +                arr[i + 3] == 'o'
    7.25 +            ) {
    7.26 +                ret = ret.clone();
    7.27 +                ret[i] = 'A';
    7.28 +                ret[i + 1] = 'h';
    7.29 +                ret[i + 2] = 'o';
    7.30 +                ret[i + 3] = 'j';
    7.31 +            }
    7.32 +        }
    7.33 +        return ret;
    7.34 +    }
    7.35 +}
    7.36 \ No newline at end of file
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/netbinox/test/unit/src/org/netbeans/modules/netbinox/AgentTest.java	Wed Nov 13 15:46:25 2013 +0100
     8.3 @@ -0,0 +1,91 @@
     8.4 +/*
     8.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     8.6 + *
     8.7 + * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
     8.8 + *
     8.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    8.10 + * Other names may be trademarks of their respective owners.
    8.11 + *
    8.12 + * The contents of this file are subject to the terms of either the GNU
    8.13 + * General Public License Version 2 only ("GPL") or the Common
    8.14 + * Development and Distribution License("CDDL") (collectively, the
    8.15 + * "License"). You may not use this file except in compliance with the
    8.16 + * License. You can obtain a copy of the License at
    8.17 + * http://www.netbeans.org/cddl-gplv2.html
    8.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    8.19 + * specific language governing permissions and limitations under the
    8.20 + * License.  When distributing the software, include this License Header
    8.21 + * Notice in each file and include the License file at
    8.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    8.23 + * particular file as subject to the "Classpath" exception as provided
    8.24 + * by Oracle in the GPL Version 2 section of the License file that
    8.25 + * accompanied this code. If applicable, add the following below the
    8.26 + * License Header, with the fields enclosed by brackets [] replaced by
    8.27 + * your own identifying information:
    8.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    8.29 + *
    8.30 + * If you wish your version of this file to be governed by only the CDDL
    8.31 + * or only the GPL Version 2, indicate your decision by adding
    8.32 + * "[Contributor] elects to include this software in this distribution
    8.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    8.34 + * single choice of license, a recipient has the option to distribute
    8.35 + * your version of this file under either the CDDL, the GPL Version 2 or
    8.36 + * to extend the choice of license to its licensees as provided above.
    8.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    8.38 + * Version 2 license, then the option applies only if the new code is
    8.39 + * made subject to such option by the copyright holder.
    8.40 + *
    8.41 + * Contributor(s):
    8.42 + *
    8.43 + * Portions Copyrighted 2013 Sun Microsystems, Inc.
    8.44 + */
    8.45 +
    8.46 +package org.netbeans.modules.netbinox;
    8.47 +
    8.48 +import java.io.File;
    8.49 +import java.util.Locale;
    8.50 +import java.util.concurrent.Callable;
    8.51 +import org.netbeans.Module;
    8.52 +import org.netbeans.ModuleManager;
    8.53 +import org.netbeans.core.startup.Main;
    8.54 +import org.netbeans.core.startup.ModuleSystem;
    8.55 +
    8.56 +/**
    8.57 + *
    8.58 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    8.59 + */
    8.60 +public class AgentTest extends NetigsoHid {
    8.61 +    private File agent;
    8.62 +    private ModuleManager mgr;
    8.63 +    public AgentTest(String name) {
    8.64 +        super(name);
    8.65 +    }
    8.66 +
    8.67 +    protected @Override void setUp() throws Exception {
    8.68 +        Locale.setDefault(Locale.US);
    8.69 +        clearWorkDir();
    8.70 +        File ud = new File(getWorkDir(), "ud");
    8.71 +        ud.mkdirs();
    8.72 +        System.setProperty("netbeans.user", ud.getPath());
    8.73 +        
    8.74 +        data = new File(getDataDir(), "jars");
    8.75 +        jars = new File(getWorkDir(), "space in path");
    8.76 +        jars.mkdirs();
    8.77 +        agent = createTestJAR("agent", null);
    8.78 +    }
    8.79 +    
    8.80 +    public void testAgentClassRedefinesHello() throws Exception {
    8.81 +        ModuleSystem ms = Main.getModuleSystem();
    8.82 +        mgr = ms.getManager();
    8.83 +        mgr.mutexPrivileged().enterWriteAccess();
    8.84 +        mgr.mutexPrivileged().enterWriteAccess();
    8.85 +        Module m = mgr.create(agent, null, false, false, false);
    8.86 +        try {
    8.87 +            mgr.enable(m);
    8.88 +            Callable<?> c = (Callable<?>) m.getClassLoader().loadClass("org.agent.HelloWorld").newInstance();
    8.89 +            assertEquals("Bytecode has been patched", "Ahoj World!", c.call());
    8.90 +        } finally {
    8.91 +            mgr.disable(m);
    8.92 +        }
    8.93 +    }
    8.94 +}
     9.1 --- a/o.n.bootstrap/apichanges.xml	Tue Nov 12 21:57:59 2013 +0000
     9.2 +++ b/o.n.bootstrap/apichanges.xml	Wed Nov 13 15:46:25 2013 +0100
     9.3 @@ -53,6 +53,22 @@
     9.4    </apidefs>
     9.5  
     9.6    <changes>
     9.7 +     <change id="patch.classes">
     9.8 +        <api name="launcher"/>
     9.9 +        <summary>Ability to patch classes</summary>
    9.10 +        <version major="2" minor="66"/>
    9.11 +        <date day="14" month="11" year="2013"/>
    9.12 +        <author login="jtulach"/>
    9.13 +        <compatibility addition="yes" binary="compatible" semantic="compatible" />
    9.14 +        <description>
    9.15 +        <p>
    9.16 +            Define your <a href="@TOP@architecture-summary.html#usecase-patch">
    9.17 +                Agent-Class
    9.18 +            </a> to participate in patching other classes.
    9.19 +        </p>
    9.20 +        </description>
    9.21 +        <issue number="237919"/>
    9.22 +    </change>
    9.23       <change id="disable.cli.server">
    9.24          <api name="launcher"/>
    9.25          <summary>Property to disable CLI server</summary>
    10.1 --- a/o.n.bootstrap/arch.xml	Tue Nov 12 21:57:59 2013 +0000
    10.2 +++ b/o.n.bootstrap/arch.xml	Wed Nov 13 15:46:25 2013 +0100
    10.3 @@ -1492,6 +1492,47 @@
    10.4               product installation structure.
    10.5           </p>
    10.6       </usecase>
    10.7 +     <usecase name="Patch classes" id="patch">
    10.8 +         <a name="usecase-patch"></a>
    10.9 +         <p>
   10.10 +         In case one wants to modify other classes before they get loaded into
   10.11 +         the VM, one can register its own
   10.12 +         <code>Agent-Class</code> and then be called whenever another
   10.13 +         class is defined. The behavior matches as closely as possible the one
   10.14 +         provided by <a href="@JDK@/java/lang/instrument/package-summary.html">
   10.15 +         JDK instrument package</a>. To patch bytecode of other classes
   10.16 +         register your class in manifest and in its static <code>agentmain</code> method
   10.17 +         add your own <a href="@JDK@/java/lang/instrument/ClassFileTransformer.html">ClassFileTransformer</a>
   10.18 +         which will then be consulted whenever new classes are loaded.
   10.19 +         </p>
   10.20 +         <pre>
   10.21 +# following line should be in manifest of your module
   10.22 +Agent-Class: your.pkg.Transformer             
   10.23 +
   10.24 +// this should be your class
   10.25 +package your.pkg;
   10.26 +public class Transformer implements ClassFileTransformer {
   10.27 +    public static void agentmain(String args, Instrumentation i) {
   10.28 +        i.addTransformer(new Transformer());
   10.29 +    }
   10.30 +             
   10.31 +    @Override public byte[] transform(
   10.32 +        ClassLoader loader, String className, Class classBeingRedefined, 
   10.33 +        ProtectionDomain protectionDomain, byte[] classfileBuffer
   10.34 +    ) {
   10.35 +        // do your transformation
   10.36 +    }
   10.37 +}
   10.38 +</pre>
   10.39 +        <p>
   10.40 +        The implementation tries to be as complient as possible with the
   10.41 +        original JDK's specification, but 
   10.42 +        some differences are inevitable. For example the <code>classBeingRedefined</code>
   10.43 +        parameter in the previous example is always <code>null</code>
   10.44 +        as NetBeans runtime container does not
   10.45 +        have access to the class instance that is being defined yet.
   10.46 +        </p>
   10.47 +     </usecase>
   10.48   </answer>
   10.49  
   10.50  
    11.1 --- a/o.n.bootstrap/manifest.mf	Tue Nov 12 21:57:59 2013 +0000
    11.2 +++ b/o.n.bootstrap/manifest.mf	Wed Nov 13 15:46:25 2013 +0100
    11.3 @@ -1,6 +1,6 @@
    11.4  Manifest-Version: 1.0
    11.5  OpenIDE-Module: org.netbeans.bootstrap/1
    11.6 -OpenIDE-Module-Specification-Version: 2.64
    11.7 +OpenIDE-Module-Specification-Version: 2.66
    11.8  OpenIDE-Module-Localizing-Bundle: org/netbeans/Bundle.properties
    11.9  OpenIDE-Module-Recommends: org.netbeans.NetigsoFramework
   11.10  
    12.1 --- a/o.n.bootstrap/src/org/netbeans/JarClassLoader.java	Tue Nov 12 21:57:59 2013 +0000
    12.2 +++ b/o.n.bootstrap/src/org/netbeans/JarClassLoader.java	Wed Nov 13 15:46:25 2013 +0100
    12.3 @@ -52,6 +52,7 @@
    12.4  import java.io.IOException;
    12.5  import java.io.InputStream;
    12.6  import java.io.OutputStream;
    12.7 +import java.lang.instrument.IllegalClassFormatException;
    12.8  import java.lang.ref.Reference;
    12.9  import java.lang.ref.SoftReference;
   12.10  import java.lang.reflect.Method;
   12.11 @@ -280,7 +281,11 @@
   12.12                      LOGGER.log(Level.FINE, null, x);
   12.13                  }
   12.14              }
   12.15 -
   12.16 +            try {
   12.17 +                data = NbInstrumentation.patchByteCode(this, name, src.getProtectionDomain(), data);
   12.18 +            } catch (IllegalClassFormatException ex) {
   12.19 +                LOGGER.log(Level.WARNING, "Problems patching" + name, ex);
   12.20 +            }
   12.21              return defineClass (name, data, 0, data.length, src.getProtectionDomain());
   12.22          } 
   12.23          return null;
    13.1 --- a/o.n.bootstrap/src/org/netbeans/Module.java	Tue Nov 12 21:57:59 2013 +0000
    13.2 +++ b/o.n.bootstrap/src/org/netbeans/Module.java	Wed Nov 13 15:46:25 2013 +0100
    13.3 @@ -103,6 +103,7 @@
    13.4      protected ClassLoader classloader;
    13.5  
    13.6      private ModuleData data;
    13.7 +    private NbInstrumentation instr;
    13.8      
    13.9      private static Method findResources;
   13.10      private static final Object DATA_LOCK = new Object();
   13.11 @@ -611,6 +612,14 @@
   13.12          return false;
   13.13      }
   13.14  
   13.15 +    final void assignInstrumentation(NbInstrumentation agent) {
   13.16 +        instr = agent;
   13.17 +    }
   13.18 +
   13.19 +    void unregisterInstrumentation() {
   13.20 +        NbInstrumentation.unregisterAgent(instr);
   13.21 +    }
   13.22 +
   13.23      /** Struct representing a package exported from a module.
   13.24       * @since org.netbeans.core/1 > 1.4
   13.25       * @see Module#getPublicPackages
    14.1 --- a/o.n.bootstrap/src/org/netbeans/ModuleData.java	Tue Nov 12 21:57:59 2013 +0000
    14.2 +++ b/o.n.bootstrap/src/org/netbeans/ModuleData.java	Wed Nov 13 15:46:25 2013 +0100
    14.3 @@ -84,6 +84,7 @@
    14.4      private final String[] provides;
    14.5      private final Dependency[] dependencies;
    14.6      private final Set<String> coveredPackages;
    14.7 +    private final String agentClass;
    14.8      
    14.9      
   14.10      ModuleData(Manifest mf, Module forModule) throws InvalidException {
   14.11 @@ -214,6 +215,7 @@
   14.12              throw (InvalidException) new InvalidException("While parsing " + codeName + " a dependency attribute: " + iae.toString()).initCause(iae); // NOI18N
   14.13          }
   14.14          this.coveredPackages = new HashSet<String>();
   14.15 +        this.agentClass = attr.getValue("Agent-Class");
   14.16      }
   14.17      
   14.18      ModuleData(Manifest mf, NetigsoModule m) throws InvalidException {
   14.19 @@ -246,6 +248,7 @@
   14.20          this.provides = computeProvides(m, mf.getMainAttributes(), false, true);
   14.21          this.dependencies = computeImported(mf.getMainAttributes());
   14.22          this.coveredPackages = new HashSet<String>();
   14.23 +        this.agentClass = getMainAttribute(mf, "Agent-Class"); // NOI18N
   14.24      }
   14.25      
   14.26      ModuleData(ObjectInput dis) throws IOException {
   14.27 @@ -261,6 +264,7 @@
   14.28              this.friendNames = readStrings(dis, new HashSet<String>(), false);
   14.29              this.specVers = new SpecificationVersion(dis.readUTF());
   14.30              this.publicPackages = Module.PackageExport.read(dis);
   14.31 +            this.agentClass = dis.readUTF();
   14.32          } catch (ClassNotFoundException cnfe) {
   14.33              throw new IOException(cnfe);
   14.34          }
   14.35 @@ -278,6 +282,7 @@
   14.36          writeStrings(dos, friendNames);
   14.37          dos.writeUTF(specVers != null ? specVers.toString() : "0");
   14.38          Module.PackageExport.write(dos, publicPackages);
   14.39 +        dos.writeUTF(agentClass == null ? "" : agentClass);
   14.40      }
   14.41  
   14.42      private Dependency[] computeImported(Attributes attr) {
   14.43 @@ -528,4 +533,7 @@
   14.44          return new SpecificationVersion(v.substring(0, pos));
   14.45      }
   14.46  
   14.47 +    final String getAgentClass() {
   14.48 +        return agentClass == null || agentClass.isEmpty() ? null : agentClass;
   14.49 +    }
   14.50  }
    15.1 --- a/o.n.bootstrap/src/org/netbeans/ModuleManager.java	Tue Nov 12 21:57:59 2013 +0000
    15.2 +++ b/o.n.bootstrap/src/org/netbeans/ModuleManager.java	Wed Nov 13 15:46:25 2013 +0100
    15.3 @@ -1195,6 +1195,13 @@
    15.4              netigso.startFramework();
    15.5              break;
    15.6          }
    15.7 +        // register bytecode manipulation agents
    15.8 +        for (Module m : toEnable) {
    15.9 +            final String agentClass = m.data().getAgentClass();
   15.10 +            if (agentClass != null) {
   15.11 +                m.assignInstrumentation(NbInstrumentation.registerAgent(m.getClassLoader(), agentClass));
   15.12 +            }
   15.13 +        }
   15.14          {
   15.15              // Take care of notifying various changes.
   15.16              Util.err.fine("enable: firing changes");
   15.17 @@ -1243,6 +1250,7 @@
   15.18              for (Module m : toDisable) {
   15.19                  installer.dispose(m);
   15.20                  m.setEnabled(false);
   15.21 +                m.unregisterInstrumentation();
   15.22                  m.classLoaderDown();
   15.23              }
   15.24              System.gc(); // hope OneModuleClassLoader.finalize() is called...
    16.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.2 +++ b/o.n.bootstrap/src/org/netbeans/NbInstrumentation.java	Wed Nov 13 15:46:25 2013 +0100
    16.3 @@ -0,0 +1,210 @@
    16.4 +/*
    16.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    16.6 + *
    16.7 + * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
    16.8 + *
    16.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
   16.10 + * Other names may be trademarks of their respective owners.
   16.11 + *
   16.12 + * The contents of this file are subject to the terms of either the GNU
   16.13 + * General Public License Version 2 only ("GPL") or the Common
   16.14 + * Development and Distribution License("CDDL") (collectively, the
   16.15 + * "License"). You may not use this file except in compliance with the
   16.16 + * License. You can obtain a copy of the License at
   16.17 + * http://www.netbeans.org/cddl-gplv2.html
   16.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   16.19 + * specific language governing permissions and limitations under the
   16.20 + * License.  When distributing the software, include this License Header
   16.21 + * Notice in each file and include the License file at
   16.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
   16.23 + * particular file as subject to the "Classpath" exception as provided
   16.24 + * by Oracle in the GPL Version 2 section of the License file that
   16.25 + * accompanied this code. If applicable, add the following below the
   16.26 + * License Header, with the fields enclosed by brackets [] replaced by
   16.27 + * your own identifying information:
   16.28 + * "Portions Copyrighted [year] [name of copyright owner]"
   16.29 + *
   16.30 + * If you wish your version of this file to be governed by only the CDDL
   16.31 + * or only the GPL Version 2, indicate your decision by adding
   16.32 + * "[Contributor] elects to include this software in this distribution
   16.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
   16.34 + * single choice of license, a recipient has the option to distribute
   16.35 + * your version of this file under either the CDDL, the GPL Version 2 or
   16.36 + * to extend the choice of license to its licensees as provided above.
   16.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
   16.38 + * Version 2 license, then the option applies only if the new code is
   16.39 + * made subject to such option by the copyright holder.
   16.40 + *
   16.41 + * Contributor(s):
   16.42 + *
   16.43 + * Portions Copyrighted 2013 Sun Microsystems, Inc.
   16.44 + */
   16.45 +
   16.46 +package org.netbeans;
   16.47 +
   16.48 +import java.lang.instrument.ClassDefinition;
   16.49 +import java.lang.instrument.ClassFileTransformer;
   16.50 +import java.lang.instrument.IllegalClassFormatException;
   16.51 +import java.lang.instrument.Instrumentation;
   16.52 +import java.lang.instrument.UnmodifiableClassException;
   16.53 +import java.lang.reflect.InvocationTargetException;
   16.54 +import java.lang.reflect.Method;
   16.55 +import java.security.ProtectionDomain;
   16.56 +import java.util.Collection;
   16.57 +import java.util.Collections;
   16.58 +import java.util.List;
   16.59 +import java.util.concurrent.CopyOnWriteArrayList;
   16.60 +import java.util.jar.JarFile;
   16.61 +import java.util.logging.Level;
   16.62 +import java.util.logging.Logger;
   16.63 +import org.openide.util.WeakSet;
   16.64 +
   16.65 +/**
   16.66 + *
   16.67 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   16.68 + */
   16.69 +final class NbInstrumentation implements Instrumentation {
   16.70 +    private static final Logger LOG = Logger.getLogger(NbInstrumentation.class.getName());
   16.71 +    private static final Object LOCK = new Object();
   16.72 +    private static volatile Collection<NbInstrumentation> ACTIVE;
   16.73 +
   16.74 +    private final List<ClassFileTransformer> transformers = new CopyOnWriteArrayList<ClassFileTransformer>();
   16.75 +    private static final ThreadLocal<Boolean> IN = new ThreadLocal<Boolean>();
   16.76 +    
   16.77 +    static NbInstrumentation registerAgent(ClassLoader l, String agentClassName) {
   16.78 +        try {
   16.79 +            return registerImpl(agentClassName, l);
   16.80 +        } catch (Throwable ex) {
   16.81 +            LOG.log(Level.WARNING, "Cannot register " + agentClassName, ex);
   16.82 +            return null;
   16.83 +        }
   16.84 +    }
   16.85 +    static void unregisterAgent(NbInstrumentation instr) {
   16.86 +        synchronized (LOCK) {
   16.87 +            if (ACTIVE != null) {
   16.88 +                Collection<NbInstrumentation> clone = new WeakSet<NbInstrumentation>(ACTIVE);
   16.89 +                clone.remove(instr);
   16.90 +                ACTIVE = clone;
   16.91 +            }
   16.92 +        }
   16.93 +    }
   16.94 +    private static NbInstrumentation registerImpl(String agentClassName, ClassLoader l) throws ClassNotFoundException, IllegalArgumentException, NoSuchMethodException, SecurityException, IllegalAccessException, InvocationTargetException {
   16.95 +        final NbInstrumentation inst = new NbInstrumentation();
   16.96 +        synchronized (LOCK) {
   16.97 +            if (ACTIVE == null) {
   16.98 +                ACTIVE = new WeakSet<NbInstrumentation>();
   16.99 +            } else {
  16.100 +                ACTIVE = new WeakSet<NbInstrumentation>(ACTIVE);
  16.101 +            }
  16.102 +            ACTIVE.add(inst);
  16.103 +        }
  16.104 +        Class<?> agentClass = Class.forName(agentClassName, true, l);
  16.105 +        try {
  16.106 +            Method m = agentClass.getMethod("agentmain", String.class, Instrumentation.class); // NOI18N
  16.107 +            m.invoke(null, "", inst);
  16.108 +        } catch (NoSuchMethodException ex) {
  16.109 +            Method m = agentClass.getMethod("agentmain", String.class); // NOI18N
  16.110 +            m.invoke(null, "");
  16.111 +        }
  16.112 +        return inst;
  16.113 +    }
  16.114 +    
  16.115 +    public static byte[] patchByteCode(ClassLoader l, String className, ProtectionDomain pd, byte[] arr) throws IllegalClassFormatException {
  16.116 +        if (ACTIVE == null) {
  16.117 +            return arr;
  16.118 +        }
  16.119 +        if (Boolean.TRUE.equals(IN.get())) {
  16.120 +            return arr;
  16.121 +        }
  16.122 +        try {
  16.123 +            IN.set(Boolean.TRUE);
  16.124 +            for (NbInstrumentation inst : ACTIVE) {
  16.125 +                for (ClassFileTransformer t : inst.transformers) {
  16.126 +                    arr = t.transform(l, className, null, pd, arr);
  16.127 +                }
  16.128 +            }
  16.129 +        } finally {
  16.130 +            IN.set(null);
  16.131 +        }
  16.132 +        return arr;
  16.133 +    }
  16.134 +    
  16.135 +    //
  16.136 +    // Instrumentation methods
  16.137 +    //
  16.138 +
  16.139 +    @Override
  16.140 +    public void addTransformer(ClassFileTransformer transformer, boolean canRetransform) {
  16.141 +        transformers.add(transformer);
  16.142 +    }
  16.143 +
  16.144 +    @Override
  16.145 +    public void addTransformer(ClassFileTransformer transformer) {
  16.146 +        transformers.add(transformer);
  16.147 +    }
  16.148 +
  16.149 +    @Override
  16.150 +    public boolean removeTransformer(ClassFileTransformer transformer) {
  16.151 +        return transformers.remove(transformer);
  16.152 +    }
  16.153 +
  16.154 +    @Override
  16.155 +    public boolean isRetransformClassesSupported() {
  16.156 +        return false;
  16.157 +    }
  16.158 +
  16.159 +    @Override
  16.160 +    public void retransformClasses(Class<?>... classes) throws UnmodifiableClassException {
  16.161 +        throw new UnmodifiableClassException();
  16.162 +    }
  16.163 +
  16.164 +    @Override
  16.165 +    public boolean isRedefineClassesSupported() {
  16.166 +        return false;
  16.167 +    }
  16.168 +
  16.169 +    @Override
  16.170 +    public void redefineClasses(ClassDefinition... definitions) throws ClassNotFoundException, UnmodifiableClassException {
  16.171 +        throw new UnmodifiableClassException();
  16.172 +    }
  16.173 +
  16.174 +    @Override
  16.175 +    public boolean isModifiableClass(Class<?> theClass) {
  16.176 +        return false;
  16.177 +    }
  16.178 +
  16.179 +    @Override
  16.180 +    public Class[] getAllLoadedClasses() {
  16.181 +        return new Class[0];
  16.182 +    }
  16.183 +
  16.184 +    @Override
  16.185 +    public Class[] getInitiatedClasses(ClassLoader loader) {
  16.186 +        return new Class[0];
  16.187 +    }
  16.188 +
  16.189 +    @Override
  16.190 +    public long getObjectSize(Object objectToSize) {
  16.191 +        return 42;
  16.192 +    }
  16.193 +
  16.194 +    @Override
  16.195 +    public void appendToBootstrapClassLoaderSearch(JarFile jarfile) {
  16.196 +    }
  16.197 +
  16.198 +    @Override
  16.199 +    public void appendToSystemClassLoaderSearch(JarFile jarfile) {
  16.200 +        throw new UnsupportedOperationException();
  16.201 +    }
  16.202 +
  16.203 +    @Override
  16.204 +    public boolean isNativeMethodPrefixSupported() {
  16.205 +        return false;
  16.206 +    }
  16.207 +
  16.208 +    @Override
  16.209 +    public void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix) {
  16.210 +        throw new UnsupportedOperationException();
  16.211 +    }
  16.212 +    
  16.213 +}
    17.1 --- a/o.n.bootstrap/src/org/netbeans/NetigsoFramework.java	Tue Nov 12 21:57:59 2013 +0000
    17.2 +++ b/o.n.bootstrap/src/org/netbeans/NetigsoFramework.java	Wed Nov 13 15:46:25 2013 +0100
    17.3 @@ -44,11 +44,15 @@
    17.4  
    17.5  import java.io.File;
    17.6  import java.io.IOException;
    17.7 +import java.lang.instrument.IllegalClassFormatException;
    17.8 +import java.lang.instrument.Instrumentation;
    17.9  import java.net.URL;
   17.10 +import java.security.ProtectionDomain;
   17.11  import java.util.*;
   17.12  import java.util.logging.Level;
   17.13  import org.openide.modules.ModuleInfo;
   17.14  import org.openide.util.Enumerations;
   17.15 +import org.openide.util.Exceptions;
   17.16  import org.openide.util.Lookup;
   17.17  
   17.18  /** This class contains abstracted calls to OSGi provided by core.netigso
   17.19 @@ -186,4 +190,26 @@
   17.20      protected final Module findModule(String cnb) {
   17.21          return mgr.get(cnb);
   17.22      }
   17.23 +    
   17.24 +    /** Gives OSGi support access to NetBeans bytecode manipulation libraries.
   17.25 +     * They are built over {@link Instrumentation} specification 
   17.26 +     * read more at <a href="@TOP@architecture-overview.html#usecase-bytecode.patching">
   17.27 +     * architecture document</a>.
   17.28 +     * 
   17.29 +     * @param l the classloader that is loading the class
   17.30 +     * @param className the class name
   17.31 +     * @param pd protection domain to use
   17.32 +     * @param arr bytecode (must not be modified)
   17.33 +     * @return same or alternative bytecode for the class
   17.34 +     * @throws IllegalStateException if the byte code is not valid
   17.35 +     * @since 2.65
   17.36 +     */
   17.37 +    protected final byte[] patchByteCode(ClassLoader l, String className, ProtectionDomain pd, byte[] arr) {
   17.38 +        try {
   17.39 +            return NbInstrumentation.patchByteCode(l, className, null, arr);
   17.40 +        } catch (IllegalClassFormatException ex) {
   17.41 +            throw new IllegalStateException(ex);
   17.42 +        }
   17.43 +    }
   17.44 +    
   17.45  }
    18.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.2 +++ b/o.n.bootstrap/test/unit/data/jars/agent.mf	Wed Nov 13 15:46:25 2013 +0100
    18.3 @@ -0,0 +1,4 @@
    18.4 +Manifest-Version: 1.0
    18.5 +OpenIDE-Module: org.agent/1
    18.6 +OpenIDE-Module-Name: Hello World Agent
    18.7 +Agent-Class: org.agent.HelloWorldAgent
    19.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.2 +++ b/o.n.bootstrap/test/unit/data/jars/agent/org/agent/HelloWorld.java	Wed Nov 13 15:46:25 2013 +0100
    19.3 @@ -0,0 +1,56 @@
    19.4 +/*
    19.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    19.6 + *
    19.7 + * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
    19.8 + *
    19.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
   19.10 + * Other names may be trademarks of their respective owners.
   19.11 + *
   19.12 + * The contents of this file are subject to the terms of either the GNU
   19.13 + * General Public License Version 2 only ("GPL") or the Common
   19.14 + * Development and Distribution License("CDDL") (collectively, the
   19.15 + * "License"). You may not use this file except in compliance with the
   19.16 + * License. You can obtain a copy of the License at
   19.17 + * http://www.netbeans.org/cddl-gplv2.html
   19.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   19.19 + * specific language governing permissions and limitations under the
   19.20 + * License.  When distributing the software, include this License Header
   19.21 + * Notice in each file and include the License file at
   19.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
   19.23 + * particular file as subject to the "Classpath" exception as provided
   19.24 + * by Oracle in the GPL Version 2 section of the License file that
   19.25 + * accompanied this code. If applicable, add the following below the
   19.26 + * License Header, with the fields enclosed by brackets [] replaced by
   19.27 + * your own identifying information:
   19.28 + * "Portions Copyrighted [year] [name of copyright owner]"
   19.29 + *
   19.30 + * If you wish your version of this file to be governed by only the CDDL
   19.31 + * or only the GPL Version 2, indicate your decision by adding
   19.32 + * "[Contributor] elects to include this software in this distribution
   19.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
   19.34 + * single choice of license, a recipient has the option to distribute
   19.35 + * your version of this file under either the CDDL, the GPL Version 2 or
   19.36 + * to extend the choice of license to its licensees as provided above.
   19.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
   19.38 + * Version 2 license, then the option applies only if the new code is
   19.39 + * made subject to such option by the copyright holder.
   19.40 + *
   19.41 + * Contributor(s):
   19.42 + *
   19.43 + * Portions Copyrighted 2013 Sun Microsystems, Inc.
   19.44 + */
   19.45 +
   19.46 +package org.agent;
   19.47 +
   19.48 +import java.util.concurrent.Callable;
   19.49 +
   19.50 +/** Sample application.
   19.51 + *
   19.52 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   19.53 + */
   19.54 +public class HelloWorld implements Callable<String> {
   19.55 +    @Override
   19.56 +    public String call() {
   19.57 +        return "Helo World!";
   19.58 +    }
   19.59 +}
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/o.n.bootstrap/test/unit/data/jars/agent/org/agent/HelloWorldAgent.java	Wed Nov 13 15:46:25 2013 +0100
    20.3 @@ -0,0 +1,33 @@
    20.4 +package org.agent;
    20.5 +
    20.6 +import java.lang.instrument.ClassFileTransformer;
    20.7 +import java.lang.instrument.IllegalClassFormatException;
    20.8 +import java.lang.instrument.Instrumentation;
    20.9 +import java.security.ProtectionDomain;
   20.10 +
   20.11 +public class HelloWorldAgent implements ClassFileTransformer {
   20.12 +    public static void agentmain(String args, Instrumentation inst) {
   20.13 +        inst.getClass(); // null check
   20.14 +        inst.addTransformer(new HelloWorldAgent());
   20.15 +    }
   20.16 +
   20.17 +    @Override
   20.18 +    public byte[] transform(
   20.19 +        ClassLoader loader, String className, Class<?> classBeingRedefined,
   20.20 +        ProtectionDomain protectionDomain, byte[] arr
   20.21 +    ) throws IllegalClassFormatException {
   20.22 +        byte[] ret = arr;
   20.23 +        for (int i = 0; i < arr.length - 4; i++) {
   20.24 +            if (arr[i] == 'H' && arr[i + 1] == 'e' && arr[i + 2] == 'l' && 
   20.25 +                arr[i + 3] == 'o'
   20.26 +            ) {
   20.27 +                ret = ret.clone();
   20.28 +                ret[i] = 'A';
   20.29 +                ret[i + 1] = 'h';
   20.30 +                ret[i + 2] = 'o';
   20.31 +                ret[i + 3] = 'j';
   20.32 +            }
   20.33 +        }
   20.34 +        return ret;
   20.35 +    }
   20.36 +}
   20.37 \ No newline at end of file
    21.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.2 +++ b/o.n.bootstrap/test/unit/src/org/netbeans/AgentTest.java	Wed Nov 13 15:46:25 2013 +0100
    21.3 @@ -0,0 +1,72 @@
    21.4 +/*
    21.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    21.6 + *
    21.7 + * Copyright 2013 Oracle and/or its affiliates. All rights reserved.
    21.8 + *
    21.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
   21.10 + * Other names may be trademarks of their respective owners.
   21.11 + *
   21.12 + * The contents of this file are subject to the terms of either the GNU
   21.13 + * General Public License Version 2 only ("GPL") or the Common
   21.14 + * Development and Distribution License("CDDL") (collectively, the
   21.15 + * "License"). You may not use this file except in compliance with the
   21.16 + * License. You can obtain a copy of the License at
   21.17 + * http://www.netbeans.org/cddl-gplv2.html
   21.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
   21.19 + * specific language governing permissions and limitations under the
   21.20 + * License.  When distributing the software, include this License Header
   21.21 + * Notice in each file and include the License file at
   21.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
   21.23 + * particular file as subject to the "Classpath" exception as provided
   21.24 + * by Oracle in the GPL Version 2 section of the License file that
   21.25 + * accompanied this code. If applicable, add the following below the
   21.26 + * License Header, with the fields enclosed by brackets [] replaced by
   21.27 + * your own identifying information:
   21.28 + * "Portions Copyrighted [year] [name of copyright owner]"
   21.29 + *
   21.30 + * If you wish your version of this file to be governed by only the CDDL
   21.31 + * or only the GPL Version 2, indicate your decision by adding
   21.32 + * "[Contributor] elects to include this software in this distribution
   21.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
   21.34 + * single choice of license, a recipient has the option to distribute
   21.35 + * your version of this file under either the CDDL, the GPL Version 2 or
   21.36 + * to extend the choice of license to its licensees as provided above.
   21.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
   21.38 + * Version 2 license, then the option applies only if the new code is
   21.39 + * made subject to such option by the copyright holder.
   21.40 + *
   21.41 + * Contributor(s):
   21.42 + *
   21.43 + * Portions Copyrighted 2013 Sun Microsystems, Inc.
   21.44 + */
   21.45 +
   21.46 +package org.netbeans;
   21.47 +
   21.48 +import java.io.File;
   21.49 +import java.util.concurrent.Callable;
   21.50 +
   21.51 +/**
   21.52 + *
   21.53 + * @author Jaroslav Tulach <jtulach@netbeans.org>
   21.54 + */
   21.55 +public class AgentTest extends SetupHid {
   21.56 +    public AgentTest(String name) {
   21.57 +        super(name);
   21.58 +    }
   21.59 +    
   21.60 +    public void testAgentClassRedefinesHello() throws Exception {
   21.61 +        File jar = new File(jars, "agent.jar");
   21.62 +        MockModuleInstaller installer = new MockModuleInstaller();
   21.63 +        MockEvents ev = new MockEvents();
   21.64 +        ModuleManager mgr = new ModuleManager(installer, ev);
   21.65 +        mgr.mutexPrivileged().enterWriteAccess();
   21.66 +        Module m = mgr.create(jar, null, false, false, false);
   21.67 +        try {
   21.68 +            mgr.enable(m);
   21.69 +            Callable<?> c = (Callable<?>) m.getClassLoader().loadClass("org.agent.HelloWorld").newInstance();
   21.70 +            assertEquals("Bytecode has been patched", "Ahoj World!", c.call());
   21.71 +        } finally {
   21.72 +            mgr.disable(m);
   21.73 +        }
   21.74 +    }
   21.75 +}
    22.1 --- a/o.n.bootstrap/test/unit/src/org/netbeans/SetupHid.java	Tue Nov 12 21:57:59 2013 +0000
    22.2 +++ b/o.n.bootstrap/test/unit/src/org/netbeans/SetupHid.java	Wed Nov 13 15:46:25 2013 +0100
    22.3 @@ -237,6 +237,7 @@
    22.4          createTestJAR("needs-foo", null);
    22.5          createTestJAR("recommends-foo", null);
    22.6          createTestJAR("prov-foo-depends-needs_foo", "prov-foo");
    22.7 +        createTestJAR("agent", "agent");
    22.8          createTestJAR("api-mod-export-all", "exposes-api");
    22.9          createTestJAR("api-mod-export-none", "exposes-api");
   22.10          File exposesAPI = createTestJAR("api-mod-export-api", "exposes-api");