diff options
Diffstat (limited to 'scripting/java/org/openoffice/netbeans/modules/office/filesystem/OpenOfficeDocFileSystem.java')
-rw-r--r-- | scripting/java/org/openoffice/netbeans/modules/office/filesystem/OpenOfficeDocFileSystem.java | 1192 |
1 files changed, 1192 insertions, 0 deletions
diff --git a/scripting/java/org/openoffice/netbeans/modules/office/filesystem/OpenOfficeDocFileSystem.java b/scripting/java/org/openoffice/netbeans/modules/office/filesystem/OpenOfficeDocFileSystem.java new file mode 100644 index 000000000000..795da90df74f --- /dev/null +++ b/scripting/java/org/openoffice/netbeans/modules/office/filesystem/OpenOfficeDocFileSystem.java @@ -0,0 +1,1192 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +package org.openoffice.netbeans.modules.office.filesystem; + +import java.beans.*; +import java.io.*; +import java.util.*; +import java.util.zip.*; + +import org.openide.ErrorManager; +import org.openide.filesystems.*; +import org.openide.filesystems.FileSystem; // override java.io.FileSystem +import org.openide.util.NbBundle; + +// ISSUES: +// - This FS saves (updates) the file on 'setDocument' or 'removeNotify'. +// It has to let the user to decide to update or not. +// +// TODOS: +// - 'Update' action on the mounted document which saves all recent modifications. +// - To introduce 'scope' editable property to control editable portion of +// the mounted document. +// - Acceptable document type identification before mount. + +/** + * OpenOffice.org Document filesystem. + * + * @author misha <misha@openoffice.org> + */ +public class OpenOfficeDocFileSystem + extends AbstractFileSystem +{ + public static final String SCRIPTS_ROOT = "Scripts"; // must be a folder + public static final String SEPARATOR = "/"; // zip file separator + + private static final int OS_UNKNOWN = 0; + private static final int OS_UNIX = 1; + private static final int OS_MACOS = 2; + private static final int OS_WINDOWS = 3; + + private static final int REFRESH_OFF = -1; // -1 is desabled + private static final int REFRESH_TIME = REFRESH_OFF; // (mS) + private static final String TMP_FILE_PREF = "sx_"; + private static final String TMP_FILE_SUFX = ".sxx"; + + private transient Map cache; // filesystem cache + private transient File docFile; // OpenOffice document + private transient ZipFile zipFile; + + private static transient int osType; // type of OS + + private transient ChildrenStrategy childrenStrategy; + private transient EditableStrategy editableStrategy; + + private transient boolean isModified; // true if an entry has been removed + + /** + * Static constructor. + */ + static { + // Identify the type of OS + String osname = System.getProperty("os.name"); + if (osname.startsWith("Mac OS")) + osType = OS_MACOS; + else if (osname.startsWith("Windows")) + osType = OS_WINDOWS; + else + osType = OS_UNIX; + } + + /** + * Default constructor. Initializes new OpenOffice filesystem. + */ + public OpenOfficeDocFileSystem() + { + // Create the filesystem cache + cache = new HashMap(); + + // Initialize strategies + editableStrategy = new EditableStrategy(SCRIPTS_ROOT); + childrenStrategy = new ChildrenStrategy(); + + // Create and use implementations of filesystem functionality: + info = new InfoImpl(); + change = new ChangeImpl(); + + // Handle filesystem.attributes files normally: + DefaultAttributes defattr = new DefaultAttributes( + info, change, new ListImpl()); + + // Handle filesystem.attributes files normally + adds virtual attribute + // "java.io.File" that is used in conversion routines FileUtil.toFile and + // FileUtil.fromFile + //defattr = new InnerAttrs(this, info, change, new ListImpl()); + // (Otherwise set attr to a special implementation, and use ListImpl for list.) + attr = defattr; + list = defattr; + + // transfer = new TransferImpl(); + setRefreshTime(REFRESH_OFF); + } + + /** + * Constructor. Initializes new OpenOffice filesystem with FS capability. + */ + public OpenOfficeDocFileSystem(FileSystemCapability cap) + { + this(); + setCapability(cap); + } + + /** + * Provides unique signature of an instance of the filesystem. + * NOTE: The scope is not a part of the signature so it is impossible + * to mount the same archive more then once. + */ + public static String computeSystemName(File file) + { + return OpenOfficeDocFileSystem.class.getName() + "[" + file + "]"; + } + + // ----------- PROPERTIES -------------- + + /** + * Provides the 'human readable' name of the instance of the filesystem. + */ + public String getDisplayName() + { + if (!isValid()) + return NbBundle.getMessage(OpenOfficeDocFileSystem.class, + "LAB_invalid_file_system", ((docFile != null)? docFile.toString(): "")); + else + return NbBundle.getMessage(OpenOfficeDocFileSystem.class, + "LAB_valid_file_system", docFile.toString()); + } + + /** + * Retrives the 'document' property. + */ + public File getDocument() + { + return docFile; + } + + /** + * Sets the 'document' property. + */ + // Bean setter. Changing the OpenOffice document (or in general, the identity + // of the root file object) should cause everything using this filesystem + // to refresh. The system name must change and refreshRoot should be used + // to ensure that everything is correctly updated. + public synchronized void setDocument(File file) + throws java.beans.PropertyVetoException, java.io.IOException + { +System.out.println("OpenOfficeDocFileSystem.setDocument: file=\"" + file.toString() + "\""); + if((file.exists() == false) || (file.isFile() == false)) { + IOException ioe = new IOException( + file.toString() + " does not exist"); + ErrorManager.getDefault().annotate(ioe, NbBundle.getMessage( + OpenOfficeDocFileSystem.class, "EXC_root_dir_does_not_exist", + file.toString())); + throw ioe; + } + // update the document + try { + updateDocument(); + } catch(IOException ioe) { + // cannot save all!!! +System.out.println("*** OpenOfficeDocFileSystem.setDocument:"); +System.out.println(" file: " + ((docFile != null)? docFile.toString(): "")); +System.out.println(" exception: " + ioe.getMessage()); + } + // new document type verification!!! + closeDocument(); + // open a new document + try { + openDocument(file); + firePropertyChange(PROP_ROOT, null, refreshRoot()); + setRefreshTime(REFRESH_TIME); + } catch(IOException ioe) { + // cannot open a new document!!! +System.out.println("*** OpenOfficeDocFileSystem.setDocument:"); +System.out.println(" file: " + ((file != null)? file.toString(): "")); +System.out.println(" exception: " + ioe.getMessage()); + } + } + + /** + * Retrives 'readonly' property. + * NOTE: The portion of the mounted document available to the user is + * always editable. + */ + public boolean isReadOnly() + { + return false; + } + + /** + * Sets 'readonly' property. + * NOTE: The portion of the mounted document available to the user is + * always editable. + */ + public void setReadOnly(boolean flag) + { + // sorry! it is not supported. + } + + // ----------- SPECIAL CAPABILITIES -------------- + + /** + * Participates in the environment configuration. + * This is how you can affect the classpath for execution, compilation, etc. + */ + public void prepareEnvironment(FileSystem.Environment environment) + { + // BUG: the compiller cannot access files withing the OpenOffice document. + //environment.addClassPath(docFile.toString()); + } + + /* ----------------------------------------------------------- + * Affect the name and icon of files on this filesystem according to their + * "status", e.g. version-control modification-commit state: + /* + private class StatusImpl implements Status { + public Image annotateIcon(Image icon, int iconType, Set files) { + // You may first modify it, e.g. by adding a check mark to the icon + // if that makes sense for this file or group of files. + return icon; + } + public String annotateName(String name, Set files) { + // E.g. add some sort of suffix to the name if some of the + // files are modified but not backed up or committed somehow: + if (theseFilesAreModified(files)) + return NbBundle.getMessage(OpenOfficeDocFileSystem.class, "LBL_modified_files", name); + else + return name; + } + } + + private transient Status status; + + public Status getStatus() { + if (status == null) { + status = new StatusImpl(); + } + return status; + } + // And use fireFileStatusChanged whenever you know something has changed. + */ + + /* + // Filesystem-specific actions, such as version-control operations. + // The actions should typically be CookieActions looking for DataObject + // cookies, where the object's primary file is on this type of filesystem. + public SystemAction[] getActions() { +// ------>>>> UPDATE OPENOFFICE DOCUMENT <<<<------ + return new SystemAction[] { + SystemAction.get(SomeAction.class), + null, // separator + SystemAction.get(SomeOtherAction.class) + }; + } + */ + + /** + * Notifies this filesystem that it has been removed from the repository. + * Concrete filesystem implementations could perform clean-up here. + * The default implementation does nothing. + * <p>Note that this method is <em>advisory</em> and serves as an optimization + * to avoid retaining resources for too long etc. Filesystems should maintain correct + * semantics regardless of whether and when this method is called. + */ + public void removeNotify() + { + setRefreshTime(REFRESH_OFF); // disable refresh + // update the document + try { + updateDocument(); + } catch(IOException ioe) { + // cannot save all!!! +System.out.println("*** OpenOfficeDocFileSystem.removeNotify:"); +System.out.println(" exception: " + ioe.getMessage()); + } + closeDocument(); + super.removeNotify(); + } + + /* + * Opens (mounts) an OpenOffice document. + */ + private void openDocument(File file) + throws IOException, PropertyVetoException + { + synchronized(cache) { + setSystemName(computeSystemName(file)); + docFile = file; + zipFile = new ZipFile(docFile); + cacheDocument(zipFile.entries(), editableStrategy); + isModified = false; + } // synchronized + } + + /* + * Closes the document and cleans up the cache. + */ + private void closeDocument() + { + synchronized(cache) { + // if a document mounted - close it + if(docFile != null) { + // close the document archive + if(zipFile != null) { + try { + zipFile.close(); + } catch(IOException ioe) { + // sorry! we can do nothing about it. + } + } + zipFile = null; + // clean up cache + scanDocument(new CleanStrategy()); + docFile = null; + isModified = false; + } + } // synchronized + } + + /* + * Creates a document cache. + */ + private void cacheDocument(Enumeration entries, Strategy editables) + { + Entry cacheEntry; + ZipEntry archEntry; + synchronized(cache) { + cache.clear(); + // root folder + cacheEntry = new ReadWriteEntry(null); + cache.put(cacheEntry.getName(), cacheEntry); + // the rest of items + while(entries.hasMoreElements()) { + archEntry = (ZipEntry)entries.nextElement(); + cacheEntry = new Entry(archEntry); + if(editables.evaluate(cacheEntry)) + cacheEntry = new ReadWriteEntry(archEntry); + cache.put(cacheEntry.getName(), cacheEntry); + } + } // synchronized + } + + /* + * Updates the document. + */ + private void updateDocument() + throws IOException + { + if(docFile == null) + return; + synchronized(cache) { + ModifiedStrategy modifiedStrategy = new ModifiedStrategy(); + scanDocument(modifiedStrategy); + if((isModified == true) || + (modifiedStrategy.isModified() == true)) + { + File tmpFile = null; + try { + // create updated document + tmpFile = File.createTempFile( + TMP_FILE_PREF, TMP_FILE_SUFX, docFile.getParentFile()); + saveDocument(tmpFile); + } catch(IOException ioe) { + if(tmpFile != null) + tmpFile.delete(); + throw ioe; + } + // close the document archive + if(zipFile != null) { + try { + zipFile.close(); + } catch(IOException ioe) { + } + } + zipFile = null; + // create the document and backup + File newFile = new File(docFile.getParentFile() + File.separator + + "~" + docFile.getName()); + if(newFile.exists()) + newFile.delete(); // delete old backup + docFile.renameTo(newFile); + tmpFile.renameTo(docFile); + // open the document archive + zipFile = new ZipFile(docFile); + } + isModified = false; + } // synchronized + } + + /* + * Saves the document in a new archive. + */ + private void saveDocument(File file) + throws IOException + { + synchronized(cache) { + SaveStrategy saver = new SaveStrategy(file); + scanDocument(saver); + saver.close(); + } // synchronized + } + + /* + * Provides each individual entry in the cached document to an apraiser. + */ + private void scanDocument(Strategy strategy) + { + synchronized(cache) { + Iterator itr = cache.values().iterator(); + while(itr.hasNext()) { + strategy.evaluate((Entry)itr.next()); + } + } // synchronized + } + + /* + * Retrives or creates a file. + */ + private Entry getFileEntry(String name) + throws IOException + { + Entry cEntry = null; + synchronized(cache) { + cEntry = (Entry)cache.get(name); + if(cEntry == null) { + // create a new file + ZipEntry zEntry = new ZipEntry(name); + zEntry.setTime(new Date().getTime()); + cEntry = new Entry(zEntry); + if(editableStrategy.evaluate(cEntry) == false) { + throw new IOException( + "cannot create/edit readonly file"); // I18N + } + cEntry = new ReadWriteEntry(zEntry); + cache.put(cEntry.getName(), cEntry); + isModified = true; + } + } // synchronized + return cEntry; + } + + /* + * Retrives or creates a folder. + */ + private Entry getFolderEntry(String name) + throws IOException + { + Entry cEntry = null; + synchronized(cache) { + cEntry = (Entry)cache.get(name); + if(cEntry == null) { + // create a new folder + ZipEntry zEntry = new ZipEntry(name + SEPARATOR); + zEntry.setMethod(ZipEntry.STORED); + zEntry.setSize(0); + CRC32 crc = new CRC32(); + zEntry.setCrc(crc.getValue()); + zEntry.setTime(new Date().getTime()); + cEntry = new Entry(zEntry); + if(editableStrategy.evaluate(cEntry) == false) { + throw new IOException( + "cannot create folder"); // I18N + } + cEntry = new ReadWriteEntry(zEntry); + cEntry.getOutputStream(); // sets up modified flag + cache.put(cEntry.getName(), cEntry); + isModified = true; + } else { + if(cEntry.isFolder() == false) + cEntry = null; + } + } // synchronized + return cEntry; + } + + /* + * Converts the name to ZIP file name. + * Removes the leading file separator if there is one. + * This is WORKAROUND of the BUG in AbstractFileObject: + * While AbstractFileObject reprecents the root of the filesystem it uses + * the absolute path (the path starts with '/'). It is inconsistent with + * the rest of the code. + * WORKAROUND: we have to strip leading '/' if it is in the name. + */ + private static String zipName(String name) + { + String zname = ((name.startsWith(File.separator))? + name.substring(File.separator.length()): name); + switch(osType) { + case OS_MACOS: + zname = zname.replace(':', '/'); // ':' by '/' + break; + case OS_WINDOWS: + zname = zname.replace((char)0x5c, '/'); // '\' by '/' + break; + default: + break; + } + return zname; + } + + // ----------- IMPLEMENTATIONS OF ABSTRACT FUNCTIONALITY ---------- + + /* ----------------------------------------------------------- + * Information about files and operations on the contents which do + * not affect the file's presence or name. + */ + private class InfoImpl + implements Info + { + public boolean folder(String name) { + synchronized(cache) { + String zname = zipName(name); + Entry entry = (Entry)cache.get(zname); + if(entry != null) + return entry.isFolder(); + // logical zip file entry + childrenStrategy.setParent(zname); + scanDocument(childrenStrategy); + return (childrenStrategy.countChildren() > 0); + } + } + + public Date lastModified(String name) { + synchronized(cache) { + Entry entry = (Entry)cache.get(zipName(name)); + return new Date((entry != null)? entry.getTime(): 0L); + } + } + + public boolean readOnly(String name) { + synchronized(cache) { + Entry entry = (Entry)cache.get(zipName(name)); + return (entry != null)? entry.isReadOnly(): false; + } + } + + public String mimeType(String name) { + // Unless you have some special means of determining MIME type + // (e.g. HTTP headers), ask IDE to use its normal heuristics: + // the MIME resolver pool and then file extensions, or if nothing + // matches, just content/unknown. + return null; + } + + public long size(String name) { + synchronized(cache) { + Entry entry = (Entry)cache.get(zipName(name)); + return (entry != null)? entry.getSize(): 0; + } // synchronized + } + + public InputStream inputStream(String name) + throws FileNotFoundException + { + synchronized(cache) { + Entry entry = (Entry)cache.get(zipName(name)); + return (entry != null)? entry.getInputStream(): null; + } // synchronized + } + + public OutputStream outputStream(String name) + throws IOException + { + return getFileEntry(zipName(name)).getOutputStream(); + } + + // AbstractFileSystem handles locking the file to the rest of the IDE. + // This only means that you should define how the file should be locked + // to the outside world--perhaps it does not need to be. + public void lock(String name) + throws IOException + { +/* + File file = getFile(name); + if (file.exists() == true && file.canWrite() == false) { + IOException ioe = new IOException("file " + file + + " could not be locked"); + ErrorManager.getDefault().annotate(ioe, NbBundle.getMessage( + OpenOfficeDocFileSystem.class, "EXC_file_could_not_be_locked", + file.getName(), getDisplayName(), file.getPath())); + throw ioe; + } +*/ + } + + public void unlock(String name) { + // Nothing special needed to unlock a file to the outside world. + } + + public void markUnimportant(String name) { + // Do nothing special. Version-control systems may use this to mark + // certain files (e.g. *.class) as not needing to be stored in the VCS + // while others (source files) are by default important. + } + + } + + /* ----------------------------------------------------------- + * Operations that change the available files. + */ + private class ChangeImpl + implements Change + { + public void createFolder(String name) + throws IOException + { + synchronized(cache) { + String zname = zipName(name); + if(cache.get(zname) != null) { + throw new IOException( + "cannot create new folder: " + name); // I18N + } + getFolderEntry(zname); + } // synchronized + } + + public void createData(String name) + throws IOException + { + synchronized(cache) { + String zname = zipName(name); + if(cache.get(zname) != null) { + throw new IOException( + "cannot create new data: " + name); // I18N + } + OutputStream os = getFileEntry(zname).getOutputStream(); + os.close(); + } // synchronized + } + + public void rename(String oldName, String newName) + throws IOException + { + String oname = zipName(oldName); + String nname = zipName(newName); + if((oname.length() == 0) || (nname.length() == 0)) { + throw new IOException( + "cannot move or rename the root folder"); // I18N + } + synchronized(cache) { + if(cache.get(nname) != null) { + throw new IOException( + "target file/folder " + newName + " exists"); // I18N + } + Entry entry = (Entry)cache.get(oname); + if(entry == null) { + throw new IOException( + "there is no such a file/folder " + oldName); // I18N + } + if(entry.isReadOnly() == true) { + throw new IOException( + "file/folder " + oldName + " is readonly"); // I18N + } + entry.rename(nname); + if(editableStrategy.evaluate(entry) == false) { + entry.rename(oname); + throw new IOException( + "cannot create file/folder"); // I18N + } + cache.remove(oname); + cache.put(entry.getName(), entry); + } // synchronized + } + + public void delete(String name) + throws IOException + { + String zname = zipName(name); + if(zname.length() == 0) { + throw new IOException( + "cannot delete the root folder"); // I18N + } + synchronized(cache) { + Entry entry = (Entry)cache.remove(zname); + if(entry != null) { + // BUG: this is the design bug. Cache has to + // remember that the entry was removed. + isModified = true; + entry.clean(); + } + } // synchronized + } + } + + /* ----------------------------------------------------------- + * Operation which provides the directory structure. + */ + private class ListImpl + implements List + { + public String[] children(String name) + { + String[] children = null; + synchronized(cache) { + String zname = zipName(name); + Entry entry = (Entry)cache.get(zname); + if(entry != null) { + // real zip file entry + if(entry.isFolder()) { + childrenStrategy.setParent(entry.getName()); + } + } else { + // logical zip file entry + // (portion of the path of a real zip file entry) + childrenStrategy.setParent(zname); + } + scanDocument(childrenStrategy); + children = childrenStrategy.getChildren(); + } // synchronize + return children; + } + + } + + /** ----------------------------------------------------------- + * This class adds new virtual attribute "java.io.File". + * Because of the fact that FileObjects of __Sample__FileSystem are convertible + * to java.io.File by means of attributes. */ + /*private static class InnerAttrs extends DefaultAttributes { + //static final long serialVersionUID = 1257351369229921993L; + __Sample__FileSystem sfs; + public InnerAttrs(__Sample__FileSystem sfs, AbstractFileSystem.Info info, + AbstractFileSystem.Change change,AbstractFileSystem.List list ) { + super(info, change, list); + this.sfs = sfs; + } + public Object readAttribute(String name, String attrName) { + if (attrName.equals("java.io.File")) // NOI18N + return sfs.getFile(name); + + return super.readAttribute(name, attrName); + } + }*/ + + /* ----------------------------------------------------------- + // Optional special implementations of copy and (cross-directory) move. + private class TransferImpl implements Transfer { + + public boolean copy(String name, Transfer target, String targetName) throws IOException { + // Only permit special implementation within single FS + // (or you could implement it across filesystems if you wished): + if (target != this) return false; + // Specially copy the file in an efficient way, e.g. implement + // a copy-on-write algorithm. + return true; + } + + public boolean move(String name, Transfer target, String targetName) throws IOException { + // Only permit special implementation within single FS + // (or you could implement it across filesystems if you wished): + if (target != this) return false; + // Specially move the file, e.g. retain rename information even + // across directories in a version-control system. + return true; + } + + } + */ + + /* ----------------------------------------------------------- + * This interface hides an action will be performed on an entry. + */ + private interface Strategy + { + public boolean evaluate(Entry entry); + } + + /* ----------------------------------------------------------- + * Recognizes editable (read-write) entires + */ + private class EditableStrategy + implements Strategy + { + private String scope; + + public EditableStrategy(String scope) + { + this.scope = scope; + } + + public boolean evaluate(Entry entry) + { + // recognizes all entries in a subtree of the + // 'scope' as editable entries + return (entry != null)? + entry.getName().startsWith(scope): false; + } + } + + /* ----------------------------------------------------------- + * Recognizes and accumulates immediate children of the parent. + */ + private class ChildrenStrategy + implements Strategy + { + private String parent; + private Collection children = new HashSet(); + + public ChildrenStrategy() + { + } + + public void setParent(String name) + { + parent = (name.length() > 0)? (name + SEPARATOR): ""; + if(children == null) + children = (java.util.List)new LinkedList(); + children.clear(); + } + + public boolean evaluate(Entry entry) + { + // do not accept "children" of a file + // ignore "read only" part of the filesystem + if(entry.isReadOnly() == false) { + // identify a child + if( (entry.getName().length() > 0) && + (entry.getName().startsWith(parent))) + { + // identify an immediate child + String child = entry.getName(); + if(parent.length() > 0) { + child = entry.getName().substring(parent.length()); + } + int idx = child.indexOf(SEPARATOR); + if(idx > 0) // more path elements ahead + child = child.substring(0, idx); + return children.add(child); + } + } + return false; + } + + public int countChildren() + { + return children.size(); + } + + public String[] getChildren() + { + String[] chn = new String[children.size()]; + Iterator itr = children.iterator(); + int idx = 0; + while(itr.hasNext()) { + chn[idx++] = (String)itr.next(); + } + return chn; + } + } + + /* ----------------------------------------------------------- + * Recognizes cache entries which have to be save into new archive. + */ + private class ModifiedStrategy + implements Strategy + { + private boolean modified; + + public boolean evaluate(Entry entry) + { + modified |= entry.isModified(); + return entry.isModified(); + } + + public boolean isModified() + { + return modified; + } + } + + /* ----------------------------------------------------------- + * Saves each entry in the filesystem cache. + */ + private class SaveStrategy + implements Strategy + { + ZipOutputStream docos; + IOException ioexp; + + public SaveStrategy(File newdoc) + throws IOException + { + docos = new ZipOutputStream(new FileOutputStream(newdoc)); + ioexp = null; // success by default + } + + public boolean evaluate(Entry entry) + { + if(entry.getName().length() == 0) + return false; + try { + entry.save(docos); + } catch(IOException ioe) { + if(ioexp == null) + ioexp = ioe; + } + return true; + } + + public void close() + throws IOException + { + if(docos != null) { + try { + docos.close(); + } catch (IOException ioe) { + ioexp = ioe; + } finally { + docos = null; + } + if(ioexp != null) { + throw ioexp; + } + } + } + } + + /* ----------------------------------------------------------- + * Cleans each entiry in the filesystem cache. + */ + private class CleanStrategy + implements Strategy + { + public boolean evaluate(Entry entry) + { + try { + entry.clean(); + } catch(java.lang.Exception exp) { + // sorry! can do nothing about it. + } + return true; + } + } + + /* ----------------------------------------------------------- + * ReadOnly cache entry + */ + private class Entry + { + private String name; + private boolean folder; + private long size; + private long time; + private File node; // data files only + + public Entry(ZipEntry entry) + { + if(entry != null) { + name = entry.getName(); + folder = entry.isDirectory(); + size = entry.getSize(); + time = entry.getTime(); + // removes tail file separator from a folder name + if((folder == true) && (name.endsWith(SEPARATOR))) { + name = name.substring( + 0, name.length() - SEPARATOR.length()); + } + } else { + // 'null' is special cace of root folder + name = ""; + folder = true; + size = 0; + time = -1; + } + } + + public boolean isReadOnly() + { + return true; + } + + public boolean isFolder() + { + return folder; + } + + public boolean isModified() + { + return false; + } + + public String getName() + { + return name; + } + + public long getSize() + { + return size; + } + + public long getTime() + { + // ajust last modified time to the java.io.File + return (time >= 0)? time: 0; + } + + public InputStream getInputStream() + throws FileNotFoundException + { + return (isFolder() == false)? new FileInputStream(getFile()): null; + } + + public OutputStream getOutputStream() + throws IOException + { + return null; + } + + public void rename(String name) + throws IOException + { +// throw new IOException( +// "cannot rename readonly file: " + getName()); // I18N + // BUG: this is the design bug. Cache has to mamage such kind + // of operation in order to keep the data integrity. + this.name = name; + } + + public void save(ZipOutputStream arch) + throws IOException + { + InputStream is = null; + ZipEntry entry = new ZipEntry( + getName() + ((isFolder())? SEPARATOR: "")); + try { + if(isFolder()) { + // folder + entry.setMethod(ZipEntry.STORED); + entry.setSize(0); + CRC32 crc = new CRC32(); + entry.setCrc(crc.getValue()); + entry.setTime(getTime()); + arch.putNextEntry(entry); + } else { + // file + if(isModified() == false) + entry.setTime(getTime()); + else + entry.setTime(node.lastModified()); + arch.putNextEntry(entry); + is = getInputStream(); + FileUtil.copy(is, arch); + } + } finally { + // close streams + if(is != null) { + try { + is.close(); + } catch(java.io.IOException ioe) { + // sorry! can do nothing about it. + } + } + if(arch != null) + arch.closeEntry(); + } + } + + public void clean() + throws IOException + { + if(node != null) + node.delete(); + } + + public String toString() + { + return ( + ((isReadOnly())? "RO ": "RW ") + + ((isFolder())? "D": "F") + + " \"" + getName() + "\""); + } + + /* package */ File getFile() + throws FileNotFoundException + { + if(node == null) { + try { + node = File.createTempFile(TMP_FILE_PREF, TMP_FILE_SUFX); + // copy the file from archive to the cache + OutputStream nos = null; + InputStream zis = null; + try { + ZipEntry entry = zipFile.getEntry(getName()); + if(entry != null) { + // copy existing file to the cache + zis = zipFile.getInputStream(entry); + nos = new FileOutputStream(node); + FileUtil.copy(zis, nos); + } + } finally { + // close streams + if(nos != null) { + try { + nos.close(); + } catch(java.io.IOException ioe) { + } + } + if(zis != null) { + try { + zis.close(); + } catch(java.io.IOException ioe) { + } + } + } + } catch(java.lang.Exception exp) { + // delete cache file + if(node != null) + node.delete(); + node = null; + throw new FileNotFoundException( + "cannot access file: " + getName()); // I18N + } + } + return node; + } + + } + + /* ----------------------------------------------------------- + * ReadWrite cache entry + */ + private class ReadWriteEntry + extends Entry + { + private boolean modified; + + // 'null' is special cace of root folder + public ReadWriteEntry(ZipEntry entry) + { + super(entry); + } + + public boolean isReadOnly() + { + return false; + } + + public boolean isModified() + { + return modified; + } + + public void rename(String name) + throws IOException + { + modified = true; + super.rename(name); + } + + public OutputStream getOutputStream() + throws IOException + { + modified = true; + return (isFolder() == false)? new FileOutputStream(getFile()): null; + } + } +} |