/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.filesystems; import java.util.*; import java.io.*; import java.lang.ref.*; import org.openide.util.Lookup; import org.openide.util.LookupEvent; import org.openide.util.LookupListener; import org.openide.util.WeakListener; /** * This class is intended to enhance MIME resolving. This class offers * only one method: findMIMEType(FileObject fo). If this method is called, then * registered subclasses of MIMEResolver are asked one by one to resolve MIME type of this FileObject. * Resolving is finished right after first resolver is able to resolve this FileObject or if all registered * resolvers returns null (not recognized). * *Resolvers are registered if they have their record in IDE_HOME\system\Services * in form *.instance e.g.: org-some-package-JavaResolver.instance * * @author rmatous */ final class MIMESupport extends Object { static private Map cache; static private LinkedList recentlist; static private int cachesize; static private MIMESupport defaultInst; private static final int MAX_CACHE_SIZE = 5; /** Asks all registered subclasses of MIMEResolver to resolve FileObject passed as parameter. * @param fo is FileObject, whose MIME should be resolved * @return MIME type or null if not resolved*/ static String findMIMEType(FileObject fo) { if (fo.isFolder ()) return null; if (!CachedFileObject.isAnyResolver ()) return null; CachedFileObject cfo = getDefault().getCachedFileObject (fo); if (cfo == null) cfo = new CachedFileObject (fo); try { return cfo.getMIMEType (); } finally { getDefault ().puCachedFileObject(cfo); } } /** Creates new MIMESupport if not exist */ private static synchronized MIMESupport getDefault() { if (defaultInst == null) defaultInst = new MIMESupport (); return defaultInst; } private MIMESupport() { cachesize = MAX_CACHE_SIZE; cache = new HashMap (cachesize); recentlist = new LinkedList (); } private CachedFileObject getCachedFileObject (FileObject fo) { synchronized (cache) { CachedFileObject retVal = (CachedFileObject)cache.get(fo); if (retVal == null) return new CachedFileObject (fo); cache.remove(fo); recentlist.remove (fo); return retVal; } } private void puCachedFileObject (CachedFileObject cfo) { synchronized (cache) { FileObject fo = cfo.fileObj; if (fo.lastModified().getTime() != cfo.lastModified ().getTime()) return; if (recentlist.size() > cachesize) { cache.remove(recentlist.getLast()); recentlist.removeLast(); } cache.put(fo,cfo); recentlist.addFirst(fo); } } // private void removeFromCache (FileObject fo) private static class CachedFileObject extends FileObject implements FileChangeListener{ String mimeType; java.util.Date lastModified; CachedInputStream fixIt; static Lookup.Result result; static MIMEResolver[] resolvers; // call getResolvers instead static int recCount = 0; private static MIMEResolver[] getResolvers () { synchronized (CachedFileObject.class) { if (resolvers != null) return resolvers; // reason: result.allInstances may also invoke this method recursively if (recCount > 0) return new MIMEResolver [] {}; recCount++; result = Lookup.getDefault().lookup(new Lookup.Template (MIMEResolver.class)); result.addLookupListener(new LookupListener() { public void resultChanged(LookupEvent evt) { synchronized (CachedFileObject.class) { result.removeLookupListener(this); resolvers = null; } } }); } Collection instances = result.allInstances(); synchronized (CachedFileObject.class) { recCount--; if (resolvers != null) return resolvers; resolvers = (MIMEResolver[])instances.toArray(new MIMEResolver[instances.size()]); return resolvers; } } /*All calls delegated to this object. Except few methods, that returns cached values*/ FileObject fileObj; CachedFileObject (FileObject fo) { fileObj = fo; lastModified = fileObj.lastModified(); fileObj.addFileChangeListener (WeakListener.fileChange(this , fileObj)); } public static boolean isAnyResolver () { return getResolvers ().length > 0; } public void freeCaches () { fixIt = null; mimeType = null; lastModified = null; } public String getMIMEType() { if (mimeType == null) mimeType = resolveMIME (); return mimeType; } private String resolveMIME () { String retVal = null; MIMEResolver[] local = getResolvers (); try { for (int i=0; i<local.length; i++) { retVal = local[i].findMIMEType(this); if (retVal != null) return retVal; } } finally { if (fixIt != null) fixIt.internalClose (); fixIt = null; } return FileUtil.getMIMEType (this.getExt ()); } public java.util.Date lastModified() { if (lastModified != null) return lastModified; return lastModified = fileObj.lastModified(); } public InputStream getInputStream() throws java.io.FileNotFoundException { if (fixIt == null) { InputStream is = fileObj.getInputStream(); if (!(is instanceof BufferedInputStream)) is = new BufferedInputStream (is); fixIt = new CachedInputStream(is); } fixIt.cacheToStart(); return fixIt; } public void fileChanged(FileEvent fe) { freeCaches (); } public void fileDeleted(FileEvent fe) { freeCaches (); //removeFromCache (fe.getFile ()); } public void fileRenamed(FileRenameEvent fe) { freeCaches (); } /*All other methods only delegate to fileObj*/ public FileObject getParent() { return fileObj.getParent (); } public String getPackageNameExt(char separatorChar,char extSepChar) { return fileObj.getPackageNameExt(separatorChar, extSepChar); } public FileObject copy(FileObject target,String name,String ext) throws IOException { return fileObj.copy(target, name, ext); } protected void fireFileDeletedEvent(Enumeration en,FileEvent fe) { fileObj.fireFileDeletedEvent(en, fe); } protected void fireFileFolderCreatedEvent(Enumeration en,FileEvent fe) { fileObj.fireFileFolderCreatedEvent(en, fe); } public void setImportant(boolean b) { fileObj.setImportant(b); } public boolean isData() { return fileObj.isData(); } public Object getAttribute(String attrName) { return fileObj.getAttribute(attrName); } public Enumeration getFolders(boolean rec) { return fileObj.getFolders(rec); } public void delete(FileLock lock) throws IOException { fileObj.delete (lock); } public boolean isRoot() { return fileObj.isRoot(); } public Enumeration getData(boolean rec) { return fileObj.getData(rec); } public FileObject[] getChildren() { return fileObj.getChildren(); } public String getNameExt() { return fileObj.getNameExt(); } public boolean isValid() { return fileObj.isValid(); } public boolean isReadOnly() { return fileObj.isReadOnly(); } public String getExt() { return fileObj.getExt(); } public String getName() { return fileObj.getName(); } public void removeFileChangeListener(FileChangeListener fcl) { fileObj.removeFileChangeListener(fcl); } protected void fireFileRenamedEvent(Enumeration en,FileRenameEvent fe) { fileObj.fireFileRenamedEvent(en, fe); } public void refresh(boolean expected) { fileObj.refresh(expected); } protected void fireFileAttributeChangedEvent(Enumeration en,FileAttributeEvent fe) { fileObj.fireFileAttributeChangedEvent(en, fe); } public long getSize() { return fileObj.getSize() ; } public Enumeration getAttributes() { return fileObj.getAttributes() ; } public void rename(FileLock lock,String name,String ext) throws IOException { fileObj.rename (lock,name,ext); } protected void fireFileChangedEvent(Enumeration en,FileEvent fe) { fileObj.fireFileChangedEvent(en, fe); } public FileObject getFileObject(String name,String ext) { return fileObj.getFileObject(name,ext); } public void refresh() { fileObj.refresh(); } public FileObject createData(String name,String ext) throws IOException { return fileObj.createData(name,ext); } public void addFileChangeListener(FileChangeListener fcl) { fileObj.addFileChangeListener(fcl); } protected void fireFileDataCreatedEvent(Enumeration en,FileEvent fe) { fileObj.fireFileDataCreatedEvent(en, fe); } public boolean isFolder() { return fileObj.isFolder(); } public FileObject createFolder(String name) throws IOException { return fileObj.createFolder(name); } public Enumeration getChildren(boolean rec) { return fileObj.getChildren(rec); } public void setAttribute(String attrName,Object value) throws IOException { fileObj.setAttribute(attrName,value); } public String getPackageName(char separatorChar) { return fileObj.getPackageName(separatorChar); } public FileSystem getFileSystem() throws FileStateInvalidException { return fileObj.getFileSystem(); } public OutputStream getOutputStream(FileLock lock) throws java.io.IOException { return fileObj.getOutputStream(lock); } public boolean existsExt(String ext) { return fileObj.existsExt(ext); } public FileObject move(FileLock lock,FileObject target,String name,String ext) throws IOException { return fileObj.move(lock, target, name, ext); } public FileLock lock() throws IOException { return fileObj.lock(); } public void fileFolderCreated(FileEvent fe) { } public void fileDataCreated(FileEvent fe) { } public void fileAttributeChanged(FileAttributeEvent fe) { } /** MIMEResolvers should not cache this FileObject. But they can cache * resolved patterns in Map with this FileObject as key.*/ public int hashCode() { return fileObj.hashCode(); } public boolean equals(java.lang.Object obj) { if (obj instanceof CachedFileObject) return ((CachedFileObject)obj).fileObj.equals (fileObj); return super.equals(obj); } } private static class CachedInputStream extends InputStream { private InputStream inputStream; private byte[] buffer = new byte[256]; private int len = 0; private int pos = 0; private boolean eof = false; CachedInputStream(InputStream is) { inputStream = is; } /** This stream can be closed only from MIMESupport. That`s why * internalClose was added*/ public void close() throws java.io.IOException { } void internalClose() { try { inputStream.close(); } catch (IOException ioe) { } } protected void finalize() { internalClose(); } public int read() throws IOException { if (eof) return -1; int c; if (pos < len) { c = buffer[pos++]; c = c < 0 ? (c + 256) : c; return c; } c = inputStream.read(); if (c < 0) { eof = true; return -1; } if (len >= buffer.length) { byte[] buf = new byte[buffer.length * 2 + 1]; System.arraycopy(buffer, 0, buf, 0, buffer.length); buffer = buf; } buffer[len++] = (byte) (c > 127 ? (c - 256) : c); pos = len; return c; } void cacheToStart () { pos = 0; eof = false; } /** for debug purposes. Returns buffered content. */ public String toString () { String retVal = super.toString()+'['+inputStream.toString ()+']' + '\n'; //NOI18N retVal += new String (buffer); return retVal; } } }