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.api.project;
043:
044: import java.beans.PropertyVetoException;
045: import java.io.File;
046: import java.io.IOException;
047: import java.io.InputStream;
048: import java.io.OutputStream;
049: import java.net.URL;
050: import java.util.Map;
051: import java.util.StringTokenizer;
052: import java.util.WeakHashMap;
053: import junit.framework.Assert;
054: import org.netbeans.junit.NbTestCase;
055: import org.netbeans.spi.project.ProjectFactory;
056: import org.netbeans.spi.project.ProjectState;
057: import org.openide.filesystems.FileObject;
058: import org.openide.filesystems.FileUtil;
059: import org.openide.filesystems.LocalFileSystem;
060: import org.openide.filesystems.Repository;
061: import org.openide.filesystems.URLMapper;
062: import org.openide.util.Lookup;
063: import org.openide.util.test.MockLookup;
064:
065: /**
066: * Help set up org.netbeans.api.project.*Test.
067: * @author Jesse Glick
068: */
069: public final class TestUtil {
070:
071: private TestUtil() {
072: }
073:
074: /**
075: * Set the global default lookup.
076: * Caution: if you don't include Lookups.metaInfServices, you may have trouble,
077: * e.g. {@link #makeScratchDir} will not work.
078: * @deprecated Use {@link MockLookup} instead.
079: */
080: @Deprecated
081: public static void setLookup(Lookup l) {
082: MockLookup.setLookup(l);
083: }
084:
085: /**
086: * Set the global default lookup with some fixed instances including META-INF/services/*.
087: * @deprecated Use {@link MockLookup} instead.
088: */
089: @Deprecated
090: public static void setLookup(Object... instances) {
091: MockLookup.setInstances(instances);
092: }
093:
094: private static boolean warned = false;
095:
096: /**
097: * Create a scratch directory for tests.
098: * Will be in /tmp or whatever, and will be empty.
099: * If you just need a java.io.File use clearWorkDir + getWorkDir.
100: */
101: public static FileObject makeScratchDir(NbTestCase test)
102: throws IOException {
103: test.clearWorkDir();
104: File root = test.getWorkDir();
105: assert root.isDirectory() && root.list().length == 0;
106: MockLookup.init(); // URLMapper asks for default lookup
107: FileObject fo = FileUtil.toFileObject(root);
108: if (fo != null) {
109: return fo;
110: } else {
111: if (!warned) {
112: warned = true;
113: System.err
114: .println("No FileObject for "
115: + root
116: + " found.\n"
117: + "Maybe you need ${openide/masterfs.dir}/modules/org-netbeans-modules-masterfs.jar\n"
118: + "in test.unit.run.cp.extra, or make sure Lookups.metaInfServices is included in Lookup.default, so that\n"
119: + "Lookup.default<URLMapper>="
120: + Lookup.getDefault().lookupAll(
121: URLMapper.class)
122: + " includes MasterURLMapper\n"
123: + "e.g. by using TestUtil.setLookup(Object[]) rather than TestUtil.setLookup(Lookup).");
124: }
125: // For the benefit of those not using masterfs.
126: LocalFileSystem lfs = new LocalFileSystem();
127: try {
128: lfs.setRootDirectory(root);
129: } catch (PropertyVetoException e) {
130: assert false : e;
131: }
132: Repository.getDefault().addFileSystem(lfs);
133: return lfs.getRoot();
134: }
135: }
136:
137: /**
138: * Delete a file and all subfiles.
139: */
140: public static void deleteRec(File f) throws IOException {
141: if (f.isDirectory()) {
142: File[] kids = f.listFiles();
143: if (kids == null) {
144: throw new IOException("List " + f);
145: }
146: for (File kid : kids) {
147: deleteRec(kid);
148: }
149: }
150: if (!f.delete()) {
151: throw new IOException("Delete " + f);
152: }
153: }
154:
155: /**
156: * Create a testing project factory which recognizes directories containing
157: * a subdirectory called "testproject".
158: * If that subdirectory contains a file named "broken" then loading the project
159: * will fail with an IOException.
160: */
161: public static ProjectFactory testProjectFactory() {
162: return new TestProjectFactory();
163: }
164:
165: /**
166: * Try to force a GC.
167: */
168: public static void gc() {
169: System.gc();
170: System.runFinalization();
171: System.gc();
172: }
173:
174: private static final Map<FileObject, Integer> loadCount = new WeakHashMap<FileObject, Integer>();
175:
176: /**
177: * Check how many times {@link ProjectFactory#loadProject} has been called
178: * (with any outcome) on a given project directory.
179: */
180: public static int projectLoadCount(FileObject dir) {
181: Integer i = loadCount.get(dir);
182: if (i != null) {
183: return i;
184: } else {
185: return 0;
186: }
187: }
188:
189: /**
190: * Mark a test project to fail with a given error when it is next saved.
191: * The error only applies to the next save, not subsequent ones.
192: * @param p a test project
193: * @param error an error to throw (IOException or Error or RuntimeException),
194: * or null if it should succeed
195: */
196: public static void setProjectSaveWillFail(Project p, Throwable error) {
197: ((TestProject) p).error = error;
198: }
199:
200: /**
201: * Get the number of times a test project was successfully saved with no error.
202: * @param p a test project
203: * @return the save count
204: */
205: public static int projectSaveCount(Project p) {
206: return ((TestProject) p).saveCount;
207: }
208:
209: /**
210: * Mark a test project as modified.
211: * @param p a test project
212: */
213: public static void modify(Project p) {
214: ((TestProject) p).state.markModified();
215: }
216:
217: /**
218: * Mark a test project as modified.
219: * @param p a test project
220: */
221: public static void notifyDeleted(Project p) {
222: ((TestProject) p).state.notifyDeleted();
223: }
224:
225: /**
226: * If set to something non-null, loading a broken project will wait for
227: * notification on this monitor before throwing an exception.
228: * @see ProjectManagerTest#testLoadExceptionWithConcurrentLoad
229: */
230: public static Object BROKEN_PROJECT_LOAD_LOCK = null;
231:
232: private static final class TestProjectFactory implements
233: ProjectFactory {
234:
235: TestProjectFactory() {
236: }
237:
238: public Project loadProject(FileObject projectDirectory,
239: ProjectState state) throws IOException {
240: Integer i = loadCount.get(projectDirectory);
241: if (i == null) {
242: i = 1;
243: } else {
244: i = i + 1;
245: }
246: loadCount.put(projectDirectory, i);
247: FileObject testproject = projectDirectory
248: .getFileObject("testproject");
249: if (testproject != null && testproject.isFolder()) {
250: if (testproject.getFileObject("broken") != null) {
251: if (BROKEN_PROJECT_LOAD_LOCK != null) {
252: synchronized (BROKEN_PROJECT_LOAD_LOCK) {
253: try {
254: BROKEN_PROJECT_LOAD_LOCK.wait();
255: } catch (InterruptedException e) {
256: assert false : e;
257: }
258: }
259: }
260: throw new IOException("Load failed of "
261: + projectDirectory);
262: } else {
263: return new TestProject(projectDirectory, state);
264: }
265: } else {
266: return null;
267: }
268: }
269:
270: public void saveProject(Project project) throws IOException,
271: ClassCastException {
272: TestProject p = (TestProject) project;
273: Throwable t = p.error;
274: if (t != null) {
275: p.error = null;
276: if (t instanceof IOException) {
277: throw (IOException) t;
278: } else if (t instanceof Error) {
279: throw (Error) t;
280: } else {
281: throw (RuntimeException) t;
282: }
283: }
284: p.saveCount++;
285: }
286:
287: public boolean isProject(FileObject dir) {
288: FileObject testproject = dir.getFileObject("testproject");
289: return testproject != null && testproject.isFolder();
290: }
291:
292: }
293:
294: private static final class TestProject implements Project {
295:
296: private final FileObject dir;
297: final ProjectState state;
298: Throwable error;
299: int saveCount = 0;
300:
301: public TestProject(FileObject dir, ProjectState state) {
302: this .dir = dir;
303: this .state = state;
304: }
305:
306: public Lookup getLookup() {
307: return Lookup.EMPTY;
308: }
309:
310: public FileObject getProjectDirectory() {
311: return dir;
312: }
313:
314: public String toString() {
315: return "testproject:" + getProjectDirectory().getNameExt();
316: }
317:
318: /* Probably unnecessary to have a ProjectInformation here:
319: public String getName() {
320: return "testproject:" + getProjectDirectory().getNameExt();
321: }
322:
323: public String getDisplayName() {
324: return "Test Project in " + getProjectDirectory().getNameExt();
325: }
326:
327: public Image getIcon() {
328: return null;
329: }
330:
331: public void addPropertyChangeListener(PropertyChangeListener listener) {}
332: public void removePropertyChangeListener(PropertyChangeListener listener) {}
333: */
334:
335: }
336:
337: /**
338: * Open a URL of content (for example from {@link Class#getResource}) and copy it to a named file.
339: * The new file can be given as a parent directory plus a relative (slash-separated) path.
340: * The file may not already exist, but intermediate directories may or may not.
341: * If the content URL is null, the file is just created, no more; if it already existed
342: * it is touched (timestamp updated) and its contents are cleared.
343: * @return the file object
344: */
345: public static FileObject createFileFromContent(URL content,
346: FileObject parent, String path) throws IOException {
347: if (parent == null) {
348: throw new IllegalArgumentException("null parent");
349: }
350: Assert.assertTrue("folder", parent.isFolder());
351: FileObject fo = parent;
352: StringTokenizer tok = new StringTokenizer(path, "/");
353: boolean touch = false;
354: while (tok.hasMoreTokens()) {
355: Assert.assertNotNull("fo is null (parent=" + parent
356: + " path=" + path + ")", fo);
357: String name = tok.nextToken();
358: if (tok.hasMoreTokens()) {
359: FileObject sub = fo.getFileObject(name);
360: if (sub == null) {
361: FileObject fo2 = fo.createFolder(name);
362: Assert.assertNotNull("createFolder(" + fo + ", "
363: + name + ") -> null", fo2);
364: fo = fo2;
365: } else {
366: Assert.assertTrue("folder", sub.isFolder());
367: fo = sub;
368: }
369: } else {
370: FileObject sub = fo.getFileObject(name);
371: if (sub == null) {
372: FileObject fo2 = fo.createData(name);
373: Assert.assertNotNull("createData(" + fo + ", "
374: + name + ") -> null", fo2);
375: fo = fo2;
376: } else {
377: fo = sub;
378: touch = true;
379: }
380: }
381: }
382: assert fo.isData();
383: if (content != null || touch) {
384: OutputStream os = fo.getOutputStream();
385: try {
386: if (content != null) {
387: InputStream is = content.openStream();
388: try {
389: FileUtil.copy(is, os);
390: } finally {
391: is.close();
392: }
393: }
394: } finally {
395: os.close();
396: }
397: }
398: return fo;
399: }
400:
401: }
|