blob: 50489012a5a24fbba966fdf19e3743ba93eca32f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.core.netigso;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.ArchiveResources;
import org.netbeans.Module;
import org.netbeans.Module.PackageExport;
import org.netbeans.NetigsoFramework;
import org.netbeans.ProxyClassLoader;
import org.netbeans.Stamps;
import static org.netbeans.core.netigso.Bundle.*;
import org.netbeans.core.startup.Main;
import org.openide.modules.ModuleInfo;
import org.openide.modules.Places;
import org.openide.util.*;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.service.packageadmin.RequiredBundle;
import org.osgi.service.startlevel.StartLevel;
/**
*
* @author Jaroslav Tulach <jtulach@netbeans.org>
*/
@ServiceProviders({
@ServiceProvider(service = NetigsoFramework.class),
@ServiceProvider(service = Netigso.class)
})
public final class Netigso extends NetigsoFramework
implements Cloneable, Stamps.Updater {
static final Logger LOG = Logger.getLogger(Netigso.class.getName());
private static final AtomicBoolean SELF_QUERY = new AtomicBoolean();
private static final String[] EMPTY = {};
private Framework framework;
private ClassLoader frameworkLoader;
private NetigsoActivator activator;
private Integer defaultStartLevel;
private String defaultCoveredPkgs;
@Override
protected NetigsoFramework clone() {
return new Netigso();
}
Framework getFramework() {
return framework;
}
@Override
protected ClassLoader findFrameworkClassLoader() {
ClassLoader l = frameworkLoader;
if (l != null) {
return l;
}
Framework f = framework;
if (f != null) {
return frameworkLoader = f.getClass().getClassLoader();
}
return getClass().getClassLoader();
}
@Override
@Messages({"#NOI18N", "FRAMEWORK_START_LEVEL="})
protected void prepare(Lookup lkp, Collection<? extends Module> preregister) {
if (framework == null) {
readBundles();
Map configMap = new HashMap();
injectSystemProperties(configMap); // ensure we read system properties
final String cache = getNetigsoCache().getPath();
configMap.put(Constants.FRAMEWORK_STORAGE, cache);
activator = new NetigsoActivator(this);
configMap.put("netigso.archive", NetigsoArchiveFactory.DEFAULT.create(this)); // NOI18N
if (!configMap.containsKey("felix.log.level")) { // NOI18N
String felixLevel = felixLogLevel(LOG);
configMap.put("felix.log.level", felixLevel); // NOI18N
}
configMap.put("felix.bootdelegation.classloaders", activator); // NOI18N
String startLevel = FRAMEWORK_START_LEVEL();
if (!startLevel.isEmpty()) {
configMap.put("org.osgi.framework.startlevel.beginning", startLevel); // NOI18N
}
FrameworkFactory frameworkFactory = lkp.lookup(FrameworkFactory.class);
if (frameworkFactory == null) {
throw new IllegalStateException(
"Cannot find OSGi framework implementation." + // NOI18N
" Is org.netbeans.libs.felix module or similar enabled?" // NOI18N
);
}
framework = frameworkFactory.newFramework(configMap);
try {
framework.init();
NetigsoServices ns = new NetigsoServices(this, framework);
} catch (BundleException ex) {
LOG.log(Level.SEVERE, "Cannot start OSGi framework", ex); // NOI18N
}
LOG.finer("OSGi Container initialized"); // NOI18N
}
for (Module mi : preregister) {
try {
fakeOneModule(mi, null);
} catch (IOException ex) {
LOG.log(Level.WARNING, "Cannot fake " + mi.getCodeName(), ex);
}
}
}
static String felixLogLevel(final Logger log) {
String felixLevel = "1"; // NOI18N
if (log.isLoggable(Level.WARNING)) {
felixLevel = "2"; // NOI18N
}
if (log.isLoggable(Level.CONFIG)) {
felixLevel = "3"; // NOI18N
}
if (log.isLoggable(Level.FINE)) {
felixLevel = "4"; // NOI18N
}
return felixLevel;
}
@Override
protected Set<String> start(Collection<? extends Module> allModules) {
return toActivate(framework, allModules);
}
@Override
protected void start() {
try {
framework.start();
} catch (BundleException ex) {
LOG.log(Level.WARNING, "Cannot start Container" + framework, ex);
}
}
/** contributed by Alex Bowen (trajar@netbeans.org) */
private void injectSystemProperties(Map configProps) {
for (Enumeration<?> e = System.getProperties().propertyNames(); e.hasMoreElements(); ) {
String key = e.nextElement().toString();
if (key.startsWith("felix.") || key.startsWith("org.osgi.framework.")) { // NOI18N
configProps.put(key, System.getProperty(key));
}
}
}
private static Set<String> toActivate(Framework f, Collection<? extends Module> allModules) {
ServiceReference sr = f.getBundleContext().getServiceReference("org.osgi.service.packageadmin.PackageAdmin"); // NOI18N
if (sr == null) {
return null;
}
PackageAdmin pkgAdm = (PackageAdmin)f.getBundleContext().getService(sr);
if (pkgAdm == null) {
return null;
}
Set<String> allCnbs = new HashSet<String>(allModules.size() * 2);
for (ModuleInfo m : allModules) {
allCnbs.add(m.getCodeNameBase());
}
Set<String> needEnablement = new HashSet<String>();
for (Bundle b : f.getBundleContext().getBundles()) {
String loc = b.getLocation();
if (loc.startsWith("netigso://")) {
loc = loc.substring("netigso://".length());
} else {
continue;
}
RequiredBundle[] arr = pkgAdm.getRequiredBundles(loc);
if (arr != null) for (RequiredBundle rb : arr) {
for (Bundle n : rb.getRequiringBundles()) {
if (allCnbs.contains(n.getSymbolicName().replace('-', '_'))) {
needEnablement.add(loc);
}
}
}
}
return needEnablement;
}
@Override
protected void shutdown() {
try {
if (framework != null) {
framework.stop();
framework.waitForStop(10000);
}
framework = null;
frameworkLoader = null;
} catch (InterruptedException ex) {
LOG.log(Level.WARNING, "Wait for shutdown failed" + framework, ex);
} catch (BundleException ex) {
LOG.log(Level.WARNING, "Cannot start Container" + framework, ex);
}
}
@Override
@Messages({"#NOI18N", "DEFAULT_BUNDLE_START_LEVEL=0"})
protected int defaultStartLevel() {
if (defaultStartLevel == null) {
defaultStartLevel = Integer.parseInt(DEFAULT_BUNDLE_START_LEVEL());
}
return defaultStartLevel;
}
@Override
@Messages({"#NOI18N", "MODULE_START_LEVEL="})
protected Set<String> createLoader(ModuleInfo m, ProxyClassLoader pcl, File jar) throws IOException {
try {
assert registered.containsKey(m.getCodeNameBase()) : m.getCodeNameBase();
Bundle b = findBundle(m.getCodeNameBase());
if (b == null) {
for (Bundle bb : framework.getBundleContext().getBundles()) {
LOG.log(Level.FINE, "Bundle {0}: {1}", new Object[] { bb.getBundleId(), bb.getSymbolicName() });
}
throw new IOException("Not found bundle:" + m.getCodeNameBase());
}
NetigsoLoader l = new NetigsoLoader(b, m, jar);
Set<String> pkgs = new HashSet<String>();
String[] knownPkgs = registered.get(m.getCodeNameBase());
Object exported = b.getHeaders("").get("Export-Package");
if (knownPkgs == EMPTY) {
try {
SELF_QUERY.set(true);
if (findCoveredPkgs(exported)) {
Enumeration<URL> en = b.findEntries("", null, true);
if (en == null) {
LOG.log(Level.INFO, "Bundle {0}: {1} is empty", new Object[] { b.getBundleId(), b.getSymbolicName() });
} else {
while (en.hasMoreElements()) {
URL url = en.nextElement();
if (url.getFile().startsWith("/META-INF")) {
pkgs.add(url.getFile().substring(9));
continue;
}
pkgs.add(url.getFile().substring(1).replaceFirst("/[^/]*$", "").replace('/', '.'));
}
}
}
if (exported instanceof String) {
for (String p : exported.toString().split(",")) { // NOI18N
int semic = p.indexOf(';');
if (semic >= 0) {
p = p.substring(0, semic);
}
pkgs.add(p);
}
}
} finally {
SELF_QUERY.set(false);
}
registered.put(m.getCodeNameBase(), pkgs.toArray(new String[0]));
Stamps.getModulesJARs().scheduleSave(this, "netigso-bundles", false); // NOI18N
} else {
pkgs.addAll(Arrays.asList(knownPkgs));
}
pcl.append(new ClassLoader[]{ l });
try {
String msl = MODULE_START_LEVEL();
boolean start = true;
if (!msl.isEmpty()) {
int moduleStartLevel = Integer.parseInt(msl);
int level = getBundleStartLevel(b, framework.getBundleContext());
start = moduleStartLevel >= level;
}
LOG.log(Level.FINE, "Starting bundle {0}: {1}", new Object[] { m.getCodeNameBase(), start });
if (start) {
b.start();
if (findCoveredPkgs(exported) && !isResolved(b) && isRealBundle(b)) {
throw new IOException("Cannot start " + m.getCodeName() + " state remains INSTALLED after start()"); // NOI18N
}
}
} catch (BundleException possible) {
if (isRealBundle(b)) {
throw possible;
}
// Bundle is a fragment, replace it with host in classloader
String fragmentHost = b.getHeaders("").get("Fragment-Host");
Bundle hostBundle = findBundle(fragmentHost);
if (hostBundle == null) {
LOG.log(Level.WARNING, "Failed to locate fragment host bundle {0} for fragment bundle {1}",
new Object[]{fragmentHost, m.getCodeNameBase()});
throw new IOException("Not found bundle: " + hostBundle);
}
l.setBundle(hostBundle);
LOG.log(Level.FINE, "Not starting fragment {0}, using host bundle {1} for classloading instead",
new Object[]{m.getCodeNameBase(), fragmentHost});
}
return pkgs;
} catch (BundleException ex) {
throw new IOException("Cannot start " + jar, ex);
}
}
private static boolean isResolved(Bundle b) {
if (b.getState() == Bundle.INSTALLED) {
// try to ask for a known resource which is known to resolve
// the bundle
b.findEntries("META-INF", "MANIFEST.MF", false); // NOI18N
}
return b.getState() != Bundle.INSTALLED;
}
private static boolean isRealBundle(Bundle b) {
return b.getHeaders("").get("Fragment-Host") == null; // NOI18N
}
@Override
protected void stopLoader(ModuleInfo m, ClassLoader loader) {
NetigsoLoader nl = (NetigsoLoader)loader;
Bundle b = nl.getBundle();
try {
assert b != null;
try {
LOG.log(Level.FINE, "Stopping bundle {0}", m.getCodeNameBase());
b.stop();
} catch (BundleException possible) {
if (isRealBundle(b)) {
throw possible;
}
LOG.log(Level.FINE, "Not stopping fragment {0}", m.getCodeNameBase());
}
} catch (BundleException ex) {
throw new IllegalStateException(ex);
}
}
@Override
protected Enumeration<URL> findResources(Module m, String resName) {
Bundle b = findBundle(m.getCodeNameBase());
URL u = b.getEntry(resName);
return u == null ? Enumerations.<URL>empty() : Enumerations.singleton(u);
}
@Override
protected void reload(Module m) throws IOException {
try {
Bundle b = findBundle(m.getCodeNameBase());
b.stop();
fakeOneModule(m, b);
} catch (BundleException ex) {
throw new IOException(ex);
}
}
//
// take care about the registered bundles
//
private final Map<String,String[]> registered = new HashMap<String,String[]>();
private static final RequestProcessor RP = new RequestProcessor("Netigso Events"); // NOI18N
final void notifyBundleChange(final String symbolicName, final Version version, final int action) {
final Exception stack = Netigso.LOG.isLoggable(Level.FINER) ? new Exception("StackTrace") : null;
final Runnable doLog = new Runnable() {
@Override
public void run() {
if (isEnabled(symbolicName)) {
return;
}
final Mutex mutex = Main.getModuleSystem().getManager().mutex();
if (!mutex.isReadAccess()) {
mutex.postReadRequest(this);
return;
}
String type = "" + action;
Level notify = Level.INFO;
switch (action) {
case BundleEvent.INSTALLED:
return; // no message for installed
case BundleEvent.RESOLVED:
type = "resolved";
break;
case BundleEvent.STARTED:
type = "started";
break;
case BundleEvent.STOPPED:
type = "stopped";
break;
case BundleEvent.UNINSTALLED:
return; // nothing for uninstalled
case BundleEvent.LAZY_ACTIVATION:
type = "lazy";
notify = Level.FINEST;
break;
case BundleEvent.STARTING:
type = "starting";
notify = Level.FINEST;
break;
}
Netigso.LOG.log(notify, "bundle {0}@{2} {1}", new Object[]{
symbolicName, type, version
});
if (stack != null) {
Netigso.LOG.log(Level.FINER, null, stack);
}
}
};
RP.post(doLog);
}
private File getNetigsoCache() throws IllegalStateException {
// Explicitly specify the directory to use for caching bundles.
return Places.getCacheSubdirectory("netigso");
}
private void deleteRec(File dir) {
File[] arr = dir.listFiles();
if (arr != null) {
for (File f : arr) {
deleteRec(f);
}
}
dir.delete();
}
private void fakeOneModule(Module m, Bundle original) throws IOException {
String cnb = m.getCodeNameBase();
if (registered.get(cnb) != null && original == null) {
return;
}
registered.put(cnb, EMPTY);
Bundle b;
try {
String symbolicName = (String) m.getAttribute("Bundle-SymbolicName");
if ("org.netbeans.core.osgi".equals(symbolicName)) { // NOI18N
// Always ignore.
} else if (symbolicName != null) { // NOI18N
if (original != null) {
LOG.log(Level.FINE, "Updating bundle {0}", original.getLocation());
FileInputStream is = new FileInputStream(m.getJarFile());
original.update(is);
is.close();
b = original;
} else {
BundleContext bc = framework.getBundleContext();
File jar = m.getJarFile();
String loc;
if (m.isReloadable()) {
loc = toURI(jar);
} else {
loc = "reference:" + toURI(jar); // NOI18N
}
LOG.log(Level.FINE, "Installing bundle {0}", loc);
b = bc.installBundle(loc);
int startLevel = m.getStartLevel();
if (startLevel == -1) {
startLevel = defaultStartLevel();
}
if (startLevel > 0) {
setBundleStartLevel(bc, b, startLevel);
}
}
} else {
InputStream is = fakeBundle(m);
if (is != null) {
if (original != null) {
original.update(is);
b = original;
} else {
assert framework != null;
BundleContext bc = framework.getBundleContext();
assert bc != null;
b = bc.installBundle("netigso://" + cnb, is);
}
is.close();
}
}
Stamps.getModulesJARs().scheduleSave(this, "netigso-bundles", false); // NOI18N
} catch (BundleException ex) {
throw new IOException(ex);
}
}
private void setFrameworkStartLevel(BundleContext bc, int startLevel) {
ServiceReference sr = bc.getServiceReference("org.osgi.service.startlevel.StartLevel"); // NOI18N
StartLevel level = null;
if (sr != null) {
level = (StartLevel) bc.getService(sr);
if (level != null) {
level.setStartLevel(startLevel);
return;
}
}
LOG.log(
Level.WARNING,
"Cannot set framewok startLevel to {1} reference: {2} level {3}",
new Object[]{null, startLevel, sr, level}
);
}
private void setBundleStartLevel(BundleContext bc, Bundle b, int startLevel) {
ServiceReference sr = bc.getServiceReference("org.osgi.service.startlevel.StartLevel"); // NOI18N
StartLevel level = null;
if (sr != null) {
level = (StartLevel) bc.getService(sr);
if (level != null) {
level.setBundleStartLevel(b, startLevel);
return;
}
}
LOG.log(
Level.WARNING,
"Cannot set startLevel for {0} to {1} reference: {2} level {3}",
new Object[]{b.getSymbolicName(), startLevel, sr, level}
);
}
private int getBundleStartLevel(Bundle b, BundleContext bc) {
ServiceReference sr = bc.getServiceReference("org.osgi.service.startlevel.StartLevel"); // NOI18N
StartLevel level = null;
if (sr != null) {
level = (StartLevel) bc.getService(sr);
if (level != null) {
return level.getBundleStartLevel(b);
}
}
return 0;
}
private static String threeDotsWithMajor(String version, String withMajor) {
int indx = withMajor.indexOf('/');
int major = 0;
if (indx > 0) {
major = Integer.parseInt(withMajor.substring(indx + 1));
}
String[] segments = (version + ".0.0.0").split("\\.");
assert segments.length >= 3 && segments[0].length() > 0;
return (Integer.parseInt(segments[0]) + major * 100) + "." + segments[1] + "." + segments[2];
}
/** Creates a fake bundle definition that represents one NetBeans module
*
* @param m the module
* @return the stream to read the definition from or null, if it does not
* make sense to represent this module as bundle
*/
private static InputStream fakeBundle(Module m) throws IOException {
String netigsoExp = (String) m.getAttribute("Netigso-Export-Package"); // NOI18N
String exp = (String) m.getAttribute("OpenIDE-Module-Public-Packages"); // NOI18N
if (netigsoExp == null) {
if ("-".equals(exp) || m.getAttribute("OpenIDE-Module-Friends") != null) { // NOI18N
return null;
}
}
ByteArrayOutputStream os = new ByteArrayOutputStream();
Manifest man = new Manifest();
man.getMainAttributes().putValue("Manifest-Version", "1.0"); // workaround for JDK bug
man.getMainAttributes().putValue("Bundle-ManifestVersion", "2"); // NOI18N
man.getMainAttributes().putValue("Bundle-SymbolicName", m.getCodeNameBase()); // NOI18N
if (m.getSpecificationVersion() != null) {
String spec = threeDotsWithMajor(m.getSpecificationVersion().toString(), m.getCodeName());
man.getMainAttributes().putValue("Bundle-Version", spec.toString()); // NOI18N
}
if (netigsoExp != null) {
man.getMainAttributes().putValue("Export-Package", netigsoExp); // NOI18N
} else if (exp != null) {
man.getMainAttributes().putValue("Export-Package", substitutePkg(m)); // NOI18N
} else {
man.getMainAttributes().putValue("Export-Package", m.getCodeNameBase()); // NOI18N
}
JarOutputStream jos = new JarOutputStream(os, man);
jos.close();
return new ByteArrayInputStream(os.toByteArray());
}
private void readBundles() {
assert registered.isEmpty();
try {
InputStream is = Stamps.getModulesJARs().asStream("netigso-bundles");
if (is == null) {
File f;
try {
f = getNetigsoCache();
} catch (IllegalStateException ex) {
return;
}
deleteRec(f);
return;
}
Properties p = new Properties();
p.load(is);
is.close();
for (Map.Entry<Object, Object> entry : p.entrySet()) {
String k = (String)entry.getKey();
String v = (String)entry.getValue();
registered.put(k, v.trim().isEmpty() ? EMPTY : v.split(","));
LOG.log(Level.FINE, "readBundle: {0}", k);
}
} catch (IOException ex) {
LOG.log(Level.WARNING, "Cannot read cache", ex);
}
}
@Override
public void flushCaches(DataOutputStream os) throws IOException {
Properties p = new Properties();
for (Map.Entry<String, String[]> entry : registered.entrySet()) {
StringBuilder sb = new StringBuilder();
String sep = "";
for (String s : entry.getValue()) {
sb.append(sep);
sb.append(s);
sep = ",";
}
p.setProperty(entry.getKey(), sb.toString());
}
p.store(os, null);
}
@Override
public void cacheReady() {
}
private Bundle findBundle(String codeNameBase) {
for (Bundle bb : framework.getBundleContext().getBundles()) {
final String bbName = bb.getSymbolicName().replace('-', '_');
if (bbName.equals(codeNameBase)) {
return bb;
}
}
return null;
}
public byte[] fromArchive(long bundleId, String resource, ArchiveResources ar) throws IOException {
if (SELF_QUERY.get()) {
return ar.resource(resource);
}
return fromArchive(ar, resource);
}
public boolean isArchiveActive() {
return !SELF_QUERY.get();
}
private static String toURI(final File file) {
class VFile extends File {
public VFile() {
super(file.getPath());
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public File getAbsoluteFile() {
return this;
}
}
return Utilities.toURI(new VFile()).toString();
}
@Messages({"#NOI18N", "FIND_COVERED_PKGS=findEntries"})
private boolean findCoveredPkgs(Object exportedPackages) {
if (defaultCoveredPkgs == null) {
defaultCoveredPkgs = FIND_COVERED_PKGS();
}
if ("exportedIfPresent".equals(defaultCoveredPkgs)) { // NOI18N
return exportedPackages == null;
}
return "findEntries".equals(defaultCoveredPkgs); // NOI18N
}
final ClassLoader findClassLoader(String cnb) {
return createClassLoader(cnb);
}
private boolean isEnabled(String cnd) {
Module m = findModule(cnd);
return m != null && m.isEnabled();
}
private static String substitutePkg(Module m) {
StringBuilder exported = new StringBuilder();
String sep = "";
PackageExport[] pblk = m.getPublicPackages();
if (pblk == null) {
pblk = new PackageExport[1];
pblk[0] = new PackageExport("", true);
}
for (Module.PackageExport packageExport : pblk) {
Set<String> pkgs;
if (packageExport.recursive) {
pkgs = findRecursivePkgs(m, packageExport);
} else {
pkgs = Collections.singleton(packageExport.pkg);
}
for (String p : pkgs) {
if (p.endsWith("/")) { // NOI18N
p = p.substring(0, p.length() - 1);
}
exported.append(sep).append(p.replace('/', '.'));
sep = ",";
}
}
if (exported.length() == 0) {
exported.append(m.getCodeNameBase());
}
return exported.toString();
}
private static Set<String> findRecursivePkgs(Module m, PackageExport packageExport) {
Set<String> pkgs = new HashSet<String>();
for (File f : m.getAllJars()) {
JarFile jf = null;
try {
jf = new JarFile(f);
Enumeration<JarEntry> en = jf.entries();
while (en.hasMoreElements()) {
JarEntry e = en.nextElement();
if (e.isDirectory()) {
continue;
}
final String entryName = e.getName();
int lastSlash = entryName.lastIndexOf('/');
if (lastSlash == -1) {
continue;
}
String pkg = entryName.substring(0, lastSlash + 1);
if (pkg.startsWith(packageExport.pkg)) {
pkgs.add(pkg);
}
}
} catch (IOException ex) {
LOG.log(Level.INFO, "Can't process " + f, ex);
} finally {
try {
jf.close();
} catch (IOException ex) {
LOG.log(Level.INFO, "Can't close " + f, ex);
}
}
}
return pkgs;
}
public final byte[] patchBC(ClassLoader l, String className, ProtectionDomain pd, byte[] arr) {
return patchByteCode(l, className, pd, arr);
}
}