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