blob: f93058a1ce95cddf5e6780fb4d08070b019e4af5 [file] [log] [blame]
/**************************************************************
*
* 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.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;
}
}
}