/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.sdk;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkAdditionalData;
import com.intellij.openapi.projectRoots.SdkModificator;
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.impl.ProjectRootManagerImpl;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.impl.jar.JarFileSystemImpl;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.util.ui.UIUtil;
import gw.fs.FileFactory;
import gw.fs.IDirectory;
import gw.fs.IFile;
import gw.lang.Gosu;
import gw.lang.GosuVersion;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.module.IModule;
import gw.plugin.ij.core.GosuAppComponent;
import gw.plugin.ij.core.IDEAExtensionFolderLocator;
import gw.plugin.ij.util.ExceptionUtil;
import gw.plugin.ij.util.GosuBundle;
import gw.plugin.ij.util.GosuModuleUtil;
import gw.util.StreamUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
public class GosuSdkUtils {
private static final Logger LOG = Logger.getInstance(GosuSdkUtils.class);
public static final Set<String> EXCLUDED_JARS = new ImmutableSet.Builder<String>()
.add("jfxrt.jar") // jfxrt.jar is NOT in classpath of JDK 7 (u6, u7)
.build();
public static final Key<Boolean> NEW_GOSU_ACTIONS_AVAILABLE_KEY = Key.create("NEW_GOSU_ACTIONS_AVAILABLE");
private static final String DEFAULT_GOSU_SDK_NAME = "Default Gosu SDK";
public static final String JAR_EXTENSION = "jar";
public static final String SDK_FOLDER = "sdk";
public static final String LIB_FOLDER = "lib";
private static final List<Pattern> IGNORED_JAR_PATTERNS = ImmutableList.of(
Pattern.compile("alt-.*"),
Pattern.compile("gw-ij-.*"),
Pattern.compile("ij-.*"));
public static boolean isIgnoredJar(String name) {
for (Pattern pattern : IGNORED_JAR_PATTERNS) {
if (pattern.matcher(name).matches()) {
return true;
}
}
return false;
}
public static Sdk initDefaultGosuSDK() {
Sdk sdk = getDefaultGosuSdk();
if (sdk != null) {
SdkAdditionalData data = sdk.getSdkAdditionalData();
if (data instanceof GosuSdkAdditionalData) {
GosuSdkAdditionalData gData = (GosuSdkAdditionalData) data;
GosuVersion version = gData.getGosuVersion();
if (version != null) {
GosuVersion embeddedInPluginVersion = Gosu.getVersion();
if (embeddedInPluginVersion.compareTo(version) > 0 || gData.getJavaSdk() == null) {
// The existing Sdk is not compatible with the plugin, delete it then create a shiny new Default Gosu Sdk
deleteDefaultGosuSDK(sdk);
// TODO - blc - show a warning that we're replacing the SDK
sdk = null;
}
}
}
}
return sdk != null ? sdk : createDefaultGosuSDK(null);
}
@Nullable
public static Sdk createDefaultGosuSDK(@Nullable Sdk jdk) {
final ProjectJdkTable table = ProjectJdkTable.getInstance();
if (jdk == null) {
jdk = findJavaSDK( null );
if (jdk == null) {
jdk = createJavaSdk();
}
if (jdk == null) {
ExceptionUtil.showInfo(GosuBundle.message("gosu.initialization"),
GosuBundle.message("error.cannot.create.sdk", DEFAULT_GOSU_SDK_NAME));
return null;
}
}
final Sdk gosuSdk = table.createSdk(DEFAULT_GOSU_SDK_NAME, GosuSdkType.getInstance());
final String homePath = getPluginRootFolder();
configure(gosuSdk, homePath, jdk);
addSdk(gosuSdk);
return gosuSdk;
}
public static void addSdk(final Sdk platformSDK) {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
SdkConfigurationUtil.addSdk(platformSDK);
}
});
// ExecutionUtil.runInDispatchThread(new Runnable() {
// @Override
// public void run() {
// SdkConfigurationUtil.addSdk(gosuSdk);
// }
// }, true);
}
public static Sdk findJavaSDK(String name) {
final ProjectJdkTable table = ProjectJdkTable.getInstance();
final List<Sdk> jdks = table.getSdksOfType(JavaSdk.getInstance());
if (name != null) {
// First, try to find by name
for (Sdk jdk : jdks) {
if (jdk.getName().equals(name)) {
return jdk;
}
}
}
// If not found, get first non-platform
for (Sdk jdk : jdks) {
if (!jdk.getName().contains("Platform")) {
if (name != null) {
LOG.warn("Cannot find Java SDK '" + name + "', selecting '" + jdk.getName() + "'");
}
if(GosuSdkUtils.isApplicableJdk(jdk)) {
return jdk;
}
}
}
return null;
}
/**
* Try to create Java SDK based on the JAVA_HOME environment variable or
* java.home system property.
*/
public static Sdk createJavaSdk() {
Sdk sdk = null;
String javaHome = System.getProperty("java.home");
if (!Strings.isNullOrEmpty(javaHome) && !javaHome.toLowerCase().contains("intellij")) {
if (javaHome.endsWith("jre")) {
javaHome = javaHome.substring(0, javaHome.length() - 3);
}
sdk = createJavaSdk(javaHome);
}
if (sdk == null) {
javaHome = System.getenv("JAVA_HOME");
if (!Strings.isNullOrEmpty(javaHome)) {
sdk = createJavaSdk(javaHome);
}
}
return sdk;
}
public static Sdk createJavaSdk(final String javaHome) {
final Sdk[] result = new Sdk[1];
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
result[0] = SdkConfigurationUtil.createAndAddSDK(javaHome, JavaSdk.getInstance());
final VirtualFile toolsFile = LocalFileSystem.getInstance().findFileByIoFile(new File(javaHome, "lib/tools.jar"));
if (toolsFile != null) {
final VirtualFile toolsfileJar = JarFileSystemImpl.getInstance().getJarRootForLocalFile(toolsFile);
SdkModificator modificator = result[0].getSdkModificator();
modificator.addRoot(toolsfileJar, OrderRootType.CLASSES);
modificator.commitChanges();
}
}
});
if (result[0] != null) {
refreshSdk(result[0]);
}
return result[0];
}
private static void refreshSdk(Sdk sdk) {
final SdkModificator modificator = sdk.getSdkModificator();
for (VirtualFile file : sdk.getRootProvider().getFiles(OrderRootType.CLASSES)) {
if (EXCLUDED_JARS.contains(file.getName())) {
modificator.removeRoot(file, OrderRootType.CLASSES);
}
}
modificator.commitChanges();
}
private static void deleteDefaultGosuSDK(@NotNull final Sdk gosuSdk) {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
ProjectJdkTable.getInstance().removeJdk(gosuSdk);
}
});
}
});
}
public static boolean isApplicableJdk(@NotNull Sdk sdk) {
if (sdk.getSdkType() instanceof JavaSdk) {
final JavaSdkVersion version = JavaSdk.getInstance().getVersion(sdk);
if(version == null) {
return false;
}
return version.getMaxLanguageLevel().isAtLeast(LanguageLevel.JDK_1_7);
}
return false;
}
public static void configure(@NotNull Sdk sdk, @NotNull String homePath, @NotNull Sdk jdk) {
SdkModificator modificator = sdk.getSdkModificator();
modificator.setHomePath(homePath);
modificator.setVersionString(jdk.getVersionString());
addJdkFiles(modificator, jdk);
File pluginHome = new File(homePath);
if (!isPluginRootAJar(pluginHome)) {
File jarsDir = findJarsDir(pluginHome);
if (jarsDir != null) {
Iterable<File> jars = listFiles(jarsDir, withExtension(JAR_EXTENSION));
addSdkElements(modificator, jars);
}
} // else pluginHome is a jar in the TH environment
addExtlibFolderToClasspath(modificator);
modificator.setSdkAdditionalData(new GosuSdkAdditionalData(jdk, findGosuVersion(homePath)));
modificator.commitChanges();
}
public static void addJdkFiles(@NotNull SdkModificator modificator, @NotNull Sdk jdk) {
// Add Java jars to SDK
VirtualFile[] jars = jdk.getRootProvider().getFiles(OrderRootType.CLASSES);
addVirtualFiles(modificator, Arrays.asList(jars));
// Add Java sources to SDK
VirtualFile[] sources = jdk.getRootProvider().getFiles(OrderRootType.SOURCES);
for (VirtualFile file : sources) {
modificator.addRoot(file, OrderRootType.SOURCES);
}
}
public static void addSdkElements(@NotNull SdkModificator modificator, Iterable<File> elements) {
addVirtualFiles(modificator, Iterables.transform(elements, new Function<File, VirtualFile>() {
public VirtualFile apply(@Nullable File file) {
final VirtualFile vfile = LocalFileSystem.getInstance().findFileByIoFile(file);
if (vfile == null ) {
LOG.warn("SDK file not present: " + file.getAbsolutePath());
return null;
}
return JAR_EXTENSION.equals(vfile.getExtension()) ?
JarFileSystemImpl.getInstance().getJarRootForLocalFile(vfile) :
vfile;
}
}));
}
public static void addVirtualFiles(@NotNull SdkModificator modificator, Iterable<VirtualFile> jars) {
addVirtualFiles(modificator, jars, OrderRootType.CLASSES);
}
public static void addVirtualFiles(@NotNull SdkModificator modificator, Iterable<VirtualFile> jars, OrderRootType type) {
for (VirtualFile file : jars) {
if (file != null && !isIgnoredJar(file.getName())) {
modificator.addRoot(file, type);
}
}
}
private static void addExtlibFolderToClasspath(@NotNull SdkModificator modificator) {
final File extensionFolder = new IDEAExtensionFolderLocator().getExtensionFolderPath();
if (extensionFolder != null) {
Iterable<File> extJars = listFiles(extensionFolder, withExtension(JAR_EXTENSION));
addSdkElements(modificator, extJars);
}
}
private static Iterable<File> getApprovedJars(File dir) {
Iterable<File> files = listFiles(dir, withExtension(JAR_EXTENSION));
return Iterables.filter(files, new Predicate<File>() {
@Override
public boolean apply(@Nullable File file) {
return !isIgnoredJar(file.getName());
}
});
}
public static Iterable<File> listFiles(File dir, Predicate<File> predicate) {
final File[] contents = dir.listFiles();
return contents != null ?
Iterables.filter(Arrays.asList(contents), predicate) :
Collections.<File>emptyList();
}
public static Predicate<File> withExtension(final String extension) {
return new Predicate<File>() {
@Override
public boolean apply(@Nullable File file) {
return FileUtil.getExtension(file.getName()).equals(extension);
}
};
}
public static Sdk getDefaultGosuSdk() {
return ProjectJdkTable.getInstance().findJdk(DEFAULT_GOSU_SDK_NAME);
}
public static String getPluginRootFolder() {
return GosuAppComponent.getEditorPlugin().getPath().getAbsolutePath();
}
public static boolean isGosuSdkSet(Project project) {
final Sdk sdk = ProjectRootManagerImpl.getInstance(project).getProjectSdk();
return sdk != null && sdk.getSdkType() instanceof GosuSdkType;
}
public static boolean isGosuApiModuleInProject(Project project) {
final IModule rootModule = GosuModuleUtil.getGlobalModule(project);
if (rootModule == null) {
return false;
}
TypeSystem.pushModule(rootModule);
try {
return TypeSystem.getByFullNameIfValid("gw.lang.reflect.IType", TypeSystem.getGlobalModule()) != null;
} finally {
TypeSystem.popModule(rootModule);
}
}
private static File findJarsDir(File sdkHome) {
for (String dirName : new String[] { SDK_FOLDER, LIB_FOLDER }) {
File dir = new File(sdkHome, dirName);
if (dir.exists()) {
return dir;
}
}
return null;
}
private static boolean isPluginRootAJar(File pluginRoot) {
// this happens to return true in TH
return (!pluginRoot.isDirectory()
&& pluginRoot.getName().endsWith(".jar"))
||
(pluginRoot.isDirectory()
&& pluginRoot.getName().equals("classes"));
}
static File findGosuCoreApiJar(File sdkHome) {
File jarsDir = findJarsDir(sdkHome);
if (jarsDir == null) {
return null;
}
Iterable<File> jars = getApprovedJars(jarsDir);
File gosuCoreApiJar = Iterables.find(jars, new Predicate<File>() {
@Override
public boolean apply(@Nullable File file) {
return file != null && file.getName().startsWith("gosu-core-api") && file.getName().endsWith(".jar");
}
}, null);
return gosuCoreApiJar;
}
static GosuVersion findGosuVersion(String sdkHome) {
File pluginRoot = new File(sdkHome);
if (isPluginRootAJar(pluginRoot)) {
return new GosuVersion(0, 0);
}
File gosuCoreApiJar = findGosuCoreApiJar(pluginRoot);
if (gosuCoreApiJar == null) {
gosuCoreApiJar = new File(pluginRoot, "classes");
}
IDirectory iDirectory = FileFactory.instance().getIDirectory(gosuCoreApiJar);
IFile gosuVersionProps = iDirectory.file("gw/lang/gosu-version.properties");
if (gosuVersionProps.exists()) {
InputStream in = null;
try {
in = gosuVersionProps.openInputStream();
Reader reader = StreamUtil.getInputStreamReader(in);
return GosuVersion.parse(reader);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
StreamUtil.close(in);
} catch (IOException e) {
System.err.println("error closing input stream: " + e.getMessage());
}
}
} else {
throw new IllegalStateException("could not find gosu-version.properties");
}
}
}