001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.junit;
043:
044: import java.io.ByteArrayInputStream;
045: import java.io.ByteArrayOutputStream;
046: import java.io.IOException;
047: import java.io.InputStream;
048: import java.io.OutputStreamWriter;
049: import java.io.PrintWriter;
050: import java.lang.reflect.Method;
051: import java.lang.reflect.Modifier;
052: import java.net.URL;
053: import java.net.URLConnection;
054: import java.net.URLStreamHandler;
055: import java.util.ArrayList;
056: import java.util.Collections;
057: import java.util.Enumeration;
058: import java.util.List;
059: import java.util.NoSuchElementException;
060: import java.util.logging.Level;
061: import java.util.logging.Logger;
062: import static junit.framework.Assert.*;
063: import junit.framework.AssertionFailedError;
064:
065: /**
066: * Lets you register mock implementations of global services.
067: * You might for example do this in {@link junit.framework.TestCase#setUp}.
068: * <p>If you need to register individual instances, and are using the <code>Lookup</code>
069: * framework, try <code>org.openide.util.test.MockLookup</code>.
070: * @see <a href="http://www.netbeans.org/download/dev/javadoc/org-openide-util/org/openide/util/Lookup.html"><code>Lookup</code></a>
071: * @see <a href="http://download.java.net/jdk6/docs/api/java/util/ServiceLoader.html"><code>ServiceLoader</code></a>
072: * @since org.netbeans.modules.nbjunit/1 1.30
073: * @author Jesse Glick, Jaroslav Tulach
074: */
075: public class MockServices {
076:
077: private MockServices() {
078: }
079:
080: /**
081: * Set (or reset) the set of mock services.
082: * Clears any previous registration.
083: * After this call, <code>Lookup</code> and <code>ServiceLoader</code> should both
084: * "see" the newly registered classes.
085: * (Other classes really registered in <code>META-INF/services/</code> will
086: * also be available, but after the ones you have registered.)
087: * Each class must be public and concrete with a public no-arg constructor.
088: * @param services a set of service classes to register
089: * @throws IllegalArgumentException if some classes are not instantiable as beans
090: */
091: public static void setServices(Class<?>... services)
092: throws IllegalArgumentException {
093: ClassLoader l = new ServiceClassLoader(services);
094: // Adapted from org.netbeans.ModuleManager.updateContextClassLoaders. See that class for comments.
095: ThreadGroup g = Thread.currentThread().getThreadGroup();
096: while (g.getParent() != null) {
097: g = g.getParent();
098: }
099: while (true) {
100: int s = g.activeCount() + 1;
101: Thread[] ts = new Thread[s];
102: int x = g.enumerate(ts, true);
103: if (x < s) {
104: for (int i = 0; i < x; i++) {
105: ts[i].setContextClassLoader(l);
106: }
107: LOG.log(Level.FINE,
108: "Set context class loader on {0} threads", x);
109: break;
110: } else {
111: LOG
112: .fine("Race condition getting all threads, restarting...");
113: continue;
114: }
115: }
116: // Need to also reset global lookup since it caches the singleton and we need to change it.
117: try {
118: Class mainLookup = Class
119: .forName("org.netbeans.core.startup.MainLookup");
120: Method sClsLoaderChanged = mainLookup.getDeclaredMethod(
121: "systemClassLoaderChanged", ClassLoader.class);
122: sClsLoaderChanged.setAccessible(true);
123: sClsLoaderChanged.invoke(null, l);
124: } catch (ClassNotFoundException x) {
125: // Fine, not using core.jar.
126: } catch (Exception exc) {
127: LOG
128: .log(
129: Level.WARNING,
130: "MainLookup couldn't be notified about the context class loader change",
131: exc);
132: }
133:
134: try {
135: Class lookup = Class.forName("org.openide.util.Lookup");
136: Method defaultLookup = lookup
137: .getDeclaredMethod("resetDefaultLookup");
138: defaultLookup.setAccessible(true);
139: defaultLookup.invoke(null);
140: } catch (ClassNotFoundException x) {
141: // Fine, not using org-openide-lookup.jar.
142: } catch (Exception x) {
143: LOG.log(Level.WARNING,
144: "Could not reset Lookup.getDefault()", x);
145: }
146: }
147:
148: private static final Logger LOG = Logger
149: .getLogger(MockServices.class.getName());
150:
151: private static final class ServiceClassLoader extends ClassLoader {
152:
153: private final Class<?>[] services;
154:
155: public ServiceClassLoader(Class<?>[] services) {
156: super (MockServices.class.getClassLoader());
157: for (Class c : services) {
158: try {
159: assertEquals(c, getParent().loadClass(c.getName()));
160: int mods = c.getModifiers();
161: if (!Modifier.isPublic(mods)
162: || Modifier.isAbstract(mods)) {
163: throw new IllegalArgumentException("Class "
164: + c.getName() + " must be public");
165: }
166: c.getConstructor();
167: } catch (IllegalArgumentException x) {
168: throw x;
169: } catch (NoSuchMethodException x) {
170: throw (IllegalArgumentException) new IllegalArgumentException(
171: "Class "
172: + c.getName()
173: + " has no public no-arg constructor")
174: .initCause(x);
175: } catch (Exception x) {
176: throw (AssertionFailedError) new AssertionFailedError(
177: x.toString()).initCause(x);
178: }
179: }
180: this .services = services;
181: }
182:
183: public URL getResource(String name) {
184: Enumeration<URL> r;
185: try {
186: r = getResources(name);
187: } catch (IOException x) {
188: return null;
189: }
190: return r.hasMoreElements() ? r.nextElement() : null;
191: }
192:
193: public Enumeration<URL> getResources(String name)
194: throws IOException {
195: if (name
196: .equals("META-INF/services/org.openide.util.Lookup")
197: || name
198: .equals("META-INF/services/org.openide.util.Lookup$Provider")) {
199: // Lookup.getDefault checks for these, and we need to really mask it.
200: return Collections.enumeration(Collections
201: .<URL> emptySet());
202: }
203: final Enumeration<URL> supe = super .getResources(name);
204: String prefix = "META-INF/services/";
205: if (name.startsWith(prefix)) {
206: try {
207: Class<?> xface = loadClass(name.substring(prefix
208: .length()));
209: List<String> impls = new ArrayList<String>();
210: for (Class<?> c : services) {
211: boolean assignable = xface.isAssignableFrom(c);
212: if (assignable) {
213: impls.add(c.getName());
214: }
215: }
216: if (!impls.isEmpty()) {
217: final ByteArrayOutputStream baos = new ByteArrayOutputStream();
218: PrintWriter pw = new PrintWriter(
219: new OutputStreamWriter(baos, "UTF-8"));
220: for (String impl : impls) {
221: pw.println(impl);
222: }
223: pw.close();
224: final URL u = new URL("metainfservices", null,
225: 0, xface.getName(),
226: new URLStreamHandler() {
227: protected URLConnection openConnection(
228: URL _u) throws IOException {
229: return new URLConnection(_u) {
230: public void connect()
231: throws IOException {
232: }
233:
234: public InputStream getInputStream()
235: throws IOException {
236: return new ByteArrayInputStream(
237: baos
238: .toByteArray());
239: }
240: };
241: }
242: });
243: return new Enumeration<URL>() {
244: private boolean parent = false;
245:
246: public boolean hasMoreElements() {
247: return !parent
248: || supe.hasMoreElements();
249: }
250:
251: public URL nextElement()
252: throws NoSuchElementException {
253: if (parent) {
254: return supe.nextElement();
255: } else {
256: parent = true;
257: return u;
258: }
259: }
260: };
261: }
262: } catch (ClassNotFoundException x) {
263: }
264: }
265: return supe;
266: }
267:
268: /*
269: public Class<?> loadClass(String name) throws ClassNotFoundException {
270: // XXX make sure services can be loaded
271: return super.loadClass(name);
272: }
273: */
274:
275: }
276:
277: }
|