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.apisupport.project.layers;
043:
044: import java.io.File;
045: import java.io.IOException;
046: import java.net.URL;
047: import java.util.Arrays;
048: import java.util.Collections;
049: import java.util.HashMap;
050: import java.util.HashSet;
051: import java.util.Iterator;
052: import java.util.Map;
053: import java.util.Set;
054: import org.netbeans.modules.apisupport.project.TestBase;
055: import org.openide.filesystems.FileAttributeEvent;
056: import org.openide.filesystems.FileChangeListener;
057: import org.openide.filesystems.FileEvent;
058: import org.openide.filesystems.FileLock;
059: import org.openide.filesystems.FileObject;
060: import org.openide.filesystems.FileRenameEvent;
061: import org.openide.filesystems.FileSystem;
062: import org.openide.filesystems.FileUtil;
063: import org.openide.loaders.DataFolder;
064: import org.openide.loaders.DataObject;
065:
066: /**
067: * Test functionality of {@link WritableXMLFileSystem}.
068: * @author Jesse Glick
069: */
070: public class WritableXMLFileSystemTest extends LayerTestBase {
071:
072: public WritableXMLFileSystemTest(String name) {
073: super (name);
074: }
075:
076: public void testBasicStructureReads() throws Exception {
077: FileSystem fs = new Layer("<file name='x'/>").read();
078: FileObject[] top = fs.getRoot().getChildren();
079: assertEquals(1, top.length);
080: FileObject x = top[0];
081: assertEquals("x", x.getNameExt());
082: assertEquals(0L, x.getSize());
083: assertTrue(x.isData());
084: fs = new Layer(
085: "<folder name='x'><file name='y'/></folder><file name='z'/>")
086: .read();
087: top = fs.getRoot().getChildren();
088: assertEquals(2, top.length);
089: FileObject z = fs.findResource("z");
090: assertNotNull(z);
091: assertTrue(z.isData());
092: FileObject y = fs.findResource("x/y");
093: assertNotNull(y);
094: assertTrue(y.isData());
095: x = fs.findResource("x");
096: assertEquals(x, y.getParent());
097: assertTrue(x.isFolder());
098: assertEquals(1, x.getChildren().length);
099: }
100:
101: public void testExternalFileReads() throws Exception {
102: FileSystem fs = new Layer("<file name='x' url='x.txt'/>",
103: Collections.singletonMap("x.txt", "stuff")).read();
104: FileObject x = fs.findResource("x");
105: assertNotNull(x);
106: assertTrue(x.isData());
107: assertEquals(5L, x.getSize());
108: assertEquals("stuff", TestBase.slurp(x));
109: fs = new Layer("<file name='x' url='subdir/x.txt'/>",
110: Collections.singletonMap("subdir/x.txt", "more stuff"))
111: .read();
112: x = fs.findResource("x");
113: assertNotNull(x);
114: assertEquals("more stuff", TestBase.slurp(x));
115: // XXX check that using a nbres: or nbresloc: URL protocol works here too (if we specify a classpath)
116: }
117:
118: public void testSimpleAttributeReads() throws Exception {
119: FileSystem fs = new Layer(
120: "<file name='x'><attr name='a' stringvalue='v'/> <attr name='b' urlvalue='file:/nothing'/></file> "
121: + "<folder name='y'> <file name='ignore'/><attr name='a' boolvalue='true'/><!--ignore--></folder>")
122: .read();
123: FileObject x = fs.findResource("x");
124: assertEquals(
125: new HashSet<String>(Arrays.asList("a", "b")),
126: new HashSet<String>(Collections.list(x.getAttributes())));
127: assertEquals("v", x.getAttribute("a"));
128: assertEquals(new URL("file:/nothing"), x.getAttribute("b"));
129: assertEquals(null, x.getAttribute("dummy"));
130: FileObject y = fs.findResource("y");
131: assertEquals(Collections.singletonList("a"), Collections.list(y
132: .getAttributes()));
133: assertEquals(Boolean.TRUE, y.getAttribute("a"));
134: fs = new Layer(
135: "<attr name='a' stringvalue='This \\u0007 is a beep!'/>")
136: .read();
137: assertEquals(
138: "Unicode escapes handled correctly (for non-XML-encodable chars)",
139: "This \u0007 is a beep!", fs.getRoot()
140: .getAttribute("a"));
141: fs = new Layer(
142: "<attr name='a' stringvalue='\\ - \\u - \\u123 - \\uxyzw'/>")
143: .read();
144: assertEquals("Malformed escapes also handled",
145: "\\ - \\u - \\u123 - \\uxyzw", fs.getRoot()
146: .getAttribute("a"));
147: fs = new Layer("<attr name='a' urlvalue='yadayada'/>").read();
148: assertEquals("Malformed URLs also handled", null, fs.getRoot()
149: .getAttribute("a"));
150: // XXX test other attr types: byte, short, int, long, float, double, char
151: }
152:
153: public void testCodeAttributeReads() throws Exception {
154: FileSystem fs = new Layer(
155: "<file name='x'><attr name='a' stringvalue='v'/><attr name='b' newvalue='some.Class'/> "
156: + "<attr name='c' methodvalue='some.Class.m'/></file>")
157: .read();
158: FileObject x = fs.findResource("x");
159: assertEquals("v", x.getAttribute("literal:a"));
160: assertEquals("new:some.Class", x.getAttribute("literal:b"));
161: assertEquals("method:some.Class.m", x.getAttribute("literal:c"));
162: // XXX serial:blahblahblah
163: // XXX test actually loading method, new, serial as interpreted objects with a classpath
164: }
165:
166: public void testCreateNewFolder() throws Exception {
167: Layer l = new Layer("");
168: FileSystem fs = l.read();
169: FileObject x = fs.getRoot().createFolder("x");
170: assertEquals("in-memory write worked", 1, fs.getRoot()
171: .getChildren().length);
172: assertEquals("wrote correct content for top-level folder",
173: " <folder name=\"x\"/>\n", l.write());
174: x.createFolder("y");
175: assertEquals(
176: "wrote correct content for 2nd-level folder",
177: " <folder name=\"x\">\n <folder name=\"y\"/>\n </folder>\n",
178: l.write());
179: l = new Layer(" <folder name=\"original\"/>\n");
180: fs = l.read();
181: fs.getRoot().createFolder("aardvark");
182: fs.getRoot().createFolder("xyzzy");
183: assertEquals(
184: "alphabetized new folders",
185: " <folder name=\"aardvark\"/>\n <folder name=\"original\"/>\n <folder name=\"xyzzy\"/>\n",
186: l.write());
187: }
188:
189: public void testCreateNewEmptyFile() throws Exception {
190: Layer l = new Layer("");
191: FileSystem fs = l.read();
192: FileUtil.createData(fs.getRoot(), "Menu/Tools/foo");
193: FileUtil.createData(fs.getRoot(), "Menu/Tools/bar");
194: FileUtil.createFolder(fs.getRoot(), "Menu/Tools/baz");
195: assertEquals("correct files written",
196: " <folder name=\"Menu\">\n"
197: + " <folder name=\"Tools\">\n"
198: + " <file name=\"bar\"/>\n"
199: + " <folder name=\"baz\"/>\n"
200: + " <file name=\"foo\"/>\n"
201: + " </folder>\n" + " </folder>\n", l
202: .write());
203: assertEquals("no external files created",
204: Collections.EMPTY_MAP, l.files());
205: }
206:
207: public void testCreateFileWithContents() throws Exception {
208: Layer l = new Layer("");
209: FileSystem fs = l.read();
210: FileObject f = FileUtil.createData(fs.getRoot(),
211: "Templates/Other/Foo.java");
212: f.setAttribute("hello", "there");
213: TestBase.dump(f, "some stuff");
214: String xml = " <folder name=\"Templates\">\n"
215: + " <folder name=\"Other\">\n"
216: +
217: // We never want to create *.java files since they would be compiled.
218: " <file name=\"Foo.java\" url=\"Foo_java\">\n"
219: + " <attr name=\"hello\" stringvalue=\"there\"/>\n"
220: + " </file>\n" + " </folder>\n"
221: + " </folder>\n";
222: assertEquals("correct XML written", xml, l.write());
223: Map<String, String> m = new HashMap<String, String>();
224: m.put("Foo_java", "some stuff");
225: assertEquals("one external file created in " + l, m, l.files());
226: TestBase.dump(f, "new stuff");
227: assertEquals("same XML as before", xml, l.write());
228: m.put("Foo_java", "new stuff");
229: assertEquals("different external file", m, l.files());
230: f = FileUtil.createData(fs.getRoot(),
231: "Templates/Other2/Foo.java");
232: TestBase.dump(f, "unrelated stuff");
233: f = FileUtil.createData(fs.getRoot(),
234: "Templates/Other2/Bar.xml");
235: TestBase.dump(f, "unrelated XML stuff");
236: f = FileUtil.createData(fs.getRoot(), "Services/foo.settings");
237: TestBase.dump(f, "scary stuff");
238: xml = " <folder name=\"Services\">\n"
239: +
240: // *.settings are also potentially dangerous in module sources, so rename them.
241: " <file name=\"foo.settings\" url=\"fooSettings.xml\"/>\n"
242: + " </folder>\n"
243: + " <folder name=\"Templates\">\n"
244: + " <folder name=\"Other\">\n"
245: + " <file name=\"Foo.java\" url=\"Foo_java\">\n"
246: + " <attr name=\"hello\" stringvalue=\"there\"/>\n"
247: + " </file>\n"
248: + " </folder>\n"
249: + " <folder name=\"Other2\">\n"
250: +
251: // But *.xml files and other things are generally OK.
252: " <file name=\"Bar.xml\" url=\"Bar.xml\"/>\n"
253: + " <file name=\"Foo.java\" url=\"Foo_1_java\"/>\n"
254: + " </folder>\n" + " </folder>\n";
255: assertEquals("right XML written for remaining files", xml, l
256: .write());
257: m.put("Foo_1_java", "unrelated stuff");
258: m.put("Bar.xml", "unrelated XML stuff");
259: m.put("fooSettings.xml", "scary stuff");
260: assertEquals("right external files", m, l.files());
261: l = new Layer("", Collections.singletonMap("file.txt",
262: "existing stuff"));
263: fs = l.read();
264: f = FileUtil.createData(fs.getRoot(), "wherever/file.txt");
265: TestBase.dump(f, "unrelated stuff");
266: xml = " <folder name=\"wherever\">\n"
267: +
268: // Need to pick a different location even though there is no conflict inside the layer.
269: // Also should use a suffix before file extension.
270: " <file name=\"file.txt\" url=\"file_1.txt\"/>\n"
271: + " </folder>\n";
272: assertEquals(
273: "wrote new file contents to non-clobbering location",
274: xml, l.write());
275: m.clear();
276: m.put("file.txt", "existing stuff");
277: m.put("file_1.txt", "unrelated stuff");
278: assertEquals("right external files", m, l.files());
279: l = new Layer("");
280: fs = l.read();
281: f = FileUtil.createData(fs.getRoot(), "one/bare");
282: TestBase.dump(f, "bare #1");
283: f = FileUtil.createData(fs.getRoot(), "two/bare");
284: TestBase.dump(f, "bare #2");
285: f = FileUtil.createData(fs.getRoot(), "three/bare");
286: TestBase.dump(f, "bare #3");
287: xml = " <folder name=\"one\">\n"
288: + " <file name=\"bare\" url=\"bare\"/>\n"
289: + " </folder>\n"
290: +
291: // Note alpha ordering.
292: " <folder name=\"three\">\n"
293: +
294: // Count _1, _2, _3, ...
295: " <file name=\"bare\" url=\"bare_2\"/>\n"
296: + " </folder>\n" + " <folder name=\"two\">\n"
297: +
298: // No extension here, fine.
299: " <file name=\"bare\" url=\"bare_1\"/>\n"
300: + " </folder>\n";
301: assertEquals("right counter usage", xml, l.write());
302: m.clear();
303: m.put("bare", "bare #1");
304: m.put("bare_1", "bare #2");
305: m.put("bare_2", "bare #3");
306: assertEquals("right external files", m, l.files());
307: }
308:
309: // XXX testReplaceExistingInlineContent
310:
311: public void testAddAttributes() throws Exception {
312: Layer l = new Layer("");
313: FileSystem fs = l.read();
314: FileObject f = fs.getRoot().createFolder("f");
315: f.createData("b");
316: FileObject a = f.createData("a");
317: f.createData("c");
318: a.setAttribute("x", "v1");
319: a.setAttribute("y", new URL("file:/v2"));
320: /*
321: f.setAttribute("a/b", Boolean.TRUE);
322: f.setAttribute("b/c", Boolean.TRUE);
323: */
324: f.setAttribute("misc", "whatever");
325: assertEquals(
326: "correct attrs written",
327: " <folder name=\"f\">\n"
328: + " <attr name=\"misc\" stringvalue=\"whatever\"/>\n"
329: + " <file name=\"a\">\n"
330: + " <attr name=\"x\" stringvalue=\"v1\"/>\n"
331: + " <attr name=\"y\" urlvalue=\"file:/v2\"/>\n"
332: + " </file>\n"
333: +
334: //" <attr name=\"a/b\" boolvalue=\"true\"/>\n" +
335: " <file name=\"b\"/>\n"
336: +
337: //" <attr name=\"b/c\" boolvalue=\"true\"/>\n" +
338: " <file name=\"c\"/>\n"
339: + " </folder>\n", l.write());
340: // XXX test also string values w/ dangerous chars
341: }
342:
343: public void testDeleteReplaceAttributes() throws Exception {
344: Layer l = new Layer(" <file name=\"x\">\n"
345: + " <attr name=\"foo\" stringvalue=\"bar\"/>\n"
346: + " </file>\n");
347: FileSystem fs = l.read();
348: FileObject x = fs.findResource("x");
349: x.setAttribute("foo", Boolean.TRUE);
350: assertEquals("replaced attr", " <file name=\"x\">\n"
351: + " <attr name=\"foo\" boolvalue=\"true\"/>\n"
352: + " </file>\n", l.write());
353: x.setAttribute("foo", null);
354: assertEquals("deleted attr", " <file name=\"x\"/>\n", l
355: .write());
356: x.setAttribute("foo", null);
357: assertEquals("cannot delete attr twice",
358: " <file name=\"x\"/>\n", l.write());
359: /* Now this stores x/y=false instead... OK?
360: fs.getRoot().createData("y");
361: fs.getRoot().setAttribute("x/y", Boolean.TRUE);
362: fs.getRoot().setAttribute("x/y", null);
363: assertEquals("deleted ordering attr",
364: " <file name=\"x\"/>\n" +
365: " <file name=\"y\"/>\n",
366: l.write());
367: */
368: }
369:
370: public void testCodeAttributeWrites() throws Exception {
371: Layer l = new Layer("");
372: FileSystem fs = l.read();
373: FileObject x = fs.getRoot().createData("x");
374: x.setAttribute("nv", "newvalue:org.foo.Clazz");
375: x.setAttribute("mv", "methodvalue:org.foo.Clazz.create");
376: assertEquals(
377: "special attrs written to XML",
378: " <file name=\"x\">\n"
379: + " <attr name=\"mv\" methodvalue=\"org.foo.Clazz.create\"/>\n"
380: + " <attr name=\"nv\" newvalue=\"org.foo.Clazz\"/>\n"
381: + " </file>\n", l.write());
382: }
383:
384: public void testFolderOrder() throws Exception {
385: Layer l = new Layer("");
386: FileSystem fs = l.read();
387: FileObject r = fs.getRoot();
388: FileObject a = r.createData("a");
389: FileObject b = r.createData("b");
390: FileObject f = r.createFolder("f");
391: FileObject c = f.createData("c");
392: FileObject d = f.createData("d");
393: FileObject e = f.createData("e");
394: DataFolder.findFolder(r).setOrder(
395: new DataObject[] { DataObject.find(a),
396: DataObject.find(b) });
397: DataFolder.findFolder(f).setOrder(
398: new DataObject[] { DataObject.find(c),
399: DataObject.find(d), DataObject.find(e) });
400: assertEquals(
401: "correct ordering XML",
402: " <file name=\"a\">\n"
403: + " <attr name=\"position\" intvalue=\"100\"/>\n"
404: + " </file>\n"
405: + " <file name=\"b\">\n"
406: + " <attr name=\"position\" intvalue=\"200\"/>\n"
407: + " </file>\n"
408: + " <folder name=\"f\">\n"
409: + " <file name=\"c\">\n"
410: + " <attr name=\"position\" intvalue=\"100\"/>\n"
411: + " </file>\n"
412: + " <file name=\"d\">\n"
413: + " <attr name=\"position\" intvalue=\"200\"/>\n"
414: + " </file>\n"
415: + " <file name=\"e\">\n"
416: + " <attr name=\"position\" intvalue=\"300\"/>\n"
417: + " </file>\n" + " </folder>\n", l
418: .write());
419: }
420:
421: public void testDeleteFileOrFolder() throws Exception {
422: Layer l = new Layer("");
423: FileSystem fs = l.read();
424: FileObject f = fs.getRoot().createFolder("f");
425: FileObject x = f.createData("x");
426: x.setAttribute("foo", "bar");
427: TestBase.dump(x, "stuff");
428: FileObject y = FileUtil.createData(fs.getRoot(), "y");
429: x.delete();
430: assertEquals("one file and one folder left",
431: " <folder name=\"f\"/>\n"
432: + " <file name=\"y\"/>\n", l.write());
433: assertEquals("no external files left", Collections.EMPTY_MAP, l
434: .files());
435: f.delete();
436: assertEquals("one file left", " <file name=\"y\"/>\n", l
437: .write());
438: y.delete();
439: assertEquals("layer now empty again", "", l.write());
440: l = new Layer("");
441: fs = l.read();
442: f = fs.getRoot().createFolder("f");
443: x = f.createData("x");
444: TestBase.dump(x, "stuff");
445: f.delete();
446: assertEquals("layer empty again", "", l.write());
447: assertEquals(
448: "no external files left even after only implicitly deleting file",
449: Collections.EMPTY_MAP, l.files());
450: // XXX should any associated ordering attrs also be deleted? not acc. to spec, but often handy...
451: l = new Layer("");
452: fs = l.read();
453: FileObject a = fs.getRoot().createData("a");
454: FileObject b = fs.getRoot().createData("b");
455: FileObject c = fs.getRoot().createData("c");
456: FileObject d = fs.getRoot().createData("d");
457: a.delete();
458: assertEquals(
459: "right indentation cleanup for deletion of first child",
460: " <file name=\"b\"/>\n" + " <file name=\"c\"/>\n"
461: + " <file name=\"d\"/>\n", l.write());
462: c.delete();
463: assertEquals(
464: "right indentation cleanup for deletion of interior child",
465: " <file name=\"b\"/>\n" + " <file name=\"d\"/>\n",
466: l.write());
467: d.delete();
468: assertEquals(
469: "right indentation cleanup for deletion of last child",
470: " <file name=\"b\"/>\n", l.write());
471: }
472:
473: public void testRename() throws Exception {
474: Layer l = new Layer(
475: " <folder name=\"folder1\">\n <file name=\"file1.txt\">\n <attr name=\"a\" stringvalue=\"v\"/>\n </file>\n </folder>\n");
476: FileSystem fs = l.read();
477: FileObject f = fs.findResource("folder1");
478: assertNotNull(f);
479: FileLock lock = f.lock();
480: f.rename(lock, "folder2", null);
481: lock.releaseLock();
482: f = fs.findResource("folder2/file1.txt");
483: assertNotNull(f);
484: lock = f.lock();
485: f.rename(lock, "file2.txt", null);
486: lock.releaseLock();
487: assertEquals(
488: "#63989: correct rename handling",
489: " <folder name=\"folder2\">\n <file name=\"file2.txt\">\n <attr name=\"a\" stringvalue=\"v\"/>\n </file>\n </folder>\n",
490: l.write());
491: // XXX should any associated ordering attrs also be renamed? might be pleasant...
492: }
493:
494: public void testSomeFormattingPreserved() throws Exception {
495: String orig = " <file name=\"orig\"><!-- hi --><attr name=\"a\" boolvalue=\"true\"/>"
496: + "<attr boolvalue=\"true\" name=\"b\"/></file>\n";
497: Layer l = new Layer(orig);
498: FileSystem fs = l.read();
499: fs.getRoot().createData("aardvark");
500: fs.getRoot().createData("zyzzyva");
501: assertEquals("kept original XML intact",
502: " <file name=\"aardvark\"/>\n" + orig
503: + " <file name=\"zyzzyva\"/>\n", l.write());
504: // XXX what doesn't work: space before />, unusual whitespace between attrs, ...
505: // This is a job for TAX to improve on. Currently uses Xerces XNI parser,
506: // which discards some formatting info when constructing its model.
507: // Not as much as using DOM + serialization, but some.
508: }
509:
510: public void testStructuralModificationsFired() throws Exception {
511: Layer l = new Layer("<folder name='f'/><file name='x'/>");
512: FileSystem fs = l.read();
513: Listener fcl = new Listener();
514: fs.addFileChangeListener(fcl);
515: FileUtil.createData(fs.getRoot(), "a");
516: FileUtil.createData(fs.getRoot(), "f/b");
517: fs.findResource("x").delete();
518: assertEquals("expected things fired", new HashSet<String>(
519: Arrays.asList("a", "f/b", "x")), fcl.changes());
520: }
521:
522: public void testTextualModificationsFired() throws Exception {
523: Layer l = new Layer(
524: "<folder name='f'><file name='x'/></folder><file name='y'/>");
525: FileSystem fs = l.read();
526: Listener fcl = new Listener();
527: fs.addFileChangeListener(fcl);
528: l.edit("<folder name='f'/><file name='y'/><file name='z'/>");
529: Set<String> changes = fcl.changes();
530: //System.err.println("changes=" + changes);
531: /* XXX does not work; fires too much... why?
532: assertEquals("expected things fired",
533: new HashSet(Arrays.asList(new String[] {"f/x", "z"})),
534: changes);
535: */
536: assertTrue("something fired", !changes.isEmpty());
537: assertNull(fs.findResource("f/x"));
538: assertNotNull(fs.findResource("z"));
539: l
540: .edit("<folder name='f'><file name='x2'/></folder><file name='y'/><file name='z'/>");
541: /* XXX fails just on JDK 1.4... why?
542: changes = fcl.changes();
543: //System.err.println("changes=" + changes);
544: assertTrue("something fired #2", !changes.isEmpty());
545: */
546: assertNotNull(fs.findResource("f/x2"));
547: assertNotNull(fs.findResource("z"));
548: }
549:
550: public void testExternalFileChangesRefired() throws Exception {
551: Layer l = new Layer("");
552: FileSystem fs = l.read();
553: FileObject foo = fs.getRoot().createData("foo");
554: TestBase.dump(foo, "foo text");
555: long start = foo.lastModified().getTime();
556: assertEquals(start, l.externalLastModified("foo"));
557: Thread.sleep(1000L); // make sure the timestamp actually changed
558: Listener fcl = new Listener();
559: fs.addFileChangeListener(fcl);
560: l.editExternal("foo", "new text");
561: long end = foo.lastModified().getTime();
562: assertEquals(end, l.externalLastModified("foo"));
563: assertTrue("foo was touched", end > start);
564: assertEquals("expected things fired", Collections
565: .singleton("foo"), fcl.changes());
566: }
567:
568: public void testHeapUsage() throws Exception {
569: Layer l = new Layer("");
570: FileSystem fs = l.read();
571: Object buffer = new byte[(int) (Runtime.getRuntime()
572: .freeMemory() * 2 / 3)];
573: int count = 1000;
574: int bytesPerFile = 4000; // found by trial and error, but seems tolerable
575: for (int i = 0; i < count; i++) {
576: try {
577: fs.getRoot().createData("file" + i);
578: } catch (OutOfMemoryError e) {
579: fail("Ran out of heap on file #" + i);
580: }
581: }
582: assertSize("Filesystem not too big", count * bytesPerFile, l);
583: }
584:
585: /**
586: * Handle for working with an XML layer.
587: */
588: private final class Layer {
589: private final File folder;
590: private final FileObject f;
591: private LayerUtils.SavableTreeEditorCookie cookie;
592:
593: /**
594: * Create a layer from a fixed bit of filesystem-DTD XML.
595: * Omit the <filesystem>...</> tag and just give the contents.
596: * But if you want to check formatting later using {@link #write()},
597: * you should use 4-space indents and a newline after every element.
598: */
599: public Layer(String xml) throws Exception {
600: folder = makeFolder();
601: f = makeLayer(xml);
602: }
603:
604: /**
605: * Create a layer from XML plus external file contents.
606: * The map's keys are relative disk filenames, and values are contents.
607: */
608: public Layer(String xml, Map/*<String,String>*/files)
609: throws Exception {
610: this (xml);
611: Iterator it = files.entrySet().iterator();
612: while (it.hasNext()) {
613: Map.Entry entry = (Map.Entry) it.next();
614: String fname = (String) entry.getKey();
615: String contents = (String) entry.getValue();
616: File file = new File(folder, fname.replace('/',
617: File.separatorChar));
618: file.getParentFile().mkdirs();
619: TestBase.dump(file, contents);
620: }
621: }
622:
623: private File makeFolder() throws Exception {
624: File file = File.createTempFile("layerdata", "",
625: getWorkDir());
626: file.delete();
627: file.mkdir();
628: return file;
629: }
630:
631: private final String HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
632: + "<!DOCTYPE filesystem PUBLIC \"-//NetBeans//DTD Filesystem 1.1//EN\" \"http://www.netbeans.org/dtds/filesystem-1_1.dtd\">\n"
633: + "<filesystem>\n";
634: private final String FOOTER = "</filesystem>\n";
635:
636: private FileObject makeLayer(String xml) throws Exception {
637: File file = new File(folder, "layer.xml");
638: TestBase.dump(file, HEADER + xml + FOOTER);
639: return FileUtil.toFileObject(file);
640: }
641:
642: /**
643: * Read the filesystem from the layer.
644: */
645: public WritableXMLFileSystem read() throws Exception {
646: cookie = LayerUtils.cookieForFile(f);
647: return new WritableXMLFileSystem(f.getURL(), cookie, null);
648: }
649:
650: /**
651: * Write the filesystem to the layer and retrieve the new contents.
652: * The header and footer are removed for you, but not other whitespace.
653: */
654: public String write() throws Exception {
655: cookie.save();
656: String raw = TestBase.slurp(f);
657: assertTrue("unexpected header in '" + raw + "'", raw
658: .startsWith(HEADER));
659: assertTrue("unexpected footer in '" + raw + "'", raw
660: .endsWith(FOOTER));
661: return raw.substring(HEADER.length(), raw.length()
662: - FOOTER.length());
663: }
664:
665: /**
666: * Edit the text of the layer.
667: */
668: public void edit(String newText) throws Exception {
669: TestBase.dump(f, HEADER + newText + FOOTER);
670: }
671:
672: /**
673: * Edit a referenced external file.
674: */
675: public void editExternal(String path, String newText)
676: throws Exception {
677: assert !path.equals("layer.xml");
678: File file = new File(folder, path.replace('/',
679: File.separatorChar));
680: assert file.isFile();
681: TestBase.dump(FileUtil.toFileObject(file), newText);
682: }
683:
684: public long externalLastModified(String path) {
685: File file = new File(folder, path.replace('/',
686: File.separatorChar));
687: return FileUtil.toFileObject(file).lastModified().getTime();
688: }
689:
690: /**
691: * Get extra files besides layer.xml which were written to.
692: * Keys are relative file paths and values are file contents.
693: */
694: public Map<String, String> files() throws IOException {
695: Map<String, String> m = new HashMap<String, String>();
696: traverse(m, folder, "");
697: return m;
698: }
699:
700: private void traverse(Map<String, String> m, File folder,
701: String prefix) throws IOException {
702: String[] kids = folder.list();
703: if (kids == null) {
704: throw new IOException(folder.toString());
705: }
706: for (int i = 0; i < kids.length; i++) {
707: String path = prefix + kids[i];
708: if (path.equals("layer.xml")) {
709: continue;
710: }
711: File file = new File(folder, kids[i]);
712: if (file.isDirectory()) {
713: traverse(m, file, prefix + kids[i] + '/');
714: } else {
715: m.put(path, TestBase.slurp(file));
716: }
717: }
718: }
719:
720: public String toString() {
721: return "Layer[" + folder + "]";
722: }
723: }
724:
725: private static final class Listener implements FileChangeListener {
726:
727: private Set<String> changes = new HashSet<String>();
728:
729: public Listener() {
730: }
731:
732: public Set<String> changes() {
733: Set<String> _changes = changes;
734: changes = new HashSet<String>();
735: return _changes;
736: }
737:
738: public void fileFolderCreated(FileEvent fe) {
739: changed(fe);
740: }
741:
742: public void fileDataCreated(FileEvent fe) {
743: changed(fe);
744: }
745:
746: public void fileChanged(FileEvent fe) {
747: changed(fe);
748: }
749:
750: public void fileDeleted(FileEvent fe) {
751: changed(fe);
752: }
753:
754: public void fileRenamed(FileRenameEvent fe) {
755: changed(fe);
756: }
757:
758: public void fileAttributeChanged(FileAttributeEvent fe) {
759: changed(fe);
760: }
761:
762: private void changed(FileEvent fe) {
763: changes.add(fe.getFile().getPath());
764: }
765:
766: }
767:
768: }
|