/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.filesystem;
import com.google.common.collect.Maps;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.impl.jar.JarFileSystemImpl;
import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl;
import com.intellij.testFramework.LightVirtualFile;
import gw.config.BaseService;
import gw.config.CommonServices;
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.plugin.ij.util.GosuModuleUtil;
import gw.plugin.ij.util.LightVirtualFileWithModule;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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.Map;
import java.util.jar.JarFile;
public class IDEAFileSystem extends BaseService implements IFileSystem {
private static final Object CACHED_FILE_SYSTEM_LOCK = new Object();
private final Map<File, IDirectory> _cachedDirInfo = Maps.newHashMap();
private final IDirectoryResourceExtractor _iDirectoryResourceExtractor = new IDirectoryResourceExtractor();
private final IFileResourceExtractor _iFileResourceExtractor = new IFileResourceExtractor();
public IDEAFileSystem() {
}
@NotNull
@Override
public IDirectory getIDirectory(@NotNull File dir) {
String pathString = dir.getAbsolutePath().replace(File.separatorChar, '/');
return getIDirectory(pathString);
}
@NotNull
public IDirectory getIDirectory(@NotNull String pathString) {
VirtualFile file = LocalFileSystemImpl.getInstance().findFileByPath(pathString);
if (file != null && pathString.endsWith(".jar")) {
file = JarFileSystem.getInstance().getJarRootForLocalFile(file);
if (file == null) {
throw new RuntimeException("Cannot load Jar file for: " + pathString);
}
return new IDEAJarDirectory(file);
}
return file != null ? new IDEADirectory(file) : new IDEADirectory(pathString);
}
@NotNull
@Override
public IFile getIFile(@NotNull File file) {
String pathString = file.getAbsolutePath().replace(File.separatorChar, '/');
return getIFile(pathString);
}
@NotNull
public IFile getIFile(@NotNull String pathString) {
VirtualFile file = LocalFileSystemImpl.getInstance().findFileByPath(pathString);
if (file == null && pathString.contains(".jar!")) {
file = JarFileSystemImpl.getInstance().findFileByPath(pathString);
}
return file != null ? new IDEAFile(file) : new IDEAFile(pathString);
}
@NotNull
public IDEAFile getIFile(VirtualFile file) {
return new IDEAFile(file);
}
@NotNull
public IDEADirectory getIDirectory(VirtualFile file) {
return new IDEADirectory(file);
}
@Nullable
@Override
public IDirectory getIDirectory(URL url) {
return _iDirectoryResourceExtractor.getClassResource(url);
}
@Nullable
@Override
public IFile getIFile(URL url) {
return _iFileResourceExtractor.getClassResource(url);
}
@NotNull
@Override
public IFile getFakeFile(@NotNull URL url, IModule module) {
LightVirtualFile virtualFile = LightVirtualFileWithModule.create(url.getFile(), module);
virtualFile.putUserData(ModuleUtil.KEY_MODULE, GosuModuleUtil.getModule(module));
return new IDEAFile(virtualFile);
}
@NotNull
private IDirectory createDir(@NotNull File dir) {
if (dir.getName().endsWith(".jar") && dir.isFile()) {
return new JarFileDirectoryImpl(dir);
} else {
return getIDirectory(dir);
}
}
@Override
public void setCachingMode(CachingMode cachingMode) {
}
@Override
public void clearAllCaches() {
}
private abstract class ResourceExtractor<J extends IResource> {
@Nullable
J getClassResource(@Nullable URL _url) {
if (_url == null) {
return null;
}
String protocol = _url.getProtocol();
switch (protocol) {
case "file":
return getIResourceFromJavaFile(_url);
case "jar":
JarURLConnection urlConnection;
JarFile jarFile;
try {
urlConnection = (JarURLConnection) _url.openConnection();
jarFile = urlConnection.getJarFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
File dir = new File(jarFile.getName());
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());
case "http":
case "https":
return getRemoteFile(_url);
default:
throw new RuntimeException("Unrecognized protocol: " + protocol);
}
}
abstract J getIResourceFromJarDirectoryAndEntryName(IDirectory jarFS, String entryName);
abstract J getIResourceFromJavaFile(URL location);
abstract J getRemoteFile(URL location);
@NotNull
protected File getFileFromURL(@NotNull 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(@NotNull IDirectory jarFS, String entryName) {
return jarFS.file(entryName);
}
IFile getIResourceFromJavaFile(@NotNull URL location) {
return CommonServices.getFileSystem().getIFile(getFileFromURL(location));
}
@Override
IFile getRemoteFile(URL location) {
return new URLFileImpl(location);
}
}
private class IDirectoryResourceExtractor extends ResourceExtractor<IDirectory> {
protected IDirectory getIResourceFromJarDirectoryAndEntryName(@NotNull IDirectory jarFS, String entryName) {
return jarFS.dir(entryName);
}
protected IDirectory getIResourceFromJavaFile(@NotNull URL location) {
return CommonServices.getFileSystem().getIDirectory(getFileFromURL(location));
}
@Override
IDirectory getRemoteFile(URL location) {
throw new UnsupportedOperationException("unable to obtain directory via remote protocol. " + location);
}
}
}