This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 194547
Collapse All | Expand All

(-)a/apisupport.project/nbproject/project.xml (-3 / +12 lines)
Lines 255-266 Link Here
255
                    </run-dependency>
255
                    </run-dependency>
256
                </dependency>
256
                </dependency>
257
                <dependency>
257
                <dependency>
258
                    <code-name-base>org.netbeans.modules.xml.tax</code-name-base>
258
                    <code-name-base>org.netbeans.modules.xml.xam</code-name-base>
259
                    <build-prerequisite/>
259
                    <build-prerequisite/>
260
                    <compile-dependency/>
260
                    <compile-dependency/>
261
                    <run-dependency>
261
                    <run-dependency>
262
                        <release-version>2</release-version>
262
                        <release-version>1</release-version>
263
                        <specification-version>1.16</specification-version>
263
                        <specification-version>1.11</specification-version>
264
                    </run-dependency>
265
                </dependency>
266
                <dependency>
267
                    <code-name-base>org.netbeans.modules.xml.xdm</code-name-base>
268
                    <build-prerequisite/>
269
                    <compile-dependency/>
270
                    <run-dependency>
271
                        <release-version>1</release-version>
272
                        <specification-version>1.12</specification-version>
264
                    </run-dependency>
273
                    </run-dependency>
265
                </dependency>
274
                </dependency>
266
                <dependency>
275
                <dependency>
(-)a/apisupport.project/src/org/netbeans/modules/apisupport/project/api/LayerHandle.java (-35 / +37 lines)
Lines 42-49 Link Here
42
42
43
package org.netbeans.modules.apisupport.project.api;
43
package org.netbeans.modules.apisupport.project.api;
44
44
45
import java.beans.PropertyChangeEvent;
46
import java.beans.PropertyChangeListener;
47
import java.io.File;
45
import java.io.File;
48
import java.io.IOException;
46
import java.io.IOException;
49
import java.util.ArrayList;
47
import java.util.ArrayList;
Lines 59-65 Link Here
59
import org.netbeans.modules.apisupport.project.NbModuleProjectGenerator;
57
import org.netbeans.modules.apisupport.project.NbModuleProjectGenerator;
60
import org.netbeans.modules.apisupport.project.Util;
58
import org.netbeans.modules.apisupport.project.Util;
61
import org.netbeans.modules.apisupport.project.layers.LayerUtils;
59
import org.netbeans.modules.apisupport.project.layers.LayerUtils;
62
import org.netbeans.modules.apisupport.project.layers.LayerUtils.SavableTreeEditorCookie;
63
import org.netbeans.modules.apisupport.project.layers.WritableXMLFileSystem;
60
import org.netbeans.modules.apisupport.project.layers.WritableXMLFileSystem;
64
import org.netbeans.modules.apisupport.project.spi.NbModuleProvider;
61
import org.netbeans.modules.apisupport.project.spi.NbModuleProvider;
65
import org.openide.ErrorManager;
62
import org.openide.ErrorManager;
Lines 68-74 Link Here
68
import org.openide.filesystems.FileEvent;
65
import org.openide.filesystems.FileEvent;
69
import org.openide.filesystems.FileObject;
66
import org.openide.filesystems.FileObject;
70
import org.openide.filesystems.FileRenameEvent;
67
import org.openide.filesystems.FileRenameEvent;
71
import org.openide.filesystems.FileStateInvalidException;
72
import org.openide.filesystems.FileSystem;
68
import org.openide.filesystems.FileSystem;
73
import org.openide.filesystems.FileUtil;
69
import org.openide.filesystems.FileUtil;
74
import org.openide.filesystems.MultiFileSystem;
70
import org.openide.filesystems.MultiFileSystem;
Lines 99-105 Link Here
99
    private final Project project;
95
    private final Project project;
100
    private final FileObject layerXML;
96
    private final FileObject layerXML;
101
    private FileSystem fs;
97
    private FileSystem fs;
102
    private SavableTreeEditorCookie cookie;
98
    private WritableXMLFileSystem wxmlfs;
103
    private boolean autosave;
99
    private boolean autosave;
104
100
105
    public LayerHandle(Project project, FileObject layerXML) {
101
    public LayerHandle(Project project, FileObject layerXML) {
Lines 120-130 Link Here
120
    public synchronized FileSystem layer(boolean create) {
116
    public synchronized FileSystem layer(boolean create) {
121
        if (fs == null) {
117
        if (fs == null) {
122
            FileObject xml = getLayerFile();
118
            FileObject xml = getLayerFile();
123
            if (xml == null) {
119
            try {
124
                if (!create) {
120
                if (xml == null) {
125
                    return new DualLayers(null);
121
                    if (!create) {
126
                }
122
                        return new DualLayers(null);
127
                try {
123
                    }
128
                    NbModuleProvider module = project.getLookup().lookup(NbModuleProvider.class);
124
                    NbModuleProvider module = project.getLookup().lookup(NbModuleProvider.class);
129
                    FileObject manifest = module.getManifestFile();
125
                    FileObject manifest = module.getManifestFile();
130
                    if (manifest != null) { // #121056
126
                    if (manifest != null) { // #121056
Lines 138-166 Link Here
138
                        }
134
                        }
139
                    }
135
                    }
140
                    xml = NbModuleProjectGenerator.createLayer(project.getProjectDirectory(), module.getResourceDirectoryPath(false) + '/' + newLayerPath());
136
                    xml = NbModuleProjectGenerator.createLayer(project.getProjectDirectory(), module.getResourceDirectoryPath(false) + '/' + newLayerPath());
141
                } catch (IOException e) {
142
                    Util.err.notify(ErrorManager.INFORMATIONAL, e);
143
                    return fs = FileUtil.createMemoryFileSystem();
144
                }
137
                }
138
                WritableXMLFileSystem _wxmlfs = new WritableXMLFileSystem(xml, LayerUtils.findResourceCP(project));
139
                fs = new DualLayers(_wxmlfs);
140
                wxmlfs = _wxmlfs;
141
                // XXX maybe cleaner & more reliable to listen to EditorCookie.Observable.PROP_MODIFIED
142
                wxmlfs.addFileChangeListener(new FileChangeListener() {
143
                    public @Override void fileFolderCreated(FileEvent fe) {
144
                        setAutosave(autosave); // causes it to save if autosave is on
145
                    }
146
                    public @Override void fileDataCreated(FileEvent fe) {
147
                        setAutosave(autosave);
148
                    }
149
                    public @Override void fileChanged(FileEvent fe) {
150
                        setAutosave(autosave);
151
                    }
152
                    public @Override void fileDeleted(FileEvent fe) {
153
                        setAutosave(autosave);
154
                    }
155
                    public @Override void fileRenamed(FileRenameEvent fe) {
156
                        setAutosave(autosave);
157
                    }
158
                    public @Override void fileAttributeChanged(FileAttributeEvent fe) {
159
                        setAutosave(autosave);
160
                    }
161
                });
162
            } catch (IOException e) {
163
                Util.err.notify(ErrorManager.INFORMATIONAL, e);
164
                fs = FileUtil.createMemoryFileSystem();
145
            }
165
            }
146
            try {
147
                fs = new DualLayers(new WritableXMLFileSystem(xml.getURL(), cookie = LayerUtils.cookieForFile(xml), LayerUtils.findResourceCP(project)));
148
            } catch (FileStateInvalidException e) {
149
                throw new AssertionError(e);
150
            }
151
            cookie.addPropertyChangeListener(new PropertyChangeListener() {
152
                public void propertyChange(PropertyChangeEvent evt) {
153
                    //System.err.println("changed in mem");
154
                    if (autosave && SavableTreeEditorCookie.PROP_DIRTY.equals(evt.getPropertyName())) {
155
                        //System.err.println("  will save...");
156
                        try {
157
                            save();
158
                        } catch (IOException e) {
159
                            Util.err.notify(ErrorManager.INFORMATIONAL, e);
160
                        }
161
                    }
162
                }
163
            });
164
        }
166
        }
165
        return fs;
167
        return fs;
166
    }
168
    }
Lines 215-224 Link Here
215
     * Note that nonempty layer entries you created will already be on disk.
217
     * Note that nonempty layer entries you created will already be on disk.
216
     */
218
     */
217
    public void save() throws IOException {
219
    public void save() throws IOException {
218
        if (cookie == null) {
220
        if (wxmlfs == null) {
219
            throw new IOException("Cannot save a nonexistent layer"); // NOI18N
221
            throw new IOException("Cannot save a nonexistent layer"); // NOI18N
220
        }
222
        }
221
        cookie.save();
223
        wxmlfs.save();
222
    }
224
    }
223
225
224
    /**
226
    /**
Lines 251-259 Link Here
251
     */
253
     */
252
    public void setAutosave(boolean autosave) {
254
    public void setAutosave(boolean autosave) {
253
        this.autosave = autosave;
255
        this.autosave = autosave;
254
        if (autosave && cookie != null) {
256
        if (autosave && wxmlfs != null) {
255
            try {
257
            try {
256
                cookie.save();
258
                save();
257
            } catch (IOException e) {
259
            } catch (IOException e) {
258
                Util.err.notify(ErrorManager.INFORMATIONAL, e);
260
                Util.err.notify(ErrorManager.INFORMATIONAL, e);
259
            }
261
            }
(-)a/apisupport.project/src/org/netbeans/modules/apisupport/project/layers/LayerUtils.java (-162 lines)
Lines 44-57 Link Here
44
44
45
package org.netbeans.modules.apisupport.project.layers;
45
package org.netbeans.modules.apisupport.project.layers;
46
46
47
import java.beans.PropertyChangeEvent;
48
import java.beans.PropertyChangeListener;
49
import java.beans.PropertyChangeSupport;
50
import java.beans.PropertyVetoException;
47
import java.beans.PropertyVetoException;
51
import java.io.File;
48
import java.io.File;
52
import java.io.FileFilter;
49
import java.io.FileFilter;
53
import java.io.IOException;
50
import java.io.IOException;
54
import java.io.OutputStream;
55
import java.net.MalformedURLException;
51
import java.net.MalformedURLException;
56
import java.net.URL;
52
import java.net.URL;
57
import java.util.ArrayList;
53
import java.util.ArrayList;
Lines 85-110 Link Here
85
import org.netbeans.modules.apisupport.project.universe.ModuleEntry;
81
import org.netbeans.modules.apisupport.project.universe.ModuleEntry;
86
import org.netbeans.modules.apisupport.project.universe.ModuleList;
82
import org.netbeans.modules.apisupport.project.universe.ModuleList;
87
import org.netbeans.modules.apisupport.project.universe.NbPlatform;
83
import org.netbeans.modules.apisupport.project.universe.NbPlatform;
88
import org.netbeans.modules.xml.tax.cookies.TreeEditorCookie;
89
import org.netbeans.modules.xml.tax.parser.XMLParsingSupport;
90
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
84
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
91
import org.netbeans.tax.TreeDocumentRoot;
92
import org.netbeans.tax.TreeException;
93
import org.netbeans.tax.TreeObject;
94
import org.netbeans.tax.io.TreeStreamResult;
95
import org.openide.ErrorManager;
85
import org.openide.ErrorManager;
96
import org.openide.filesystems.FileAttributeEvent;
97
import org.openide.filesystems.FileChangeListener;
98
import org.openide.filesystems.FileEvent;
99
import org.openide.filesystems.FileObject;
86
import org.openide.filesystems.FileObject;
100
import org.openide.filesystems.FileRenameEvent;
101
import org.openide.filesystems.FileStateInvalidException;
87
import org.openide.filesystems.FileStateInvalidException;
102
import org.openide.filesystems.FileSystem;
88
import org.openide.filesystems.FileSystem;
103
import org.openide.filesystems.FileSystem.AtomicAction;
104
import org.openide.filesystems.FileUtil;
89
import org.openide.filesystems.FileUtil;
105
import org.openide.filesystems.XMLFileSystem;
90
import org.openide.filesystems.XMLFileSystem;
106
import org.openide.util.Task;
107
import org.xml.sax.InputSource;
108
91
109
/**
92
/**
110
 * Misc support for dealing with layers.
93
 * Misc support for dealing with layers.
Lines 296-446 Link Here
296
    }
279
    }
297
    
280
    
298
    /**
281
    /**
299
     * Representation of in-memory TAX tree which can be saved upon request.
300
     */
301
    public interface SavableTreeEditorCookie extends TreeEditorCookie {
302
        
303
        /** property change fired when dirty flag changes */
304
        String PROP_DIRTY = "dirty"; // NOI18N
305
        
306
        /** true if there are in-memory mods */
307
        boolean isDirty();
308
        
309
        /** try to save any in-memory mods to disk */
310
        void save() throws IOException;
311
        
312
    }
313
    
314
    private static final class CookieImpl implements SavableTreeEditorCookie, FileChangeListener, AtomicAction {
315
        private TreeDocumentRoot root;
316
        private boolean dirty;
317
        private Exception problem;
318
        private final FileObject f;
319
        private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
320
        public CookieImpl(FileObject f) {
321
            //System.err.println("new CookieImpl for " + f);
322
            this.f = f;
323
            f.addFileChangeListener(FileUtil.weakFileChangeListener(this, f));
324
        }
325
        public TreeDocumentRoot getDocumentRoot() {
326
            return root;
327
        }
328
        public int getStatus() {
329
            if (problem != null) {
330
                return TreeEditorCookie.STATUS_ERROR;
331
            } else if (root != null) {
332
                return TreeEditorCookie.STATUS_OK;
333
            } else {
334
                return TreeEditorCookie.STATUS_NOT;
335
            }
336
        }
337
        public TreeDocumentRoot openDocumentRoot() throws IOException, TreeException {
338
            if (root == null && f.isValid()) {
339
                try {
340
                    //System.err.println("openDocumentRoot: really opening");
341
                    boolean oldDirty = dirty;
342
                    int oldStatus = getStatus();
343
                    root = new XMLParsingSupport().parse(new InputSource(f.getURL().toExternalForm()));
344
                    problem = null;
345
                    dirty = false;
346
                    pcs.firePropertyChange(PROP_DIRTY, oldDirty, false);
347
                    pcs.firePropertyChange(PROP_STATUS, oldStatus, TreeEditorCookie.STATUS_OK);
348
                    //pcs.firePropertyChange(PROP_DOCUMENT_ROOT, null, root);
349
                } catch (IOException e) {
350
                    problem = e;
351
                    throw e;
352
                } catch (TreeException e) {
353
                    problem = e;
354
                    throw e;
355
                }
356
                ((TreeObject) root).addPropertyChangeListener(new PropertyChangeListener() {
357
                    public void propertyChange(PropertyChangeEvent evt) {
358
                        //System.err.println("tree modified");
359
                        modified();
360
                    }
361
                });
362
            }
363
            return root;
364
        }
365
        public Task prepareDocumentRoot() {
366
            throw new UnsupportedOperationException();
367
        }
368
        public void addPropertyChangeListener(PropertyChangeListener listener) {
369
            pcs.addPropertyChangeListener(listener);
370
        }
371
        public void removePropertyChangeListener(PropertyChangeListener listener) {
372
            pcs.removePropertyChangeListener(listener);
373
        }
374
        private void modified() {
375
            //System.err.println("modified(): dirty=" + dirty + " in " + Thread.currentThread().getName() + " for " + this);
376
            if (!dirty) {
377
                dirty = true;
378
                pcs.firePropertyChange(PROP_DIRTY, false, true);
379
            }
380
        }
381
        public boolean isDirty() {
382
            return dirty;
383
        }
384
        public synchronized void save() throws IOException {
385
            //System.err.println("save(): dirty=" + dirty + " in " + Thread.currentThread().getName() + " for " + this);
386
            if (root == null || !dirty) {
387
                return;
388
            }
389
            //System.err.println("saving in " + Thread.currentThread().getName() + " for " + this);
390
            f.getFileSystem().runAtomicAction(this);
391
            //System.err.println("!saving in " + Thread.currentThread().getName() + " for " + this);
392
            dirty = false;
393
            pcs.firePropertyChange(PROP_DIRTY, true, false);
394
        }
395
        public void run() throws IOException {
396
            OutputStream os = f.getOutputStream();
397
            try {
398
                new TreeStreamResult(os).getWriter(root).writeDocument();
399
            } catch (TreeException e) {
400
                throw (IOException) new IOException(e.toString()).initCause(e);
401
            } finally {
402
                os.close();
403
            }
404
        }
405
        public void fileChanged(FileEvent fe) {
406
            changed(fe);
407
        }
408
        public void fileDeleted(FileEvent fe) {
409
            changed(fe);
410
        }
411
        public void fileRenamed(FileRenameEvent fe) {
412
            changed(fe);
413
        }
414
        public void fileAttributeChanged(FileAttributeEvent fe) {
415
            // ignore
416
        }
417
        public void fileFolderCreated(FileEvent fe) {
418
            assert false;
419
        }
420
        public void fileDataCreated(FileEvent fe) {
421
            assert false;
422
        }
423
        private void changed(FileEvent fe) {
424
            //System.err.println("changed on disk in " + Thread.currentThread().getName() + " for " + this);
425
            //Thread.dumpStack();
426
            synchronized (this) {
427
                if (fe.firedFrom(this)) {
428
                    //System.err.println("(my own change)");
429
                    return;
430
                }
431
                problem = null;
432
                dirty = false;
433
                root = null;
434
            }
435
            pcs.firePropertyChange(PROP_DOCUMENT_ROOT, null, null);
436
        }
437
    }
438
    
439
    public static SavableTreeEditorCookie cookieForFile(FileObject f) {
440
        return new CookieImpl(f);
441
    }
442
    
443
    /**
444
     * Get a filesystem that will look like what this project would "see".
282
     * Get a filesystem that will look like what this project would "see".
445
     * <p>There are four possibilities:</p>
283
     * <p>There are four possibilities:</p>
446
     * <ol>
284
     * <ol>
(-)a/apisupport.project/src/org/netbeans/modules/apisupport/project/layers/WritableXMLFileSystem.java (-690 / +249 lines)
Lines 65-88 Link Here
65
import java.util.Date;
65
import java.util.Date;
66
import java.util.Enumeration;
66
import java.util.Enumeration;
67
import java.util.HashSet;
67
import java.util.HashSet;
68
import java.util.Iterator;
69
import java.util.Set;
68
import java.util.Set;
70
import org.netbeans.api.java.classpath.ClassPath;
69
import org.netbeans.api.java.classpath.ClassPath;
71
import org.netbeans.modules.apisupport.project.Util;
70
import org.netbeans.editor.BaseDocument;
72
import org.netbeans.modules.xml.tax.cookies.TreeEditorCookie;
71
import org.netbeans.modules.xml.xam.ModelSource;
73
import org.netbeans.tax.InvalidArgumentException;
72
import org.netbeans.modules.xml.xdm.XDMModel;
74
import org.netbeans.tax.ReadOnlyException;
73
import org.openide.cookies.EditorCookie;
75
import org.netbeans.tax.TreeAttribute;
76
import org.netbeans.tax.TreeCDATASection;
77
import org.netbeans.tax.TreeChild;
78
import org.netbeans.tax.TreeDocumentRoot;
79
import org.netbeans.tax.TreeDocumentType;
80
import org.netbeans.tax.TreeElement;
81
import org.netbeans.tax.TreeException;
82
import org.netbeans.tax.TreeObjectList;
83
import org.netbeans.tax.TreeParentNode;
84
import org.netbeans.tax.TreeText;
85
import org.openide.ErrorManager;
86
import org.openide.filesystems.AbstractFileSystem;
74
import org.openide.filesystems.AbstractFileSystem;
87
import org.openide.filesystems.FileAttributeEvent;
75
import org.openide.filesystems.FileAttributeEvent;
88
import org.openide.filesystems.FileChangeListener;
76
import org.openide.filesystems.FileChangeListener;
Lines 91-109 Link Here
91
import org.openide.filesystems.FileRenameEvent;
79
import org.openide.filesystems.FileRenameEvent;
92
import org.openide.filesystems.FileUtil;
80
import org.openide.filesystems.FileUtil;
93
import org.openide.filesystems.URLMapper;
81
import org.openide.filesystems.URLMapper;
82
import org.openide.loaders.DataObject;
94
import org.openide.util.Enumerations;
83
import org.openide.util.Enumerations;
95
import org.openide.util.WeakListeners;
84
import org.openide.util.lookup.Lookups;
85
import org.openide.xml.XMLUtil;
86
import org.w3c.dom.CharacterData;
87
import org.w3c.dom.Element;
88
import org.w3c.dom.Node;
89
import org.w3c.dom.NodeList;
96
90
97
/**
91
/**
98
 * A filesystem which is based on a TAX document and implements
92
 * A filesystem which is based on an XML document and implements
99
 * the same syntax as XMLFileSystem, from which inspiration is taken.
93
 * the same syntax as XMLFileSystem, from which inspiration is taken.
100
 * Not implemented similarly to XMLFileSystem because this is writable
94
 * Not implemented similarly to XMLFileSystem because this is writable
101
 * and designed specifically to write human-readable XML and work nicely
95
 * and designed specifically to write human-readable XML and work nicely
102
 * as an authoring tool. The filesystem expects to get an XML document
96
 * as an authoring tool. The filesystem expects to get an XML document
103
 * according to DTD "-//NetBeans//DTD Filesystem 1.0//EN" (or 1.1 is OK).
97
 * according to DTD "-//NetBeans//DTD Filesystem 1.0//EN" (or 1.1 or 1.2 is OK).
104
 * When it is changed via FileSystems API, it will fire TAX
98
 * When it is changed via the FileSystems API, it will update the document.
105
 * events. Not intended to be efficient or terribly robust, since it
99
 * Not intended to be efficient or terribly robust, since it is development-time only.
106
 * is development-time only.
107
 * @author Jesse Glick
100
 * @author Jesse Glick
108
 */
101
 */
109
public final class WritableXMLFileSystem extends AbstractFileSystem
102
public final class WritableXMLFileSystem extends AbstractFileSystem
Lines 111-147 Link Here
111
        AbstractFileSystem.Change,
104
        AbstractFileSystem.Change,
112
        AbstractFileSystem.Info,
105
        AbstractFileSystem.Info,
113
        AbstractFileSystem.List,
106
        AbstractFileSystem.List,
114
        //AbstractFileSystem.Transfer,
115
        FileChangeListener,
107
        FileChangeListener,
116
        PropertyChangeListener {
108
        PropertyChangeListener {
117
    
109
    
118
    private final TreeEditorCookie cookie;
110
    private final EditorCookie editorCookie;
119
    private TreeDocumentRoot doc; // may be null if malformed
111
    private final XDMModel model;
120
    private URL location;
112
    private URL location;
121
    private String suffix; // for branding/localization like "_f4j_ce_ja"; never null, at worst ""
122
    private final FileChangeListener fileChangeListener;
113
    private final FileChangeListener fileChangeListener;
123
    private ClassPath classpath; // OK to be null
114
    private ClassPath classpath; // OK to be null
124
    
115
    
125
    public WritableXMLFileSystem(URL location, TreeEditorCookie cookie, ClassPath classpath) {
116
    @SuppressWarnings("LeakingThisInConstructor")
126
        this.attr = this;
117
    public WritableXMLFileSystem(FileObject xml, ClassPath classpath) throws IOException {
127
        this.change = this;
118
        editorCookie = DataObject.find(xml).getLookup().lookup(EditorCookie.class);
128
        this.info = this;
119
        if (editorCookie == null) {
129
        this.list = this;
120
            throw new IOException("Noneditable layer: " + xml);
130
        //this.transfer = this;
131
        this.cookie = cookie;
132
        suffix = "";
133
        try {
134
            doc = cookie.openDocumentRoot();
135
        } catch (TreeException e) {
136
            Util.err.notify(ErrorManager.INFORMATIONAL, e);
137
        } catch (IOException e) {
138
            Util.err.notify(ErrorManager.INFORMATIONAL, e);
139
        }
121
        }
122
        model = new XDMModel(new ModelSource(Lookups.fixed(xml, (BaseDocument) editorCookie.openDocument()), true));
123
        model.sync();
124
        model.addPropertyChangeListener(this);
125
        model.setPretty(true);
126
        model.setIndentation("    "); // NOI18N
127
        attr = this;
128
        change = this;
129
        info = this;
130
        list = this;
140
        fileChangeListener = FileUtil.weakFileChangeListener(this, null);
131
        fileChangeListener = FileUtil.weakFileChangeListener(this, null);
141
        cookie.addPropertyChangeListener(WeakListeners.propertyChange(this, cookie));
132
        setLocation(xml.getURL());
142
        setLocation(location);
143
        setClasspath(classpath);
133
        setClasspath(classpath);
144
    }
134
    }
135
136
    /**
137
     * Saves any outstanding modifications to disk.
138
     * (Modifications are always applied immediately to an editor document.)
139
     * @throws IOException if the document could not be saved
140
     */
141
    public void save() throws IOException {
142
        editorCookie.saveDocument();
143
    }
145
    
144
    
146
    private void writeObject(ObjectOutputStream out) throws IOException {
145
    private void writeObject(ObjectOutputStream out) throws IOException {
147
        throw new NotSerializableException("WritableXMLFileSystem is not persistent"); // NOI18N
146
        throw new NotSerializableException("WritableXMLFileSystem is not persistent"); // NOI18N
Lines 159-165 Link Here
159
        this.classpath = classpath;
158
        this.classpath = classpath;
160
    }
159
    }
161
    
160
    
162
    public String getDisplayName() {
161
    public @Override String getDisplayName() {
163
        FileObject fo = URLMapper.findFileObject(location);
162
        FileObject fo = URLMapper.findFileObject(location);
164
        if (fo != null) {
163
        if (fo != null) {
165
            return FileUtil.getFileDisplayName(fo);
164
            return FileUtil.getFileDisplayName(fo);
Lines 168-202 Link Here
168
        }
167
        }
169
    }
168
    }
170
    
169
    
171
    public boolean isReadOnly() {
170
    public @Override boolean isReadOnly() {
172
        return false;
171
        return false;
173
    }
172
    }
174
    
173
    
175
    private TreeElement getRootElement() {
174
    private Element getRootElement() {
176
        if (doc == null) {
175
        return model.getDocument().getDocumentElement();
177
            return null;
178
        }
179
        Iterator it;
180
        it = doc.getChildNodes().iterator();
181
        while (it.hasNext()) {
182
            Object next = it.next();
183
            if (next instanceof TreeElement) {
184
                return (TreeElement) next;
185
            }
186
        }
187
        return null;
188
    }
176
    }
189
    
177
    
190
    /** Given a resource name, find the matching DOM element.
178
    /** Given a resource name, find the matching DOM element.
191
     * @return a <folder> or <file> or <filesystem> element, or null if file does not exist
179
     * @return a <folder> or <file> or <filesystem> element, or null if file does not exist
192
     */
180
     */
193
    private TreeElement findElement(String name) {
181
    private Element findElement(String name) {
194
        return findElementIn(getRootElement(), name);
182
        return findElementIn(getRootElement(), name);
195
    }
183
    }
196
    /** helper method only */
184
    /** helper method only */
197
    private static TreeElement findElementIn(TreeElement el, String name) {
185
    private static Element findElementIn(Element el, String name) {
198
        if (el == null) return null;
186
        if (el == null) {
199
        if (name.equals("")) { // NOI18N
187
            return null;
188
        }
189
        if (name.isEmpty()) {
200
            return el;
190
            return el;
201
        } else {
191
        } else {
202
            int idx = name.indexOf('/');
192
            int idx = name.indexOf('/');
Lines 208-221 Link Here
208
                nextName = name.substring(0, idx);
198
                nextName = name.substring(0, idx);
209
                remainder = name.substring(idx + 1);
199
                remainder = name.substring(idx + 1);
210
            }
200
            }
211
            TreeElement subel = null;
201
            Element subel = null;
212
            Iterator it = el.getChildNodes(TreeElement.class).iterator();
202
            for (Element e : XMLUtil.findSubElements(el)) {
213
            while (it.hasNext()) {
203
                if (e.getTagName().equals("file") || // NOI18N
214
                TreeElement e = (TreeElement) it.next();
204
                        e.getTagName().equals("folder")) { // NOI18N
215
                if (e.getLocalName().equals("file") || // NOI18N
205
                    String nameAttr = e.getAttribute("name"); // NOI18N
216
                        e.getLocalName().equals("folder")) { // NOI18N
206
                    if (nameAttr.equals(nextName)) {
217
                    TreeAttribute nameAttr = e.getAttribute("name"); // NOI18N
218
                    if (nameAttr != null && nameAttr.getValue().equals(nextName)) {
219
                        subel = e;
207
                        subel = e;
220
                        break;
208
                        break;
221
                    }
209
                    }
Lines 225-277 Link Here
225
        }
213
        }
226
    }
214
    }
227
    
215
    
228
    public boolean folder(String name) {
216
    public @Override boolean folder(String name) {
229
        TreeElement el = findElement(name);
217
        Element el = findElement(name);
230
        if (el == null) {
218
        if (el == null) {
231
            //System.err.println("folder <" + name + ">: false, no such element");
219
            //System.err.println("folder <" + name + ">: false, no such element");
232
            return false;
220
            return false;
233
        }
221
        }
234
        boolean res = el.getLocalName().equals("folder"); // NOI18N
222
        boolean res = el.getTagName().equals("folder"); // NOI18N
235
        //System.err.println("folder <" + name + ">: " + res);
223
        //System.err.println("folder <" + name + ">: " + res);
236
        return res;
224
        return res;
237
    }
225
    }
238
    
226
    
239
    /*
227
    public @Override String[] children(String f) {
240
    private static final Set warnedAboutDupeKids = new HashSet(1); // Set<String>
228
        Element el = findElement(f);
241
     */
242
    public String[] children(String f) {
243
        TreeElement el = findElement(f);
244
        if (el == null) {
229
        if (el == null) {
245
            //System.err.println("children <" + f + ">: none, no such element");
230
            //System.err.println("children <" + f + ">: none, no such element");
246
            return new String[] {};
231
            return new String[] {};
247
        }
232
        }
248
        ArrayList<String> kids = new ArrayList<String>();
233
        ArrayList<String> kids = new ArrayList<String>();
249
        Set<String> allNames = new HashSet<String>();
234
        Set<String> allNames = new HashSet<String>();
250
        Iterator it = el.getChildNodes(TreeElement.class).iterator();
235
        for (Element sub : XMLUtil.findSubElements(el)) {
251
        while (it.hasNext()) {
236
            if (sub.getTagName().equals("file") || // NOI18N
252
            TreeElement sub = (TreeElement) it.next();
237
                    sub.getTagName().equals("folder")) { // NOI18N
253
            if (sub.getLocalName().equals("file") || // NOI18N
238
                String name = sub.getAttribute("name"); // NOI18N
254
                    sub.getLocalName().equals("folder")) { // NOI18N
239
                if (name.isEmpty()) {
255
                TreeAttribute childName = sub.getAttribute("name"); // NOI18N
256
                if (childName == null) {
257
                    continue;
240
                    continue;
258
                }
241
                }
259
                String name = childName.getValue(); // NOI18N
260
                if (allNames.add(name)) {
242
                if (allNames.add(name)) {
261
                    kids.add(name);
243
                    kids.add(name);
262
                        /*
263
                } else {
264
                    if (warnedAboutDupeKids.add(location + ":" + f + "/" + name)) { // NOI18N
265
                        // #18699: will deadlock if you try to change anything.
266
                        if (f.equals("")) { // NOI18N
267
                            LayerDataNode.getErr().println("WARNING: in " + xmlfile + " the root folder contains the child " + name + " more than once.");
268
                        } else {
269
                            LayerDataNode.getErr().println("WARNING: in " + xmlfile + " the folder " + f + " contains the child " + name + " more than once.");
270
                        }
271
                        //LayerDataNode.getErr().println("The Open APIs Support module will not work properly with such a layer.");
272
                        //LayerDataNode.getErr().println("Please edit the XML text and merge together all children of a <folder> with the same name.");
273
                    }
274
                         */
275
                }
244
                }
276
            }
245
            }
277
        }
246
        }
Lines 281-301 Link Here
281
    
250
    
282
    /** retrieve byte contents of a named resource */
251
    /** retrieve byte contents of a named resource */
283
    private byte[] getContentsOf(final String name) throws FileNotFoundException {
252
    private byte[] getContentsOf(final String name) throws FileNotFoundException {
284
        TreeElement el = findElement(name);
253
        Element el = findElement(name);
285
        if (el == null) throw new FileNotFoundException(name);
254
        if (el == null) {
286
        TreeAttribute urlAttr = el.getAttribute("url"); // NOI18N
255
            throw new FileNotFoundException(name);
287
        if (urlAttr != null) {
256
        }
257
        String sURL = el.getAttribute("url"); // NOI18N
258
        if (!sURL.isEmpty()) {
288
            try {
259
            try {
289
                String sURL = urlAttr.getValue();
290
                URI uri = new URI(null, sURL, null);
260
                URI uri = new URI(null, sURL, null);
291
                boolean nbmRelative = sURL.startsWith("nbres:") || sURL.startsWith("nbresloc:");
261
                boolean nbmRelative = sURL.startsWith("nbres:") || sURL.startsWith("nbresloc:");
292
                URL url = nbmRelative ? uri.toURL() : new URL(location, uri.getRawPath());
262
                URL url = nbmRelative ? uri.toURL() : new URL(location, uri.getRawPath());
293
                URL[] u = LayerUtils.currentify(url, suffix, classpath);
263
                URL[] u = LayerUtils.currentify(url, "", classpath);
294
                URLConnection conn = u[0].openConnection();
264
                URLConnection conn = u[0].openConnection();
295
                conn.connect();
265
                conn.connect();
296
                InputStream is = conn.getInputStream();
266
                InputStream is = conn.getInputStream();
297
                byte[] buf = new byte[conn.getContentLength()];
267
                byte[] buf = new byte[conn.getContentLength()];
298
                if (is.read(buf) != buf.length) throw new IOException("wrong content length"); // NOI18N
268
                if (is.read(buf) != buf.length) {
269
                    throw new IOException("wrong content length");
270
                }
299
                // Also listen to changes in it.
271
                // Also listen to changes in it.
300
                FileObject fo = URLMapper.findFileObject(u[0]);
272
                FileObject fo = URLMapper.findFileObject(u[0]);
301
                if (fo != null) {
273
                if (fo != null) {
Lines 309-322 Link Here
309
                throw new FileNotFoundException(use.getMessage());
281
                throw new FileNotFoundException(use.getMessage());
310
            }
282
            }
311
        } else {
283
        } else {
312
            StringBuffer buf = new StringBuffer();
284
            StringBuilder buf = new StringBuilder();
313
            Iterator it = el.getChildNodes().iterator();
285
            NodeList nl = el.getChildNodes();
314
            while (it.hasNext()) {
286
            for (int i = 0; i < nl.getLength(); i++) {
315
                Object o = it.next();
287
                Node n = nl.item(i);
316
                if (o instanceof TreeCDATASection) {
288
                if (n.getNodeType() == Node.CDATA_SECTION_NODE || n.getNodeType() == Node.TEXT_NODE) {
317
                    buf.append(((TreeCDATASection) o).getData());
289
                    buf.append(((CharacterData) n).getData());
318
                } else if (o instanceof TreeText) {
319
                    buf.append(((TreeText) o).getData().trim());
320
                }
290
                }
321
            }
291
            }
322
            try {
292
            try {
Lines 331-348 Link Here
331
    // [PENDING] should I/O from/to external text files be done via EditorCookie?
301
    // [PENDING] should I/O from/to external text files be done via EditorCookie?
332
    // Not clear if this is safe (call from FS -> DS) even tho in separate FSs...
302
    // Not clear if this is safe (call from FS -> DS) even tho in separate FSs...
333
    
303
    
334
    public InputStream inputStream(String name) throws FileNotFoundException {
304
    public @Override InputStream inputStream(String name) throws FileNotFoundException {
335
        return new ByteArrayInputStream(getContentsOf(name));
305
        return new ByteArrayInputStream(getContentsOf(name));
336
    }
306
    }
337
    
307
    
338
    public OutputStream outputStream(final String name) throws IOException {
308
    public @Override OutputStream outputStream(final String name) throws IOException {
339
        final TreeElement el = findElement(name);
309
        final Element el = findElement(name);
340
        if (el == null) {
310
        if (el == null) {
341
            throw new FileNotFoundException(name);
311
            throw new FileNotFoundException(name);
342
        }
312
        }
343
        TreeAttribute urlAttr = el.getAttribute("url"); // NOI18N
313
        String u = el.getAttribute("url"); // NOI18N
344
        if (urlAttr != null) {
314
        if (!u.isEmpty()) {
345
            String u = urlAttr.getValue();
346
            if (URI.create(u).isAbsolute()) {
315
            if (URI.create(u).isAbsolute()) {
347
                // What to do? Can't overwrite it, obviously.
316
                // What to do? Can't overwrite it, obviously.
348
                throw new IOException(name);
317
                throw new IOException(name);
Lines 356-362 Link Here
356
        }
325
        }
357
        // We will change the layer file.
326
        // We will change the layer file.
358
        return new ByteArrayOutputStream() {
327
        return new ByteArrayOutputStream() {
359
            public void close() throws IOException {
328
            public @Override void close() throws IOException {
360
                super.close();
329
                super.close();
361
                final byte[] contents = toByteArray();
330
                final byte[] contents = toByteArray();
362
                /* If desired to kill any existing inline content:
331
                /* If desired to kill any existing inline content:
Lines 380-386 Link Here
380
                final String externalName = LayerUtils.findGeneratedName(parent, name);
349
                final String externalName = LayerUtils.findGeneratedName(parent, name);
381
                assert externalName.indexOf('/') == -1 : externalName;
350
                assert externalName.indexOf('/') == -1 : externalName;
382
                parent.getFileSystem().runAtomicAction(new AtomicAction() {
351
                parent.getFileSystem().runAtomicAction(new AtomicAction() {
383
                    public void run() throws IOException {
352
                    public @Override void run() throws IOException {
384
                        FileObject externalFile = parent.createData(externalName);
353
                        FileObject externalFile = parent.createData(externalName);
385
                        OutputStream os = externalFile.getOutputStream();
354
                        OutputStream os = externalFile.getOutputStream();
386
                        try {
355
                        try {
Lines 391-403 Link Here
391
                        externalFile.addFileChangeListener(fileChangeListener);
360
                        externalFile.addFileChangeListener(fileChangeListener);
392
                    }
361
                    }
393
                });
362
                });
394
                try {
363
                model.setAttribute((org.netbeans.modules.xml.xdm.nodes.Element) el, "url", externalName); // NOI18N
395
                    el.addAttribute("url", externalName); // NOI18N
396
                } catch (ReadOnlyException e) {
397
                    throw (IOException) new IOException(e.toString()).initCause(e);
398
                } catch (InvalidArgumentException e) {
399
                    assert false : e;
400
                }
401
            }
364
            }
402
        };
365
        };
403
    }
366
    }
Lines 422-473 Link Here
422
            parentName = name.substring(0, idx);
385
            parentName = name.substring(0, idx);
423
            baseName = name.substring(idx + 1);
386
            baseName = name.substring(idx + 1);
424
        }
387
        }
425
        TreeElement el = findElement(parentName);
388
        Element el = findElement(parentName);
426
        if (el == null) {
389
        if (el == null) {
427
            throw new FileNotFoundException(parentName);
390
            throw new FileNotFoundException(parentName);
428
        }
391
        }
429
        try {
392
        Element nue = el.getOwnerDocument().createElement(folder ? "folder" : "file"); // NOI18N
430
            TreeElement nue = new TreeElement(folder ? "folder" : "file", true); // NOI18N
393
        nue.setAttribute("name", baseName); // NOI18N
431
            nue.addAttribute("name", baseName); // NOI18N
394
        model.insertBefore((org.netbeans.modules.xml.xdm.nodes.Node) el,
432
            appendWithIndent(el, nue);
395
                (org.netbeans.modules.xml.xdm.nodes.Node) nue,
433
        } catch (InvalidArgumentException e) {
396
                (org.netbeans.modules.xml.xdm.nodes.Node) findInsertionPosition(el, nue));
434
            assert false : e;
435
        } catch (ReadOnlyException e) {
436
            throw (IOException) new IOException(e.toString()).initCause(e);
437
        }
438
    }
397
    }
439
    
398
    
440
    public void createFolder(String name) throws IOException {
399
    public @Override void createFolder(String name) throws IOException {
441
        createFileOrFolder(name, true);
400
        createFileOrFolder(name, true);
442
    }
401
    }
443
    
402
    
444
    public void createData(String name) throws IOException {
403
    public @Override void createData(String name) throws IOException {
445
        createFileOrFolder(name, false);
404
        createFileOrFolder(name, false);
446
    }
405
    }
447
    
406
    
448
    public void delete(String name) throws IOException {
407
    public @Override void delete(String name) throws IOException {
449
        TreeElement el = findElement(name);
408
        Element el = findElement(name);
450
        if (el == null) {
409
        if (el == null) {
451
            throw new FileNotFoundException(name);
410
            throw new FileNotFoundException(name);
452
        }
411
        }
453
        TreeAttribute externalName = el.getAttribute("url"); // NOI18N
412
        String externalName = el.getAttribute("url"); // NOI18N
454
        if (externalName != null && !URI.create(externalName.getValue()).isAbsolute()) {
413
        if (!externalName.isEmpty() && !URI.create(externalName).isAbsolute()) {
455
            // Delete the external file if it can be found.
414
            // Delete the external file if it can be found.
456
            FileObject externalFile = URLMapper.findFileObject(new URL(location, externalName.getValue()));
415
            FileObject externalFile = URLMapper.findFileObject(new URL(location, externalName));
457
            if (externalFile != null) {
416
            if (externalFile != null) {
458
                externalFile.removeFileChangeListener(fileChangeListener);
417
                externalFile.removeFileChangeListener(fileChangeListener);
459
                externalFile.delete();
418
                externalFile.delete();
460
            }
419
            }
461
        }
420
        }
462
        try {
421
        model.removeChildNodes((org.netbeans.modules.xml.xdm.nodes.Node) el.getParentNode(), Collections.singleton((org.netbeans.modules.xml.xdm.nodes.Node) el));
463
            deleteWithIndent((TreeChild) el);
464
        } catch (ReadOnlyException e) {
465
            throw (IOException) new IOException(e.toString()).initCause(e);
466
        }
467
    }
422
    }
468
    
423
    
469
    public void rename(String oldName, String newName) throws IOException {
424
    public @Override void rename(String oldName, String newName) throws IOException {
470
        TreeElement el = findElement(oldName);
425
        Element el = findElement(oldName);
471
        if (el == null) {
426
        if (el == null) {
472
            throw new FileNotFoundException(oldName);
427
            throw new FileNotFoundException(oldName);
473
        }
428
        }
Lines 478-585 Link Here
478
        String newBaseName = newName.substring(idx);
433
        String newBaseName = newName.substring(idx);
479
        assert newBaseName.indexOf('/') == -1;
434
        assert newBaseName.indexOf('/') == -1;
480
        assert newBaseName.length() > 0;
435
        assert newBaseName.length() > 0;
481
        try {
436
        model.setAttribute((org.netbeans.modules.xml.xdm.nodes.Element) el, "name", newBaseName); // NOI18N
482
            el.getAttribute("name").setValue(newBaseName); // NOI18N
483
        } catch (ReadOnlyException e) {
484
            throw (IOException) new IOException(e.toString()).initCause(e);
485
        } catch (InvalidArgumentException e) {
486
            assert false : e;
487
        }
488
    }
437
    }
489
    
438
    
490
    /*
439
    public @Override Enumeration<String> attributes(String name) {
491
    public boolean copy(String name, Transfer target, String targetName) throws IOException {
492
        if (! (target instanceof WritableXMLFileSystem)) return false;
493
        WritableXMLFileSystem otherfs = (WritableXMLFileSystem) target;
494
        Element el = findElement(name);
440
        Element el = findElement(name);
495
        if (el == null) throw new FileNotFoundException(name);
496
        Element el2;
497
        if (otherfs == this) {
498
            el2 = (Element) el.cloneNode(true);
499
        } else {
500
            el2 = (Element) otherfs.doc.importNode(el, true);
501
        }
502
        String path, base;
503
        int idx = targetName.lastIndexOf('/');
504
        if (idx == -1) {
505
            path = ""; // NOI18N
506
            base = targetName;
507
        } else {
508
            path = targetName.substring(0, idx);
509
            base = targetName.substring(idx + 1);
510
        }
511
        Element parent = otherfs.findElement(path);
512
        if (parent == null) throw new FileNotFoundException(path);
513
        el2.setAttribute("name", base); // NOI18N
514
        Element old = otherfs.findElement(targetName);
515
        if (old != null) {
516
            parent.replaceChild(el2, old);
517
        } else {
518
            appendWithIndent(parent, el2);
519
        }
520
        return true;
521
    }
522
    
523
    public boolean move(String name, Transfer target, String targetName) throws IOException {
524
        if (! (target instanceof WritableXMLFileSystem)) return false;
525
        WritableXMLFileSystem otherfs = (WritableXMLFileSystem) target;
526
        Element el = findElement(name);
527
        if (el == null) throw new FileNotFoundException(name);
528
        Element el2;
529
        if (otherfs == this) {
530
            // Just move it, no need to clone.
531
            el2 = el;
532
        } else {
533
            el2 = (Element) otherfs.doc.importNode(el, true);
534
        }
535
        String path, base;
536
        int idx = targetName.lastIndexOf('/');
537
        if (idx == -1) {
538
            path = ""; // NOI18N
539
            base = targetName;
540
        } else {
541
            path = targetName.substring(0, idx);
542
            base = targetName.substring(idx + 1);
543
        }
544
        Element parent = otherfs.findElement(path);
545
        if (parent == null) throw new FileNotFoundException(path);
546
        el2.setAttribute("name", base); // NOI18N
547
        Element old = otherfs.findElement(targetName);
548
        if (el != el2) {
549
            // Cross-document import, so need to remove old one.
550
            el.getParentNode().removeChild(el);
551
        }
552
        if (old != null) {
553
            parent.replaceChild(el2, old);
554
        } else {
555
            appendWithIndent(parent, el2);
556
        }
557
        return true;
558
    }
559
     */
560
    
561
    public Enumeration<String> attributes(String name) {
562
        TreeElement el = findElement(name);
563
        if (el == null) {
441
        if (el == null) {
564
            return Enumerations.empty();
442
            return Enumerations.empty();
565
        }
443
        }
566
        java.util.List<String> l = new ArrayList<String>(10);
444
        java.util.List<String> l = new ArrayList<String>(10);
567
        Iterator it = el.getChildNodes(TreeElement.class).iterator();
445
        for (Element sub : XMLUtil.findSubElements(el)) {
568
        while (it.hasNext()) {
446
            if (sub.getTagName().equals("attr")) { // NOI18N
569
            TreeElement sub = (TreeElement) it.next();
447
                String nameAttr = sub.getAttribute("name"); // NOI18N
570
            if (sub.getLocalName().equals("attr")) { // NOI18N
448
                if (nameAttr.isEmpty()) {
571
                TreeAttribute nameAttr = sub.getAttribute("name"); // NOI18N
572
                if (nameAttr == null) {
573
                    // Malformed.
449
                    // Malformed.
574
                    continue;
450
                    continue;
575
                }
451
                }
576
                l.add(nameAttr.getValue());
452
                l.add(nameAttr);
577
            }
453
            }
578
        }
454
        }
579
        return Collections.enumeration(l);
455
        return Collections.enumeration(l);
580
    }
456
    }
581
    
457
    
582
    public Object readAttribute(String name, String attrName) {
458
    public @Override Object readAttribute(String name, String attrName) {
583
        if (attrName.equals("WritableXMLFileSystem.cp")) { // NOI18N
459
        if (attrName.equals("WritableXMLFileSystem.cp")) { // NOI18N
584
            // XXX currently unused
460
            // XXX currently unused
585
            return classpath;
461
            return classpath;
Lines 589-597 Link Here
589
            return new URL[] {location};
465
            return new URL[] {location};
590
        }
466
        }
591
        if (attrName.equals("DataFolder.Index.reorderable")) { // NOI18N
467
        if (attrName.equals("DataFolder.Index.reorderable")) { // NOI18N
592
            return Boolean.TRUE;
468
            return true; // XXX is this still needed?
593
        }
469
        }
594
        TreeElement el = findElement(name);
470
        Element el = findElement(name);
595
        if (el == null) {
471
        if (el == null) {
596
            return null;
472
            return null;
597
        }
473
        }
Lines 600-628 Link Here
600
            attrName = attrName.substring("literal:".length()); // NOI18N
476
            attrName = attrName.substring("literal:".length()); // NOI18N
601
            literal = true;
477
            literal = true;
602
        }
478
        }
603
        Iterator it = el.getChildNodes(TreeElement.class).iterator();
479
        for (Element sub : XMLUtil.findSubElements(el)) {
604
        while (it.hasNext()) {
480
            if (!sub.getTagName().equals("attr")) { // NOI18N
605
            TreeElement sub = (TreeElement) it.next();
606
            if (!sub.getLocalName().equals("attr")) { // NOI18N
607
                continue;
481
                continue;
608
            }
482
            }
609
            TreeAttribute nameAttr = sub.getAttribute("name"); // NOI18N
483
            if (!attrName.equals(sub.getAttribute("name"))) { // NOI18N
610
            if (nameAttr == null) {
611
                // Malformed.
612
                continue;
613
            }
614
            if (!attrName.equals(nameAttr.getValue())) {
615
                continue;
484
                continue;
616
            }
485
            }
617
            try {
486
            try {
618
                if ((nameAttr = sub.getAttribute("stringvalue")) != null) { // NOI18N
487
                String val = sub.getAttribute("stringvalue"); // NOI18N
488
                if (!val.isEmpty()) {
619
                    // Stolen from XMLMapAttr, with tweaks:
489
                    // Stolen from XMLMapAttr, with tweaks:
620
                    String inStr = nameAttr.getValue();
490
                    StringBuilder outStr = new StringBuilder(val.length());
621
                    StringBuffer outStr = new StringBuffer(inStr.length());
491
                    for (int j = 0; j < val.length(); j++) {
622
                    for (int j = 0; j < inStr.length(); j++) {
492
                        char ch = val.charAt(j);
623
                        char ch = inStr.charAt(j);
493
                        if (ch == '\\' && val.charAt(j + 1) == 'u' && j + 5 < val.length()) {
624
                        if (ch == '\\' && inStr.charAt(j + 1) == 'u' && j + 5 < inStr.length()) {
494
                            String hex = val.substring(j + 2, j + 6);
625
                            String hex = inStr.substring(j + 2, j + 6);
626
                            try {
495
                            try {
627
                                outStr.append((char) Integer.parseInt(hex, 16));
496
                                outStr.append((char) Integer.parseInt(hex, 16));
628
                                j += 5;
497
                                j += 5;
Lines 635-670 Link Here
635
                        }
504
                        }
636
                    }
505
                    }
637
                    return outStr.toString();
506
                    return outStr.toString();
638
                } else if ((nameAttr = sub.getAttribute("boolvalue")) != null) { // NOI18N
507
                } else if (!(val = sub.getAttribute("boolvalue")).isEmpty()) { // NOI18N
639
                    return Boolean.valueOf(nameAttr.getValue());
508
                    return Boolean.valueOf(val);
640
                } else if ((nameAttr = sub.getAttribute("urlvalue")) != null) { // NOI18N
509
                } else if (!(val = sub.getAttribute("urlvalue")).isEmpty()) { // NOI18N
641
                    return new URL(nameAttr.getValue());
510
                    return new URL(val);
642
                } else if ((nameAttr = sub.getAttribute("charvalue")) != null) { // NOI18N
511
                } else if (!(val = sub.getAttribute("charvalue")).isEmpty()) { // NOI18N
643
                    return new Character(nameAttr.getValue().charAt(0));
512
                    return Character.valueOf(val.charAt(0));
644
                } else if ((nameAttr = sub.getAttribute("bytevalue")) != null) { // NOI18N
513
                } else if (!(val = sub.getAttribute("bytevalue")).isEmpty()) { // NOI18N
645
                    return Byte.valueOf(nameAttr.getValue());
514
                    return Byte.valueOf(val);
646
                } else if ((nameAttr = sub.getAttribute("shortvalue")) != null) { // NOI18N
515
                } else if (!(val = sub.getAttribute("shortvalue")).isEmpty()) { // NOI18N
647
                    return Short.valueOf(nameAttr.getValue());
516
                    return Short.valueOf(val);
648
                } else if ((nameAttr = sub.getAttribute("intvalue")) != null) { // NOI18N
517
                } else if (!(val = sub.getAttribute("intvalue")).isEmpty()) { // NOI18N
649
                    return Integer.valueOf(nameAttr.getValue());
518
                    return Integer.valueOf(val);
650
                } else if ((nameAttr = sub.getAttribute("longvalue")) != null) { // NOI18N
519
                } else if (!(val = sub.getAttribute("longvalue")).isEmpty()) { // NOI18N
651
                    return Long.valueOf(nameAttr.getValue());
520
                    return Long.valueOf(val);
652
                } else if ((nameAttr = sub.getAttribute("floatvalue")) != null) { // NOI18N
521
                } else if (!(val = sub.getAttribute("floatvalue")).isEmpty()) { // NOI18N
653
                    return Float.valueOf(nameAttr.getValue());
522
                    return Float.valueOf(val);
654
                } else if ((nameAttr = sub.getAttribute("doublevalue")) != null) { // NOI18N
523
                } else if (!(val = sub.getAttribute("doublevalue")).isEmpty()) { // NOI18N
655
                    return Double.valueOf(nameAttr.getValue());
524
                    return Double.valueOf(val);
656
                } else if ((nameAttr = sub.getAttribute("newvalue")) != null) { // NOI18N
525
                } else if (!(val = sub.getAttribute("newvalue")).isEmpty()) { // NOI18N
657
                    String clazz = nameAttr.getValue();
526
                    String clazz = val;
658
                    if (literal) {
527
                    if (literal) {
659
                        return "new:" + clazz; // NOI18N
528
                        return "new:" + clazz; // NOI18N
660
                    } // else XXX
529
                    } // else XXX
661
                } else if ((nameAttr = sub.getAttribute("methodvalue")) != null) { // NOI18N
530
                } else if (!(val = sub.getAttribute("methodvalue")).isEmpty()) { // NOI18N
662
                    String clazz = nameAttr.getValue();
531
                    String clazz = val;
663
                    if (literal) {
532
                    if (literal) {
664
                        return "method:" + clazz; // NOI18N
533
                        return "method:" + clazz; // NOI18N
665
                    } // else XXX
534
                    } // else XXX
666
                } else if ((nameAttr = sub.getAttribute("bundlevalue")) != null) { // NOI18N
535
                } else if (!(val = sub.getAttribute("bundlevalue")).isEmpty()) { // NOI18N
667
                    String bundle = nameAttr.getValue();
536
                    String bundle = val;
668
                    if (literal) {
537
                    if (literal) {
669
                        return "bundle:" + bundle; // NOI18N
538
                        return "bundle:" + bundle; // NOI18N
670
                    } else {
539
                    } else {
Lines 680-701 Link Here
680
        }
549
        }
681
        return null;
550
        return null;
682
        /*
551
        /*
683
                        if ((v = sub.getAttributeNode("bytevalue")) != null) { // NOI18N
684
                            return new Byte(v.getValue());
685
                        } else if ((v = sub.getAttributeNode("shortvalue")) != null) { // NOI18N
686
                            return new Short(v.getValue());
687
                        } else if ((v = sub.getAttributeNode("intvalue")) != null) { // NOI18N
688
                            return new Integer(v.getValue());
689
                        } else if ((v = sub.getAttributeNode("longvalue")) != null) { // NOI18N
690
                            return new Long(v.getValue());
691
                        } else if ((v = sub.getAttributeNode("floatvalue")) != null) { // NOI18N
692
                            return new Float(v.getValue());
693
                        } else if ((v = sub.getAttributeNode("doublevalue")) != null) { // NOI18N
694
                            // When was the last time you set a file attribute to a double?!
695
                            // Useless list of primitives...
696
                            return new Double(v.getValue());
697
                        } else if ((v = sub.getAttributeNode("charvalue")) != null) { // NOI18N
698
                            return new Character(v.getValue().charAt(0));
699
                        } else if ((v = sub.getAttributeNode("methodvalue")) != null) { // NOI18N
552
                        } else if ((v = sub.getAttributeNode("methodvalue")) != null) { // NOI18N
700
                            String value = v.getValue();
553
                            String value = v.getValue();
701
                            Object[] params = new Object[] { findResource(name), attrName };
554
                            Object[] params = new Object[] { findResource(name), attrName };
Lines 812-924 Link Here
812
         */
665
         */
813
    }
666
    }
814
    
667
    
815
    public void writeAttribute(String name, String attrName, Object v) throws IOException {
668
    public @Override void writeAttribute(String name, String attrName, Object v) throws IOException {
816
        //System.err.println("wA: " + name + " " + attrName + " " + v);
669
        //System.err.println("wA: " + name + " " + attrName + " " + v);
817
        if (v != null && v.getClass().getName().equals("org.openide.filesystems.MultiFileObject$VoidValue")) { // NOI18N
670
        if (v != null && v.getClass().getName().equals("org.openide.filesystems.MultiFileObject$VoidValue")) { // NOI18N
818
            // XXX is this legitimate? Definitely not pretty. But needed for testOpenideFolderOrder to pass.
671
            // XXX is this legitimate? Definitely not pretty. But needed for testOpenideFolderOrder to pass.
819
            v = null;
672
            v = null;
820
        }
673
        }
821
        TreeElement el = findElement(name);
674
        Element el = findElement(name);
822
        if (el == null) {
675
        if (el == null) {
823
            throw new FileNotFoundException(name);
676
            throw new FileNotFoundException(name);
824
        }
677
        }
825
        // Find any existing <attr>.
678
        // Find any existing <attr>.
826
        TreeChild existingAttr = null;
679
        Element existingAttr = null;
827
        Iterator it = el.getChildNodes(TreeElement.class).iterator();
680
        for (Element sub : XMLUtil.findSubElements(el)) {
828
        while (it.hasNext()) {
681
            if (sub.getTagName().equals("attr") && sub.getAttribute("name").equals(attrName)) { // NOI18N
829
            TreeElement sub = (TreeElement) it.next();
682
                existingAttr = sub;
830
            if (sub.getLocalName().equals("attr")) { // NOI18N
683
                break;
831
                TreeAttribute attr = sub.getAttribute("name"); // NOI18N
832
                if (attr == null) {
833
                    // Malformed.
834
                    continue;
835
                }
836
                if (attr.getValue().equals(attrName)) {
837
                    existingAttr = sub;
838
                    break;
839
                }
840
            }
684
            }
841
        }
685
        }
842
        TreeElement attr;
686
        Element attrE = el.getOwnerDocument().createElement("attr"); // NOI18N
843
        try {
687
        attrE.setAttribute("name", attrName); // NOI18N
844
            attr = new TreeElement("attr", true); // NOI18N
688
        if (v instanceof String) {
845
            attr.addAttribute("name", attrName); // NOI18N
689
            String inStr = (String) v;
846
            if (v instanceof String) {
690
            final String newValueMagic = "newvalue:"; // NOI18N
847
                String inStr = (String) v;
691
            final String methodValueMagic = "methodvalue:"; // NOI18N
848
                final String newValueMagic = "newvalue:"; // NOI18N
692
            final String bundleValueMagic = "bundlevalue:"; // NOI18N
849
                final String methodValueMagic = "methodvalue:"; // NOI18N
693
            if (inStr.startsWith(newValueMagic)) {
850
                final String bundleValueMagic = "bundlevalue:"; // NOI18N
694
                // Impossible to set this (reliably) as a real value, so use this magic technique instead:
851
                if (inStr.startsWith(newValueMagic)) {
695
                attrE.setAttribute("newvalue", inStr.substring(newValueMagic.length())); // NOI18N
852
                    // Impossible to set this (reliably) as a real value, so use this magic technique instead:
696
            } else if (inStr.startsWith(methodValueMagic)) {
853
                    attr.addAttribute("newvalue", inStr.substring(newValueMagic.length())); // NOI18N
697
                // Same here:
854
                } else if (inStr.startsWith(methodValueMagic)) {
698
                attrE.setAttribute("methodvalue", inStr.substring(methodValueMagic.length())); // NOI18N
855
                    // Same here:
699
            } else if (inStr.startsWith(bundleValueMagic)) {
856
                    attr.addAttribute("methodvalue", inStr.substring(methodValueMagic.length())); // NOI18N
700
                // Same here:
857
                } else if (inStr.startsWith(bundleValueMagic)) {
701
                attrE.setAttribute("bundlevalue", inStr.substring(bundleValueMagic.length())); // NOI18N
858
                    // Same here:
702
                /* XXX figure out how to set DOCTYPE:
859
                    attr.addAttribute("bundlevalue", inStr.substring(bundleValueMagic.length())); // NOI18N
703
                el.getOwnerDocument().getDoctype().setPublicId("-//NetBeans//DTD Filesystem 1.2//EN");
860
                    TreeObjectList nodes = doc.getChildNodes();
704
                el.getOwnerDocument().getDoctype().setSystemId("http://www.netbeans.org/dtds/filesystem-1_2.dtd");
861
                    for (int i = 0; i < nodes.size(); i++) {
705
                 */
862
                        Object object = nodes.get(i);
706
            } else {
863
                        if (object instanceof TreeDocumentType) {
707
                // Regular string value.
864
                            TreeDocumentType tdt = (TreeDocumentType)object;
708
                // Stolen from XMLMapAttr w/ mods:
865
                            tdt.setPublicId("-//NetBeans//DTD Filesystem 1.2//EN");
709
                StringBuilder outStr = new StringBuilder();
866
                            tdt.setSystemId("http://www.netbeans.org/dtds/filesystem-1_2.dtd");
710
                for (int i = 0; i < inStr.length(); i++) {
867
                            break;
711
                    char c = inStr.charAt(i);
868
                        }
712
                    if (Character.isISOControl(c) || c == '&' || c == '<' || c == '>' || c == '"' || c == '\'') {
713
                        outStr.append(encodeChar(c));
714
                    } else {
715
                        outStr.append(c);
869
                    }
716
                    }
870
                } else {
871
                    // Regular string value.
872
                    // Stolen from XMLMapAttr w/ mods:
873
                    StringBuffer outStr = new StringBuffer();
874
                    for (int i = 0; i < inStr.length(); i++) {
875
                        char c = inStr.charAt(i);
876
                        if (Character.isISOControl(c) || c == '&' || c == '<' || c == '>' || c == '"' || c == '\'') {
877
                            outStr.append(encodeChar(c));
878
                        } else {
879
                            outStr.append(c);
880
                        }
881
                    }
882
                    attr.addAttribute("stringvalue", outStr.toString()); // NOI18N
883
                }
717
                }
884
            } else if (v instanceof URL) {
718
                attrE.setAttribute("stringvalue", outStr.toString()); // NOI18N
885
                attr.addAttribute("urlvalue", ((URL) v).toExternalForm()); // NOI18N
886
            } else if (v instanceof Boolean) {
887
                attr.addAttribute("boolvalue", v.toString()); // NOI18N
888
            } else if (v instanceof Character) {
889
                attr.addAttribute("charvalue", v.toString()); // NOI18N
890
            } else if (v instanceof Integer) {
891
                attr.addAttribute("intvalue", v.toString()); // NOI18N
892
            } else if (v != null) {
893
                throw new UnsupportedOperationException("XXX: " + v); // NOI18N
894
            }
719
            }
895
            if (v != null && existingAttr == null) {
720
        } else if (v instanceof URL) {
896
                appendWithIndent(el, attr);
721
            attrE.setAttribute("urlvalue", ((URL) v).toExternalForm()); // NOI18N
897
            } else if (v != null) {
722
        } else if (v instanceof Boolean) {
898
                ((TreeParentNode) el).replaceChild(existingAttr, attr);
723
            attrE.setAttribute("boolvalue", v.toString()); // NOI18N
899
            } else if (existingAttr != null) {
724
        } else if (v instanceof Character) {
900
                deleteWithIndent(existingAttr);
725
            attrE.setAttribute("charvalue", v.toString()); // NOI18N
901
            }
726
        } else if (v instanceof Integer) {
902
        } catch (InvalidArgumentException e) {
727
            attrE.setAttribute("intvalue", v.toString()); // NOI18N
903
            throw new AssertionError(e);
728
        } else if (v != null) {
904
        } catch (ReadOnlyException e) {
729
            throw new UnsupportedOperationException("XXX: " + v); // NOI18N
905
            throw (IOException) new IOException(e.toString()).initCause(e);
730
        }
731
        if (v != null && existingAttr == null) {
732
            model.insertBefore((org.netbeans.modules.xml.xdm.nodes.Node) el,
733
                    (org.netbeans.modules.xml.xdm.nodes.Node) attrE,
734
                    (org.netbeans.modules.xml.xdm.nodes.Node) findInsertionPosition(el, attrE));
735
        } else if (v != null) {
736
            model.replaceChild((org.netbeans.modules.xml.xdm.nodes.Node) el,
737
                    (org.netbeans.modules.xml.xdm.nodes.Node) attrE,
738
                    (org.netbeans.modules.xml.xdm.nodes.Node) existingAttr);
739
        } else if (existingAttr != null) {
740
            model.removeChildNodes((org.netbeans.modules.xml.xdm.nodes.Node) el,
741
                    Collections.singleton((org.netbeans.modules.xml.xdm.nodes.Node) existingAttr));
906
        }
742
        }
907
        /*
743
        /*
908
        if (v instanceof Byte) {
909
            attr.setAttribute("bytevalue", v.toString()); // NOI18N
910
        } else if (v instanceof Short) {
911
            attr.setAttribute("shortvalue", v.toString()); // NOI18N
912
        } else if (v instanceof Integer) {
913
            attr.setAttribute("intvalue", v.toString()); // NOI18N
914
        } else if (v instanceof Long) {
915
            attr.setAttribute("longvalue", v.toString()); // NOI18N
916
        } else if (v instanceof Float) {
917
            attr.setAttribute("floatvalue", v.toString()); // NOI18N
918
        } else if (v instanceof Double) {
919
            attr.setAttribute("doublevalue", v.toString()); // NOI18N
920
        } else if (v instanceof Character) {
921
            attr.setAttribute("charvalue", v.toString()); // NOI18N
922
        } else {
744
        } else {
923
            // Stolen from XMLMapAttr, mostly.
745
            // Stolen from XMLMapAttr, mostly.
924
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
746
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
Lines 1000-1022 Link Here
1000
        return "\\u" + "0000".substring(0, "0000".length() - encChar.length()).concat(encChar); // NOI18N
822
        return "\\u" + "0000".substring(0, "0000".length() - encChar.length()).concat(encChar); // NOI18N
1001
    }
823
    }
1002
    
824
    
1003
    public void renameAttributes(String oldName, String newName) {
825
    public @Override void renameAttributes(String oldName, String newName) {
1004
        // do nothing
826
        // do nothing
1005
    }
827
    }
1006
    
828
    
1007
    public void deleteAttributes(String name) {
829
    public @Override void deleteAttributes(String name) {
1008
        // do nothing
830
        // do nothing
1009
    }
831
    }
1010
    
832
    
1011
    public boolean readOnly(String name) {
833
    public @Override boolean readOnly(String name) {
1012
        return false;
834
        return false;
1013
    }
835
    }
1014
    
836
    
1015
    public String mimeType(String name) {
837
    public @Override String mimeType(String name) {
1016
        return null; // i.e. use default resolvers
838
        return null; // i.e. use default resolvers
1017
    }
839
    }
1018
    
840
    
1019
    public long size(String name) {
841
    public @Override long size(String name) {
1020
        try {
842
        try {
1021
            return getContentsOf(name).length;
843
            return getContentsOf(name).length;
1022
        } catch (FileNotFoundException fnfe) {
844
        } catch (FileNotFoundException fnfe) {
Lines 1024-1043 Link Here
1024
        }
846
        }
1025
    }
847
    }
1026
    
848
    
1027
    public void markUnimportant(String name) {
849
    public @Override void markUnimportant(String name) {
1028
        // do nothing
850
        // do nothing
1029
    }
851
    }
1030
    
852
    
1031
    public Date lastModified(String name) {
853
    public @Override Date lastModified(String name) {
1032
        final TreeElement el = findElement(name);
854
        final Element el = findElement(name);
1033
        if (el == null) {
855
        if (el == null) {
1034
            return new Date(0L);
856
            return new Date(0L);
1035
        }
857
        }
1036
        TreeAttribute attr = el.getAttribute("url"); // NOI18N
858
        String u = el.getAttribute("url"); // NOI18N
1037
        if (attr == null) {
859
        if (u.isEmpty()) {
1038
            return new Date(0L);
860
            return new Date(0L);
1039
        }
861
        }
1040
        String u = attr.getValue();
1041
        URI uri = null;
862
        URI uri = null;
1042
        try {
863
        try {
1043
            uri = new URI(null, u, null);
864
            uri = new URI(null, u, null);
Lines 1063-1117 Link Here
1063
    
884
    
1064
    // These are not important for us:
885
    // These are not important for us:
1065
    
886
    
1066
    public void lock(String name) throws IOException {
887
    public @Override void lock(String name) throws IOException {
1067
        // [PENDING] should this try to lock the XML document??
888
        // [PENDING] should this try to lock the XML document??
1068
        // (not clear if it is safe to do so from FS call, even tho
889
        // (not clear if it is safe to do so from FS call, even tho
1069
        // on a different FS)
890
        // on a different FS)
1070
    }
891
    }
1071
    
892
    
1072
    public void unlock(String name) {
893
    public @Override void unlock(String name) {
1073
        // do nothing
894
        // do nothing
1074
    }
895
    }
1075
    
896
    
1076
    // don't bother making configurable; or could use an indentation engine
1077
    private static final int INDENT_STEP = 4;
1078
    /**
1079
     * Add a new element to a parent in the correct position.
1080
     * Inserts whitespace to retain indentation rules.
1081
     */
1082
    private static void appendWithIndent(TreeElement parent, TreeChild child) throws ReadOnlyException {
1083
        TreeParentNode doc = parent;
1084
        int depth = -2; // will get <filesystem> then TreeDocument then null
1085
        while (doc != null) {
1086
            doc = ((TreeChild) doc).getParentNode();
1087
            depth++;
1088
        }
1089
        TreeChild position = insertBefore(parent, child);
1090
        try {
1091
            if (position != null) {
1092
                parent.insertBefore(child, position);
1093
                parent.insertBefore(new TreeText("\n" + spaces((depth + 1) * INDENT_STEP)), position); // NOI18N
1094
            } else {
1095
                if (/*XXX this is clumsy*/ parent.hasChildNodes()) {
1096
                    parent.appendChild(new TreeText(spaces(INDENT_STEP)));
1097
                } else {
1098
                    parent.appendChild(new TreeText("\n" + spaces((depth + 1) * INDENT_STEP))); // NOI18N
1099
                }
1100
                parent.appendChild(child);
1101
                parent.appendChild(new TreeText("\n" + spaces(depth * INDENT_STEP))); // NOI18N
1102
            }
1103
            parent.normalize();
1104
            /* XXX could check for adding position attr and resort parent of parent?
1105
            TreeElement childe = (TreeElement) child;
1106
            if (childe.getQName().equals("attr") && childe.getAttribute("name").getValue().indexOf('/') != -1) { // NOI18N
1107
                // Check for ordering attributes, which we have to handle specially.
1108
                resort(parent);
1109
            }
1110
             */
1111
        } catch (InvalidArgumentException e) {
1112
            assert false : e;
1113
        }
1114
    }
1115
    /**
897
    /**
1116
     * Find the proper location for a newly inserted element.
898
     * Find the proper location for a newly inserted element.
1117
     * Rules:
899
     * Rules:
Lines 1119-1387 Link Here
1119
     * 2. <attr> must be added to top of element in alpha order w.r.t. existing ones.
901
     * 2. <attr> must be added to top of element in alpha order w.r.t. existing ones.
1120
     * Returns a position to insert before (null for end).
902
     * Returns a position to insert before (null for end).
1121
     */
903
     */
1122
    private static TreeChild insertBefore(TreeElement parent, TreeChild child) throws ReadOnlyException {
904
    private static Element findInsertionPosition(Element parent, Element child) {
1123
        if (!(child instanceof TreeElement)) {
905
        if (child.getTagName().equals("file") || child.getTagName().equals("folder")) { // NOI18N
1124
            return null; // TBD for now
906
            String name = child.getAttribute("name"); // NOI18N
1125
        }
907
            for (Element kid : XMLUtil.findSubElements(parent)) {
1126
        TreeElement childe = (TreeElement) child;
908
                if ((kid.getTagName().equals("file") || kid.getTagName().equals("folder")) && kid.getAttribute("name").compareTo(name) > 0) { // NOI18N
1127
        if (childe.getQName().equals("file") || childe.getQName().equals("folder")) { // NOI18N
909
                    return kid;
1128
            String name = childe.getAttribute("name").getValue(); // NOI18N
1129
            Iterator it = parent.getChildNodes(TreeElement.class).iterator();
1130
            while (it.hasNext()) {
1131
                TreeElement kid = (TreeElement) it.next();
1132
                if (kid.getQName().equals("file") || kid.getQName().equals("folder")) { // NOI18N
1133
                    TreeAttribute attr = kid.getAttribute("name"); // NOI18N
1134
                    if (attr != null) { // #66816 - layer might be malformed
1135
                        String kidname = attr.getValue();
1136
                        if (kidname.compareTo(name) > 0) {
1137
                            return kid;
1138
                        }
1139
                    }
1140
                }
910
                }
1141
            }
911
            }
1142
            return null;
912
            return null;
1143
        } else if (childe.getQName().equals("attr")) { // NOI18N
913
        } else if (child.getTagName().equals("attr")) { // NOI18N
1144
            String name = childe.getAttribute("name").getValue(); // NOI18N
914
            String name = child.getAttribute("name"); // NOI18N
1145
                Iterator it = parent.getChildNodes(TreeElement.class).iterator();
915
            for (Element kid : XMLUtil.findSubElements(parent)) {
1146
                while (it.hasNext()) {
916
                    if (kid.getTagName().equals("file") || kid.getTagName().equals("folder")) { // NOI18N
1147
                    TreeElement kid = (TreeElement) it.next();
1148
                    if (kid.getQName().equals("file") || kid.getQName().equals("folder")) { // NOI18N
1149
                        return kid;
917
                        return kid;
1150
                    } else if (kid.getQName().equals("attr")) { // NOI18N
918
                    } else if (kid.getTagName().equals("attr") && kid.getAttribute("name").compareTo(name) > 0) { // NOI18N
1151
                        TreeAttribute attr = kid.getAttribute("name"); // NOI18N
919
                        return kid;
1152
                        if (attr != null) {
1153
                            String kidname = attr.getValue();
1154
                            if (kidname.compareTo(name) > 0) {
1155
                                return kid;
1156
                            }
1157
                        }
1158
                    } else {
1159
                        throw new AssertionError("Weird child: " + kid.getQName());
1160
                    }
920
                    }
1161
                }
921
                }
1162
                return null;
922
                return null;
1163
        } else {
923
        } else {
1164
            throw new AssertionError("Weird child: " + childe.getQName());
924
            throw new AssertionError("Weird child: " + child.getTagName());
1165
        }
925
        }
1166
    }
926
    }
1167
    /**
1168
     * Resort all files and folders and attributes in a folder context. The order is:
1169
     * 1. Attributes are alpha-sorted at the top.
1170
     * 2. Files and folders are alpha-sorted if they have no position.
1171
     * 3. Files with positions are sorted numerically above files without.
1172
     */
1173
    /* XXX not important yet
1174
    private static void resort(TreeElement parent) throws ReadOnlyException {
1175
        class Item {
1176
            public TreeElement child;
1177
            boolean isAttr() {
1178
                return child.getQName().equals("attr"); // NOI18N
1179
            }
1180
            String getName() {
1181
                TreeAttribute attr = child.getAttribute("name"); // NOI18N
1182
                return attr != null ? attr.getValue() : "";
1183
            }
1184
            boolean isOrderingAttr() {
1185
                return isAttr() && getName().indexOf('/') != -1;
1186
            }
1187
            String getFormer() {
1188
                String n = getName();
1189
                return n.substring(0, n.indexOf('/'));
1190
            }
1191
            String getLatter() {
1192
                String n = getName();
1193
                return n.substring(n.indexOf('/') + 1);
1194
            }
1195
        }
1196
        Set<Item> items = new LinkedHashSet();
1197
        SortedSet<Integer> indices = new TreeSet();
1198
        for (int i = 0; i < parent.getChildrenNumber(); i++) {
1199
            TreeChild child = (TreeChild) parent.getChildNodes().get(i);
1200
            if (child instanceof TreeElement) {
1201
                Item item = new Item();
1202
                item.child = (TreeElement) child;
1203
                items.add(item);
1204
                indices.add(new Integer(i));
1205
            }
1206
        }
1207
        Map<Item,Collection<Item>> edges = new LinkedHashMap();
1208
        Map<String,Item> filesAndFolders = new LinkedHashMap();
1209
        Map<String,Item> attrs = new LinkedHashMap();
1210
        Set<String> orderedFilesAndFolders = new LinkedHashSet();
1211
        Iterator it = items.iterator();
1212
        while (it.hasNext()) {
1213
            Item item = (Item) it.next();
1214
            String name = item.getName();
1215
            if (item.isAttr()) {
1216
                attrs.put(name, item);
1217
                if (item.isOrderingAttr()) {
1218
                    orderedFilesAndFolders.add(item.getFormer());
1219
                    orderedFilesAndFolders.add(item.getLatter());
1220
                }
1221
            } else {
1222
                filesAndFolders.put(name, item);
1223
            }
1224
        }
1225
        class NameComparator implements Comparator {
1226
            public int compare(Object o1, Object o2) {
1227
                Item i1 = (Item) o1;
1228
                Item i2 = (Item) o2;
1229
                return i1.getName().compareTo(i2.getName());
1230
            }
1231
        }
1232
        Set<Item> sortedAttrs = new TreeSet(new NameComparator());
1233
        Set<Item> sortedFilesAndFolders = new TreeSet(new NameComparator());
1234
        Set<Item> orderableItems = new LinkedHashSet();
1235
        it = items.iterator();
1236
        while (it.hasNext()) {
1237
            Item item = (Item) it.next();
1238
            String name = item.getName();
1239
            if (item.isAttr()) {
1240
                if (item.isOrderingAttr()) {
1241
                    Item former = (Item) filesAndFolders.get(item.getFormer());
1242
                    if (former != null) {
1243
                        Set<Item> formerConstraints = (Set) edges.get(former);
1244
                        if (formerConstraints == null) {
1245
                            formerConstraints = new LinkedHashSet();
1246
                            edges.put(former, formerConstraints);
1247
                        }
1248
                        formerConstraints.add(item);
1249
                    }
1250
                    Item latter = (Item) filesAndFolders.get(item.getLatter());
1251
                    if (latter != null) {
1252
                        Set<Item> constraints = new LinkedHashSet();
1253
                        constraints.add(latter);
1254
                        edges.put(item, constraints);
1255
                    }
1256
                    orderableItems.add(item);
1257
                } else {
1258
                    sortedAttrs.add(item);
1259
                }
1260
            } else {
1261
                if (orderedFilesAndFolders.contains(name)) {
1262
                    orderableItems.add(item);
1263
                } else {
1264
                    sortedFilesAndFolders.add(item);
1265
                }
1266
            }
1267
        }
1268
        java.util.List<Item> orderedItems;
1269
        try {
1270
            orderedItems = Utilities.topologicalSort(orderableItems, edges);
1271
        } catch (TopologicalSortException e) {
1272
            // OK, ignore.
1273
            return;
1274
        }
1275
        it = items.iterator();
1276
        while (it.hasNext()) {
1277
            Item item = (Item) it.next();
1278
            parent.removeChild(item.child);
1279
        }
1280
        java.util.List<Item> allOrderedItems = new ArrayList(sortedAttrs);
1281
        allOrderedItems.addAll(orderedItems);
1282
        allOrderedItems.addAll(sortedFilesAndFolders);
1283
        assert new HashSet(allOrderedItems).equals(items);
1284
        it = allOrderedItems.iterator();
1285
        Iterator indexIt = indices.iterator();
1286
        while (it.hasNext()) {
1287
            Item item = (Item) it.next();
1288
            int index = ((Integer) indexIt.next()).intValue();
1289
            parent.insertChildAt(item.child, index);
1290
        }
1291
    }
1292
     */
1293
    private static String spaces(int size) {
1294
        char[] chars = new char[size];
1295
        for (int i = 0; i < size; i++) {
1296
            chars[i] = ' ';
1297
        }
1298
        return new String(chars);
1299
    }
1300
    /**
1301
     * Remove an element (and its children), deleting any surrounding newlines and indentation too.
1302
     */
1303
    private static void deleteWithIndent(TreeChild child) throws ReadOnlyException {
1304
        TreeChild next = child.getNextSibling();
1305
        // XXX better might be to delete any maximal [ \t]+ previous plus \n next (means splitting up TreeText's)
1306
        if (next instanceof TreeText && ((TreeText) next).getData().matches("(\r|\n|\r\n)[ \t]+")) { // NOI18N
1307
            next.removeFromContext();
1308
        } else {
1309
            TreeChild previous = child.getPreviousSibling();
1310
            if (previous instanceof TreeText && ((TreeText) previous).getData().matches("(\r|\n|\r\n)[ \t]+")) { // NOI18N
1311
                previous.removeFromContext();
1312
            } else {
1313
                // Well, not sure what is here, so skip it.
1314
            }
1315
        }
1316
        TreeElement parent = (TreeElement) child.getParentNode();
1317
        TreeObjectList list = parent.getChildNodes();
1318
        boolean kill = true;
1319
        Iterator it = list.iterator();
1320
        while (it.hasNext()) {
1321
            Object o = it.next();
1322
            if (o == child) {
1323
                continue;
1324
            }
1325
            if (!(o instanceof TreeText)) {
1326
                kill = false;
1327
                break;
1328
            }
1329
            if (((TreeText) o).getData().trim().length() > 0) {
1330
                kill = false;
1331
                break;
1332
            }
1333
        }
1334
        if (kill) {
1335
            try {
1336
                // Special case for root of filesystem.
1337
                if (((TreeChild) parent).getParentNode() instanceof TreeDocumentRoot) {
1338
                    it = list.iterator();
1339
                    while (it.hasNext()) {
1340
                        ((TreeChild) it.next()).removeFromContext();
1341
                    }
1342
                    parent.appendChild(new TreeText("\n")); // NOI18N
1343
                } else {
1344
                    // Make sure we convert it to an empty tag (seems to only affect elements
1345
                    // which were originally parsed?):
1346
                    TreeElement parent2 = new TreeElement(parent.getQName(), true);
1347
                    TreeAttribute attr = parent.getAttribute("name"); // NOI18N
1348
                    if (attr != null) {
1349
                        parent2.addAttribute("name", attr.getValue()); // NOI18N
1350
                    }
1351
                    TreeParentNode grandparent = ((TreeChild) parent).getParentNode();
1352
                    // TreeElement.empty is sticky - cannot be changed retroactively (argh!).
1353
                    grandparent.replaceChild(parent, parent2);
1354
                    parent = parent2; // for normalize() below
1355
                }
1356
            } catch (InvalidArgumentException e) {
1357
                assert false : e;
1358
            }
1359
        }
1360
        child.removeFromContext();
1361
        parent.normalize();
1362
    }
1363
    
927
    
1364
    // Listen to changes in files used as url= external contents. If these change,
928
    // Listen to changes in files used as url= external contents. If these change,
1365
    // the filesystem needs to show something else. Properly we would
929
    // the filesystem needs to show something else. Properly we would
1366
    // keep track of *which* file changed and thus which of our resources
930
    // keep track of *which* file changed and thus which of our resources
1367
    // is affected. Practically this would be a lot of work and gain
931
    // is affected. Practically this would be a lot of work and gain
1368
    // very little.
932
    // very little.
1369
    public void fileDeleted(FileEvent fe) {
933
    public @Override void fileDeleted(FileEvent fe) {
1370
        someFileChange();
934
        someFileChange();
1371
    }
935
    }
1372
    public void fileFolderCreated(FileEvent fe) {
936
    public @Override void fileFolderCreated(FileEvent fe) {
1373
        // does not apply to us
937
        // does not apply to us
1374
    }
938
    }
1375
    public void fileDataCreated(FileEvent fe) {
939
    public @Override void fileDataCreated(FileEvent fe) {
1376
        // not interesting here
940
        // not interesting here
1377
    }
941
    }
1378
    public void fileAttributeChanged(FileAttributeEvent fe) {
942
    public @Override void fileAttributeChanged(FileAttributeEvent fe) {
1379
        // don't care about attributes on included files...
943
        // don't care about attributes on included files...
1380
    }
944
    }
1381
    public void fileRenamed(FileRenameEvent fe) {
945
    public @Override void fileRenamed(FileRenameEvent fe) {
1382
        someFileChange();
946
        someFileChange();
1383
    }
947
    }
1384
    public void fileChanged(FileEvent fe) {
948
    public @Override void fileChanged(FileEvent fe) {
1385
        someFileChange();
949
        someFileChange();
1386
    }
950
    }
1387
    private void someFileChange() {
951
    private void someFileChange() {
Lines 1389-1402 Link Here
1389
        refreshResource("", true);
953
        refreshResource("", true);
1390
    }
954
    }
1391
    
955
    
1392
    public void propertyChange(PropertyChangeEvent evt) {
956
    public @Override void propertyChange(PropertyChangeEvent evt) {
1393
        if (!evt.getPropertyName().equals(TreeEditorCookie.PROP_DOCUMENT_ROOT)) {
957
        /* XXX do we need to synch periodically?
1394
            return;
958
        model.sync();
1395
        }
959
         */
1396
        if (cookie.getStatus() == TreeEditorCookie.STATUS_OK || cookie.getStatus() == TreeEditorCookie.STATUS_NOT) {
960
        model.flush();
961
        // XXX if supporting external changes:
962
        if (false/*model.getStatus() == XDMModel.Status.STABLE*/) {
1397
            // Document was modified, and reparsed OK. See what changed.
963
            // Document was modified, and reparsed OK. See what changed.
1398
            try {
1399
                doc = cookie.openDocumentRoot();
1400
                /* Neither of the following work:
964
                /* Neither of the following work:
1401
                refreshResource("", true); // only works on root folder
965
                refreshResource("", true); // only works on root folder
1402
                refreshRoot();             // seems to do nothing at all
966
                refreshRoot();             // seems to do nothing at all
Lines 1409-1419 Link Here
1409
                }
973
                }
1410
                //System.err.println("got changes; new files: " + Collections.list(getRoot().getChildren(true)));
974
                //System.err.println("got changes; new files: " + Collections.list(getRoot().getChildren(true)));
1411
                //Thread.dumpStack();
975
                //Thread.dumpStack();
1412
            } catch (TreeException e) {
1413
                Util.err.notify(ErrorManager.INFORMATIONAL, e);
1414
            } catch (IOException e) {
1415
                Util.err.notify(ErrorManager.INFORMATIONAL, e);
1416
            }
1417
        }
976
        }
1418
    }
977
    }
1419
    
978
    
(-)a/apisupport.project/test/unit/src/org/netbeans/modules/apisupport/project/layers/WritableXMLFileSystemTest.java (-7 / +4 lines)
Lines 56-62 Link Here
56
import java.util.Set;
56
import java.util.Set;
57
import org.netbeans.junit.RandomlyFails;
57
import org.netbeans.junit.RandomlyFails;
58
import org.netbeans.modules.apisupport.project.TestBase;
58
import org.netbeans.modules.apisupport.project.TestBase;
59
import org.netbeans.modules.apisupport.project.layers.LayerUtils.SavableTreeEditorCookie;
60
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
59
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
61
import org.openide.filesystems.FileAttributeEvent;
60
import org.openide.filesystems.FileAttributeEvent;
62
import org.openide.filesystems.FileChangeListener;
61
import org.openide.filesystems.FileChangeListener;
Lines 123-130 Link Here
123
        Layer orig = new Layer("<file name='x' url='nbres:/org/test/x.txt'/><file name='y' url='nbresloc:/org/test/resources/y.txt'/>", files);
122
        Layer orig = new Layer("<file name='x' url='nbres:/org/test/x.txt'/><file name='y' url='nbresloc:/org/test/resources/y.txt'/>", files);
124
        FileObject orgTest = FileUtil.createFolder(new File(orig.folder, "org/test"));
123
        FileObject orgTest = FileUtil.createFolder(new File(orig.folder, "org/test"));
125
        FileObject lf = orig.f.copy(orgTest, "layer", "xml");
124
        FileObject lf = orig.f.copy(orgTest, "layer", "xml");
126
        SavableTreeEditorCookie cookie = LayerUtils.cookieForFile(lf);
125
        FileSystem fs = new WritableXMLFileSystem(lf, 
127
        FileSystem fs = new WritableXMLFileSystem(lf.getURL(), cookie, 
128
                ClassPathSupport.createClassPath(new FileObject[] { FileUtil.toFileObject(orig.folder) } ));
126
                ClassPathSupport.createClassPath(new FileObject[] { FileUtil.toFileObject(orig.folder) } ));
129
        FileObject x = fs.findResource("x");
127
        FileObject x = fs.findResource("x");
130
        assertNotNull(x);
128
        assertNotNull(x);
Lines 677-683 Link Here
677
    private final class Layer {
675
    private final class Layer {
678
        private final File folder;
676
        private final File folder;
679
        private final FileObject f;
677
        private final FileObject f;
680
        private LayerUtils.SavableTreeEditorCookie cookie;
678
        private WritableXMLFileSystem fs;
681
        /**
679
        /**
682
         * Create a layer from a fixed bit of filesystem-DTD XML.
680
         * Create a layer from a fixed bit of filesystem-DTD XML.
683
         * Omit the <filesystem>...</> tag and just give the contents.
681
         * Omit the <filesystem>...</> tag and just give the contents.
Lines 731-738 Link Here
731
         * Read the filesystem from the layer.
729
         * Read the filesystem from the layer.
732
         */
730
         */
733
        public WritableXMLFileSystem read() throws Exception {
731
        public WritableXMLFileSystem read() throws Exception {
734
            cookie = LayerUtils.cookieForFile(f);
732
            return fs = new WritableXMLFileSystem(f, null);
735
            return new WritableXMLFileSystem(f.getURL(), cookie, null);
736
        }
733
        }
737
        /**
734
        /**
738
         * Write the filesystem to the layer and retrieve the new contents.
735
         * Write the filesystem to the layer and retrieve the new contents.
Lines 742-748 Link Here
742
            return write(HEADER, FOOTER);
739
            return write(HEADER, FOOTER);
743
        }
740
        }
744
        String write(String head, String foot) throws Exception {
741
        String write(String head, String foot) throws Exception {
745
            cookie.save();
742
            fs.save();
746
            String raw = TestBase.slurp(f);
743
            String raw = TestBase.slurp(f);
747
744
748
            for (int i = 1; i <= 2; i++) {
745
            for (int i = 1; i <= 2; i++) {
(-)a/xml.tax/nbproject/project.xml (-1 lines)
Lines 157-163 Link Here
157
                </dependency>
157
                </dependency>
158
            </module-dependencies>
158
            </module-dependencies>
159
            <friend-packages>
159
            <friend-packages>
160
                <friend>org.netbeans.modules.apisupport.project</friend>
161
                <friend>org.netbeans.modules.xml.tools</friend>
160
                <friend>org.netbeans.modules.xml.tools</friend>
162
                <friend>org.netbeans.modules.xml.tools.java</friend>
161
                <friend>org.netbeans.modules.xml.tools.java</friend>
163
                <package>org.netbeans.tax</package>
162
                <package>org.netbeans.tax</package>

Return to bug 194547