From c570e61f3bd3039723017a8e06fa2b61ff1afbd7 Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Mon, 18 Apr 2016 14:41:11 +0200 Subject: [PATCH] instrumenters: support resolution against java9 platform classes (IDEA-153804) --- .idea/modules.xml | 3 +- .../instrumentation-util-8.iml | 14 ++ .../compiler/instrumentation/JrtLoader.java | 102 ++++++++ .../InstrumentationClassFinder.java | 231 ++++++++---------- .../ClassProcessingBuilder.java | 24 +- .../compiler/FormsInstrumenter.java | 7 +- 6 files changed, 244 insertions(+), 137 deletions(-) create mode 100644 java/compiler/instrumentation-util-8/instrumentation-util-8.iml create mode 100644 java/compiler/instrumentation-util-8/src/com/intellij/compiler/instrumentation/JrtLoader.java diff --git a/.idea/modules.xml b/.idea/modules.xml index 8fedac9d8313..1e07d739f243 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -114,6 +114,7 @@ + @@ -262,11 +263,11 @@ - + diff --git a/java/compiler/instrumentation-util-8/instrumentation-util-8.iml b/java/compiler/instrumentation-util-8/instrumentation-util-8.iml new file mode 100644 index 000000000000..c00964488054 --- /dev/null +++ b/java/compiler/instrumentation-util-8/instrumentation-util-8.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/compiler/instrumentation-util-8/src/com/intellij/compiler/instrumentation/JrtLoader.java b/java/compiler/instrumentation-util-8/src/com/intellij/compiler/instrumentation/JrtLoader.java new file mode 100644 index 000000000000..1185754304ae --- /dev/null +++ b/java/compiler/instrumentation-util-8/src/com/intellij/compiler/instrumentation/JrtLoader.java @@ -0,0 +1,102 @@ +/* + * Copyright 2000-2016 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.compiler.instrumentation; + +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.*; + +/** + * Invoked via reflection from InstrumentationClassFinder + */ +public class JrtLoader extends InstrumentationClassFinder.ClassFinderClasspath.Loader{ + private static final URI ROOT_URI = URI.create("jrt:/"); + private static final InstrumentationClassFinder.Resource NULL_RESOURCE = ()-> null; + private List myRoots; + private final Map myCache = new HashMap<>(); + + public JrtLoader(URL url, int index) { + super(url, index); + } + + @Override + public InstrumentationClassFinder.Resource getResource(String name) { + final InstrumentationClassFinder.Resource cached = myCache.get(name); + if (cached != null) { + return cached != NULL_RESOURCE ? cached : null; + } + try { + for (Path root : getRoots()) { + final Path path = root.resolve(name); + if (Files.exists(path)) { + final InstrumentationClassFinder.Resource res = () -> Files.newInputStream(path, StandardOpenOption.READ); + myCache.put(name, res); + return res; + } + } + } + catch (IOException e) { + e.printStackTrace(); + } + myCache.put(name, NULL_RESOURCE); + return null; + } + + @Override + public synchronized void releaseResources() { + myCache.clear(); + final List roots = myRoots; + if (roots != null) { + try { + if (!roots.isEmpty()) { + roots.iterator().next().getFileSystem().close(); + } + } + catch (IOException e) { + e.printStackTrace(); + } + finally { + roots.clear(); + myRoots = null; + } + } + } + + private synchronized List getRoots() throws IOException { + final List cached = myRoots; + if (cached != null) { + return cached; + } + final FileSystem fs = FileSystems.newFileSystem(ROOT_URI, Collections.singletonMap("java.home", getBaseURL().getPath())); + final List roots = new ArrayList<>(); + final Path modulesDir = fs.getPath("modules"); + Files.walkFileTree(modulesDir, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + if (!modulesDir.equals(dir)) { + roots.add(dir); + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; + } + }); + myRoots = roots; + return roots; + } +} diff --git a/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumentationClassFinder.java b/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumentationClassFinder.java index 02f4ffcd5f37..e34e7acfa517 100644 --- a/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumentationClassFinder.java +++ b/java/compiler/instrumentation-util/src/com/intellij/compiler/instrumentation/InstrumentationClassFinder.java @@ -4,9 +4,9 @@ import org.jetbrains.org.objectweb.asm.ClassReader; import org.jetbrains.org.objectweb.asm.ClassVisitor; import org.jetbrains.org.objectweb.asm.MethodVisitor; import org.jetbrains.org.objectweb.asm.Opcodes; -import sun.misc.Resource; import java.io.*; +import java.lang.reflect.Constructor; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; @@ -152,9 +152,9 @@ public class InstrumentationClassFinder { InputStream is = null; // first look into platformCp final String resourceName = internalName + CLASS_RESOURCE_EXTENSION; - Resource resource = myPlatformClasspath.getResource(resourceName, false); + Resource resource = myPlatformClasspath.getResource(resourceName); if (resource != null) { - is = new ByteArrayInputStream(resource.getBytes()); + is = resource.getInputStream(); } // second look into memory and classspath if (is == null) { @@ -162,9 +162,9 @@ public class InstrumentationClassFinder { } if (is == null) { - resource = myClasspath.getResource(resourceName, false); + resource = myClasspath.getResource(resourceName); if (resource != null) { - is = new ByteArrayInputStream(resource.getBytes()); + is = resource.getInputStream(); } } @@ -177,15 +177,15 @@ public class InstrumentationClassFinder { public InputStream getResourceAsStream(String resourceName) throws IOException { InputStream is = null; - Resource resource = myPlatformClasspath.getResource(resourceName, false); + Resource resource = myPlatformClasspath.getResource(resourceName); if (resource != null) { - is = new ByteArrayInputStream(resource.getBytes()); + is = resource.getInputStream(); } if (is == null) { - resource = myClasspath.getResource(resourceName, false); + resource = myClasspath.getResource(resourceName); if (resource != null) { - is = new ByteArrayInputStream(resource.getBytes()); + is = resource.getInputStream(); } } @@ -433,8 +433,11 @@ public class InstrumentationClassFinder { } } + public interface Resource { + InputStream getInputStream() throws IOException; + } + static class ClassFinderClasspath { - private static final String FILE_PROTOCOL = "file"; private final Stack myUrls = new Stack(); private final List myLoaders = new ArrayList(); @@ -448,10 +451,10 @@ public class InstrumentationClassFinder { } } - public Resource getResource(String s, boolean flag) { + public Resource getResource(String s) { int i = 0; for (Loader loader; (loader = getLoader(i)) != null; i++) { - Resource resource = loader.getResource(s, flag); + Resource resource = loader.getResource(s); if (resource != null) { return resource; } @@ -500,7 +503,7 @@ public class InstrumentationClassFinder { return myLoaders.get(i); } - private Loader getLoader(final URL url, int index) throws IOException { + private static Loader getLoader(final URL url, int index) throws IOException { String s; try { s = url.toURI().getSchemeSpecificPart(); @@ -510,23 +513,28 @@ public class InstrumentationClassFinder { s = url.getFile(); } - Loader loader = null; - if (s != null && new File(s).isDirectory()) { - if (FILE_PROTOCOL.equals(url.getProtocol())) { - loader = new FileLoader(url, index); + if (s != null && s.length() > 0) { + final String protocol = url.getProtocol(); + if (Loader.JRT_PROTOCOL.equals(protocol)) { + final Loader jrtLoader = JrtClassHolder.create(url, index); + if (jrtLoader != null) { + return jrtLoader; + } + } + if (new File(s).isDirectory()) { + return Loader.FILE_PROTOCOL.equals(protocol) ? new FileLoader(url, index) : null; } } - else { - loader = new JarLoader(url, index); - } - return loader; + // by default treat the url as a jar archive + return new JarLoader(url, index); } - private abstract static class Loader { + abstract static class Loader { protected static final String JAR_PROTOCOL = "jar"; protected static final String FILE_PROTOCOL = "file"; + protected static final String JRT_PROTOCOL = "jrt"; private final URL myURL; private final int myIndex; @@ -541,7 +549,7 @@ public class InstrumentationClassFinder { return myURL; } - public abstract Resource getResource(final String name, boolean flag); + public abstract Resource getResource(final String name); public abstract void releaseResources(); @@ -568,76 +576,38 @@ public class InstrumentationClassFinder { public void releaseResources() { } - public Resource getResource(final String name, boolean check) { - URL url = null; - File file = null; - + public Resource getResource(final String name) { try { - url = new URL(getBaseURL(), name); + final URL url = new URL(getBaseURL(), name); if (!url.getFile().startsWith(getBaseURL().getFile())) { return null; } - file = new File(myRootDir, name.replace('/', File.separatorChar)); - if (!check || file.exists()) { // check means we load or process resource so we check its existence via old way - return new FileResource(name, url, file, !check); + final File file = new File(myRootDir, name.replace('/', File.separatorChar)); + if (file.exists()) { + return new Resource() { + @Override + public InputStream getInputStream() throws IOException { + return new BufferedInputStream(new FileInputStream(file)); + } + + public String toString() { + return file.getAbsolutePath(); + } + }; } } - catch (Exception exception) { - if (!check && file != null && file.exists()) { - try { // we can not open the file if it is directory, Resource still can be created - return new FileResource(name, url, file, false); - } - catch (IOException ex) { - } - } + catch (Exception ignored) { } return null; } - private class FileResource extends Resource { - private final String myName; - private final URL myUrl; - private final File myFile; - - public FileResource(String name, URL url, File file, boolean willLoadBytes) throws IOException { - myName = name; - myUrl = url; - myFile = file; - if (willLoadBytes) getByteBuffer(); // check for existence by creating cached file input stream - } - - public String getName() { - return myName; - } - - public URL getURL() { - return myUrl; - } - - public URL getCodeSourceURL() { - return getBaseURL(); - } - - public InputStream getInputStream() throws IOException { - return new BufferedInputStream(new FileInputStream(myFile)); - } - - public int getContentLength() throws IOException { - return -1; - } - - public String toString() { - return myFile.getAbsolutePath(); - } - } - public String toString() { return "FileLoader [" + myRootDir + "]"; } } - private class JarLoader extends Loader { + private static class JarLoader extends Loader { private final URL myURL; private ZipFile myZipFile; @@ -674,21 +644,40 @@ public class InstrumentationClassFinder { if (!new File(s).exists()) { throw new FileNotFoundException(s); } - else { - return new ZipFile(s); - } + return new ZipFile(s); } return null; } - public Resource getResource(String name, boolean flag) { + public Resource getResource(String name) { try { final ZipFile file = acquireZipFile(); if (file != null) { final ZipEntry entry = file.getEntry(name); if (entry != null) { - return new JarResource(entry, new URL(getBaseURL(), name)); + return new Resource() { + @Override + public InputStream getInputStream() throws IOException { + try { + final ZipFile file = acquireZipFile(); + if (file != null) { + final InputStream inputStream = file.getInputStream(entry); + if (inputStream != null) { + return new FilterInputStream(inputStream) {}; + } + } + } + catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public String toString() { + return "JarLoader [" + myURL + "!/" + entry.getName() + "]"; + } + }; } } } @@ -698,54 +687,6 @@ public class InstrumentationClassFinder { return null; } - private class JarResource extends Resource { - private final ZipEntry myEntry; - private final URL myUrl; - - public JarResource(ZipEntry name, URL url) { - myEntry = name; - myUrl = url; - } - - public String getName() { - return myEntry.getName(); - } - - public URL getURL() { - return myUrl; - } - - public URL getCodeSourceURL() { - return myURL; - } - - public InputStream getInputStream() throws IOException { - try { - final ZipFile file = acquireZipFile(); - if (file == null) { - return null; - } - - final InputStream inputStream = file.getInputStream(myEntry); - if (inputStream == null) { - return null; // if entry was not found - } - return new FilterInputStream(inputStream) {}; - } - catch (IOException e) { - e.printStackTrace(); - return null; - } - } - - public int getContentLength() { - return (int)myEntry.getSize(); - } - } - - public String toString() { - return "JarLoader [" + myURL + "]"; - } } } @@ -828,4 +769,34 @@ public class InstrumentationClassFinder { return result.toByteArray(); } + private static final class JrtClassHolder { + public static final Class ourClass; + public static final Constructor ourConstructor; + + static { + Class aClass = null; + Constructor constructor = null; + try { + aClass = (Class)Class.forName("com.intellij.compiler.instrumentation.JrtLoader"); + constructor = aClass.getDeclaredConstructor(URL.class, int.class); + constructor.setAccessible(true); + } + catch (Throwable ignored) { + } + ourClass = aClass; + ourConstructor = constructor; + } + + public static ClassFinderClasspath.Loader create(URL url, int index) { + if (ourConstructor != null) { + try { + return ourConstructor.newInstance(url, index); + } + catch (Throwable ignored) { + } + } + return null; + } + } + } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/instrumentation/ClassProcessingBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/instrumentation/ClassProcessingBuilder.java index bc353aa4b144..fa458f0d6976 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/instrumentation/ClassProcessingBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/instrumentation/ClassProcessingBuilder.java @@ -18,13 +18,18 @@ package org.jetbrains.jps.incremental.instrumentation; import com.intellij.compiler.instrumentation.InstrumentationClassFinder; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.ModuleChunk; import org.jetbrains.jps.ProjectPaths; import org.jetbrains.jps.builders.DirtyFilesHolder; import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor; import org.jetbrains.jps.incremental.*; import org.jetbrains.jps.incremental.messages.ProgressMessage; +import org.jetbrains.jps.model.JpsDummyElement; +import org.jetbrains.jps.model.java.JpsJavaSdkType; +import org.jetbrains.jps.model.library.sdk.JpsSdk; import org.jetbrains.org.objectweb.asm.ClassReader; import org.jetbrains.org.objectweb.asm.ClassVisitor; import org.jetbrains.org.objectweb.asm.ClassWriter; @@ -85,8 +90,8 @@ public abstract class ClassProcessingBuilder extends ModuleLevelBuilder { final Collection classpath = new ArrayList(); classpath.addAll(ProjectPaths.getCompilationClasspath(chunk, false)); classpath.addAll(ProjectPaths.getSourceRootsWithDependents(chunk).keySet()); - - finder = createInstrumentationClassFinder(platformCp, classpath, outputConsumer); + final JpsSdk sdk = chunk.representativeTarget().getModule().getSdk(JpsJavaSdkType.INSTANCE); + finder = createInstrumentationClassFinder(sdk, platformCp, classpath, outputConsumer); CLASS_FINDER.set(context, finder); } @@ -109,10 +114,21 @@ public abstract class ClassProcessingBuilder extends ModuleLevelBuilder { // utility methods - public static InstrumentationClassFinder createInstrumentationClassFinder(Collection platformCp, Collection cp, final OutputConsumer outputConsumer) throws + public static InstrumentationClassFinder createInstrumentationClassFinder(@Nullable JpsSdk sdk, + Collection platformCp, + Collection cp, + final OutputConsumer outputConsumer) throws MalformedURLException { - final URL[] platformUrls = new URL[platformCp.size()]; + final URL[] platformUrls; int index = 0; + if (sdk != null && JpsJavaSdkType.getJavaVersion(sdk) >= 9) { + platformUrls = new URL[1 + platformCp.size()]; + platformUrls[index++] = new URL("jrt", null, FileUtil.toSystemIndependentName(sdk.getHomePath())); + } + else { + platformUrls = new URL[platformCp.size()]; + } + for (File file : platformCp) { platformUrls[index++] = file.toURI().toURL(); } diff --git a/plugins/ui-designer/jps-plugin/src/org/jetbrains/jps/uiDesigner/compiler/FormsInstrumenter.java b/plugins/ui-designer/jps-plugin/src/org/jetbrains/jps/uiDesigner/compiler/FormsInstrumenter.java index 0a4ead6ef9b1..244463148b89 100644 --- a/plugins/ui-designer/jps-plugin/src/org/jetbrains/jps/uiDesigner/compiler/FormsInstrumenter.java +++ b/plugins/ui-designer/jps-plugin/src/org/jetbrains/jps/uiDesigner/compiler/FormsInstrumenter.java @@ -40,7 +40,10 @@ import org.jetbrains.jps.incremental.messages.BuildMessage; import org.jetbrains.jps.incremental.messages.CompilerMessage; import org.jetbrains.jps.incremental.messages.ProgressMessage; import org.jetbrains.jps.incremental.storage.OneToManyPathsMapping; +import org.jetbrains.jps.model.JpsDummyElement; import org.jetbrains.jps.model.JpsProject; +import org.jetbrains.jps.model.java.JpsJavaSdkType; +import org.jetbrains.jps.model.library.sdk.JpsSdk; import org.jetbrains.jps.uiDesigner.model.JpsUiDesignerConfiguration; import org.jetbrains.jps.uiDesigner.model.JpsUiDesignerExtensionService; import org.jetbrains.org.objectweb.asm.ClassReader; @@ -94,8 +97,8 @@ public class FormsInstrumenter extends FormsBuilder { classpath.add(getResourcePath(GridConstraints.class)); // forms_rt.jar final Map chunkSourcePath = ProjectPaths.getSourceRootsWithDependents(chunk); classpath.addAll(chunkSourcePath.keySet()); // sourcepath for loading forms resources - - final InstrumentationClassFinder finder = ClassProcessingBuilder.createInstrumentationClassFinder(platformCp, classpath, outputConsumer); + final JpsSdk sdk = chunk.representativeTarget().getModule().getSdk(JpsJavaSdkType.INSTANCE); + final InstrumentationClassFinder finder = ClassProcessingBuilder.createInstrumentationClassFinder(sdk, platformCp, classpath, outputConsumer); try { final Map> processed = instrumentForms(context, chunk, chunkSourcePath, finder, formsToCompile, outputConsumer);