0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.modules.apisupport.project.layers;
0043:
0044: import java.beans.PropertyChangeEvent;
0045: import java.beans.PropertyChangeListener;
0046: import java.io.ByteArrayInputStream;
0047: import java.io.ByteArrayOutputStream;
0048: import java.io.FileNotFoundException;
0049: import java.io.FilterOutputStream;
0050: import java.io.IOException;
0051: import java.io.InputStream;
0052: import java.io.NotSerializableException;
0053: import java.io.ObjectOutputStream;
0054: import java.io.OutputStream;
0055: import java.io.UnsupportedEncodingException;
0056: import java.net.MalformedURLException;
0057: import java.net.URI;
0058: import java.net.URL;
0059: import java.net.URLConnection;
0060: import java.util.ArrayList;
0061: import java.util.Collections;
0062: import java.util.Date;
0063: import java.util.Enumeration;
0064: import java.util.HashSet;
0065: import java.util.Iterator;
0066: import java.util.Set;
0067: import org.netbeans.api.java.classpath.ClassPath;
0068: import org.netbeans.modules.apisupport.project.Util;
0069: import org.netbeans.modules.xml.tax.cookies.TreeEditorCookie;
0070: import org.netbeans.tax.InvalidArgumentException;
0071: import org.netbeans.tax.ReadOnlyException;
0072: import org.netbeans.tax.TreeAttribute;
0073: import org.netbeans.tax.TreeCDATASection;
0074: import org.netbeans.tax.TreeChild;
0075: import org.netbeans.tax.TreeDocumentRoot;
0076: import org.netbeans.tax.TreeElement;
0077: import org.netbeans.tax.TreeException;
0078: import org.netbeans.tax.TreeObjectList;
0079: import org.netbeans.tax.TreeParentNode;
0080: import org.netbeans.tax.TreeText;
0081: import org.openide.ErrorManager;
0082: import org.openide.filesystems.AbstractFileSystem;
0083: import org.openide.filesystems.FileAttributeEvent;
0084: import org.openide.filesystems.FileChangeListener;
0085: import org.openide.filesystems.FileEvent;
0086: import org.openide.filesystems.FileLock;
0087: import org.openide.filesystems.FileObject;
0088: import org.openide.filesystems.FileRenameEvent;
0089: import org.openide.filesystems.FileUtil;
0090: import org.openide.filesystems.URLMapper;
0091: import org.openide.util.Enumerations;
0092: import org.openide.util.WeakListeners;
0093:
0094: /**
0095: * A filesystem which is based on a TAX document and implements
0096: * the same syntax as XMLFileSystem, from which inspiration is taken.
0097: * Not implemented similarly to XMLFileSystem because this is writable
0098: * and designed specifically to write human-readable XML and work nicely
0099: * as an authoring tool. The filesystem expects to get an XML document
0100: * according to DTD "-//NetBeans//DTD Filesystem 1.0//EN" (or 1.1 is OK).
0101: * When it is changed via FileSystems API, it will fire TAX
0102: * events. Not intended to be efficient or terribly robust, since it
0103: * is development-time only.
0104: * @author Jesse Glick
0105: */
0106: final class WritableXMLFileSystem extends AbstractFileSystem implements
0107: AbstractFileSystem.Attr, AbstractFileSystem.Change,
0108: AbstractFileSystem.Info, AbstractFileSystem.List,
0109: //AbstractFileSystem.Transfer,
0110: FileChangeListener, PropertyChangeListener {
0111:
0112: private final TreeEditorCookie cookie;
0113: private TreeDocumentRoot doc; // may be null if malformed
0114: private URL location;
0115: private String suffix; // for branding/localization like "_f4j_ce_ja"; never null, at worst ""
0116: private final FileChangeListener fileChangeListener;
0117: private ClassPath classpath; // OK to be null
0118:
0119: public WritableXMLFileSystem(URL location, TreeEditorCookie cookie,
0120: ClassPath classpath) {
0121: this .attr = this ;
0122: this .change = this ;
0123: this .info = this ;
0124: this .list = this ;
0125: //this.transfer = this;
0126: this .cookie = cookie;
0127: try {
0128: doc = cookie.openDocumentRoot();
0129: } catch (TreeException e) {
0130: Util.err.notify(ErrorManager.INFORMATIONAL, e);
0131: } catch (IOException e) {
0132: Util.err.notify(ErrorManager.INFORMATIONAL, e);
0133: }
0134: fileChangeListener = FileUtil
0135: .weakFileChangeListener(this , null);
0136: cookie.addPropertyChangeListener(WeakListeners.propertyChange(
0137: this , cookie));
0138: setLocation(location);
0139: setClasspath(classpath);
0140: }
0141:
0142: private void writeObject(ObjectOutputStream out) throws IOException {
0143: throw new NotSerializableException(
0144: "WritableXMLFileSystem is not persistent"); // NOI18N
0145: }
0146:
0147: private void setLocation(URL location) {
0148: String u = location.toExternalForm();
0149: if (u.endsWith("/")) { // NOI18N
0150: throw new IllegalArgumentException(u);
0151: }
0152: this .location = location;
0153: }
0154:
0155: private void setClasspath(ClassPath classpath) {
0156: this .classpath = classpath;
0157: }
0158:
0159: public String getDisplayName() {
0160: FileObject fo = URLMapper.findFileObject(location);
0161: if (fo != null) {
0162: return FileUtil.getFileDisplayName(fo);
0163: } else {
0164: return location.toExternalForm();
0165: }
0166: }
0167:
0168: public boolean isReadOnly() {
0169: return false;
0170: }
0171:
0172: private TreeElement getRootElement() {
0173: if (doc == null) {
0174: return null;
0175: }
0176: Iterator it;
0177: it = doc.getChildNodes().iterator();
0178: while (it.hasNext()) {
0179: Object next = it.next();
0180: if (next instanceof TreeElement) {
0181: return (TreeElement) next;
0182: }
0183: }
0184: return null;
0185: }
0186:
0187: /** Given a resource name, find the matching DOM element.
0188: * @return a <folder> or <file> or <filesystem> element, or null if file does not exist
0189: */
0190: private TreeElement findElement(String name) {
0191: return findElementIn(getRootElement(), name);
0192: }
0193:
0194: /** helper method only */
0195: private static TreeElement findElementIn(TreeElement el, String name) {
0196: if (el == null)
0197: return null;
0198: if (name.equals("")) { // NOI18N
0199: return el;
0200: } else {
0201: int idx = name.indexOf('/');
0202: String nextName, remainder;
0203: if (idx == -1) {
0204: nextName = name;
0205: remainder = ""; // NOI18N
0206: } else {
0207: nextName = name.substring(0, idx);
0208: remainder = name.substring(idx + 1);
0209: }
0210: TreeElement subel = null;
0211: Iterator it = el.getChildNodes(TreeElement.class)
0212: .iterator();
0213: while (it.hasNext()) {
0214: TreeElement e = (TreeElement) it.next();
0215: if (e.getLocalName().equals("file") || // NOI18N
0216: e.getLocalName().equals("folder")) { // NOI18N
0217: TreeAttribute nameAttr = e.getAttribute("name"); // NOI18N
0218: if (nameAttr != null
0219: && nameAttr.getValue().equals(nextName)) {
0220: subel = e;
0221: break;
0222: }
0223: }
0224: }
0225: return findElementIn(subel, remainder);
0226: }
0227: }
0228:
0229: public boolean folder(String name) {
0230: TreeElement el = findElement(name);
0231: if (el == null) {
0232: //System.err.println("folder <" + name + ">: false, no such element");
0233: return false;
0234: }
0235: boolean res = el.getLocalName().equals("folder"); // NOI18N
0236: //System.err.println("folder <" + name + ">: " + res);
0237: return res;
0238: }
0239:
0240: /*
0241: private static final Set warnedAboutDupeKids = new HashSet(1); // Set<String>
0242: */
0243: public String[] children(String f) {
0244: TreeElement el = findElement(f);
0245: if (el == null) {
0246: //System.err.println("children <" + f + ">: none, no such element");
0247: return new String[] {};
0248: }
0249: ArrayList<String> kids = new ArrayList<String>();
0250: Set<String> allNames = new HashSet<String>();
0251: Iterator it = el.getChildNodes(TreeElement.class).iterator();
0252: while (it.hasNext()) {
0253: TreeElement sub = (TreeElement) it.next();
0254: if (sub.getLocalName().equals("file") || // NOI18N
0255: sub.getLocalName().equals("folder")) { // NOI18N
0256: TreeAttribute childName = sub.getAttribute("name"); // NOI18N
0257: if (childName == null) {
0258: continue;
0259: }
0260: String name = childName.getValue(); // NOI18N
0261: if (allNames.add(name)) {
0262: kids.add(name);
0263: /*
0264: } else {
0265: if (warnedAboutDupeKids.add(location + ":" + f + "/" + name)) { // NOI18N
0266: // #18699: will deadlock if you try to change anything.
0267: if (f.equals("")) { // NOI18N
0268: LayerDataNode.getErr().println("WARNING: in " + xmlfile + " the root folder contains the child " + name + " more than once.");
0269: } else {
0270: LayerDataNode.getErr().println("WARNING: in " + xmlfile + " the folder " + f + " contains the child " + name + " more than once.");
0271: }
0272: //LayerDataNode.getErr().println("The Open APIs Support module will not work properly with such a layer.");
0273: //LayerDataNode.getErr().println("Please edit the XML text and merge together all children of a <folder> with the same name.");
0274: }
0275: */
0276: }
0277: }
0278: }
0279: //System.err.println("children <" + f + ">: " + kids);
0280: return kids.toArray(new String[kids.size()]);
0281: }
0282:
0283: /** retrieve byte contents of a named resource */
0284: private byte[] getContentsOf(final String name)
0285: throws FileNotFoundException {
0286: TreeElement el = findElement(name);
0287: if (el == null)
0288: throw new FileNotFoundException(name);
0289: TreeAttribute urlAttr = el.getAttribute("url"); // NOI18N
0290: if (urlAttr != null) {
0291: try {
0292: URL[] u = LayerUtils.currentify(new URL(location,
0293: urlAttr.getValue()), suffix, classpath);
0294: URLConnection conn = u[0].openConnection();
0295: conn.connect();
0296: InputStream is = conn.getInputStream();
0297: byte[] buf = new byte[conn.getContentLength()];
0298: if (is.read(buf) != buf.length)
0299: throw new IOException("wrong content length"); // NOI18N
0300: // Also listen to changes in it.
0301: FileObject fo = URLMapper.findFileObject(u[0]);
0302: if (fo != null) {
0303: fo.removeFileChangeListener(fileChangeListener);
0304: fo.addFileChangeListener(fileChangeListener);
0305: }
0306: return buf;
0307: } catch (IOException ioe) {
0308: throw new FileNotFoundException(ioe.getMessage());
0309: }
0310: } else {
0311: StringBuffer buf = new StringBuffer();
0312: Iterator it = el.getChildNodes().iterator();
0313: while (it.hasNext()) {
0314: Object o = it.next();
0315: if (o instanceof TreeCDATASection) {
0316: buf.append(((TreeCDATASection) o).getData());
0317: } else if (o instanceof TreeText) {
0318: buf.append(((TreeText) o).getData().trim());
0319: }
0320: }
0321: try {
0322: // This encoding is intentional...
0323: return buf.toString().getBytes("UTF-8"); // NOI18N
0324: } catch (UnsupportedEncodingException uee) {
0325: throw new FileNotFoundException(uee.getMessage());
0326: }
0327: }
0328: }
0329:
0330: // [PENDING] should I/O from/to external text files be done via EditorCookie?
0331: // Not clear if this is safe (call from FS -> DS) even tho in separate FSs...
0332:
0333: public InputStream inputStream(String name)
0334: throws FileNotFoundException {
0335: return new ByteArrayInputStream(getContentsOf(name));
0336: }
0337:
0338: public OutputStream outputStream(final String name)
0339: throws IOException {
0340: final TreeElement el = findElement(name);
0341: if (el == null) {
0342: throw new FileNotFoundException(name);
0343: }
0344: TreeAttribute urlAttr = el.getAttribute("url"); // NOI18N
0345: if (urlAttr != null) {
0346: String u = urlAttr.getValue();
0347: if (URI.create(u).isAbsolute()) {
0348: // What to do? Can't overwrite it, obviously.
0349: throw new IOException(name);
0350: }
0351: // We have an existing external file. Try to write to that instead.
0352: FileObject external = URLMapper.findFileObject(new URL(
0353: location, u));
0354: if (external == null) {
0355: throw new FileNotFoundException(name);
0356: }
0357: final FileLock lock = external.lock();
0358: return new FilterOutputStream(external
0359: .getOutputStream(lock)) {
0360: public void close() throws IOException {
0361: super .close();
0362: lock.releaseLock();
0363: }
0364: };
0365: }
0366: // We will change the layer file.
0367: return new ByteArrayOutputStream() {
0368: public void close() throws IOException {
0369: super .close();
0370: byte[] contents = toByteArray();
0371: /* If desired to kill any existing inline content:
0372: Iterator it = el.getChildNodes().iterator();
0373: ArrayList/ *<TreeCDATASection>* / allCdata = new ArrayList();
0374: while (it.hasNext()) {
0375: Object o = it.next();
0376: if (o instanceof TreeCDATASection) {
0377: allCdata.add(o);
0378: } else if (o instanceof TreeText &&
0379: ((TreeText) o).getData().trim().length() > 0) {
0380: el.removeChild((TreeText) o);
0381: }
0382: }
0383: Iterator it = allCdata.iterator();
0384: while (it.hasNext()) {
0385: el.removeChild((CDATASection) it.next());
0386: }
0387: */
0388: FileObject parent = findLayerParent();
0389: String externalName = LayerUtils.findGeneratedName(
0390: parent, name);
0391: assert externalName.indexOf('/') == -1 : externalName;
0392: FileObject externalFile = parent
0393: .createData(externalName);
0394: FileLock lock = externalFile.lock();
0395: try {
0396: OutputStream os = externalFile
0397: .getOutputStream(lock);
0398: try {
0399: os.write(contents);
0400: } finally {
0401: os.close();
0402: }
0403: } finally {
0404: lock.releaseLock();
0405: }
0406: externalFile.addFileChangeListener(fileChangeListener);
0407: try {
0408: el.addAttribute("url", externalName); // NOI18N
0409: } catch (ReadOnlyException e) {
0410: throw (IOException) new IOException(e.toString())
0411: .initCause(e);
0412: } catch (InvalidArgumentException e) {
0413: assert false : e;
0414: }
0415: }
0416: };
0417: }
0418:
0419: private FileObject findLayerParent() throws IOException {
0420: String loc = location.toExternalForm();
0421: int slash = loc.lastIndexOf('/');
0422: assert slash != -1 : loc;
0423: FileObject parent = URLMapper.findFileObject(new URL(loc
0424: .substring(0, slash + 1)));
0425: if (parent == null) {
0426: throw new IOException(loc);
0427: }
0428: return parent;
0429: }
0430:
0431: private void createFileOrFolder(String name, boolean folder)
0432: throws IOException {
0433: String parentName, baseName;
0434: int idx = name.lastIndexOf('/');
0435: if (idx == -1) {
0436: parentName = ""; // NOI18N
0437: baseName = name;
0438: } else {
0439: parentName = name.substring(0, idx);
0440: baseName = name.substring(idx + 1);
0441: }
0442: TreeElement el = findElement(parentName);
0443: if (el == null) {
0444: throw new FileNotFoundException(parentName);
0445: }
0446: try {
0447: TreeElement nue = new TreeElement(folder ? "folder"
0448: : "file", true); // NOI18N
0449: nue.addAttribute("name", baseName); // NOI18N
0450: appendWithIndent(el, nue);
0451: } catch (InvalidArgumentException e) {
0452: assert false : e;
0453: } catch (ReadOnlyException e) {
0454: throw (IOException) new IOException(e.toString())
0455: .initCause(e);
0456: }
0457: }
0458:
0459: public void createFolder(String name) throws IOException {
0460: createFileOrFolder(name, true);
0461: }
0462:
0463: public void createData(String name) throws IOException {
0464: createFileOrFolder(name, false);
0465: }
0466:
0467: public void delete(String name) throws IOException {
0468: TreeElement el = findElement(name);
0469: if (el == null) {
0470: throw new FileNotFoundException(name);
0471: }
0472: TreeAttribute externalName = el.getAttribute("url"); // NOI18N
0473: if (externalName != null
0474: && !URI.create(externalName.getValue()).isAbsolute()) {
0475: // Delete the external file if it can be found.
0476: FileObject externalFile = URLMapper.findFileObject(new URL(
0477: location, externalName.getValue()));
0478: if (externalFile != null) {
0479: externalFile
0480: .removeFileChangeListener(fileChangeListener);
0481: externalFile.delete();
0482: }
0483: }
0484: try {
0485: deleteWithIndent((TreeChild) el);
0486: } catch (ReadOnlyException e) {
0487: throw (IOException) new IOException(e.toString())
0488: .initCause(e);
0489: }
0490: }
0491:
0492: public void rename(String oldName, String newName)
0493: throws IOException {
0494: TreeElement el = findElement(oldName);
0495: if (el == null) {
0496: throw new FileNotFoundException(oldName);
0497: }
0498: int idx = newName.lastIndexOf('/') + 1;
0499: if (idx != oldName.lastIndexOf('/') + 1
0500: || !oldName.substring(0, idx).equals(
0501: newName.substring(0, idx))) {
0502: throw new IOException("Cannot rename to a different dir: "
0503: + oldName + " -> " + newName); // NOI18N
0504: }
0505: String newBaseName = newName.substring(idx);
0506: assert newBaseName.indexOf('/') == -1;
0507: assert newBaseName.length() > 0;
0508: try {
0509: el.getAttribute("name").setValue(newBaseName); // NOI18N
0510: } catch (ReadOnlyException e) {
0511: throw (IOException) new IOException(e.toString())
0512: .initCause(e);
0513: } catch (InvalidArgumentException e) {
0514: assert false : e;
0515: }
0516: }
0517:
0518: /*
0519: public boolean copy(String name, Transfer target, String targetName) throws IOException {
0520: if (! (target instanceof WritableXMLFileSystem)) return false;
0521: WritableXMLFileSystem otherfs = (WritableXMLFileSystem) target;
0522: Element el = findElement(name);
0523: if (el == null) throw new FileNotFoundException(name);
0524: Element el2;
0525: if (otherfs == this) {
0526: el2 = (Element) el.cloneNode(true);
0527: } else {
0528: el2 = (Element) otherfs.doc.importNode(el, true);
0529: }
0530: String path, base;
0531: int idx = targetName.lastIndexOf('/');
0532: if (idx == -1) {
0533: path = ""; // NOI18N
0534: base = targetName;
0535: } else {
0536: path = targetName.substring(0, idx);
0537: base = targetName.substring(idx + 1);
0538: }
0539: Element parent = otherfs.findElement(path);
0540: if (parent == null) throw new FileNotFoundException(path);
0541: el2.setAttribute("name", base); // NOI18N
0542: Element old = otherfs.findElement(targetName);
0543: if (old != null) {
0544: parent.replaceChild(el2, old);
0545: } else {
0546: appendWithIndent(parent, el2);
0547: }
0548: return true;
0549: }
0550:
0551: public boolean move(String name, Transfer target, String targetName) throws IOException {
0552: if (! (target instanceof WritableXMLFileSystem)) return false;
0553: WritableXMLFileSystem otherfs = (WritableXMLFileSystem) target;
0554: Element el = findElement(name);
0555: if (el == null) throw new FileNotFoundException(name);
0556: Element el2;
0557: if (otherfs == this) {
0558: // Just move it, no need to clone.
0559: el2 = el;
0560: } else {
0561: el2 = (Element) otherfs.doc.importNode(el, true);
0562: }
0563: String path, base;
0564: int idx = targetName.lastIndexOf('/');
0565: if (idx == -1) {
0566: path = ""; // NOI18N
0567: base = targetName;
0568: } else {
0569: path = targetName.substring(0, idx);
0570: base = targetName.substring(idx + 1);
0571: }
0572: Element parent = otherfs.findElement(path);
0573: if (parent == null) throw new FileNotFoundException(path);
0574: el2.setAttribute("name", base); // NOI18N
0575: Element old = otherfs.findElement(targetName);
0576: if (el != el2) {
0577: // Cross-document import, so need to remove old one.
0578: el.getParentNode().removeChild(el);
0579: }
0580: if (old != null) {
0581: parent.replaceChild(el2, old);
0582: } else {
0583: appendWithIndent(parent, el2);
0584: }
0585: return true;
0586: }
0587: */
0588:
0589: public Enumeration<String> attributes(String name) {
0590: TreeElement el = findElement(name);
0591: if (el == null) {
0592: return Enumerations.empty();
0593: }
0594: java.util.List<String> l = new ArrayList<String>(10);
0595: Iterator it = el.getChildNodes(TreeElement.class).iterator();
0596: while (it.hasNext()) {
0597: TreeElement sub = (TreeElement) it.next();
0598: if (sub.getLocalName().equals("attr")) { // NOI18N
0599: TreeAttribute nameAttr = sub.getAttribute("name"); // NOI18N
0600: if (nameAttr == null) {
0601: // Malformed.
0602: continue;
0603: }
0604: l.add(nameAttr.getValue());
0605: }
0606: }
0607: return Collections.enumeration(l);
0608: }
0609:
0610: public Object readAttribute(String name, String attrName) {
0611: if (attrName.equals("WritableXMLFileSystem.cp")) { // NOI18N
0612: // XXX currently unused
0613: return classpath;
0614: }
0615: if (attrName.equals("WritableXMLFileSystem.location")) { // NOI18N
0616: // XXX used from PickIconAction; use a constant instead
0617: return location;
0618: }
0619: if (attrName.equals("DataFolder.Index.reorderable")) { // NOI18N
0620: return Boolean.TRUE;
0621: }
0622: TreeElement el = findElement(name);
0623: if (el == null) {
0624: return null;
0625: }
0626: boolean literal = false;
0627: if (attrName.startsWith("literal:")) { // NOI18N
0628: attrName = attrName.substring("literal:".length()); // NOI18N
0629: literal = true;
0630: }
0631: Iterator it = el.getChildNodes(TreeElement.class).iterator();
0632: while (it.hasNext()) {
0633: TreeElement sub = (TreeElement) it.next();
0634: if (!sub.getLocalName().equals("attr")) { // NOI18N
0635: continue;
0636: }
0637: TreeAttribute nameAttr = sub.getAttribute("name"); // NOI18N
0638: if (nameAttr == null) {
0639: // Malformed.
0640: continue;
0641: }
0642: if (!attrName.equals(nameAttr.getValue())) {
0643: continue;
0644: }
0645: try {
0646: if ((nameAttr = sub.getAttribute("stringvalue")) != null) { // NOI18N
0647: // Stolen from XMLMapAttr, with tweaks:
0648: String inStr = nameAttr.getValue();
0649: StringBuffer outStr = new StringBuffer(inStr
0650: .length());
0651: for (int j = 0; j < inStr.length(); j++) {
0652: char ch = inStr.charAt(j);
0653: if (ch == '\\' && inStr.charAt(j + 1) == 'u'
0654: && j + 5 < inStr.length()) {
0655: String hex = inStr.substring(j + 2, j + 6);
0656: try {
0657: outStr.append((char) Integer.parseInt(
0658: hex, 16));
0659: j += 5;
0660: } catch (NumberFormatException e) {
0661: // OK, just treat as literal text.
0662: outStr.append(ch);
0663: }
0664: } else {
0665: outStr.append(ch);
0666: }
0667: }
0668: return outStr.toString();
0669: } else if ((nameAttr = sub.getAttribute("boolvalue")) != null) { // NOI18N
0670: return Boolean.valueOf(nameAttr.getValue());
0671: } else if ((nameAttr = sub.getAttribute("urlvalue")) != null) { // NOI18N
0672: return new URL(nameAttr.getValue());
0673: } else if ((nameAttr = sub.getAttribute("charvalue")) != null) { // NOI18N
0674: return new Character(nameAttr.getValue().charAt(0));
0675: } else if ((nameAttr = sub.getAttribute("bytevalue")) != null) { // NOI18N
0676: return Byte.valueOf(nameAttr.getValue());
0677: } else if ((nameAttr = sub.getAttribute("shortvalue")) != null) { // NOI18N
0678: return Short.valueOf(nameAttr.getValue());
0679: } else if ((nameAttr = sub.getAttribute("intvalue")) != null) { // NOI18N
0680: return Integer.valueOf(nameAttr.getValue());
0681: } else if ((nameAttr = sub.getAttribute("longvalue")) != null) { // NOI18N
0682: return Long.valueOf(nameAttr.getValue());
0683: } else if ((nameAttr = sub.getAttribute("floatvalue")) != null) { // NOI18N
0684: return Float.valueOf(nameAttr.getValue());
0685: } else if ((nameAttr = sub.getAttribute("doublevalue")) != null) { // NOI18N
0686: return Double.valueOf(nameAttr.getValue());
0687: } else if ((nameAttr = sub.getAttribute("newvalue")) != null) { // NOI18N
0688: String clazz = nameAttr.getValue();
0689: if (literal) {
0690: return "new:" + clazz; // NOI18N
0691: } // else XXX
0692: } else if ((nameAttr = sub.getAttribute("methodvalue")) != null) { // NOI18N
0693: String clazz = nameAttr.getValue();
0694: if (literal) {
0695: return "method:" + clazz; // NOI18N
0696: } // else XXX
0697: }
0698: } catch (Exception e) {
0699: // MalformedURLException, etc.
0700: // XXX notify?
0701: return null;
0702: }
0703: // XXX warn that this attr had no recognized *value?
0704: }
0705: return null;
0706: /*
0707: if ((v = sub.getAttributeNode("bytevalue")) != null) { // NOI18N
0708: return new Byte(v.getValue());
0709: } else if ((v = sub.getAttributeNode("shortvalue")) != null) { // NOI18N
0710: return new Short(v.getValue());
0711: } else if ((v = sub.getAttributeNode("intvalue")) != null) { // NOI18N
0712: return new Integer(v.getValue());
0713: } else if ((v = sub.getAttributeNode("longvalue")) != null) { // NOI18N
0714: return new Long(v.getValue());
0715: } else if ((v = sub.getAttributeNode("floatvalue")) != null) { // NOI18N
0716: return new Float(v.getValue());
0717: } else if ((v = sub.getAttributeNode("doublevalue")) != null) { // NOI18N
0718: // When was the last time you set a file attribute to a double?!
0719: // Useless list of primitives...
0720: return new Double(v.getValue());
0721: } else if ((v = sub.getAttributeNode("charvalue")) != null) { // NOI18N
0722: return new Character(v.getValue().charAt(0));
0723: } else if ((v = sub.getAttributeNode("methodvalue")) != null) { // NOI18N
0724: String value = v.getValue();
0725: Object[] params = new Object[] { findResource(name), attrName };
0726: // Stolen from XMLMapAttr:
0727: String className,methodName;
0728: int j = value.lastIndexOf('.');
0729: if (j != -1) {
0730:
0731: methodName = value.substring(j+1);
0732:
0733: className = value.substring(0,j);
0734: ClassLoader cl = cp.getClassLoader(true);
0735: Class cls = Class.forName(className, true, cl);
0736: // Note that unlike XMLMapAttr, we want to use currentClassLoader.
0737:
0738: Object objArray[][] = {null,null,null};
0739: Method methArray[] = {null,null,null};
0740:
0741:
0742: Class fParam = null, sParam = null;
0743:
0744: if (params != null) {
0745: if (params.length > 0) fParam = params[0].getClass();
0746: if (params.length > 1) sParam = params[1].getClass();
0747: }
0748:
0749: Method[] allMethods = cls.getDeclaredMethods();
0750: Class[] paramClss;
0751:
0752: for (int k=0; k < allMethods.length; k++) {
0753:
0754: if (!allMethods[k].getName().equals(methodName)) continue;
0755:
0756:
0757: paramClss = allMethods[k].getParameterTypes();
0758:
0759: if (params == null || params.length == 0 || paramClss.length == 0) {
0760: if (paramClss.length == 0 && methArray[0] == null && objArray[0] == null) {
0761: methArray[paramClss.length] = allMethods[k];
0762: objArray[paramClss.length] = new Object[] {};
0763: continue;
0764: }
0765:
0766: continue;
0767: }
0768:
0769:
0770: if (paramClss.length == 2 && params.length >= 2 && methArray[2] == null && objArray[2] == null) {
0771: if (paramClss[0].isAssignableFrom(fParam) && paramClss[1].isAssignableFrom(sParam)) {
0772: methArray[paramClss.length] = allMethods[k];
0773: objArray[paramClss.length] = new Object[] {params[0],params[1]};
0774: break;
0775: }
0776:
0777: if (paramClss[0].isAssignableFrom(sParam) && paramClss[1].isAssignableFrom(fParam)) {
0778: methArray[paramClss.length] = allMethods[k];
0779: objArray[paramClss.length] = new Object[] {params[1],params[0]};
0780: break;
0781: }
0782:
0783: continue;
0784: }
0785:
0786: if (paramClss.length == 1 && params.length >= 1 && methArray[1] == null && objArray[1] == null) {
0787: if (paramClss[0].isAssignableFrom(fParam)) {
0788: methArray[paramClss.length] = allMethods[k];
0789: objArray[paramClss.length] = new Object[] {params[0]};
0790: continue;
0791: }
0792:
0793: if (paramClss[0].isAssignableFrom(sParam)) {
0794: methArray[paramClss.length] = allMethods[k];
0795: objArray[paramClss.length] = new Object[] {params[1]};
0796: continue;
0797: }
0798:
0799: continue;
0800: }
0801:
0802: }
0803:
0804: for (int l = methArray.length-1; l >= 0; l-- ) {//clsArray.length
0805: if (methArray[l] != null && objArray[l] != null) {
0806: //Method meth = cls.getDeclaredMethod(methodName,clsArray[l]);
0807: methArray[l].setAccessible(true); //otherwise cannot invoke private
0808: return methArray[l].invoke(null,objArray[l]);
0809: }
0810: }
0811: }
0812: // Some message to logFile
0813: throw new InstantiationException(value);
0814: } else if ((v = sub.getAttributeNode("serialvalue")) != null) { // NOI18N
0815: // Copied from XMLMapAttr:
0816: String value = v.getValue();
0817: if (value.length() == 0) return null;
0818:
0819: byte[] bytes = new byte[value.length()/2];
0820: int tempJ;
0821: int count = 0;
0822: for (int j = 0; j < value.length(); j += 2) {
0823: tempJ = Integer.parseInt(value.substring(j,j+2),16);
0824: if (tempJ > 127) tempJ -=256;
0825: bytes[count++] = (byte) tempJ;
0826: }
0827:
0828: ByteArrayInputStream bis = new ByteArrayInputStream(bytes, 0, count);
0829: // XXX this should be using a subclass that specifies the right class loader:
0830: ObjectInputStream ois = new NbObjectInputStream(bis);
0831: return ois.readObject();
0832: } else if ((v = sub.getAttributeNode("newvalue")) != null) { // NOI18N
0833: ClassLoader cl = cp.getClassLoader(true);
0834: return Class.forName(v.getValue(), true, cl).newInstance();
0835: }
0836: */
0837: }
0838:
0839: public void writeAttribute(String name, String attrName, Object v)
0840: throws IOException {
0841: //System.err.println("wA: " + name + " " + attrName + " " + v);
0842: if (v != null
0843: && v
0844: .getClass()
0845: .getName()
0846: .equals(
0847: "org.openide.filesystems.MultiFileObject$VoidValue")) { // NOI18N
0848: // XXX is this legitimate? Definitely not pretty. But needed for testOpenideFolderOrder to pass.
0849: v = null;
0850: }
0851: TreeElement el = findElement(name);
0852: if (el == null) {
0853: throw new FileNotFoundException(name);
0854: }
0855: // Find any existing <attr>.
0856: TreeChild existingAttr = null;
0857: Iterator it = el.getChildNodes(TreeElement.class).iterator();
0858: while (it.hasNext()) {
0859: TreeElement sub = (TreeElement) it.next();
0860: if (sub.getLocalName().equals("attr")) { // NOI18N
0861: TreeAttribute attr = sub.getAttribute("name"); // NOI18N
0862: if (attr == null) {
0863: // Malformed.
0864: continue;
0865: }
0866: if (attr.getValue().equals(attrName)) {
0867: existingAttr = sub;
0868: break;
0869: }
0870: }
0871: }
0872: TreeElement attr;
0873: try {
0874: attr = new TreeElement("attr", true); // NOI18N
0875: attr.addAttribute("name", attrName); // NOI18N
0876: if (v instanceof String) {
0877: String inStr = (String) v;
0878: String newValueMagic = "newvalue:"; // NOI18N
0879: String methodValueMagic = "methodvalue:"; // NOI18N
0880: if (inStr.startsWith(newValueMagic)) {
0881: // Impossible to set this (reliably) as a real value, so use this magic technique instead:
0882: attr.addAttribute("newvalue", inStr
0883: .substring(newValueMagic.length())); // NOI18N
0884: } else if (inStr.startsWith(methodValueMagic)) {
0885: // Same here:
0886: attr.addAttribute("methodvalue", inStr
0887: .substring(methodValueMagic.length())); // NOI18N
0888: } else {
0889: // Regular string value.
0890: // Stolen from XMLMapAttr w/ mods:
0891: StringBuffer outStr = new StringBuffer();
0892: for (int i = 0; i < inStr.length(); i++) {
0893: char c = inStr.charAt(i);
0894: if (Character.isISOControl(c) || c == '&'
0895: || c == '<' || c == '>' || c == '"'
0896: || c == '\'') {
0897: outStr.append(encodeChar(c));
0898: } else {
0899: outStr.append(c);
0900: }
0901: }
0902: attr.addAttribute("stringvalue", outStr.toString()); // NOI18N
0903: }
0904: } else if (v instanceof URL) {
0905: attr.addAttribute("urlvalue", ((URL) v)
0906: .toExternalForm()); // NOI18N
0907: } else if (v instanceof Boolean) {
0908: attr.addAttribute("boolvalue", v.toString()); // NOI18N
0909: } else if (v instanceof Character) {
0910: attr.addAttribute("charvalue", v.toString()); // NOI18N
0911: } else if (v instanceof Integer) {
0912: attr.addAttribute("intvalue", v.toString()); // NOI18N
0913: } else if (v != null) {
0914: throw new UnsupportedOperationException("XXX: " + v); // NOI18N
0915: }
0916: if (v != null && existingAttr == null) {
0917: appendWithIndent(el, attr);
0918: } else if (v != null) {
0919: ((TreeParentNode) el).replaceChild(existingAttr, attr);
0920: } else if (existingAttr != null) {
0921: deleteWithIndent(existingAttr);
0922: }
0923: } catch (InvalidArgumentException e) {
0924: throw new AssertionError(e);
0925: } catch (ReadOnlyException e) {
0926: throw (IOException) new IOException(e.toString())
0927: .initCause(e);
0928: }
0929: /*
0930: if (v instanceof Byte) {
0931: attr.setAttribute("bytevalue", v.toString()); // NOI18N
0932: } else if (v instanceof Short) {
0933: attr.setAttribute("shortvalue", v.toString()); // NOI18N
0934: } else if (v instanceof Integer) {
0935: attr.setAttribute("intvalue", v.toString()); // NOI18N
0936: } else if (v instanceof Long) {
0937: attr.setAttribute("longvalue", v.toString()); // NOI18N
0938: } else if (v instanceof Float) {
0939: attr.setAttribute("floatvalue", v.toString()); // NOI18N
0940: } else if (v instanceof Double) {
0941: attr.setAttribute("doublevalue", v.toString()); // NOI18N
0942: } else if (v instanceof Character) {
0943: attr.setAttribute("charvalue", v.toString()); // NOI18N
0944: } else {
0945: // Stolen from XMLMapAttr, mostly.
0946: ByteArrayOutputStream bos = new ByteArrayOutputStream();
0947: ObjectOutputStream oos = new ObjectOutputStream(bos);
0948: oos.writeObject(v);
0949: oos.close();
0950: byte bArray[] = bos.toByteArray();
0951: // Check to see if this is the same as a default instance.
0952: Class clazz = v.getClass();
0953: boolean usenewinstance = false;
0954: try {
0955: Object v2 = clazz.newInstance();
0956: bos = new ByteArrayOutputStream();
0957: oos = new ObjectOutputStream(bos);
0958: oos.writeObject(v2);
0959: oos.close();
0960: byte[] bArray2 = bos.toByteArray();
0961: usenewinstance = Utilities.compareObjects(bArray, bArray2);
0962: } catch (Exception e) {
0963: // quite expectable - ignore
0964: }
0965: if (usenewinstance) {
0966: attr.setAttribute("newvalue", clazz.getName()); // NOI18N
0967: } else {
0968: StringBuffer strBuff = new StringBuffer(bArray.length*2);
0969: for(int i = 0; i < bArray.length;i++) {
0970: if (bArray[i] < 16 && bArray[i] >= 0) strBuff.append("0");// NOI18N
0971: strBuff.append(Integer.toHexString(bArray[i] < 0?bArray[i]+256:bArray[i]));
0972: }
0973: attr.setAttribute("serialvalue", strBuff.toString()); // NOI18N
0974: // Also mention what the original value was, for reference.
0975: // Do it after adding element since otherwise we cannot indent correctly.
0976: String asString;
0977: if (clazz.isArray()) {
0978: // Default toString sucks for arrays. Pretty common so worth special-casing.
0979: asString = Arrays.asList((Object[]) v).toString();
0980: } else {
0981: asString = v.toString();
0982: }
0983: serialComment = " (" + attrName + "=" + asString + ") ";
0984: }
0985: }
0986: if (adding) {
0987: appendWithIndent(el, attr);
0988: }
0989: // Deal with serial comments now.
0990: Comment comment = findSerialComment(el, attrName);
0991: if (serialComment != null) {
0992: if (comment != null) {
0993: comment.setData(serialComment);
0994: } else {
0995: appendWithIndent(el, doc.createComment(serialComment));
0996: }
0997: } else if (comment != null) {
0998: // Changed from some serialvalue to simple value; kill comment.
0999: el.removeChild(comment);
1000: }
1001: if (attrName.startsWith("SystemFileSystem")) { // NOI18N
1002: fireFileStatusChanged(new FileStatusEvent(this, findResource(name), true, true));
1003: }
1004: }
1005: private Comment findSerialComment(Element el, String attrName) {
1006: NodeList nl = el.getChildNodes();
1007: for (int i = 0; i < nl.getLength(); i++) {
1008: if (nl.item(i).getNodeType() == Node.COMMENT_NODE) {
1009: String comm = nl.item(i).getNodeValue();
1010: if (comm.startsWith(" (" + attrName + "=") && comm.endsWith(") ")) {
1011: return (Comment) nl.item(i);
1012: }
1013: }
1014: }
1015: return null;
1016: */
1017: }
1018:
1019: /** stolen from XMLMapAttr */
1020: private static String encodeChar(char ch) {
1021: String encChar = Integer.toString((int) ch, 16);
1022: return "\\u"
1023: + "0000".substring(0,
1024: "0000".length() - encChar.length()).concat(
1025: encChar); // NOI18N
1026: }
1027:
1028: public void renameAttributes(String oldName, String newName) {
1029: // do nothing
1030: }
1031:
1032: public void deleteAttributes(String name) {
1033: // do nothing
1034: }
1035:
1036: public boolean readOnly(String name) {
1037: return false;
1038: }
1039:
1040: public String mimeType(String name) {
1041: return null; // i.e. use default resolvers
1042: }
1043:
1044: public long size(String name) {
1045: try {
1046: return getContentsOf(name).length;
1047: } catch (FileNotFoundException fnfe) {
1048: return 0;
1049: }
1050: }
1051:
1052: public void markUnimportant(String name) {
1053: // do nothing
1054: }
1055:
1056: public Date lastModified(String name) {
1057: final TreeElement el = findElement(name);
1058: if (el == null) {
1059: return new Date(0L);
1060: }
1061: TreeAttribute attr = el.getAttribute("url"); // NOI18N
1062: if (attr == null) {
1063: return new Date(0L);
1064: }
1065: String u = attr.getValue();
1066: if (URI.create(u).isAbsolute()) {
1067: return new Date(0L);
1068: }
1069: FileObject external;
1070: try {
1071: external = URLMapper.findFileObject(new URL(location, u));
1072: } catch (MalformedURLException e) {
1073: assert false : e;
1074: return new Date(0L);
1075: }
1076: if (external == null) {
1077: return new Date(0L);
1078: }
1079: return external.lastModified();
1080: }
1081:
1082: // These are not important for us:
1083:
1084: public void lock(String name) throws IOException {
1085: // [PENDING] should this try to lock the XML document??
1086: // (not clear if it is safe to do so from FS call, even tho
1087: // on a different FS)
1088: }
1089:
1090: public void unlock(String name) {
1091: // do nothing
1092: }
1093:
1094: // don't bother making configurable; or could use an indentation engine
1095: private static final int INDENT_STEP = 4;
1096:
1097: /**
1098: * Add a new element to a parent in the correct position.
1099: * Inserts whitespace to retain indentation rules.
1100: */
1101: private static void appendWithIndent(TreeElement parent,
1102: TreeChild child) throws ReadOnlyException {
1103: TreeParentNode doc = parent;
1104: int depth = -2; // will get <filesystem> then TreeDocument then null
1105: while (doc != null) {
1106: doc = ((TreeChild) doc).getParentNode();
1107: depth++;
1108: }
1109: TreeChild position = insertBefore(parent, child);
1110: try {
1111: if (position != null) {
1112: parent.insertBefore(child, position);
1113: parent.insertBefore(new TreeText("\n"
1114: + spaces((depth + 1) * INDENT_STEP)), position); // NOI18N
1115: } else {
1116: if (/*XXX this is clumsy*/parent.hasChildNodes()) {
1117: parent
1118: .appendChild(new TreeText(
1119: spaces(INDENT_STEP)));
1120: } else {
1121: parent.appendChild(new TreeText("\n"
1122: + spaces((depth + 1) * INDENT_STEP))); // NOI18N
1123: }
1124: parent.appendChild(child);
1125: parent.appendChild(new TreeText("\n"
1126: + spaces(depth * INDENT_STEP))); // NOI18N
1127: }
1128: parent.normalize();
1129: /* XXX could check for adding position attr and resort parent of parent?
1130: TreeElement childe = (TreeElement) child;
1131: if (childe.getQName().equals("attr") && childe.getAttribute("name").getValue().indexOf('/') != -1) { // NOI18N
1132: // Check for ordering attributes, which we have to handle specially.
1133: resort(parent);
1134: }
1135: */
1136: } catch (InvalidArgumentException e) {
1137: assert false : e;
1138: }
1139: }
1140:
1141: /**
1142: * Find the proper location for a newly inserted element.
1143: * Rules:
1144: * 1. <file> or <folder> must be added in alphabetical order w.r.t. existing ones.
1145: * 2. <attr> must be added to top of element in alpha order w.r.t. existing ones.
1146: * Returns a position to insert before (null for end).
1147: */
1148: private static TreeChild insertBefore(TreeElement parent,
1149: TreeChild child) throws ReadOnlyException {
1150: if (!(child instanceof TreeElement)) {
1151: return null; // TBD for now
1152: }
1153: TreeElement childe = (TreeElement) child;
1154: if (childe.getQName().equals("file")
1155: || childe.getQName().equals("folder")) { // NOI18N
1156: String name = childe.getAttribute("name").getValue(); // NOI18N
1157: Iterator it = parent.getChildNodes(TreeElement.class)
1158: .iterator();
1159: while (it.hasNext()) {
1160: TreeElement kid = (TreeElement) it.next();
1161: if (kid.getQName().equals("file")
1162: || kid.getQName().equals("folder")) { // NOI18N
1163: TreeAttribute attr = kid.getAttribute("name"); // NOI18N
1164: if (attr != null) { // #66816 - layer might be malformed
1165: String kidname = attr.getValue();
1166: if (kidname.compareTo(name) > 0) {
1167: return kid;
1168: }
1169: }
1170: }
1171: }
1172: return null;
1173: } else if (childe.getQName().equals("attr")) { // NOI18N
1174: String name = childe.getAttribute("name").getValue(); // NOI18N
1175: Iterator it = parent.getChildNodes(TreeElement.class)
1176: .iterator();
1177: while (it.hasNext()) {
1178: TreeElement kid = (TreeElement) it.next();
1179: if (kid.getQName().equals("file")
1180: || kid.getQName().equals("folder")) { // NOI18N
1181: return kid;
1182: } else if (kid.getQName().equals("attr")) { // NOI18N
1183: TreeAttribute attr = kid.getAttribute("name"); // NOI18N
1184: if (attr != null) {
1185: String kidname = attr.getValue();
1186: if (kidname.compareTo(name) > 0) {
1187: return kid;
1188: }
1189: }
1190: } else {
1191: throw new AssertionError("Weird child: "
1192: + kid.getQName());
1193: }
1194: }
1195: return null;
1196: } else {
1197: throw new AssertionError("Weird child: "
1198: + childe.getQName());
1199: }
1200: }
1201:
1202: /**
1203: * Resort all files and folders and attributes in a folder context. The order is:
1204: * 1. Attributes are alpha-sorted at the top.
1205: * 2. Files and folders are alpha-sorted if they have no position.
1206: * 3. Files with positions are sorted numerically above files without.
1207: */
1208: /* XXX not important yet
1209: private static void resort(TreeElement parent) throws ReadOnlyException {
1210: class Item {
1211: public TreeElement child;
1212: boolean isAttr() {
1213: return child.getQName().equals("attr"); // NOI18N
1214: }
1215: String getName() {
1216: TreeAttribute attr = child.getAttribute("name"); // NOI18N
1217: return attr != null ? attr.getValue() : "";
1218: }
1219: boolean isOrderingAttr() {
1220: return isAttr() && getName().indexOf('/') != -1;
1221: }
1222: String getFormer() {
1223: String n = getName();
1224: return n.substring(0, n.indexOf('/'));
1225: }
1226: String getLatter() {
1227: String n = getName();
1228: return n.substring(n.indexOf('/') + 1);
1229: }
1230: }
1231: Set<Item> items = new LinkedHashSet();
1232: SortedSet<Integer> indices = new TreeSet();
1233: for (int i = 0; i < parent.getChildrenNumber(); i++) {
1234: TreeChild child = (TreeChild) parent.getChildNodes().get(i);
1235: if (child instanceof TreeElement) {
1236: Item item = new Item();
1237: item.child = (TreeElement) child;
1238: items.add(item);
1239: indices.add(new Integer(i));
1240: }
1241: }
1242: Map<Item,Collection<Item>> edges = new LinkedHashMap();
1243: Map<String,Item> filesAndFolders = new LinkedHashMap();
1244: Map<String,Item> attrs = new LinkedHashMap();
1245: Set<String> orderedFilesAndFolders = new LinkedHashSet();
1246: Iterator it = items.iterator();
1247: while (it.hasNext()) {
1248: Item item = (Item) it.next();
1249: String name = item.getName();
1250: if (item.isAttr()) {
1251: attrs.put(name, item);
1252: if (item.isOrderingAttr()) {
1253: orderedFilesAndFolders.add(item.getFormer());
1254: orderedFilesAndFolders.add(item.getLatter());
1255: }
1256: } else {
1257: filesAndFolders.put(name, item);
1258: }
1259: }
1260: class NameComparator implements Comparator {
1261: public int compare(Object o1, Object o2) {
1262: Item i1 = (Item) o1;
1263: Item i2 = (Item) o2;
1264: return i1.getName().compareTo(i2.getName());
1265: }
1266: }
1267: Set<Item> sortedAttrs = new TreeSet(new NameComparator());
1268: Set<Item> sortedFilesAndFolders = new TreeSet(new NameComparator());
1269: Set<Item> orderableItems = new LinkedHashSet();
1270: it = items.iterator();
1271: while (it.hasNext()) {
1272: Item item = (Item) it.next();
1273: String name = item.getName();
1274: if (item.isAttr()) {
1275: if (item.isOrderingAttr()) {
1276: Item former = (Item) filesAndFolders.get(item.getFormer());
1277: if (former != null) {
1278: Set<Item> formerConstraints = (Set) edges.get(former);
1279: if (formerConstraints == null) {
1280: formerConstraints = new LinkedHashSet();
1281: edges.put(former, formerConstraints);
1282: }
1283: formerConstraints.add(item);
1284: }
1285: Item latter = (Item) filesAndFolders.get(item.getLatter());
1286: if (latter != null) {
1287: Set<Item> constraints = new LinkedHashSet();
1288: constraints.add(latter);
1289: edges.put(item, constraints);
1290: }
1291: orderableItems.add(item);
1292: } else {
1293: sortedAttrs.add(item);
1294: }
1295: } else {
1296: if (orderedFilesAndFolders.contains(name)) {
1297: orderableItems.add(item);
1298: } else {
1299: sortedFilesAndFolders.add(item);
1300: }
1301: }
1302: }
1303: java.util.List<Item> orderedItems;
1304: try {
1305: orderedItems = Utilities.topologicalSort(orderableItems, edges);
1306: } catch (TopologicalSortException e) {
1307: // OK, ignore.
1308: return;
1309: }
1310: it = items.iterator();
1311: while (it.hasNext()) {
1312: Item item = (Item) it.next();
1313: parent.removeChild(item.child);
1314: }
1315: java.util.List<Item> allOrderedItems = new ArrayList(sortedAttrs);
1316: allOrderedItems.addAll(orderedItems);
1317: allOrderedItems.addAll(sortedFilesAndFolders);
1318: assert new HashSet(allOrderedItems).equals(items);
1319: it = allOrderedItems.iterator();
1320: Iterator indexIt = indices.iterator();
1321: while (it.hasNext()) {
1322: Item item = (Item) it.next();
1323: int index = ((Integer) indexIt.next()).intValue();
1324: parent.insertChildAt(item.child, index);
1325: }
1326: }
1327: */
1328: private static String spaces(int size) {
1329: char[] chars = new char[size];
1330: for (int i = 0; i < size; i++) {
1331: chars[i] = ' ';
1332: }
1333: return new String(chars);
1334: }
1335:
1336: /**
1337: * Remove an element (and its children), deleting any surrounding newlines and indentation too.
1338: */
1339: private static void deleteWithIndent(TreeChild child)
1340: throws ReadOnlyException {
1341: TreeChild next = child.getNextSibling();
1342: // XXX better might be to delete any maximal [ \t]+ previous plus \n next (means splitting up TreeText's)
1343: if (next instanceof TreeText
1344: && ((TreeText) next).getData().matches(
1345: "(\r|\n|\r\n)[ \t]+")) { // NOI18N
1346: next.removeFromContext();
1347: } else {
1348: TreeChild previous = child.getPreviousSibling();
1349: if (previous instanceof TreeText
1350: && ((TreeText) previous).getData().matches(
1351: "(\r|\n|\r\n)[ \t]+")) { // NOI18N
1352: previous.removeFromContext();
1353: } else {
1354: // Well, not sure what is here, so skip it.
1355: }
1356: }
1357: TreeElement parent = (TreeElement) child.getParentNode();
1358: TreeObjectList list = parent.getChildNodes();
1359: boolean kill = true;
1360: Iterator it = list.iterator();
1361: while (it.hasNext()) {
1362: Object o = it.next();
1363: if (o == child) {
1364: continue;
1365: }
1366: if (!(o instanceof TreeText)) {
1367: kill = false;
1368: break;
1369: }
1370: if (((TreeText) o).getData().trim().length() > 0) {
1371: kill = false;
1372: break;
1373: }
1374: }
1375: if (kill) {
1376: try {
1377: // Special case for root of filesystem.
1378: if (((TreeChild) parent).getParentNode() instanceof TreeDocumentRoot) {
1379: it = list.iterator();
1380: while (it.hasNext()) {
1381: ((TreeChild) it.next()).removeFromContext();
1382: }
1383: parent.appendChild(new TreeText("\n")); // NOI18N
1384: } else {
1385: // Make sure we convert it to an empty tag (seems to only affect elements
1386: // which were originally parsed?):
1387: TreeElement parent2 = new TreeElement(parent
1388: .getQName(), true);
1389: TreeAttribute attr = parent.getAttribute("name"); // NOI18N
1390: if (attr != null) {
1391: parent2.addAttribute("name", attr.getValue()); // NOI18N
1392: }
1393: TreeParentNode grandparent = ((TreeChild) parent)
1394: .getParentNode();
1395: // TreeElement.empty is sticky - cannot be changed retroactively (argh!).
1396: grandparent.replaceChild(parent, parent2);
1397: parent = parent2; // for normalize() below
1398: }
1399: } catch (InvalidArgumentException e) {
1400: assert false : e;
1401: }
1402: }
1403: child.removeFromContext();
1404: parent.normalize();
1405: }
1406:
1407: // Listen to changes in files used as url= external contents. If these change,
1408: // the filesystem needs to show something else. Properly we would
1409: // keep track of *which* file changed and thus which of our resources
1410: // is affected. Practically this would be a lot of work and gain
1411: // very little.
1412: public void fileDeleted(FileEvent fe) {
1413: someFileChange();
1414: }
1415:
1416: public void fileFolderCreated(FileEvent fe) {
1417: // does not apply to us
1418: }
1419:
1420: public void fileDataCreated(FileEvent fe) {
1421: // not interesting here
1422: }
1423:
1424: public void fileAttributeChanged(FileAttributeEvent fe) {
1425: // don't care about attributes on included files...
1426: }
1427:
1428: public void fileRenamed(FileRenameEvent fe) {
1429: someFileChange();
1430: }
1431:
1432: public void fileChanged(FileEvent fe) {
1433: someFileChange();
1434: }
1435:
1436: private void someFileChange() {
1437: // If used as url=, refresh contents, timestamp, ...
1438: refreshResource("", true);
1439: }
1440:
1441: public void propertyChange(PropertyChangeEvent evt) {
1442: if (!evt.getPropertyName().equals(
1443: TreeEditorCookie.PROP_DOCUMENT_ROOT)) {
1444: return;
1445: }
1446: if (cookie.getStatus() == TreeEditorCookie.STATUS_OK
1447: || cookie.getStatus() == TreeEditorCookie.STATUS_NOT) {
1448: // Document was modified, and reparsed OK. See what changed.
1449: try {
1450: doc = cookie.openDocumentRoot();
1451: /* Neither of the following work:
1452: refreshResource("", true); // only works on root folder
1453: refreshRoot(); // seems to do nothing at all
1454: */
1455: Enumeration<? extends FileObject> e = existingFileObjects(getRoot());
1456: while (e.hasMoreElements()) {
1457: FileObject fo = (FileObject) e.nextElement();
1458: // fo.refresh() does not work
1459: refreshResource(fo.getPath(), true);
1460: }
1461: //System.err.println("got changes; new files: " + Collections.list(getRoot().getChildren(true)));
1462: //Thread.dumpStack();
1463: } catch (TreeException e) {
1464: Util.err.notify(ErrorManager.INFORMATIONAL, e);
1465: } catch (IOException e) {
1466: Util.err.notify(ErrorManager.INFORMATIONAL, e);
1467: }
1468: }
1469: }
1470:
1471: }
|