| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.openide.filesystems; |
| |
| import java.beans.PropertyVetoException; |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.ObjectInputStream; |
| import java.io.OutputStream; |
| import java.lang.ref.Reference; |
| import java.lang.ref.WeakReference; |
| import java.net.URI; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Stack; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.openide.util.Enumerations; |
| import org.openide.util.Exceptions; |
| import org.openide.util.NbBundle; |
| import org.openide.util.BaseUtilities; |
| import org.openide.xml.XMLUtil; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| import org.xml.sax.XMLReader; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| /** XML-based filesystem. |
| * <PRE> |
| * Description of format of XML file (which can be parsed by XMLFileSystem) |
| * ================================================================== |
| * Allowed Elements: filesystem,file,folder,attr |
| * |
| * Mandatory attributes: |
| * -for filesystem version=... (e.g. "1.0") |
| * -for file,folder,attr name=.... (e.g.: <folder name="Config">) |
| * -for attr is mandatory one of bytevalue,shortvalue,intvalue,longvalue,floatvalue,doublevalue,boolvalue,charvalue,stringvalue,methodvalue,serialvalue,urlvalue,bundlevalue |
| * |
| * Allowed atributes: |
| * -for file: url=.... (e.g.: <file name="sample.xml" url="file:/c:/sample.xml">) |
| * -for folder,filesystem nothing allowed |
| * |
| * |
| * |
| * Note: file can contain content e.g.: |
| * < file name="sample.java"> |
| * < ![CDATA[ |
| * package org.sample; |
| * import java.io; |
| * ]]> |
| * < /file> |
| * But using url="..." is preferred. |
| * |
| * |
| * This class implements virtual FileSystem. It is special case of FileSystem in XML format. |
| * |
| * Description of this format best ilustrate <a href="http://www.netbeans.org/dtds/filesystem-1_2.dtd">DTD file</a> |
| * that is showed in the following few lines: |
| * < !ELEMENT filesystem (file | folder)*> |
| * < !ATTLIST filesystem version CDATA #REQUIRED> //version not checkked yet |
| * < !ELEMENT folder (file |folder | attr)*> |
| * < !ATTLIST folder name CDATA #REQUIRED> //name of folder |
| * < !ELEMENT file (#PCDATA | attr)*> |
| * < !ATTLIST file name CDATA #REQUIRED> //name of file |
| * < !ATTLIST file url CDATA #IMPLIED> //content of the file can be find at url |
| * < !ELEMENT attr EMPTY> |
| * < !ATTLIST attr name CDATA #REQUIRED> //name of attribute |
| * < !ATTLIST attr bytevalue CDATA #IMPLIED>//the rest - types of attributes |
| * < !ATTLIST attr shortvalue CDATA #IMPLIED> |
| * < !ATTLIST attr intvalue CDATA #IMPLIED> |
| * < !ATTLIST attribute 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 newvalue CDATA #IMPLIED> |
| * < !ATTLIST attr serialvalue CDATA #IMPLIED> |
| * < !ATTLIST attr urlvalue CDATA #IMPLIED> |
| * < !ATTLIST attr bundlevalue CDATA #IMPLIED> <!-- since version 7.10 --> |
| * </PRE> |
| * |
| * <p> |
| * The <em>methodvalue</em> attribute can be in form of <code>pgk1.pkg2.ClassName.methodName</code> |
| * which should point to existing class with static method usually having no, one |
| * or two arguments. This method does not need to be public or in public class, if |
| * the filesystem has permissions to call the method. The method can take one |
| * of the following signatures: |
| * <pre> |
| * static Value methodName(); |
| * static Value methodName(FileObject fo); |
| * static Value methodName(FileObject fo, String attrName); |
| * static Value methodName(Map<String,Object> attrs); // since 7.0 |
| * static Value methodName(Map<String,Object> attrs, String attrName); // since 7.0 |
| * </pre> |
| * where <code>Value</code> can be any java type. |
| * <p> |
| * The <em>newvalue</em> should identify (using fully qualified name) a class |
| * with a no-arguments constructor. The constructor does not need to be public, |
| * just the filesystem has to have a permission to invoke it. |
| * <div class="nonnormative"> |
| * Existing implementations of filesystems that read XML layers, do not cache |
| * instances returned from <em>newvalue</em> attributes and always return new ones.</div> |
| * |
| * <p> |
| * If you are interested just in the Class of an attribute, but |
| * without creating its instance, use <code>fileObject.getAttribute("class:attrName")</code>. |
| * This instructs the XMLFileSystem to scan its XML files for definition of <code>attrName</code> |
| * attribute and <i>guess</i> its class. The <i>guessing</i> is usually easy, |
| * just for <code>methodvalue</code> types, the system needs to use |
| * some kind of heuristic: it locates the appropriate factory method and returns |
| * its return type. This may not be the actual type of the returned object at the end, |
| * but it seems as the best guess without instantiating it. |
| |
| * @author Radek Matous |
| */ |
| public final class XMLFileSystem extends AbstractFileSystem { |
| private static final long serialVersionUID = 28974107313702326L; |
| |
| // <?xml version="1.0"?> |
| // <!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.0//EN" "http://www.netbeans.org/dtds/filesystem-1_0.dtd"> |
| // <filesystem>...</filesystem> |
| private static final Map<String, String> DTD_MAP = new HashMap<String, String>(); |
| |
| static { |
| DTD_MAP.put("-//NetBeans//DTD Filesystem 1.0//EN", "org/openide/filesystems/filesystem.dtd"); //NOI18N |
| DTD_MAP.put("-//NetBeans//DTD Filesystem 1.1//EN", "org/openide/filesystems/filesystem1_1.dtd"); //NOI18N |
| DTD_MAP.put("-//NetBeans//DTD Filesystem 1.2//EN", "org/openide/filesystems/filesystem1_2.dtd"); //NOI18N |
| } |
| |
| /** Url location of XML document */ |
| private URL[] urlsToXml = new URL[] { }; |
| private transient FileObjRef<? extends FileObject> rootRef; |
| |
| /** Constructor. Creates new XMLFileSystem */ |
| public XMLFileSystem() { |
| Impl impl = new Impl(this); |
| this.list = impl; |
| this.info = impl; |
| this.change = impl; |
| this.attr = impl; |
| } |
| |
| /** Constructor. Creates new XMLFileSystem. |
| * @param uri to file with definition of XMLFileSystem |
| * @throws SAXException if parsing is not succesful |
| */ |
| public XMLFileSystem(String uri) throws SAXException { |
| this(); |
| |
| if (uri == null) { |
| throw new NullPointerException("Null uri"); // NOI18N |
| } |
| |
| try { |
| setXmlUrl(new URL(uri)); |
| } catch (Exception e) { |
| throw (SAXException) ExternalUtil.copyAnnotation(new SAXException(e.getMessage()), e); |
| } |
| } |
| |
| /** Constructor. Creates new XMLFileSystem. |
| * @param url to definition of XMLFileSystem |
| * @throws SAXException if parsing not succesful |
| */ |
| public XMLFileSystem(URL url) throws SAXException { |
| this(); |
| |
| if (url == null) { |
| throw new NullPointerException("Null url"); // NOI18N |
| } |
| |
| try { |
| setXmlUrl(url); |
| } catch (Exception e) { |
| throw (SAXException) ExternalUtil.copyAnnotation(new SAXException(e.getMessage()), e); |
| } |
| } |
| |
| /** Getter of url field. |
| * @return URL associated with XMLFileSystem or null if no URL was set. |
| * In case that definition of XMLFileSystem |
| * is merged from more URLs than the first is returned. |
| */ |
| public URL getXmlUrl() { |
| return (urlsToXml.length > 0) ? urlsToXml[0] : null; |
| } |
| |
| /** |
| * Setter of url field. Set name of the XML file. |
| * @param url with definition of XMLFileSystem |
| * @throws PropertyVetoException if the change is not allowed by a listener |
| * @throws IOException if the file is not valid |
| */ |
| public synchronized void setXmlUrl(URL url) throws IOException, PropertyVetoException { |
| setXmlUrl(url, false); |
| } |
| |
| /** |
| * Setter of url field. Set name of the XML file. |
| * @param url with definition of XMLFileSystem |
| * @param validate sets validating of SAXParser |
| * @throws PropertyVetoException if the change is not allowed by a listener |
| * @throws IOException if the file is not valid |
| */ |
| public void setXmlUrl(URL url, boolean validate) throws IOException, PropertyVetoException { |
| try { |
| beginAtomicAction(); |
| |
| synchronized (this) { |
| setXmlUrls(new URL[] { url }, validate); |
| } |
| } finally { |
| finishAtomicAction(); |
| } |
| } |
| |
| /** Getter of url fields. |
| * @return URLs associated with XMLFileSystem. |
| * @since 1.14 |
| */ |
| public URL[] getXmlUrls() { |
| return urlsToXml; |
| } |
| |
| /** Setter of url fields. First URL in array sets name of XMLFileSystem. |
| * If more then one url in array of URLs defines the same FileObject, then |
| * url with lower index in array overrides (means content and attributes) the other. |
| * @param urls array of definitions (in xml form) of XMLFileSystem |
| * @throws IOException if the file is not valid |
| * @throws PropertyVetoException if the change is not allowed by a listener |
| * @since 1.14 |
| */ |
| public void setXmlUrls(URL[] urls) throws IOException, PropertyVetoException { |
| try { |
| beginAtomicAction(); |
| |
| synchronized (this) { |
| setXmlUrls(urls, false); |
| } |
| } finally { |
| finishAtomicAction(); |
| } |
| } |
| |
| @SuppressWarnings("deprecation") // need to set it for compat |
| private void _setSystemName(String s) throws PropertyVetoException { |
| setSystemName(s); |
| } |
| |
| private synchronized void setXmlUrls(URL[] urls, boolean validate) |
| throws IOException, PropertyVetoException { |
| if (urls == null) { |
| throw new NullPointerException("Null URL list"); // NOI18N |
| } |
| Collection<URL> asList = Arrays.asList(urls); |
| if (asList.contains(null)) { |
| throw new NullPointerException("Null URL list member: " + asList); // NOI18N |
| } |
| |
| ResourceElem rootElem; |
| String oldDisplayName = getDisplayName(); |
| |
| if (urls.length == 0) { |
| urlsToXml = urls; |
| refreshChildrenInAtomicAction((AbstractFolder) getRoot(), rootElem = new ResourceElem(true, urls)); // NOI18N |
| rootElem = null; |
| |
| return; |
| } |
| |
| Handler handler = new Handler(DTD_MAP, rootElem = new ResourceElem(true, urls), validate); // NOI18N |
| |
| try { |
| _setSystemName("XML_" + urls[0].toExternalForm().replace('/','-')); // NOI18N |
| } catch (PropertyVetoException pvx) { |
| rootElem = null; |
| throw pvx; |
| } |
| |
| URL act = null; |
| |
| try { |
| XMLReader xp = XMLUtil.createXMLReader(validate, false); |
| xp.setEntityResolver(handler); |
| xp.setContentHandler(handler); |
| xp.setErrorHandler(handler); |
| |
| for (int index = 0; index < urls.length; index++) { |
| act = urls[index]; |
| handler.urlContext = act; |
| |
| String systemId = act.toExternalForm(); |
| |
| xp.parse(systemId); |
| } |
| urlsToXml = urls.clone(); |
| refreshChildrenInAtomicAction((AbstractFolder) getRoot(), rootElem); |
| } catch (IOException iox) { |
| Exceptions.attachMessage(iox, Arrays.toString(urls)); |
| throw iox; |
| } catch (Exception e) { |
| throw (IOException) new IOException(act + ": " + e.toString()).initCause(e); // NOI18N |
| } finally { |
| rootElem = null; |
| } |
| |
| firePropertyChange(PROP_DISPLAY_NAME, oldDisplayName, getDisplayName()); |
| } |
| |
| /** |
| * @return if value of lastModified should be cached |
| */ |
| @Override |
| boolean isLastModifiedCacheEnabled() { |
| return false; |
| } |
| |
| /** |
| * Test if the file is folder or contains data. |
| * @param name name of the file |
| * @return true if the file is folder, false otherwise |
| */ |
| private boolean isFolder(String name) { |
| Reference<? extends FileObject> ref = findReference(name); |
| |
| if ((ref != null) && (ref instanceof FileObjRef)) { |
| return ((FileObjRef) ref).isFolder(); |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Get input stream. |
| * |
| * @param name the file to test |
| * @return an input stream to read the contents of this file |
| * @exception FileNotFoundException if the file does not exists or is invalid |
| */ |
| private InputStream getInputStream(String name) throws java.io.FileNotFoundException { |
| Reference<? extends FileObject> ref = findReference(name); |
| |
| if ((ref != null) && (ref instanceof FileObjRef)) { |
| return (((FileObjRef) ref).getInputStream(name)); |
| } |
| |
| throw new FileNotFoundException(NbBundle.getMessage(XMLFileSystem.class, "EXC_CanntRead", name)); // NOI18N |
| } |
| |
| /** |
| * Get URL. |
| * |
| * @param name of the file to test |
| * @return URL of resource or null |
| * @exception FileNotFoundException if the file does not exists or is invalid |
| */ |
| URL getURL(String name) throws java.io.FileNotFoundException { |
| Reference<? extends FileObject> ref = findReference(name); |
| |
| if ((ref != null) && (ref instanceof FileObjRef)) { |
| return ((FileObjRef) ref).createAbsoluteUrl(name); |
| } |
| |
| throw new FileNotFoundException(NbBundle.getMessage(XMLFileSystem.class, "EXC_CanntRead", name)); // NOI18N |
| } |
| |
| /** Get size of stream*/ |
| private long getSize(String name) { |
| Reference<? extends FileObject> ref = findReference(name); |
| |
| if ((ref != null) && (ref instanceof FileObjRef)) { |
| return ((FileObjRef) ref).getSize(name); |
| } |
| |
| return 0; |
| } |
| |
| /**returns value of last modification*/ |
| private java.util.Date lastModified(String name) { |
| Reference<? extends FileObject> ref = findReference(name); |
| |
| if ((ref != null) && (ref instanceof FileObjRef)) { |
| return ((FileObjRef) ref).lastModified(name); |
| } |
| |
| /**return value for resource that does not exists*/ |
| return new Date(0); |
| } |
| |
| /** Provides a name for the system that can be presented to the user. |
| * @return user presentable name of the filesystem |
| */ |
| public String getDisplayName() { |
| if ((urlsToXml.length == 0) || (urlsToXml[0] == null) || (urlsToXml[0].toExternalForm().length() == 0)) { |
| return NbBundle.getMessage(XMLFileSystem.class, "XML_NotValidXMLFileSystem"); // NOI18N |
| } |
| |
| return "XML:" + urlsToXml[0].toExternalForm().trim(); // NOI18N |
| } |
| |
| /** Test if the filesystem is read-only or not. |
| * @return true if the system is read-only |
| */ |
| public boolean isReadOnly() { |
| return true; |
| } |
| |
| /** Initializes the root of FS. |
| */ |
| private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { |
| //ois.defaultReadObject (); |
| ObjectInputStream.GetField fields = ois.readFields(); |
| URL[] urls = (URL[]) fields.get("urlsToXml", null); // NOI18N |
| |
| if (urls == null) { |
| urls = new URL[1]; |
| urls[0] = (URL) fields.get("uriId", null); // NOI18N |
| if (urls[0] == null) { |
| throw new IOException("missing uriId"); // NOI18N |
| } |
| } |
| |
| try { |
| setXmlUrls(urls); |
| } catch (PropertyVetoException ex) { |
| IOException x = new IOException(ex.getMessage()); |
| ExternalUtil.copyAnnotation(x, ex); |
| throw x; |
| } |
| } |
| |
| /** Notifies this filesystem that it has been added to the repository. |
| * Various initialization tasks should go here. Default implementation is noop. |
| */ |
| @Override |
| public void addNotify() { |
| } |
| |
| /** Notifies this filesystem that it has been removed from the repository. |
| * Concrete filesystem implementations should perform clean-up here. |
| * Default implementation is noop. |
| */ |
| @Override |
| public void removeNotify() { |
| } |
| |
| @Override |
| protected <T extends FileObject> Reference<T> createReference(T fo) { |
| return new FileObjRef<T>(fo); |
| } |
| |
| private void refreshChildrenInAtomicAction(AbstractFolder fo, ResourceElem resElem) { |
| try { |
| beginAtomicAction(); |
| Collection<AbstractFolder> oldChildren = new HashSet<AbstractFolder>(Collections.list(fo.existingSubFiles(true))); |
| |
| refreshChildren(fo, resElem); |
| |
| Collection<AbstractFolder> newChildren = Collections.list(fo.existingSubFiles(true)); |
| oldChildren.removeAll(newChildren); |
| for (Iterator<AbstractFolder> it = oldChildren.iterator(); it.hasNext();) { |
| AbstractFileObject invalid = (AbstractFileObject)it.next(); |
| if (invalid.validFlag) { |
| invalid.validFlag = false; |
| invalid.fileDeleted0(new FileEvent(invalid)); |
| } |
| } |
| } finally { |
| finishAtomicAction(); |
| } |
| } |
| |
| /** refreshes children recursively.*/ |
| private void refreshChildren(AbstractFolder fo, ResourceElem resElem) { |
| if (fo.isRoot()) { |
| initializeReference(rootRef = new FileObjRef<AbstractFolder>(fo), resElem); |
| } |
| |
| java.util.List<String> nameList = resElem.getChildren(); |
| String[] names = new String[nameList.size()]; |
| ResourceElem[] children = new ResourceElem[names.length]; |
| |
| nameList.toArray(names); |
| |
| for (int i = 0; i < names.length; i++) |
| children[i] = resElem.getChild(names[i]); |
| |
| fo.refresh(null, null, true, true, names); |
| |
| for (int i = 0; i < children.length; i++) { |
| AbstractFolder fo2 = (AbstractFolder) fo.getFileObject(names[i]); |
| FileObjRef currentRef = (FileObjRef) findReference(fo2.getPath()); |
| int diff = initializeReference(currentRef, children[i]); |
| fo2.lastModified(); |
| |
| if (fo2.isFolder()) { |
| refreshChildren(fo2, children[i]); |
| } else { |
| if ((diff & 0x01) != 0) { |
| fo2.fileChanged0(new FileEvent(fo2)); |
| } else { |
| if ((diff & 0x02) != 0) { |
| fo2.fileAttributeChanged0(new FileAttributeEvent(fo2, null, null, null)); |
| } |
| } |
| } |
| } |
| } |
| |
| /** Initialize a reference with parsed element. |
| * @param currentRef the reference |
| * @param resElem the new element |
| * @return ret&0x01 if content changed, ret&0x02 if attributes changed. |
| */ |
| private int initializeReference(FileObjRef currentRef, ResourceElem resElem) { |
| if (!currentRef.isInitialized()) { |
| currentRef.initialize(resElem); |
| return 0x00; |
| } else { |
| boolean attrDiff = currentRef.attacheAttrs(resElem.getAttr(false)); |
| currentRef.setUrlContext(resElem.getUrlContext()); |
| |
| boolean diff = false; |
| if (resElem.getContent() != null) { |
| diff = !(currentRef.content instanceof byte[]) || !java.util.Arrays.equals((byte[])currentRef.content, resElem.getContent()); |
| currentRef.content = resElem.getContent(); |
| } else if (resElem.getURI() != null) { |
| diff = !resElem.getURI().equals(currentRef.content); |
| currentRef.content = resElem.getURI(); |
| } |
| |
| return (diff ? 0x01 : 0x00) + (attrDiff ? 0x02 : 0x00); |
| } |
| } |
| |
| /** Temporary hierarchical structure of resources. Used while parsing.*/ |
| private static class ResourceElem { |
| private java.util.List<ResourceElem> children; |
| private java.util.List<String> names; |
| private byte[] content; |
| private int weight = Integer.MIN_VALUE; // initially unset |
| private java.util.List<URL> urlContext = new ArrayList<URL>(); |
| private XMLMapAttr foAttrs; |
| private boolean isFolder; |
| private String uri; |
| |
| public ResourceElem(boolean isFolder, URL... urlContext) { |
| this.isFolder = isFolder; |
| this.urlContext.addAll(Arrays.asList(urlContext)); |
| |
| if (isFolder) { |
| children = new ArrayList<ResourceElem>(); |
| names = new ArrayList<String>(); |
| } |
| } |
| |
| ResourceElem addChild(String name, ResourceElem child) { |
| if (!isFolder) { |
| children = new ArrayList<ResourceElem>(); |
| names = new ArrayList<String>(); |
| content = null; |
| isFolder = true; |
| } |
| assert name != null && name.indexOf("/") == -1:(child.isFolder ? "<folder name=":"<file name=")+name+" ...";//NOI18N |
| |
| ResourceElem retVal = child; |
| int idx = names.indexOf(name); |
| |
| if (idx == -1) { |
| names.add(name); |
| children.add(child); |
| } else { |
| // already exists |
| retVal = children.get(idx); |
| Set<URL> mergedContext = new HashSet<URL>(); |
| mergedContext.addAll(Arrays.asList(retVal.getUrlContext())); |
| mergedContext.addAll(Arrays.asList(child.getUrlContext())); |
| retVal.setUrlContext(mergedContext); |
| } |
| |
| return retVal; |
| } |
| |
| java.util.List<String> getChildren() { |
| return names; |
| } |
| |
| ResourceElem getChild(String name) { |
| return children.get(names.indexOf(name)); |
| } |
| |
| XMLMapAttr getAttr(boolean create) { |
| if (create && (foAttrs == null)) { |
| foAttrs = new XMLMapAttr(); |
| } |
| |
| return foAttrs; |
| } |
| |
| byte[] getContent() { |
| return content; |
| } |
| |
| URL[] getUrlContext() { |
| URL[] retVal = new URL[urlContext.size()]; |
| urlContext.toArray(retVal); |
| |
| return retVal; |
| } |
| |
| void setUrlContext(Collection<? extends URL> context) { |
| if (context != null) { |
| urlContext.clear(); |
| urlContext.addAll(context); |
| } |
| } |
| |
| String getURI() { |
| return uri; |
| } |
| |
| void setContent(byte[] content, String uri, int weight) { |
| if (weight > this.weight) { |
| this.weight = weight; |
| this.content = content != null ? content.clone() : null; |
| this.uri = uri; |
| } |
| } |
| |
| boolean isFolder() { |
| return isFolder; |
| } |
| } |
| |
| //private void debugInfo(String dbgStr) { System.out.println(dbgStr);} |
| |
| /** Implementation of all interfaces List, Change, Info and Attr |
| * that delegates to XMLFileSystem |
| */ |
| public static class Impl extends Object implements AbstractFileSystem.List, AbstractFileSystem.Info, |
| AbstractFileSystem.Change, AbstractFileSystem.Attr { |
| /** generated Serialized Version UID */ |
| private static final long serialVersionUID = -67233358102597232L; |
| |
| /** the pointer to filesystem */ |
| private XMLFileSystem fs; |
| |
| /** Constructor. |
| * @param fs the filesystem to delegate to |
| */ |
| public Impl(XMLFileSystem fs) { |
| this.fs = fs; |
| } |
| |
| public String[] children(String name) { |
| FileObject fo2name; |
| |
| if ((fo2name = fs.findResource(name)) == null) { |
| return new String[] { }; |
| } |
| |
| synchronized (fo2name) { |
| return ((AbstractFolder) fo2name).getChildrenArray(); |
| } |
| } |
| |
| // |
| // Change |
| // |
| |
| public void createFolder(String name) throws java.io.IOException { |
| throw new IOException(); |
| } |
| |
| public void createData(String name) throws IOException { |
| throw new IOException(); |
| } |
| |
| public void rename(String oldName, String newName) |
| throws IOException { |
| throw new IOException(); |
| } |
| |
| public void delete(String name) throws IOException { |
| throw new IOException(); |
| } |
| |
| // Info |
| |
| public java.util.Date lastModified(String name) { |
| return fs.lastModified(name); |
| } |
| |
| public boolean folder(String name) { |
| return fs.isFolder(name); |
| } |
| |
| public boolean readOnly(String name) { |
| return true; |
| } |
| |
| public String mimeType(String name) { |
| return null; |
| } |
| |
| public long size(String name) { |
| if (fs.isFolder(name)) { |
| return 0; |
| } |
| |
| return fs.getSize(name); |
| } |
| |
| public InputStream inputStream(String name) throws java.io.FileNotFoundException { |
| InputStream is = fs.getInputStream(name); |
| |
| if (is == null) { |
| throw new java.io.FileNotFoundException(name); |
| } |
| |
| return is; |
| } |
| |
| public OutputStream outputStream(String name) throws java.io.IOException { |
| throw new IOException(); |
| } |
| |
| public void lock(String name) throws IOException { |
| throw new FSException(NbBundle.getMessage(XMLFileSystem.class, "EXC_CannotLock", null, null, name)); |
| } |
| |
| public void unlock(String name) { |
| } |
| |
| public void markUnimportant(String name) { |
| } |
| |
| public Object readAttribute(String name, String attrName) { |
| FileObjRef ref = (FileObjRef) fs.findReference(name); |
| |
| if ((ref == null) && (name.length() == 0) && (fs.rootRef != null)) { |
| ref = fs.rootRef; |
| } |
| |
| if (ref == null) { |
| return null; |
| } |
| |
| return ref.readAttribute(attrName); |
| } |
| |
| public void writeAttribute(String name, String attrName, Object value) |
| throws IOException { |
| throw new IOException(); |
| } |
| |
| public Enumeration<String> attributes(String name) { |
| FileObjRef<? extends FileObject> ref = (FileObjRef<? extends FileObject>) fs.findReference(name); |
| |
| if ((ref == null) && (name.length() == 0) && (fs.rootRef != null)) { |
| ref = fs.rootRef; |
| } |
| |
| if (ref == null) { |
| return Enumerations.empty(); |
| } |
| |
| return ref.attributes(); |
| } |
| |
| public void renameAttributes(String oldName, String newName) { |
| } |
| |
| public void deleteAttributes(String name) { |
| } |
| } |
| |
| /** Strong reference to FileObject. To FileObject may be attached attributes (XMLMapAttr) |
| * and info about if it is folder or not. |
| */ |
| private static class FileObjRef<T extends FileObject> extends WeakReference<T> { |
| private T fo; |
| private Object content; |
| private XMLMapAttr foAttrs; |
| byte isFolder = -1; |
| Object urlContext = null; |
| |
| public FileObjRef(T fo) { |
| super(fo); |
| this.fo = fo; |
| } |
| |
| public boolean isInitialized() { |
| return (isFolder != -1); |
| } |
| |
| public void initialize(ResourceElem res) { |
| content = res.getContent(); |
| |
| XMLMapAttr tmp = res.getAttr(false); |
| |
| if ((tmp != null) && !tmp.isEmpty()) { |
| foAttrs = tmp; |
| } |
| |
| isFolder = (byte) (res.isFolder() ? 1 : 0); |
| |
| if (content == null) { |
| content = res.getURI(); |
| } |
| |
| setUrlContext(res.getUrlContext()); |
| } |
| |
| public boolean isFolder() { |
| return (isFolder == 1); |
| } |
| |
| /** @return true if at lest one attribute changed */ |
| public boolean attacheAttrs(XMLMapAttr attrs) { |
| if ((attrs == null) || attrs.isEmpty()) { |
| return false; |
| } |
| |
| if (foAttrs == null) { |
| foAttrs = new XMLMapAttr(); |
| } |
| |
| Iterator<Map.Entry> it = attrs.entrySet().iterator(); |
| boolean ch = false; |
| while (it.hasNext()) { |
| Map.Entry attrEntry = it.next(); |
| Object prev = foAttrs.put(attrEntry.getKey(), attrEntry.getValue()); |
| |
| ch |= (prev == null && attrEntry.getValue() != null) || !prev.equals(attrEntry.getValue()); |
| } |
| |
| return ch; |
| } |
| |
| public void setUrlContext(URL[] ctx) { |
| if (ctx.length > 0) { |
| if (ctx.length > 1) { |
| urlContext = ctx; |
| } else { |
| urlContext = ctx[0]; |
| } |
| } |
| } |
| |
| public Enumeration<String> attributes() { |
| if (foAttrs == null) { |
| return Enumerations.empty(); |
| } else { |
| Set<String> s = new HashSet<String>(foAttrs.keySet()); |
| |
| return Collections.enumeration(s); |
| } |
| } |
| |
| private URL[] getLayers() { |
| if (urlContext == null) { |
| return null; |
| } |
| |
| if (urlContext instanceof URL[]) { |
| return (URL[]) urlContext; |
| } |
| |
| return new URL[] { (URL) urlContext }; |
| } |
| |
| public Object readAttribute(String attrName) { |
| if (attrName.equals("layers")) { //NOI18N |
| |
| return getLayers(); |
| } |
| |
| if (foAttrs == null) { |
| return null; |
| } |
| |
| FileObject topFO = MultiFileObject.attrAskedFileObject.get(); |
| FileObject f = (topFO == null) ? fo : topFO; |
| |
| MultiFileObject.attrAskedFileObject.set(null); |
| |
| try { |
| Object[] objs = new Object[] { f, attrName }; |
| |
| return foAttrs.get(attrName, objs); |
| } finally { |
| MultiFileObject.attrAskedFileObject.set(topFO); |
| } |
| } |
| |
| /** |
| * Get input stream. |
| * |
| * @return an input stream to read the contents of this file |
| * @param context |
| * @param name the file to test |
| * @exception FileNotFoundException if the file does not exists or is invalid */ |
| public InputStream getInputStream(String name) |
| throws java.io.FileNotFoundException { |
| InputStream is = null; |
| IOException ex = null; |
| |
| if (content == null) { |
| return new ByteArrayInputStream(new byte[] { }); |
| } |
| |
| if (content instanceof String) { |
| try { |
| is = createAbsoluteConnection(name).getInputStream(); |
| } catch (IOException x) { |
| ex = x; |
| } |
| } |
| |
| if (content instanceof byte[]) { |
| is = new ByteArrayInputStream((byte[]) content); |
| } |
| |
| if (is == null) { |
| FileNotFoundException fnfe = new FileNotFoundException(name); |
| if (ex != null) { |
| fnfe.initCause(ex); |
| } |
| throw fnfe; |
| } |
| |
| return is; |
| } |
| |
| private URLConnection createAbsoluteConnection(String name) throws FileNotFoundException { |
| URLConnection conn = null; |
| IOException ex = null; |
| |
| if (!(content instanceof String)) { |
| return null; |
| } |
| String uri = (String) content; |
| URL[] uc = getLayers(); |
| if (uc != null) { |
| for (URL u : uc) { |
| try { |
| conn = new URL(u, uri).openConnection(); |
| conn.connect(); |
| break; |
| } catch (IOException iox) { |
| conn = null; |
| ex = iox; |
| } |
| |
| } |
| } |
| if (conn == null) { |
| try { |
| conn = new URL(uri).openConnection(); |
| conn.connect(); |
| } catch (IOException iox) { |
| FileNotFoundException x = new FileNotFoundException(name); |
| ExternalUtil.copyAnnotation(x, iox); |
| throw x; |
| } |
| } |
| return conn; |
| } |
| |
| private URL createAbsoluteUrl(String name) throws java.io.FileNotFoundException { |
| if (!(content instanceof String)) { |
| return null; |
| } |
| |
| String uri = (String) content; |
| |
| try { |
| URL[] uc = getLayers(); |
| URL retVal = ((uc == null) || (uc.length == 0)) ? new URL(uri) : new URL(uc[0], uri); |
| |
| return retVal; |
| } catch (IOException ex) { // neni koser osetreni - RM |
| |
| FileNotFoundException x = new FileNotFoundException(name); |
| ExternalUtil.copyAnnotation(x, ex); |
| throw x; |
| } |
| } |
| |
| public long getSize(String name) { |
| if (content == null) { |
| return 0; |
| } |
| |
| if (content instanceof byte[]) { |
| return ((byte[]) content).length; |
| } |
| |
| if (content instanceof String) { |
| try { |
| URLConnection urlConnection = createAbsoluteConnection(name); |
| try { |
| return urlConnection.getContentLength(); |
| } finally { |
| urlConnection.getInputStream().close(); |
| } |
| } catch (IOException iex) { |
| } |
| } |
| |
| return 0; |
| } |
| |
| private static final Set<String> NETWORK_PROTOCOLS = new HashSet<String>(Arrays.asList("http", "https", "ftp")); // NOI18N |
| /** One item cache to eliminate File.lastModified queries (see #160390). */ |
| private static File lastFile = null; |
| private static Date lastFileDate = null; |
| |
| public Date lastModified(String name) { |
| URL url = null; |
| Date retval = null; |
| |
| if ((content == null) || !(content instanceof String)) { |
| URL[] all = getLayers(); |
| url = (all != null && all.length > 0) ? all[0] : null; |
| } else { |
| try { |
| url = createAbsoluteUrl(name); |
| } catch (IOException iex) { |
| url = null; |
| } |
| } |
| |
| if (url != null) { |
| String protocol = url.getProtocol(); |
| if ("jar".equals(protocol)) {//NOI18N |
| URL tmp = FileUtil.getArchiveFile(url); |
| url = (tmp != null) ? tmp : url; |
| protocol = url.getProtocol(); |
| } |
| |
| if ("file".equals(protocol)) { //NOI18N |
| try { |
| File f = BaseUtilities.toFile(URI.create(url.toExternalForm())); |
| if (!f.equals(lastFile)) { |
| lastFile = f; |
| lastFileDate = new Date(f.lastModified()); |
| } |
| retval = lastFileDate; |
| } catch (IllegalArgumentException x) { |
| Logger.getLogger(XMLFileSystem.class.getName()).log(Level.FINE, "#121777: " + url, x); |
| } |
| } else /* #96928 */ if (!NETWORK_PROTOCOLS.contains(protocol)) { |
| retval = timeFromDateHeaderField(url); |
| } |
| } |
| |
| if (retval == null) { |
| retval = new Date(0); |
| } |
| |
| return retval; |
| } |
| |
| private java.util.Date timeFromDateHeaderField(URL url) { |
| URLConnection urlConn; |
| |
| try { |
| urlConn = url.openConnection(); |
| return new Date(urlConn.getLastModified()); |
| } catch (IOException ie) { |
| return new java.util.Date(0); |
| } |
| } |
| |
| } |
| |
| /** Class that can be used to parse XML document (Expects array of ElementHandler clasess). Calls handler methods of ElementHandler clasess. |
| */ |
| static class Handler extends DefaultHandler { |
| private static final int FOLDER_CODE = "folder".hashCode(); // NOI18N |
| private static final int FILE_CODE = "file".hashCode(); // NOI18N |
| private static final int ATTR_CODE = "attr".hashCode(); // NOI18N |
| private ResourceElem rootElem; |
| private boolean validate = false; |
| Stack<ResourceElem> resElemStack = new Stack<ResourceElem>(); |
| Stack<String> elementStack = new Stack<String>(); |
| URL urlContext; |
| private Map dtdMap; |
| private ResourceElem topRE; |
| private StringBuffer pcdata = new StringBuffer(); |
| private int weight; |
| private String uri; |
| |
| Handler(Map dtdMap, ResourceElem rootElem, boolean validate) { |
| this.dtdMap = dtdMap; |
| this.rootElem = rootElem; |
| this.validate = validate; |
| } |
| |
| @Override |
| public void error(SAXParseException exception) |
| throws SAXException { |
| throw exception; |
| } |
| |
| @Override |
| public void warning(SAXParseException exception) |
| throws SAXException { |
| throw exception; |
| } |
| |
| @Override |
| public void fatalError(SAXParseException exception) |
| throws SAXException { |
| throw exception; |
| } |
| |
| @Override |
| public void startElement(String xmluri, String lname, String name, Attributes amap) |
| throws SAXException { |
| int controlCode = name.hashCode(); |
| |
| elementStack.push(name); |
| |
| String foName = amap.getValue("name"); // NOI18N |
| |
| if (controlCode == FOLDER_CODE) { |
| if (foName == null) { |
| throw new SAXException(NbBundle.getMessage(XMLFileSystem.class, "XML_MisssingAttr")); // NOI18N |
| } |
| |
| ResourceElem newRes = new ResourceElem(true, urlContext); |
| |
| topRE = topRE.addChild(foName, newRes); |
| resElemStack.push(topRE); |
| |
| return; |
| } |
| |
| if (controlCode == FILE_CODE) { |
| if (foName == null) { |
| throw new SAXException(NbBundle.getMessage(XMLFileSystem.class, "XML_MisssingAttr")); // NOI18N |
| } |
| |
| foName = foName.intern(); |
| |
| uri = null; |
| |
| if (amap.getLength() > 1) { |
| uri = amap.getValue("url"); // NOI18N |
| } |
| |
| ResourceElem newRes = new ResourceElem(false, urlContext); |
| |
| topRE = topRE.addChild(foName, newRes); |
| resElemStack.push(topRE); |
| |
| pcdata.setLength(0); |
| weight = 0; |
| |
| return; |
| } |
| |
| if (controlCode == ATTR_CODE) { |
| if (foName == null) { |
| throw new SAXException(NbBundle.getMessage(XMLFileSystem.class, "XML_MisssingAttr")); // NOI18N |
| } |
| |
| int len = amap.getLength(); |
| |
| for (int i = 0; i < len; i++) { |
| String key = amap.getQName(i); |
| String value = amap.getValue(i); |
| |
| if (XMLMapAttr.Attr.isValid(key) != -1) { |
| XMLMapAttr.Attr attr = XMLMapAttr.createAttributeAndDecode(key, value); |
| XMLMapAttr attrMap = topRE.getAttr(true); |
| Object retVal = attrMap.put(foName, attr); |
| |
| if (retVal != null) { |
| attrMap.put(foName, retVal); |
| } |
| } |
| } |
| |
| if (MultiFileObject.WEIGHT_ATTRIBUTE.equals(amap.getValue("name"))) { |
| String weightS = amap.getValue("intvalue"); |
| if (weightS != null) { |
| try { |
| weight = Integer.parseInt(weightS); |
| } catch (NumberFormatException x) { |
| // ignore here (other places should report it) |
| } |
| } |
| } |
| |
| return; |
| } |
| } |
| |
| @Override |
| public void endElement(String uri, String lname, String name) throws SAXException { |
| if ((elementStack.peek().hashCode() == FILE_CODE) && !topRE.isFolder()) { |
| String string = pcdata.toString().trim(); |
| |
| topRE.setContent(string.length() > 0 ? string.getBytes() : null, this.uri, weight); |
| |
| pcdata.setLength(0); |
| } |
| |
| int controlCode = name.hashCode(); |
| |
| elementStack.pop(); |
| |
| if ((controlCode == FOLDER_CODE) || (controlCode == FILE_CODE)) { |
| resElemStack.pop(); |
| topRE = resElemStack.peek(); |
| |
| return; |
| } |
| } |
| |
| @Override |
| public void characters(char[] ch, int start, int length) |
| throws SAXException { |
| if (elementStack.peek().hashCode() != FILE_CODE) { |
| return; |
| } |
| |
| if (topRE.isFolder()) { |
| return; |
| } |
| |
| pcdata.append(new String(ch, start, length)); |
| } |
| |
| @Override |
| public InputSource resolveEntity(String pid, String sid) |
| throws SAXException { |
| String publicURL = (String) dtdMap.get(pid); |
| |
| if (publicURL != null) { |
| if (validate) { |
| publicURL = getClass().getClassLoader().getResource(publicURL).toExternalForm(); |
| |
| return new InputSource(publicURL); |
| } else { |
| return new InputSource(new ByteArrayInputStream(new byte[0])); |
| } |
| } |
| |
| return new InputSource(sid); |
| } |
| |
| @Override |
| public void startDocument() throws SAXException { |
| super.startDocument(); |
| resElemStack = new Stack<ResourceElem>(); |
| resElemStack.push(rootElem); |
| topRE = rootElem; |
| |
| elementStack = new Stack<String>(); |
| elementStack.push("<root>"); // NOI18N |
| } |
| |
| @Override |
| public void endDocument() throws SAXException { |
| super.endDocument(); |
| resElemStack.pop(); |
| elementStack.pop(); |
| } |
| } |
| } |