From 2347bcad1fa6e2b639f7d0f3491bc55bd935f289 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Fri, 24 Jul 2015 13:03:27 +0200 Subject: [PATCH] use classpath jar instead of custom class loaders for CommandLineWrapper (IDEA-133617; IDEA-130440) --- .../JavaTestFrameworkRunnableState.java | 7 +- .../rt/execution/CommandLineWrapper.java | 139 ++++++------------ .../ForkedByModuleSplitter.java | 63 +++++--- .../jps/incremental/ExternalProcessUtil.java | 17 +-- .../openapi/projectRoots/JdkUtil.java | 96 ++++-------- .../execution/CommandLineWrapperUtil.java | 77 ++++++++++ .../execution/CommandLineWrapperUtilTest.java | 55 +++++++ 7 files changed, 256 insertions(+), 198 deletions(-) create mode 100644 platform/util/src/com/intellij/execution/CommandLineWrapperUtil.java create mode 100644 platform/util/testSrc/com/intellij/execution/CommandLineWrapperUtilTest.java diff --git a/java/execution/impl/src/com/intellij/execution/JavaTestFrameworkRunnableState.java b/java/execution/impl/src/com/intellij/execution/JavaTestFrameworkRunnableState.java index 27a063e8f8d2..758f6aa78cdf 100644 --- a/java/execution/impl/src/com/intellij/execution/JavaTestFrameworkRunnableState.java +++ b/java/execution/impl/src/com/intellij/execution/JavaTestFrameworkRunnableState.java @@ -251,12 +251,7 @@ public abstract class JavaTestFrameworkRunnableStateIDEA-126859 for additional details - */ - public static final String PROPERTY_DO_NOT_ESCAPE_CLASSPATH_URL = "idea.do.not.escape.classpath.url"; - private static final String PREFIX = "-D"; public static void main(String[] args) throws Exception { - final boolean notEscapeClasspathUrl = Boolean.valueOf(System.getProperty(PROPERTY_DO_NOT_ESCAPE_CLASSPATH_URL)).booleanValue(); - final List urls = new ArrayList(); - final File file = new File(args[0]); - final StringBuffer buf = new StringBuffer(); - final BufferedReader reader = new BufferedReader(new FileReader(file)); + final File jarFile = new File(args[0]); + JarInputStream inputStream = null; try { - while(reader.ready()) { - final String fileName = reader.readLine(); - if (buf.length() > 0) { - buf.append(File.pathSeparator); - } - buf.append(fileName); - File classpathElement = new File(fileName); - try { - //noinspection Since15, deprecation - urls.add(notEscapeClasspathUrl ? classpathElement.toURL() : classpathElement.toURI().toURL()); - } - catch (NoSuchMethodError e) { - //noinspection deprecation - urls.add(classpathElement.toURL()); + inputStream = new JarInputStream(new FileInputStream(jarFile)); + final Manifest manifest = inputStream.getManifest(); + final String vmParams = manifest.getMainAttributes().getValue("VM-Options"); + if (vmParams != null) { + final HashMap vmOptions = new HashMap(); + parseVmOptions(vmParams, vmOptions); + for (Iterator iterator = vmOptions.keySet().iterator(); iterator.hasNext(); ) { + String optionName = (String)iterator.next(); + System.setProperty(optionName, (String)vmOptions.get(optionName)); } } } + catch (IOException ignore) {} finally { - reader.close(); - } - if (!file.delete()) file.deleteOnExit(); - System.setProperty("java.class.path", buf.toString()); - - int startArgsIdx = 2; - if (args[1].equals("@vm_params")) { - startArgsIdx = 4; - final File vmParamsFile = new File(args[2]); - final BufferedReader vmParamsReader = new BufferedReader(new FileReader(vmParamsFile)); - try { - while (vmParamsReader.ready()) { - final String vmParam = vmParamsReader.readLine().trim(); - final int eqIdx = vmParam.indexOf('='); - String vmParamName; - String vmParamValue; - - if (eqIdx > -1 && eqIdx < vmParam.length() - 1) { - vmParamName = vmParam.substring(0, eqIdx); - vmParamValue = vmParam.substring(eqIdx + 1); - } else { - vmParamName = vmParam; - vmParamValue = ""; - } - vmParamName = vmParamName.trim(); - if (vmParamName.startsWith(PREFIX)) { - vmParamName = vmParamName.substring(PREFIX.length()); - System.setProperty(vmParamName, vmParamValue); - } - } + if (inputStream != null) { + inputStream.close(); } - finally { - vmParamsReader.close(); - } - if (!vmParamsFile.delete()) vmParamsFile.deleteOnExit(); + jarFile.deleteOnExit(); } - - String mainClassName = args[startArgsIdx - 1]; - String[] mainArgs = new String[args.length - startArgsIdx]; - System.arraycopy(args, startArgsIdx, mainArgs, 0, mainArgs.length); - - for (int i = 0; i < urls.size(); i++) { - URL url = (URL)urls.get(i); - urls.set(i, internFileProtocol(url)); - } - - ClassLoader loader = new URLClassLoader((URL[])urls.toArray(new URL[urls.size()]), null); - final String classLoader = System.getProperty("java.system.class.loader"); - if (classLoader != null) { - try { - loader = (ClassLoader)Class.forName(classLoader).getConstructor(new Class[]{ClassLoader.class}).newInstance(new Object[]{loader}); - } - catch (Exception e) { - //leave URL class loader - } - } - - Class mainClass = loader.loadClass(mainClassName); - Thread.currentThread().setContextClassLoader(loader); + + String mainClassName = args[1]; + String[] mainArgs = new String[args.length - 2]; + System.arraycopy(args, 2, mainArgs, 0, mainArgs.length); + Class mainClass = Class.forName(mainClassName); //noinspection SSBasedInspection Class mainArgType = (new String[0]).getClass(); Method main = mainClass.getMethod("main", new Class[]{mainArgType}); @@ -130,15 +68,24 @@ public class CommandLineWrapper { main.invoke(null, new Object[]{mainArgs}); } - private static URL internFileProtocol(URL url) { - try { - if ("file".equals(url.getProtocol())) { - return new URL("file", url.getHost(), url.getPort(), url.getFile()); + public static void parseVmOptions(String vmParams, Map vmOptions) { + int idx = vmParams.indexOf(PREFIX); + while (idx >= 0) { + final int indexOf = vmParams.indexOf(PREFIX, idx + PREFIX.length()); + final String vmParam = indexOf < 0 ? vmParams.substring(idx) : vmParams.substring(idx, indexOf - 1); + final int eqIdx = vmParam.indexOf('='); + String vmParamName; + String vmParamValue; + if (eqIdx > -1 && eqIdx < vmParam.length() - 1) { + vmParamName = vmParam.substring(0, eqIdx); + vmParamValue = vmParam.substring(eqIdx + 1); + } else { + vmParamName = vmParam; + vmParamValue = ""; } + vmOptions.put(vmParamName.trim().substring(PREFIX.length()), vmParamValue); + idx = indexOf; } - catch (MalformedURLException ignored) { - } - return url; } private static void ensureAccess(Object reflectionObject) { diff --git a/java/java-runtime/src/com/intellij/rt/execution/testFrameworks/ForkedByModuleSplitter.java b/java/java-runtime/src/com/intellij/rt/execution/testFrameworks/ForkedByModuleSplitter.java index de6c5d1950cb..2ffdcaffe5f8 100644 --- a/java/java-runtime/src/com/intellij/rt/execution/testFrameworks/ForkedByModuleSplitter.java +++ b/java/java-runtime/src/com/intellij/rt/execution/testFrameworks/ForkedByModuleSplitter.java @@ -20,6 +20,10 @@ import com.intellij.rt.execution.CommandLineWrapper; import java.io.*; import java.util.ArrayList; import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipOutputStream; public abstract class ForkedByModuleSplitter { protected final ForkedDebuggerHelper myForkedDebuggerHelper = new ForkedDebuggerHelper(); @@ -79,26 +83,8 @@ public abstract class ForkedByModuleSplitter { builder.add("-classpath"); if (myDynamicClasspath.length() > 0) { try { - final File classpathFile = File.createTempFile("classpath", null); - classpathFile.deleteOnExit(); - final PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(classpathFile), "UTF-8")); - try { - int idx = 0; - while (idx < classpath.length()) { - final int endIdx = classpath.indexOf(File.pathSeparator, idx); - if (endIdx < 0) { - writer.println(classpath.substring(idx)); - break; - } - writer.println(classpath.substring(idx, endIdx)); - idx = endIdx + File.pathSeparator.length(); - } - } - finally { - writer.close(); - } - - builder.add(myDynamicClasspath); + final File classpathFile = createClasspathJarFile(new Manifest(), classpath); + builder.add(myDynamicClasspath + File.pathSeparator + classpathFile.getAbsolutePath()); builder.add(CommandLineWrapper.class.getName()); builder.add(classpathFile.getAbsolutePath()); } @@ -172,4 +158,41 @@ public abstract class ForkedByModuleSplitter { protected void sendTime(long time) {} protected void sendTree(Object rootDescription) {} + + public static File createClasspathJarFile(Manifest manifest, String classpath) throws IOException { + final Attributes attributes = manifest.getMainAttributes(); + attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + + String classpathForManifest = ""; + int idx = 0; + int endIdx = 0; + while (endIdx >= 0) { + endIdx = classpath.indexOf(File.pathSeparator, idx); + String path = endIdx < 0 ? classpath.substring(idx) : classpath.substring(idx, endIdx); + if (classpathForManifest.length() > 0) { + classpathForManifest += " "; + } + try { + //noinspection Since15 + classpathForManifest += new File(path).toURI().toURL().toString(); + } + catch (NoSuchMethodError e) { + classpathForManifest += new File(path).toURL().toString(); + } + idx = endIdx + File.pathSeparator.length(); + } + attributes.put(Attributes.Name.CLASS_PATH, classpathForManifest); + + File jarFile = File.createTempFile("classpath", ".jar"); + ZipOutputStream jarPlugin = null; + try { + BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(jarFile)); + jarPlugin = new JarOutputStream(out, manifest); + } + finally { + if (jarPlugin != null) jarPlugin.close(); + } + jarFile.deleteOnExit(); + return jarFile; + } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/ExternalProcessUtil.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/ExternalProcessUtil.java index 73359c243392..24e994e023ad 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/ExternalProcessUtil.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/ExternalProcessUtil.java @@ -15,6 +15,7 @@ */ package org.jetbrains.jps.incremental; +import com.intellij.execution.CommandLineWrapperUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; @@ -26,6 +27,7 @@ import java.io.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.jar.Manifest; /** * @author Eugene Zhuravlev @@ -100,21 +102,12 @@ public class ExternalProcessUtil { final Class wrapperClass = getCommandLineWrapperClass(); if (wrapperClass != null) { try { - File classpathFile = FileUtil.createTempFile("classpath", null); - final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(classpathFile))); - try { - for (String path : classpath) { - writer.println(path); - } - } - finally { - writer.close(); - } + final String classpathFile = CommandLineWrapperUtil.createClasspathJarFile(new Manifest(), classpath).getAbsolutePath(); commandLineWrapperArgs = Arrays.asList( "-classpath", - ClasspathBootstrap.getResourcePath(wrapperClass), + ClasspathBootstrap.getResourcePath(wrapperClass) + File.pathSeparator + classpathFile, wrapperClass.getName(), - classpathFile.getAbsolutePath() + classpathFile ); } catch (IOException ex) { diff --git a/platform/lang-api/src/com/intellij/openapi/projectRoots/JdkUtil.java b/platform/lang-api/src/com/intellij/openapi/projectRoots/JdkUtil.java index b8d12b2bfa9c..a9dbabd90361 100644 --- a/platform/lang-api/src/com/intellij/openapi/projectRoots/JdkUtil.java +++ b/platform/lang-api/src/com/intellij/openapi/projectRoots/JdkUtil.java @@ -15,10 +15,12 @@ */ package com.intellij.openapi.projectRoots; +import com.intellij.execution.CommandLineWrapperUtil; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.ParametersList; import com.intellij.execution.configurations.SimpleJavaParameters; import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; @@ -27,8 +29,7 @@ import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.encoding.EncodingManager; import com.intellij.util.PathUtil; -import com.intellij.util.lang.UrlClassLoader; -import gnu.trove.THashMap; +import com.intellij.util.execution.ParametersListUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -36,6 +37,7 @@ import java.io.*; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException; +import java.util.ArrayList; import java.util.List; import java.util.jar.Attributes; import java.util.jar.JarFile; @@ -45,6 +47,11 @@ import java.util.jar.Manifest; * @author max */ public class JdkUtil { + /** + * The VM property is needed to workaround incorrect escaped URLs handling in WebSphere, + * see IDEA-126859 for additional details + */ + public static final String PROPERTY_DO_NOT_ESCAPE_CLASSPATH_URL = "idea.do.not.escape.classpath.url"; private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.projectRoots.JdkUtil"); private static final String WRAPPER_CLASS = "com.intellij.rt.execution.CommandLineWrapper"; @@ -161,78 +168,39 @@ public class JdkUtil { final Class commandLineWrapper; if ((commandLineWrapper = getCommandLineWrapperClass()) != null) { - if (forceDynamicClasspath) { - File classpathFile = null; - File vmParamsFile = null; - if (!vmParametersList.hasParameter("-classpath") && !vmParametersList.hasParameter("-cp")) { + if (forceDynamicClasspath && !vmParametersList.hasParameter("-classpath") && !vmParametersList.hasParameter("-cp")) { + try { + final Manifest manifest = new Manifest(); + manifest.getMainAttributes().putValue("Created-By", + ApplicationNamesInfo.getInstance().getFullProductName()); if (javaParameters.isDynamicVMOptions() && useDynamicVMOptions()) { - try { - vmParamsFile = FileUtil.createTempFile("vm_params", null); - final PrintWriter writer = new PrintWriter(vmParamsFile); - try { - for (String param : vmParametersList.getList()) { - if (param.startsWith("-D")) { - writer.println(param); - } - } - } - finally { - writer.close(); - } - } - catch (IOException e) { - LOG.error(e); - } - final List list = vmParametersList.getList(); - for (String param : list) { - if (!param.trim().startsWith("-D")) { - commandLine.addParameter(param); + List dParams = new ArrayList(); + for (String param : vmParametersList.getList()) { + if (param.startsWith("-D")) { + dParams.add(param); } } + + manifest.getMainAttributes().putValue("VM-Options", ParametersListUtil.join(dParams)); + final ArrayList restParams = new ArrayList(vmParametersList.getList()); + restParams.removeAll(dParams); + commandLine.addParameters(restParams); } else { commandLine.addParameters(vmParametersList.getList()); } - try { - classpathFile = FileUtil.createTempFile("classpath", null); - final PrintWriter writer = new PrintWriter(classpathFile); - try { - for (String path : javaParameters.getClassPath().getPathList()) { - writer.println(path); - } - } - finally { - writer.close(); - } + final boolean notEscape = vmParametersList.hasParameter(PROPERTY_DO_NOT_ESCAPE_CLASSPATH_URL); + final List classPathList = javaParameters.getClassPath().getPathList(); + final String jarFile = CommandLineWrapperUtil.createClasspathJarFile(manifest, classPathList, notEscape).getAbsolutePath(); + commandLine.addParameter("-classpath"); + commandLine.addParameter(PathUtil.getJarPathForClass(commandLineWrapper) + File.pathSeparator + jarFile); - String classpath = PathUtil.getJarPathForClass(commandLineWrapper); - final String utilRtPath = PathUtil.getJarPathForClass(StringUtilRt.class); - if (!classpath.equals(utilRtPath)) { - classpath += File.pathSeparator + utilRtPath; - } - final Class ourUrlClassLoader = UrlClassLoader.class; - if (ourUrlClassLoader.getName().equals(vmParametersList.getPropertyValue("java.system.class.loader"))) { - classpath += File.pathSeparator + PathUtil.getJarPathForClass(ourUrlClassLoader); - classpath += File.pathSeparator + PathUtil.getJarPathForClass(THashMap.class); - } - - commandLine.addParameter("-classpath"); - commandLine.addParameter(classpath); - } - catch (IOException e) { - LOG.error(e); - } - } - - appendEncoding(javaParameters, commandLine, vmParametersList); - if (classpathFile != null) { + appendEncoding(javaParameters, commandLine, vmParametersList); commandLine.addParameter(commandLineWrapper.getName()); - commandLine.addParameter(classpathFile.getAbsolutePath()); + commandLine.addParameter(jarFile); } - - if (vmParamsFile != null) { - commandLine.addParameter("@vm_params"); - commandLine.addParameter(vmParamsFile.getAbsolutePath()); + catch (IOException e) { + LOG.error(e); } } else { diff --git a/platform/util/src/com/intellij/execution/CommandLineWrapperUtil.java b/platform/util/src/com/intellij/execution/CommandLineWrapperUtil.java new file mode 100644 index 000000000000..5a5545ba4d14 --- /dev/null +++ b/platform/util/src/com/intellij/execution/CommandLineWrapperUtil.java @@ -0,0 +1,77 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed 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 com.intellij.execution; + +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.Function; +import org.jetbrains.annotations.NotNull; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipOutputStream; + +public class CommandLineWrapperUtil { + @NotNull + public static File createClasspathJarFile(Manifest manifest, List pathList) throws IOException { + return createClasspathJarFile(manifest, pathList, false); + } + + @NotNull + public static File createClasspathJarFile(Manifest manifest, List pathList, final boolean notEscape) throws IOException { + final Attributes attributes = manifest.getMainAttributes(); + attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + + final Ref ex = new Ref(); + final String classPathAttribute = StringUtil.join(pathList, new Function() { + @Override + public String fun(String path) { + final File classpathElement = new File(path); + try { + return (notEscape ? classpathElement.toURL() : classpathElement.toURI().toURL()).toString(); + } + catch (IOException e) { + ex.set(e); + return null; + } + } + }, " "); + + final IOException thrownException = ex.get(); + if (thrownException != null) { + throw thrownException; + } + attributes.put(Attributes.Name.CLASS_PATH, classPathAttribute); + + File jarFile = FileUtil.createTempFile("classpath", ".jar"); + ZipOutputStream jarPlugin = null; + try { + BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(jarFile)); + jarPlugin = new JarOutputStream(out, manifest); + } + finally { + if (jarPlugin != null) jarPlugin.close(); + } + return jarFile; + } +} diff --git a/platform/util/testSrc/com/intellij/execution/CommandLineWrapperUtilTest.java b/platform/util/testSrc/com/intellij/execution/CommandLineWrapperUtilTest.java new file mode 100644 index 000000000000..ba5a56eb8c6c --- /dev/null +++ b/platform/util/testSrc/com/intellij/execution/CommandLineWrapperUtilTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed 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 com.intellij.execution; + +import com.intellij.openapi.util.io.FileUtil; +import org.junit.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.util.Arrays; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.JarInputStream; +import java.util.jar.Manifest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class CommandLineWrapperUtilTest { + + @Test + public void testManifestWithJarsAndDirectories() throws Exception { + final File tempDirectory = FileUtil.createTempDirectory("dirWithClasses", "suffix"); + File jarFile = null; + try { + final List paths = Arrays.asList(tempDirectory.getAbsolutePath(), "/directory with spaces/some.jar"); + jarFile = CommandLineWrapperUtil.createClasspathJarFile(new Manifest(), paths); + final JarInputStream inputStream = new JarInputStream(new FileInputStream(jarFile)); + final Manifest manifest = inputStream.getManifest(); + final String classPath = manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH); + final String tempDirectoryUrl = tempDirectory.toURI().toURL().toString(); + assertTrue(tempDirectoryUrl, tempDirectoryUrl.endsWith("/")); + assertEquals(tempDirectoryUrl + " file:/directory%20with%20spaces/some.jar", classPath); + } + finally { + FileUtil.delete(tempDirectory); + if (jarFile != null) { + FileUtil.delete(jarFile); + } + } + } +} \ No newline at end of file