/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.module.fs; import gw.config.BaseService; import gw.config.CommonServices; import gw.fs.FileFactory; import gw.fs.IDirectory; import gw.fs.IFile; import gw.fs.IResource; import gw.fs.jar.JarFileDirectoryImpl; import gw.fs.url.URLFileImpl; import gw.lang.reflect.module.IFileSystem; import gw.lang.reflect.module.IModule; import gw.lang.reflect.module.IProtocolAdapter; import gw.util.GosuStringUtil; import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarFile; public class FileSystemImpl extends BaseService implements IFileSystem { private Map<File, IDirectory> _cachedDirInfo; private CachingMode _cachingMode; private FileSystemImpl.IDirectoryResourceExtractor _iDirectoryResourceExtractor; private FileSystemImpl.IFileResourceExtractor _iFileResourceExtractor; private Map<String, IProtocolAdapter> _protocolAdapters; public static boolean USE_NEW_API = false; // Really gross, non-granular synchronization, but in general we shouldn't // be hitting this cache much after startup anyway, so it ought to not // turn into a perf issue static final Object CACHED_FILE_SYSTEM_LOCK = new Object(); public FileSystemImpl(CachingMode cachingMode) { _cachedDirInfo = new HashMap<File, IDirectory>(); _cachingMode = cachingMode; _iDirectoryResourceExtractor = new IDirectoryResourceExtractor(); _iFileResourceExtractor = new IFileResourceExtractor(); _protocolAdapters = new ConcurrentHashMap<String, IProtocolAdapter>(); loadProtocolAdapters(); } @Override public IDirectory getIDirectory(File dir) { if (USE_NEW_API) { return FileFactory.instance().getIDirectory(dir); } if (dir == null) { return null; } dir = normalizeFile(dir); synchronized (CACHED_FILE_SYSTEM_LOCK) { IDirectory directory = _cachedDirInfo.get(dir); if (directory == null) { directory = createDir( dir ); _cachedDirInfo.put( dir, directory ); } return directory; } } @Override public IFile getIFile(File file) { if (USE_NEW_API) { return FileFactory.instance().getIFile(file); } if (file == null) { return null; } else { return new JavaFileImpl( normalizeFile( file ) ); } } public static File normalizeFile(File file) { // return file; String absolutePath = file.getAbsolutePath(); List<String> components = new ArrayList<String>(); boolean reallyNormalized = false; int lastIndex = 0; for (int i = 0; i < absolutePath.length(); i++) { char c = absolutePath.charAt(i); if (c == '/' || c == '\\') { String component = absolutePath.substring(lastIndex, i); if (component.equals(".")) { reallyNormalized = true; } else if (component.equals("..")) { components.remove(components.size() - 1); reallyNormalized = true; } else { components.add(component); } lastIndex = i + 1; } } String component = absolutePath.substring(lastIndex); if (component.equals(".")) { reallyNormalized = true; } else if (component.equals("..")) { components.remove(components.size() - 1); reallyNormalized = true; } else { components.add(component); } return reallyNormalized ? new File(GosuStringUtil.join(components, "/")) : file; } @Override public void setCachingMode(CachingMode cachingMode) { synchronized (CACHED_FILE_SYSTEM_LOCK) { _cachingMode = cachingMode; for (IDirectory dir : _cachedDirInfo.values()) { if (dir instanceof JavaDirectoryImpl) { ((JavaDirectoryImpl) dir).setCachingMode(cachingMode); } } } } private IDirectory createDir( File dir ) { if (dir.getName().endsWith(".jar")) { return new JarFileDirectoryImpl( dir ); } else { return new JavaDirectoryImpl( dir, _cachingMode ); } } public void clearAllCaches() { if (USE_NEW_API) { FileFactory.instance().getDefaultPhysicalFileSystem().clearAllCaches(); return; } synchronized (CACHED_FILE_SYSTEM_LOCK) { for (IDirectory dir : _cachedDirInfo.values()) { dir.clearCaches(); } } } static boolean isDirectory(File f) { String name = f.getName(); if (isAssumedFileSuffix(getFileSuffix(name))) { return false; } else { return f.isDirectory(); } } private static String getFileSuffix(String name) { int dotIndex = name.lastIndexOf('.'); if (dotIndex == -1) { return null; } else { return name.substring(dotIndex + 1); } } @Override public IDirectory getIDirectory(URL url) { if (url == null) { return null; } IProtocolAdapter protocolAdapter = _protocolAdapters.get(url.getProtocol()); if (protocolAdapter != null) { return protocolAdapter.getIDirectory(url); } return _iDirectoryResourceExtractor.getClassResource(url); } @Override public IFile getIFile( URL url ) { if (url == null) { return null; } IProtocolAdapter protocolAdapter = _protocolAdapters.get(url.getProtocol()); if (protocolAdapter != null) { return protocolAdapter.getIFile(url); } if (USE_NEW_API) { return FileFactory.instance().getIFile(url); } return _iFileResourceExtractor.getClassResource(url); } @Override public IFile getFakeFile(URL url, IModule module) { return null; } private void loadProtocolAdapters() { ServiceLoader<IProtocolAdapter> adapters = ServiceLoader.load(IProtocolAdapter.class, getClass().getClassLoader()); for (IProtocolAdapter adapter : adapters) { for (String protocol : adapter.getSupportedProtocols()) { _protocolAdapters.put(protocol, adapter); } } } private void loadProtocolAdapter(Collection<IProtocolAdapter> adapters, String adapterName) { try { Class<? extends IProtocolAdapter> adapterClass = Class.forName(adapterName, true, Thread.currentThread().getContextClassLoader()).asSubclass(IProtocolAdapter.class); adapters.add(adapterClass.newInstance()); } catch (ClassNotFoundException e) { // It's not in the classpath, just ignore } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } private abstract class ResourceExtractor<J extends IResource> { J getClassResource(URL _url) { if (_url == null) { return null; } if ( _url.getProtocol().equals( "file" ) ) { return getIResourceFromJavaFile(_url); } else if ( _url.getProtocol().equals( "jar" ) ) { JarURLConnection urlConnection; URL jarFileUrl; try { urlConnection = (JarURLConnection) _url.openConnection(); jarFileUrl = urlConnection.getJarFileURL(); } catch (IOException e) { throw new RuntimeException(e); } File dir = new File(jarFileUrl.getFile()); IDirectory jarFileDirectory; synchronized (CACHED_FILE_SYSTEM_LOCK) { jarFileDirectory = _cachedDirInfo.get(dir); if (jarFileDirectory == null) { jarFileDirectory = createDir( dir ); _cachedDirInfo.put( dir, jarFileDirectory ); } } return getIResourceFromJarDirectoryAndEntryName(jarFileDirectory,urlConnection.getEntryName()); } else if ( _url.getProtocol().equals( "http" ) ) { J res = getIResourceFromURL(_url); if ( res != null ) { return res; } } throw new RuntimeException( "Unrecognized protocol: " + _url.getProtocol() ); } abstract J getIResourceFromURL(URL location); abstract J getIResourceFromJarDirectoryAndEntryName(IDirectory jarFS, String entryName); abstract J getIResourceFromJavaFile(URL location); protected File getFileFromURL(URL url) { try { URI uri = url.toURI(); if ( uri.getFragment() != null ) { uri = new URI( uri.getScheme(), uri.getSchemeSpecificPart(), null ); } return new File( uri ); } catch ( URISyntaxException ex ) { throw new RuntimeException( ex ); } catch ( IllegalArgumentException ex ) { // debug getting IAE only in TH - unable to parse URL with fragment identifier throw new IllegalArgumentException( "Unable to parse URL " + url.toExternalForm(), ex ); } } } private class IFileResourceExtractor extends ResourceExtractor<IFile> { IFile getIResourceFromJarDirectoryAndEntryName(IDirectory jarFS, String entryName) { return jarFS.file(entryName); } IFile getIResourceFromJavaFile(URL location) { return CommonServices.getFileSystem().getIFile( getFileFromURL(location) ); } @Override IFile getIResourceFromURL(URL location) { return new URLFileImpl(location); } } private class IDirectoryResourceExtractor extends ResourceExtractor<IDirectory> { protected IDirectory getIResourceFromJarDirectoryAndEntryName(IDirectory jarFS, String entryName) { return jarFS.dir(entryName); } protected IDirectory getIResourceFromJavaFile(URL location) { return CommonServices.getFileSystem().getIDirectory( getFileFromURL(location) ); } @Override IDirectory getIResourceFromURL(URL location) { return null; } } private static final Set<String> FILE_SUFFIXES; static { FILE_SUFFIXES = new HashSet<String>(); FILE_SUFFIXES.add("class"); FILE_SUFFIXES.add("eti"); FILE_SUFFIXES.add("etx"); FILE_SUFFIXES.add("gif"); FILE_SUFFIXES.add("gr"); FILE_SUFFIXES.add("grs"); FILE_SUFFIXES.add("gs"); FILE_SUFFIXES.add("gst"); FILE_SUFFIXES.add("gsx"); FILE_SUFFIXES.add("gti"); FILE_SUFFIXES.add("gx"); FILE_SUFFIXES.add("jar"); FILE_SUFFIXES.add("java"); FILE_SUFFIXES.add("pcf"); FILE_SUFFIXES.add("png"); FILE_SUFFIXES.add("properties"); FILE_SUFFIXES.add("tti"); FILE_SUFFIXES.add("ttx"); FILE_SUFFIXES.add("txt"); FILE_SUFFIXES.add("wsdl"); FILE_SUFFIXES.add("xml"); FILE_SUFFIXES.add("xsd"); } private static boolean isAssumedFileSuffix(String suffix) { return FILE_SUFFIXES.contains(suffix); } }