blob: 41163dc5ba622f30690a95ab3d4c9d1151967774 [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.junit;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;
import junit.framework.Assert;
import junit.framework.AssertionFailedError;
/**
* Lets you register mock implementations of global services.
* You might for example do this in {@link junit.framework.TestCase#setUp}.
* <p>If you need to register individual instances, and are using the <code>Lookup</code>
* framework, try <code>org.openide.util.test.MockLookup</code>.
* @see <a href="http://www.netbeans.org/download/dev/javadoc/org-openide-util/org/openide/util/Lookup.html"><code>Lookup</code></a>
* @see <a href="http://download.java.net/jdk6/docs/api/java/util/ServiceLoader.html"><code>ServiceLoader</code></a>
* @since org.netbeans.modules.nbjunit/1 1.30
* @author Jesse Glick, Jaroslav Tulach
*/
public class MockServices {
private MockServices() {}
/**
* Set (or reset) the set of mock services.
* Clears any previous registration.
* After this call, <code>Lookup</code> and <code>ServiceLoader</code> should both
* "see" the newly registered classes.
* (Other classes really registered in <code>META-INF/services/</code> will
* also be available, but after the ones you have registered.)
* Each class must be public and concrete with a public no-arg constructor.
* @param services a set of service classes to register
* @throws IllegalArgumentException if some classes are not instantiable as beans
*/
public static void setServices(Class<?>... services) throws IllegalArgumentException {
try {
if (
System.getProperty("netbeans.home") != null &&
System.getProperty("netbeans.user") != null
) {
Class<?> mainLookup = forName("org.netbeans.core.startup.MainLookup");
ClassLoader l = new ServiceClassLoader(services, Thread.currentThread().getContextClassLoader(), false);
Method sClsLoaderChanged = mainLookup.getDeclaredMethod("systemClassLoaderChanged", ClassLoader.class);
sClsLoaderChanged.setAccessible(true);
sClsLoaderChanged.invoke(null, l);
return;
}
} catch (ClassNotFoundException ex) {
// Fine, not using core.jar.
} catch (Exception exc) {
LOG.log(Level.WARNING, "MainLookup couldn't be notified about the context class loader change", exc);
}
ClassLoader l = new ServiceClassLoader(services);
// Adapted from org.netbeans.ModuleManager.updateContextClassLoaders. See that class for comments.
ThreadGroup g = Thread.currentThread().getThreadGroup();
while (g.getParent() != null) {
g = g.getParent();
}
while (true) {
int s = g.activeCount() + 1;
Thread[] ts = new Thread[s];
int x = g.enumerate(ts, true);
if (x < s) {
for (int i = 0; i < x; i++) {
ts[i].setContextClassLoader(l);
}
LOG.log(Level.FINE, "Set context class loader on {0} threads", x);
break;
} else {
LOG.fine("Race condition getting all threads, restarting...");
continue;
}
}
// Need to also reset global lookup since it caches the singleton and we need to change it.
try {
Class<?> mainLookup = Class.forName("org.netbeans.core.startup.MainLookup");
Method sClsLoaderChanged = mainLookup.getDeclaredMethod("systemClassLoaderChanged",ClassLoader.class);
sClsLoaderChanged.setAccessible(true);
sClsLoaderChanged.invoke(null,l);
} catch (ClassNotFoundException x) {
// Fine, not using core.jar.
} catch(Exception exc) {
LOG.log(Level.WARNING, "MainLookup couldn't be notified about the context class loader change", exc);
}
try {
Class<?> lookup = Class.forName("org.openide.util.Lookup");
Method defaultLookup = lookup.getDeclaredMethod("resetDefaultLookup");
defaultLookup.setAccessible(true);
defaultLookup.invoke(null);
} catch (ClassNotFoundException x) {
// Fine, not using org-openide-lookup.jar.
} catch (Exception x) {
LOG.log(Level.WARNING, "Could not reset Lookup.getDefault()", x);
}
}
private static Class<?> forName(String name) throws ClassNotFoundException {
ClassLoader l = Thread.currentThread().getContextClassLoader();
if (l != null) {
try {
return Class.forName(name, true, l);
} catch (ClassNotFoundException ex) {
// OK, try again
}
}
return Class.forName(name);
}
private static final Logger LOG = Logger.getLogger(MockServices.class.getName());
private static final class ServiceClassLoader extends ClassLoader {
private final Class<?>[] services;
public ServiceClassLoader(Class<?>[] services) {
this(services, MockServices.class.getClassLoader(), true);
}
public ServiceClassLoader(Class<?>[] services, ClassLoader l, boolean test) {
super(l);
for (Class<?> c : services) {
try {
if (test) {
Assert.assertEquals(c, getParent().loadClass(c.getName()));
}
int mods = c.getModifiers();
if (!Modifier.isPublic(mods) || Modifier.isAbstract(mods)) {
throw new IllegalArgumentException("Class " + c.getName() + " must be public");
}
c.getConstructor();
} catch (IllegalArgumentException x) {
throw x;
} catch (NoSuchMethodException x) {
throw (IllegalArgumentException) new IllegalArgumentException("Class " + c.getName() + " has no public no-arg constructor").initCause(x);
} catch (Exception x) {
throw (AssertionFailedError) new AssertionFailedError(x.toString()).initCause(x);
}
}
this.services = services;
}
public URL getResource(String name) {
Enumeration<URL> r;
try {
r = getResources(name);
} catch (IOException x) {
return null;
}
return r.hasMoreElements() ? r.nextElement() : null;
}
public Enumeration<URL> getResources(String name) throws IOException {
if (name.equals("META-INF/services/org.openide.util.Lookup") || name.equals("META-INF/services/org.openide.util.Lookup$Provider")) {
// Lookup.getDefault checks for these, and we need to really mask it.
return Collections.enumeration(Collections.<URL>emptySet());
}
final Enumeration<URL> supe = super.getResources(name);
String prefix = "META-INF/services/";
if (name.startsWith(prefix)) {
try {
Class<?> xface = loadClass(name.substring(prefix.length()));
List<String> impls = new ArrayList<String>();
for (Class<?> c : services) {
boolean assignable = xface.isAssignableFrom(c);
if (assignable) {
impls.add(c.getName());
}
}
if (!impls.isEmpty()) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintWriter pw = new PrintWriter(new OutputStreamWriter(baos, "UTF-8"));
for (String impl : impls) {
pw.println(impl);
pw.println("#position=100");
}
pw.close();
final URL u = new URL("metainfservices", null, 0, xface.getName(), new URLStreamHandler() {
protected URLConnection openConnection(URL _u) throws IOException {
return new URLConnection(_u) {
public void connect() throws IOException {}
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(baos.toByteArray());
}
};
}
});
return new Enumeration<URL>() {
private boolean parent = false;
public boolean hasMoreElements() {
return !parent || supe.hasMoreElements();
}
public URL nextElement() throws NoSuchElementException {
if (parent) {
return supe.nextElement();
} else {
parent = true;
return u;
}
}
};
}
} catch (ClassNotFoundException x) {}
}
return supe;
}
/*
public Class<?> loadClass(String name) throws ClassNotFoundException {
// XXX make sure services can be loaded
return super.loadClass(name);
}
*/
}
}