/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.filesystems; import java.io.*; import java.lang.ref.*; import java.util.*; import org.openide.util.NbBundle; import org.openide.util.enums.EmptyEnumeration; import org.openide.util.io.NbMarshalledObject; import org.openide.util.Utilities; import org.xml.sax.*; import javax.xml.parsers.*; /** Implementation of <code>AbstractFileSystem.Attr</code> using a special file * in each folder for holding attributes. * It needs to hide * the file from the rest of system, so it also implements * <code>AbstractFileSystem.List</code> to exclude the file from the children list * (it can then serve to filter a plain list implementation). * *Description of format of special file ilustrates best DTD file that is showed in next lines: *<!ELEMENT attributes (fileobject)*> * <!ATTLIST attributes version CDATA #REQUIRED> * <!ELEMENT fileobject (attr)*> * <!ATTLIST fileobject name CDATA #REQUIRED> * <!ELEMENT attr EMPTY> * <!ATTLIST attr name CDATA #REQUIRED> * <!ATTLIST attr bytevalue CDATA #IMPLIED> * <!ATTLIST attr shortvalue CDATA #IMPLIED> * <!ATTLIST attr intvalue CDATA #IMPLIED> * <!ATTLIST attr longvalue CDATA #IMPLIED> * <!ATTLIST attr floatvalue CDATA #IMPLIED> * <!ATTLIST attr doublevalue CDATA #IMPLIED> * <!ATTLIST attr boolvalue CDATA #IMPLIED> * <!ATTLIST attr charvalue CDATA #IMPLIED> * <!ATTLIST attr stringvalue CDATA #IMPLIED> * <!ATTLIST attr methodvalue CDATA #IMPLIED> * <!ATTLIST attr serialvalue CDATA #IMPLIED> * <!ATTLIST attr urlvalue CDATA #IMPLIED> * * @author Jaroslav Tulach */ public class DefaultAttributes extends Object implements AbstractFileSystem.Attr, AbstractFileSystem.List { static final long serialVersionUID=-5801291358293736478L; /** File name of special file in each folder where attributes are saved. * @deprecated does not handle XML attributes */ public final static String ATTR_NAME = "filesystem"; // NOI18N /** Extension of special file in each folder where attributes are saved. * @deprecated does not handle XML attributes */ public final static String ATTR_EXT = "attributes"; // NOI18N /** Name with extension of special file in each folder where attributes are saved. * @deprecated does not handle XML attributes */ public final static String ATTR_NAME_EXT = ATTR_NAME + '.' + ATTR_EXT; private final static String ATTR_NAME_EXT_XML = System.getProperty ("org.openide.filesystems.DefaultAttributes.ATTR_NAME_EXT_XML", ".nbattrs"); // NOI18N /** description of the fs to work on - info about files */ private AbstractFileSystem.Info info; /** description of the fs to work on - work with files */ private AbstractFileSystem.Change change; /** description of the fs to work on - listing of files */ private AbstractFileSystem.List list; /** readOnlyAttrs is name of virtual attribute. This name of virtual attribute * is shared between classes (and cannot be changed without breaking compatibility): * - org.openide.filesystems.DefaultAttributes * - org.openide.loaders.ExecutionSupport * - org.openide.loaders.CompilerSupport * - org.netbeans.core.ExJarFileSystem */ private final static String READONLY_ATTRIBUTES = "readOnlyAttrs"; //NOI18N /** Cache of attributes. * For name of folder gives map of maps of attibutes * (String, Reference (Table)) */ private transient Map cache; // <?xml version="1.0"?> // <!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD DefaultAttributes 1.0//EN" "http://www.netbeans.org/dtds/attributes-1_0.dtd"> // <attributes>...</attributes> private static final String PUBLIC_ID = "-//NetBeans//DTD DefaultAttributes 1.0//EN";// NOI18N private static final String DTD_PATH = "org/openide/filesystems/attributes.dtd";// NOI18N /** Constructor. * @param info file object information to use * @param change file change hooks to use * @param list list to filter (can be <code>null</code>, but then this object cannot work as a list) */ public DefaultAttributes ( AbstractFileSystem.Info info, AbstractFileSystem.Change change, AbstractFileSystem.List list ) { this.info = info; this.change = change; this.list = list; } /** Methods to ensure backward compatibility for storing and * loading classes. */ private void readObject (ObjectInputStream ois) throws ClassNotFoundException, IOException { ObjectInputStream.GetField fields = ois.readFields (); Object o1 = AbstractFileSystem.readImpl ("change", fields); // NOI18N Object o2 = AbstractFileSystem.readImpl ("info", fields); // NOI18N Object o3 = AbstractFileSystem.readImpl ("list", fields); // NOI18N change = (AbstractFileSystem.Change)o1; info = (AbstractFileSystem.Info)o2; list = (AbstractFileSystem.List)o3; } /** Get the children list, filtering out the special attributes file. * You <em>must</em> have provided a non-<code>null</code> {@link AbstractFileSystem.List} * in the constructor for this to work. If you did not, the rest of the class will work * fine, but this method should not be called and this object should not be used * as a <code>List</code> implementation. * * @param f the folder, by name; e.g. <code>top/next/afterthat</code> * @return a list of children of the folder, as <code>file.ext</code> (no path) */ public String[] children (String f) { String[] arr = list.children (f); int lookUpIndex = 0; if (arr == null) { return null; } int size = arr.length; if (size == 1) { // In NB 3.2.x for OpenVMS, we had to use "_nbattrs." as a attribute file. // However, OpenVMS now supports a file name beginning with "." // So we now have to copy the existing "_nbattrs." file into ".nbattrs" // if (Utilities.getOperatingSystem () == Utilities.OS_VMS && arr[0] != null && f != null) { if (arr[0].equalsIgnoreCase ("_nbattrs.")) { try { change.delete(f+"/"+arr[0]); // NOI18N } catch (IOException ioe) {} arr[0] = ATTR_NAME_EXT_XML; } } if ((ATTR_NAME_EXT_XML.equals(arr[0]) || ATTR_NAME_EXT.equals(arr[0]))) { try { change.delete(f+"/"+arr[0]); // NOI18N } catch (IOException iox) {} return new String[] {}; } } for (int i = 0; i < size; i++) { // In NB 3.2.x for OpenVMS, we had to use "_nbattrs." as a attribute file. // However, OpenVMS now supports a file name beginning with "." // So we now have to copy the existing "_nbattrs." file into ".nbattrs" // if (Utilities.getOperatingSystem () == Utilities.OS_VMS && arr[i] != null && f != null) { if (arr[i].equalsIgnoreCase ("_nbattrs.")) { try { File fp = new File (f+"/"+".nbattrs"); if ( ! fp.exists() ) { cache = null; copyVMSAttrFile (f); } } catch (IOException ioe) {} arr[i] = ATTR_NAME_EXT_XML; } } if (ATTR_NAME_EXT.equals (arr[i]) || ATTR_NAME_EXT_XML.equals (arr[i])) { // exclude this index arr[i] = null; // there can be two files with attributes if (++lookUpIndex >= 2) break; } } return arr; } /** Renames the attribute file for OpenVMS platform. * The method renames "_nbattrs." into ".nbattrs". * We cannot simply use the change.rename method * because of the special property of OpenVMS having to do with * a file name starting with "." * * @param f the folder containg the attribute file */ private void copyVMSAttrFile (String f) throws IOException { InputStream is = null; OutputStream os = null; try { change.createData (f+"/"+ATTR_NAME_EXT_XML); is = info.inputStream (f+"/"+"_nbattrs."); os = info.outputStream (f+"/"+ATTR_NAME_EXT_XML); byte [] buf = new byte[256]; int readi; while ( (readi=is.read(buf,0,256)) >= 0x0 ) { os.write (buf,0,readi); } is.close (); //change.delete (f+"/"+"_nbattrs."); is = null; } catch (IOException ie) { } finally { if (is != null) is.close (); if (os != null) os.close (); } } // JST: Description // // // The class should be written in such a way that the access to disk is // synchronized (this). But during the access nobody is allowed to // perform serialization and deserialization // of unknown objects, so all objects should be wrapped into NbMarshalledObject // serialized or in reverse target NbMarshalledObject should be deserialized // and then not holding the lock the object obtained from it by a call to // marshall.get (). // // JST: Got it? /* Get the file attribute with the specified name. * @param name the file * @param attrName name of the attribute * @return appropriate (serializable) value or <CODE>null</CODE> if the attribute is unset (or could not be properly restored for some reason) */ public Object readAttribute(String name, String attrName) { Table t; String[] arr = new String[2]; split (name, arr); /** At the momement substitutes lack of API */ if (attrName.equals (READONLY_ATTRIBUTES)) return info.readOnly(arr[0]) ? Boolean.TRUE : Boolean.FALSE; synchronized (this) { // synchronized so only one table for each folder // can exist t = loadTable (arr[0]); } // JST: // had to split the code to do getAttr out of synchronized block // because the attribute can be serialized FileObject and // so the code returns back to FileSystem (that is usually synchronized) // // this leads to deadlocks between FS & DefaultAttributes implementation // // I do not know if the table should not be somehow synchronized, // but it seems ok. return t.getAttr (arr[1], attrName); } /* Set the file attribute with the specified name. * @param name the file * @param attrName name of the attribute * @param value new value or <code>null</code> to clear the attribute. Must be serializable, although particular filesystems may or may not use serialization to store attribute values. * @exception IOException if the attribute cannot be set. If serialization is used to store it, this may in fact be a subclass such as {@link NotSerializableException}. */ public void writeAttribute(String name, String attrName, Object value) throws IOException { // create object that should be serialized //NbMarshalledObject marshall = new NbMarshalledObject (value); int objType; String[] arr = new String[2]; split (name, arr); for (;;) { int version; Table t; synchronized (this) { t = loadTable (arr[0]); version = t.version; } // Tests if the attribute is changing Object prev = t.getAttr(arr[1], attrName); if (prev == value /*|| (value != null && value.equals (prev))*/) { return; } synchronized (this) { Table t2 = loadTable (arr[0]); if (t == t2 && version == t2.version) { // no modification between reading of the value => // save! //Class cls = value.getClass(); if (value == null) { t.setAttr (arr[1], attrName, null); // clear the attribute }else { if ((objType = XMLMapAttr.Attr.distinguishObject (value)) == XMLMapAttr.Attr.isValid("SERIALVALUE") ) { // NOI18N t.setAttr (arr[1], attrName, value);//change value instead of marshall } else { t.setAttr (arr[1], attrName, new XMLMapAttr.Attr(objType,value.toString()) ); } } saveTable (arr[0], t); // ok, saved return; } } // otherwise try it again } } /* Get all file attribute names for the file. * @param name the file * @return enumeration of keys (as strings) */ public synchronized Enumeration attributes(String name) { String[] arr = new String[2]; split (name, arr); Table t = loadTable (arr[0]); return t.attrs (arr[1]); } /* Called when a file is renamed, to appropriatelly update its attributes. * <p> * @param oldName old name of the file * @param newName new name of the file */ public synchronized void renameAttributes (String oldName, String newName) { try { String[] arr = new String[2]; split (oldName, arr); Table t = loadTable (arr[0]); Map v = (Map) t.remove (arr[1]); // System.out.println ("ARg[0] = " + arr[0] + " arr[1] = " + arr[1] + " value: " + v); // NOI18N if (v == null) { // no attrs no change return; } split (newName, arr); // Remove transient attributes: Iterator it = v.entrySet ().iterator (); while (it.hasNext ()) { Map.Entry pair = (Map.Entry) it.next (); if (FileUtil.transientAttributes.contains (pair.getKey ())) it.remove (); } t.put (arr[1], v); // System.out.println ("xyz[0] = " + arr[0] + " xyz[1] = " + arr[1] + " value: " + v); // NOI18N saveTable (arr[0], t); } catch (IOException e) { ExternalUtil.exception (e); } } /* Called when a file is deleted to also delete its attributes. * * @param name name of the file */ public synchronized void deleteAttributes (String name) { try { String[] arr = new String[2]; split (name, arr); Table t = loadTable (arr[0]); if (t.remove (arr[1]) != null) { // if there is a change saveTable (arr[0], t); } } catch (IOException e) { ExternalUtil.exception (e); } } /** Getter for the cache. */ private Map getCache () { if (cache == null) { cache = new HashMap (31); } return cache; } /** Splits name of a file to name of folder and to name of the file. * @param name of file * @param arr arr[0] will hold name of folder and arr[1] name of the file */ private static void split (String name, String[] arr) { int i = name.lastIndexOf ('/'); if (i == -1) { arr[0] = ""; // NOI18N arr[1] = name; return; } // folder name arr[0] = name.substring (0, i); // increase the i to be beyond the length if (++i == name.length ()) { arr[1] = ""; // NOI18N } else { // split it arr[1] = name.substring (i); } } /** Save attributes. * @param name name of folder to save attributes for * @param map map to save */ private void saveTable(String name, Table map) throws IOException { String fullName = (name.length()==0 ? "": (name + '/')) + ATTR_NAME_EXT_XML; // NOI18N /** OpenVMS now supports various special characters including "~"*/ String safeName = fullName+"~"; // NOI18N if (info.folder (fullName)) { if (map.size () == 0) { // ok no need to delete return; } // find parent change.createData (fullName); } else { if (map.size() == 0) { change.delete (fullName); return; } } PrintWriter pw = null; IOException ioexc = null; try { pw = new PrintWriter(new OutputStreamWriter (new BufferedOutputStream(info.outputStream (safeName)),"UTF8")); // NOI18N map.writeToXML(pw); pw.flush(); } catch (IOException iex) { ioexc = iex; } finally { if (pw != null) pw.close(); if (ioexc != null) { this.change.delete (safeName); throw ioexc; } else { try { this.change.delete (fullName); } catch (IOException iex2) { /** if delete fails, then also rename fails and exception will * be fired */ } this.change.rename(safeName,fullName); } } } /** Load attributes from cache or * from disk. * @param name of folder to load data from */ private Table loadTable(String name) { //throws IOException { Reference r = (Reference)getCache ().get (name); if (r != null) { Table m = (Table)r.get (); if (m != null) { return m; } } // have to load new table Table t = load (name); t.attach (name, this); getCache ().put (name, new SoftReference (t)); return t; } /** Loads the table. Does no initialization. */ private Table load (String name) { String acceptNames[] = {(name.length()==0 ? "": (name + '/')) + ATTR_NAME_EXT_XML,// NOI18N (name.length()==0 ? "": (name + '/')) + ATTR_NAME_EXT}; // NOI18N for (int i = 0; i < acceptNames.length;i++) { if (info.size (acceptNames[i]) > 0L) { try { InputStream fis = info.inputStream (acceptNames[i]); try { return loadTable (fis,acceptNames[i]); } finally { try { fis.close (); } catch (IOException e) { // ignore--who cares? } } } catch (FileNotFoundException ex) { ExternalUtil.exception(ex); } } } return new Table (); } /** Loads the Table of extended attributes for a input stream from binary serialized file or from XML. * @param is input stream * @param folderName name of file for better error message * @return the attributes table for this input stream */ static Table loadTable (InputStream is,String folderName) { Table retTable = new Table (); PushbackInputStream pbStream = null; boolean isSerialized = false; try { if (!folderName.endsWith (ATTR_NAME_EXT_XML)) { pbStream = new PushbackInputStream (is, 4 /*is.available()*/); isSerialized = isSerialized (pbStream); } if (isSerialized && pbStream != null) { BufferedInputStream fis = new BufferedInputStream(pbStream); ObjectInputStream ois = new org.openide.util.io.NbObjectInputStream (fis); Object o = ois.readObject(); if (o instanceof Table) { return (Table)o; } } else { retTable.readFromXML(new BufferedInputStream(is), false); return retTable; } } catch (Exception e) { // [PENDING] use multi-arg getMessage (MessageFormat-style) properly here: IOException summaryEx = new IOException (NbBundle.getMessage(DefaultAttributes.class,"EXC_DefAttrReadErr")+": "+folderName); ExternalUtil.copyAnnotation (summaryEx, e); ExternalUtil.exception (summaryEx); } // create empty table, what else return new Table (); } /** Tests whether InputStream contains serialized data * @param pbStream is pushback input stream; tests 4 bytes and then returns them back * @return true if the file has serialized form */ static private final boolean isSerialized (PushbackInputStream pbStream) throws IOException{ int serialPattern [] = {'\u00AC','\u00ED','\u0000','\u0005'};//NOI18N patern for serialized objects byte checkedArray[] = new byte[serialPattern.length]; int unsignedConv = 0; pbStream.read(checkedArray,0,checkedArray.length); pbStream.unread(checkedArray); for (int i = 0; i < checkedArray.length;i++) { unsignedConv = (checkedArray[i] < 0) ? checkedArray[i]+256 : checkedArray[i]; if (serialPattern[i] != unsignedConv) return false; } return true; } /** Remove from cache */ synchronized void removeTable (String name) { getCache ().remove (name); } // // FileUtil.extractJar methods // /** Does the name seems like file with extended attributes? * @param name the name * @return true if so */ static boolean acceptName (String name) { return (name.endsWith (ATTR_NAME_EXT) || name.endsWith (ATTR_NAME_EXT_XML)); } /** Table that hold mapping between files and attributes. * Hold mapping of type (String, Map (String, Object)) */ final static class Table extends HashMap implements Externalizable { static final long serialVersionUID = 2353458763249746934L; /** name of folder we belong to */ private transient String name; /** attributes to belong to */ private transient DefaultAttributes attrs; /** version counting */ private transient int version = 0; /** Constructor */ public Table () { super (11); } /** Attaches to file in attributes */ public void attach (String name, DefaultAttributes attrs) { this.name = name; this.attrs = attrs; } /** Remove itself from the cache if finalized. */ protected void finalize () { // System.out.println ("Finalizing table for: " + name); // NOI18N attrs.removeTable (name); } /** For given file finds requested attribute. * @param fileName name of the file * @param attrName name of the attribute * @return attribute or null (if not found) */ public Object getAttr (String fileName, String attrName) { XMLMapAttr m = (XMLMapAttr)get (fileName); if (m != null) { Object o = null; try { o = m.getAttribute (attrName); } catch (Exception e) { ExternalUtil.annotate (e, "fileName = "+fileName); //NOI18N ExternalUtil.exception (e); } if (o == null) return null; if (!(o instanceof NbMarshalledObject)) return o; NbMarshalledObject mo = (NbMarshalledObject) o; try { return mo == null ? null : mo.get (); } catch (IOException e) { ExternalUtil.log("Cannot load attribute " + attrName + " from " + fileName); // NOI18N ExternalUtil.exception (e); } catch (ClassNotFoundException e) { ExternalUtil.log("Cannot load attribute " + attrName + " from " + fileName); // NOI18N ExternalUtil.exception (e); } } return null; } /** Sets an marshaled attribute to the table. */ final void setMarshalledAttr ( String fileName, String attrName, NbMarshalledObject obj ) { setAttr (fileName, attrName, obj); } /** * Sets an attribute to the table. * New added - for Sandwich project (XML format instead of serialization) . * @param fileName - name of file * @param attrName - name of attribute * @param obj - attribute */ final void setAttr (String fileName, String attrName, Object obj) { Map m = (Map)get (fileName); if (m == null) { m = new XMLMapAttr();//HashMap (7);//XMLMapAttr(); put (fileName, m); } m.put (attrName, obj); // increments the version version++; } /** Enum of attributes for one file. */ public Enumeration attrs (String fileName) { Map m = (Map)get (fileName); if (m == null) { return EmptyEnumeration.EMPTY; } else { HashSet s = new HashSet (m.keySet ()); return Collections.enumeration (s); } } /** * Parses element: <CODE><Attributes version="1.0"></CODE> * @return new instance of subclass (anonymous class)of ElementHandler */ private ElementHandler parseFirstLevel() { ElementHandler elemService = new ElementHandler() { private final String[] ELM_KEYS = {"ATTRIBUTES"};// NOI18N private final String[] MANDAT_ATTR_KEYS = {"VERSION"};// NOI18N public void internalStartElement(String elemName, HashMap mapMandatory,HashMap mapAllowed) throws SAXException { // later can check version } protected String[] getKeys() {return ELM_KEYS;} protected String[] getMandatoryAttrs() {return MANDAT_ATTR_KEYS;} }; return elemService; } /** * Parses element: <CODE><fileobject name="fileName"></CODE> * @param fileName is parsed from XML * @return new instance of subclass (anonymous class)of ElementHandler */ private ElementHandler parseSecondLevel(final StringBuffer fileName) { ElementHandler elemService = new ElementHandler() { private final String[] ELM_KEYS = {"FILEOBJECT"};// NOI18N private final String[] MANDAT_ATTR_KEYS = {"NAME"};// NOI18N public void internalStartElement(String elemName, HashMap mapMandatory,HashMap mapAllowed) throws SAXException { String temp; fileName.delete(0,fileName.length()); temp = (String)mapMandatory.get("NAME");// NOI18N if (temp == null) temp = (String)mapMandatory.get("name");// NOI18N if (temp != null) fileName.append(temp); } public void endElement(String elementName) throws SAXException {} protected String[] getKeys() {return ELM_KEYS;} protected String[] getMandatoryAttrs() {return MANDAT_ATTR_KEYS;} }; return elemService; } /** * Parses element: <CODE><attr StringValue="This is attribute"></CODE> * @param fileName is name of fileobject, which is assigned to attribute * @return new instance of subclass (anonymous class)of ElementHandler */ private ElementHandler parseThirdLevel(final StringBuffer fileName) { ElementHandler elemService = new ElementHandler() { private final String[] ELM_KEYS = {"ATTR"};// NOI18N private final String[] MANDAT_ATTR_KEYS = {"NAME"};// NOI18N public void internalStartElement(String elemName, HashMap mapMandatory,HashMap mapAllowed) throws SAXException { String attrName; if (mapAllowed.isEmpty()) return; attrName = (String)mapMandatory.get("NAME");// NOI18N if (attrName == null) attrName = (String)mapMandatory.get("name");// NOI18N if (attrName == null) return; Iterator it = mapAllowed.entrySet ().iterator (); while (it.hasNext ()) { Map.Entry pair = (Map.Entry) it.next (); if (XMLMapAttr.Attr.isValid((String)pair.getKey()) != -1) { XMLMapAttr.Attr attr = new XMLMapAttr.Attr((String)pair.getKey(),(String)pair.getValue()); setAttr (fileName.toString(), attrName,attr); } } } protected String[] getKeys() {return ELM_KEYS;} protected String[] getMandatoryAttrs() {return MANDAT_ATTR_KEYS;} protected String[] getAllowedAttrs() {return XMLMapAttr.Attr.getAttrTypes ();} //ALLOWED_ATTR_KEYS }; return elemService; } /** Writes itself to XML * @param pw is PrintWriter */ public void writeToXML (PrintWriter pw) /*throws IOException */{ // list of names Iterator it = keySet ().iterator (); XMLMapAttr.writeHeading(pw); while (it.hasNext ()) { String file = (String)it.next (); XMLMapAttr attr = (XMLMapAttr)get (file); if (attr != null && !attr.isEmpty ()) { attr.write(pw, file, " ");// NOI18N } } XMLMapAttr.writeEnding(pw); } /** * Reads itself from XML format * New added - for Sandwich project (XML format instead of serialization) . * @param is input stream (which is parsed) * @return Table */ public void readFromXML (InputStream is, boolean validate) throws SAXException { StringBuffer fileName = new StringBuffer(); ElementHandler[] elmKeyService = {parseFirstLevel(),parseSecondLevel(fileName),parseThirdLevel(fileName)};// String dtd = getClass().getClassLoader().getResource( DTD_PATH ).toExternalForm(); InnerParser parser = new InnerParser(PUBLIC_ID, dtd, elmKeyService); try { parser.parseXML(is, validate); } catch(Exception ioe) { throw (SAXException)ExternalUtil.copyAnnotation (new SAXException (NbBundle.getMessage(DefaultAttributes.class,"EXC_DefAttrReadErr")),ioe); } catch (FactoryConfigurationError fce) { // ??? see http://openide.netbeans.org/servlets/ReadMsg?msgId=340881&listName=dev throw (SAXException)ExternalUtil.copyAnnotation(new SAXException(NbBundle.getMessage(DefaultAttributes.class, "EXC_DefAttrReadErr")), fce); } } /** Writes external. * @param oo * @throws IOException */ public void writeExternal (ObjectOutput oo) throws IOException { // list of names Iterator it = keySet ().iterator (); while (it.hasNext ()) { String file = (String)it.next (); Map attr = (Map)get (file); if (attr != null && !attr.isEmpty ()) { oo.writeObject (file); Iterator entries = attr.entrySet ().iterator (); while (entries.hasNext ()) { Map.Entry entry = (Map.Entry)entries.next (); String key = (String)entry.getKey (); Object value = entry.getValue (); if (key != null && value != null) { oo.writeObject (key); oo.writeObject (value); } } oo.writeObject (null); } } oo.writeObject (null); } /** Reads external. */ public void readExternal (ObjectInput oi) throws IOException, ClassNotFoundException { for (;;) { String file = (String)oi.readObject (); if (file == null) break; for (;;) { String attr = (String)oi.readObject (); if (attr == null) break; Object o = oi.readObject (); // backward compatibility if (o instanceof java.rmi.MarshalledObject) { o = ((java.rmi.MarshalledObject)o).get (); o = new NbMarshalledObject (o); } // end of backward compatibility if (o instanceof NbMarshalledObject) { setAttr (file, attr, o); } } } } } /** Element handler should be used as superclass for future classes. These future classes should be passed * to constructor of InnerParser as Array. (ElementHandler[]). Each subclass of ElementHandler is responsible for * processing one or more elements in XML file. * Each subclass of ElementHandler should overwrite one or more of these methods: * - protected String[] getKeys() * - protected String[] getMandatoryAttrs() * - protected String[] getAllowedAttrs() * - protected void endElement(String name) throws SAXException {} * - protected void characters(char[] ch, int start, int length) throws SAXException {} * - protected void internalStartElement(String elemName, HashMap mapMandatory,HashMap mapAllowed) throws SAXException {} */ static abstract class ElementHandler { private int mandatAttrCount; public void startElement(String elemName, AttributeList attrList) throws SAXException { HashMap mapAllowed =new HashMap(); HashMap mapMandatory =new HashMap(); if ( checkAttributes(attrList,mapMandatory,mapAllowed) == false ) { throw new SAXException (NbBundle.getMessage(DefaultAttributes.class,"XML_InaccurateParam")+": "+elemName);// NOI18N } internalStartElement(elemName, mapMandatory,mapAllowed); } /** Inner parser calls this method to notify this class that start element was parsed (<someelement>) * @param elemName name of element * @param mapMandatory map(String attributeName,String attributeValue) which holds pairs attributeName and attributeValue, which are mandatory for this element * @param mapAllowed map(String attributeName,String attributeValue) which holds pairs attributeName and attributeValue, which are optional for this element * @throws SAXException */ protected void internalStartElement(String elemName, HashMap mapMandatory,HashMap mapAllowed) throws SAXException {} /** Inner parser calls this method to notify this class that there is content between start element and end element * @param ch[] array of characters found between start and end element * @param start is start position in ch[] * @param length is length of content * @throws SAXException */ protected void characters(char[] ch, int start, int length) throws SAXException {} /** Inner parser calls this method to notify this class that end element was parsed * @param elemName name of element * @throws SAXException */ protected void endElement(String elemName) throws SAXException {} private static final String[] EMPTY = {}; /** @return names of elements which this class can process */ protected String[] getKeys() { return EMPTY; } /** @return names of attributes which are checked and are mandatory */ protected String[] getMandatoryAttrs() { return getKeys(); } /** @return names of attributes which are allowed, are expected, but are not mandatory */ protected String[] getAllowedAttrs() { return EMPTY; } private int isMyTag(String name) {return isInArray(name,getKeys());} private int isAllowedAttr(String name) {return isInArray(name,getAllowedAttrs());} private boolean isMandatOK() {return (mandatAttrCount == getMandatoryAttrs().length);} private int isMandatoryAttr(String name) { int retValue = isInArray(name,getMandatoryAttrs()); if ( retValue != -1 ) mandatAttrCount++; return retValue; } private int isInArray(String name,String[] arr) { if (arr == null || name == null) return -1; String correctStr = name.trim(); for( int i = 0; i < arr.length; i++) { if (correctStr.equalsIgnoreCase(arr[i]) == true) return i; } return -1; } private boolean checkAttributes(AttributeList attrList,HashMap mapMandatory,HashMap mapAllowed) { String temp; mandatAttrCount = 0; if (attrList == null) return false; for (int i = 0; i < attrList.getLength(); i++) { if (isMandatoryAttr(attrList.getName(i)) != -1) { temp = attrList.getName(i).toUpperCase(); mapMandatory.put(temp,attrList.getValue(i)); continue; } if ( isAllowedAttr(attrList.getName(i)) != -1) { temp = attrList.getName(i).toUpperCase(); mapAllowed.put(temp,attrList.getValue(i)); continue; } } return isMandatOK(); } } /** Class that can be used to parse XML document (Expects array of ElementHandler clasess). Calls handler methods of ElementHandler clasess. */ static class InnerParser extends HandlerBase { private ElementHandler[] elmKeyService;// = {fileSystemElement(attrStack),folderElement(attrStack),fileElement(attrStack),attrElement(attrStack)}; private String tagInProcess = ""; // NOI18N private String publicId; private String publicURL; InnerParser(String publicId, String publicURL, ElementHandler[] elmKeyService) { this.elmKeyService = elmKeyService; this.publicId = publicId; this.publicURL = publicURL; } /** Starts parsing document, that can be localized by means of uri parameter * @param validate * @param uri adress of document, that will be parsed * @throws ParserConfigurationException * @throws IOException * @throws SAXException */ public void parseXML(String uri, boolean validate) throws IOException, SAXException, ParserConfigurationException, FactoryConfigurationError { Parser parser = getParser(validate); parser.parse(uri); } /** Starts parsing document - if you have document`s InputStream * @param validate * @param is document`s InputStream * @throws ParserConfigurationException * @throws IOException * @throws SAXException */ public void parseXML(InputStream is, boolean validate) throws IOException, SAXException, ParserConfigurationException, FactoryConfigurationError { InputSource iSource = new InputSource(is); Parser parser = getParser(validate); parser.parse(iSource); } private Parser getParser(boolean validate) throws SAXException, ParserConfigurationException, FactoryConfigurationError { Parser parser; SAXParserFactory factory = SAXParserFactory.newInstance (); factory.setValidating (validate); factory.setNamespaceAware(false); parser = factory.newSAXParser ().getParser (); if (parser == null) { throw new InternalError (NbBundle.getMessage(DefaultAttributes.class,"EXC_UnreachableCode")); } // create document handler and register it //parser.setEntityResolver(entityRes); parser.setEntityResolver(this); parser.setDocumentHandler(this);//before new InnerParser() - now this parser.setErrorHandler(this); return parser; } public void error(SAXParseException exception) throws SAXException { throw exception; } public void warning(SAXParseException exception) throws SAXException { throw exception; } public void fatalError(SAXParseException exception) throws SAXException { throw exception; } public void startDocument() throws SAXException {} public void endDocument() throws SAXException {} public void startElement(String name, AttributeList amap) throws SAXException { tagInProcess = name = name.trim(); for (int i = 0; i < elmKeyService.length;i++) { if ( elmKeyService[i].isMyTag(name) != -1) { elmKeyService[i].startElement(name,amap); return; } } throw new SAXException (NbBundle.getMessage(DefaultAttributes.class,"XML_UnknownElement")+" "+name);// NOI18N } public void endElement(String name) throws SAXException { for (int i = 0; i < elmKeyService.length;i++) { if ( elmKeyService[i].isMyTag(name.trim()) != -1) { elmKeyService[i].endElement(name.trim()); return; } } throw new SAXException (NbBundle.getMessage(DefaultAttributes.class,"XML_UnknownElement")+" "+name);// NOI18N } public void characters(char[] ch, int start, int length) throws SAXException { for (int i = 0; i < elmKeyService.length;i++) { if ( elmKeyService[i].isMyTag(tagInProcess) != -1) { elmKeyService[i].characters(ch,start,length); return; } } throw new SAXException (NbBundle.getMessage(DefaultAttributes.class,"XML_UnknownElement")+" "+tagInProcess);// NOI18N } public InputSource resolveEntity(java.lang.String pid,java.lang.String sid) throws SAXException { if (pid != null && pid.equals(publicId)) return new InputSource (publicURL); return new InputSource (sid); } } }