diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/DeduplicationUtilTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/DeduplicationUtilTest.java index 2fab70e4d82..fb914bd9393 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/DeduplicationUtilTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/DeduplicationUtilTest.java @@ -13,6 +13,7 @@ *******************************************************************************/ package org.eclipse.jdt.core.tests.compiler; +import java.util.Arrays; import java.util.List; import java.util.function.Supplier; @@ -38,6 +39,7 @@ public void testDeduplication() { assertDeduplicationString("aNeverUsedBefore".toCharArray()); assertDeduplicationCharArray("aNeverUsedBefore"); assertDeduplicationObject(() -> List.of("aNeverUsedBefore")); + assertDeduplicationList(() -> Arrays.asList("1","2","3")); assertDeduplicationString("bNeverUsedBefore".toCharArray()); assertDeduplicationCharArray("bNeverUsedBefore"); @@ -133,6 +135,52 @@ private void assertDeduplicationObject(Supplier supplier) { } } + private void assertDeduplicationList(Supplier> supplier) { + { // weak + List a = supplier.get(); + List b = supplier.get(); + assertNotSame(a, b); + assertEquals(a, b); + List expected = DeduplicationUtil.intern(b); + for (int i = 0; i < a.size(); i++) { + assertSame(b.get(i), expected.get(i)); + } + b = null; + expected = null; + + forceGc(); + + // Now "b" is not referenced anymore, can be garbage collected + // and DeduplicationUtil is supposed to release weak reference to it + // so after trying to intern "a" we will get "a" and not previously set "b" + List actual = DeduplicationUtil.intern(a); + + // It is impossible to rely on GC to run immediately, so loop few times + for (int i = 0; i < 42; i++) { + if(actual != a) { + forceGc(); + actual = DeduplicationUtil.intern(a); + } else { + break; + } + } + for (int i = 0; i < a.size(); i++) { + assertSame(a.get(i), actual.get(i)); + } + } + { // strong + List a = supplier.get(); + List b = supplier.get(); + assertNotSame(a, b); + assertEquals(a, b); + List actual = DeduplicationUtil.intern(a); + List expected = DeduplicationUtil.intern(b); + + // since "a" is still referenced, we should get it + assertSame(expected, actual); + } + } + private void forceGc() { System.gc(); System.runFinalization(); diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavaProjectTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavaProjectTests.java index b4f12d4311e..0edd430396b 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavaProjectTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavaProjectTests.java @@ -19,6 +19,8 @@ import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; import java.util.Hashtable; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -1686,6 +1688,18 @@ public void testPackageFragmentRootNonJavaResources9() throws Exception { // the bug occurred only if META-INF/MANIFEST.MF was before META-INF in the ZIP file // Altered the test for 534624. Usage of Zip file system for traversal no longer sees two different entries, but just the file. zip.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); + + // a java class: + zip.putNextEntry(new ZipEntry("p1/p2/p3/java.class")); + + // some more resources: + zip.putNextEntry(new ZipEntry("legalPackageName/MANIFEST2.MF")); + zip.putNextEntry(new ZipEntry("r1/r2/r3/resource.png")); + zip.putNextEntry(new ZipEntry("p/x.y/Test.txt")); + zip.putNextEntry(new ZipEntry("p1/-invalid-/p3/fake.class")); + + /// just another resource: + zip.putNextEntry(new ZipEntry("invalid-/legalPackageName2/-MANIFEST.MF2")); } createJavaProject("P", new String[0], new String[] {getExternalResourcePath("lib.jar")}, ""); waitForManualRefresh(); @@ -1693,14 +1707,48 @@ public void testPackageFragmentRootNonJavaResources9() throws Exception { Object[] resources = root.getNonJavaResources(); assertResourceTreeEquals( "unexpected non java resources", - "META-INF\n" + - " MANIFEST.MF", - resources); + """ + META-INF + MANIFEST.MF + invalid- + legalPackageName2 + -MANIFEST.MF2""" + ,resources); + IJavaElement[] children= root.getChildren(); + Arrays.sort(children, Comparator.comparing(IJavaElement::getElementName)); + String expected = """ + [in lib.jar] + legalPackageName [in lib.jar] + p [in lib.jar] + p1 [in lib.jar] + p1.p2 [in lib.jar] + p1.p2.p3 [in lib.jar] + r1 [in lib.jar] + r1.r2 [in lib.jar] + r1.r2.r3 [in lib.jar]"""; + expected = expected.replace("lib.jar", root.getPath().toOSString()); + assertElementsEqual("Unexpected package fragment roots", expected, children); + + IPackageFragment pf= getPackageFragment("P", getExternalResourcePath("lib.jar"), "legalPackageName"); + assertResourceTreeEquals( + "unexpected non java resources", + """ + MANIFEST2.MF""" + , pf.getNonJavaResources()); + IPackageFragment pf2= getPackageFragment("P", getExternalResourcePath("lib.jar"), "p1"); + assertResourceTreeEquals( + "unexpected non java resources", + """ + -invalid- + p3 + fake.class""" + , pf2.getNonJavaResources()); } finally { deleteExternalResource("lib.jar"); deleteProject("P"); } } + /** * Test raw entry inference performance for package fragment root */ diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JModPackageFragmentRoot.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JModPackageFragmentRoot.java index b4a753a8348..e0ac8275eda 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JModPackageFragmentRoot.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JModPackageFragmentRoot.java @@ -15,9 +15,7 @@ import org.eclipse.core.runtime.IPath; import org.eclipse.jdt.core.IClasspathAttribute; -import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.core.builder.ClasspathJMod; -import org.eclipse.jdt.internal.core.util.HashtableOfArrayToObject; /** * A package fragment root that corresponds to a JMod file. @@ -47,15 +45,10 @@ protected JModPackageFragmentRoot(IPath externalPath, JavaProject project, IClas */ @Override public String getClassFilePath(String entryName) { - char[] name = CharOperation.append(ClasspathJMod.CLASSES_FOLDER, entryName.toCharArray()); - return new String(name); + return ClasspathJMod.CLASSES_FOLDER + entryName; } @Override - protected void initRawPackageInfo(HashtableOfArrayToObject rawPackageInfo, String entryName, boolean isDirectory, String compliance) { - char[] name = entryName.toCharArray(); - if (CharOperation.prefixEquals(ClasspathJMod.CLASSES_FOLDER, name)) { - name = CharOperation.subarray(name, ClasspathJMod.CLASSES_FOLDER.length, name.length); - } - super.initRawPackageInfo(rawPackageInfo, new String(name), isDirectory, compliance); + protected String getClassNameSubFolder() { + return ClasspathJMod.CLASSES_FOLDER; } } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragment.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragment.java index ddad310cc1d..8c06e62327f 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragment.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragment.java @@ -14,9 +14,11 @@ package org.eclipse.jdt.internal.core; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; -import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IPath; @@ -29,6 +31,7 @@ import org.eclipse.jdt.core.IJavaModelStatusConstants; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.core.JarPackageFragmentRootInfo.PackageContent; import org.eclipse.jdt.internal.core.util.DeduplicationUtil; import org.eclipse.jdt.internal.core.util.Util; @@ -37,7 +40,6 @@ * * @see org.eclipse.jdt.core.IPackageFragment */ -@SuppressWarnings({ "rawtypes", "unchecked" }) class JarPackageFragment extends PackageFragment { /** * Constructs a package fragment that is contained within a jar or a zip. @@ -52,16 +54,16 @@ protected JarPackageFragment(PackageFragmentRoot root, String[] names) { protected boolean buildStructure(OpenableElementInfo info, IProgressMonitor pm, Map newElements, IResource underlyingResource) throws JavaModelException { JarPackageFragmentRoot root = (JarPackageFragmentRoot) getParent(); JarPackageFragmentRootInfo parentInfo = (JarPackageFragmentRootInfo) root.getElementInfo(); - ArrayList[] entries = (ArrayList[]) parentInfo.rawPackageInfo.get(this.names); + PackageContent entries = parentInfo.rawPackageInfo.get(Arrays.asList(this.names)); if (entries == null) throw newNotPresentException(); JarPackageFragmentInfo fragInfo = (JarPackageFragmentInfo) info; // compute children - fragInfo.setChildren(computeChildren(entries[0/*class files*/])); + fragInfo.setChildren(computeChildren(entries.javaClasses())); // compute non-Java resources - fragInfo.setNonJavaResources(computeNonJavaResources(entries[1/*non Java resources*/])); + fragInfo.setNonJavaResources(computeNonJavaResources(entries.resources())); newElements.put(this, fragInfo); return true; @@ -70,13 +72,13 @@ protected boolean buildStructure(OpenableElementInfo info, IProgressMonitor pm, * Compute the children of this package fragment. Children of jar package fragments * can only be IClassFile (representing .class files). */ -private IJavaElement[] computeChildren(ArrayList namesWithoutExtension) { +private IJavaElement[] computeChildren(List namesWithoutExtension) { int size = namesWithoutExtension.size(); if (size == 0) return NO_ELEMENTS; IJavaElement[] children = new IJavaElement[size]; for (int i = 0; i < size; i++) { - String nameWithoutExtension = (String) namesWithoutExtension.get(i); + String nameWithoutExtension = namesWithoutExtension.get(i); if (TypeConstants.MODULE_INFO_NAME_STRING.equals(nameWithoutExtension)) children[i] = new ModularClassFile(this); else @@ -87,15 +89,14 @@ private IJavaElement[] computeChildren(ArrayList namesWithoutExtension) { /** * Compute all the non-java resources according to the given entry names. */ -private Object[] computeNonJavaResources(ArrayList entryNames) { - int length = entryNames.size(); - if (length == 0) +private Object[] computeNonJavaResources(List entryNames) { + if (entryNames.isEmpty()) { return JavaElementInfo.NO_NON_JAVA_RESOURCES; - HashMap jarEntries = new HashMap(); // map from IPath to IJarEntryResource - HashMap childrenMap = new HashMap(); // map from IPath to ArrayList - ArrayList topJarEntries = new ArrayList(); - for (int i = 0; i < length; i++) { - String resName = (String) entryNames.get(i); + } + HashMap jarEntries = new HashMap<>(); + HashMap> childrenMap = new HashMap<>(); + ArrayList topJarEntries = new ArrayList<>(); + for (String resName: entryNames) { // consider that a .java file is not a non-java resource (see bug 12246 Packages view shows .class and .java files when JAR has source) if (!Util.isJavaLikeFileName(resName)) { IPath filePath = new Path(resName); @@ -112,11 +113,11 @@ private Object[] computeNonJavaResources(ArrayList entryNames) { } else { IPath parentPath = childPath.removeLastSegments(1); while (parentPath.segmentCount() > 0) { - ArrayList parentChildren = (ArrayList) childrenMap.get(parentPath); + ArrayList parentChildren = childrenMap.get(parentPath); if (parentChildren == null) { - Object dir = new JarEntryDirectory(parentPath.lastSegment()); + JarEntryDirectory dir = new JarEntryDirectory(parentPath.lastSegment()); jarEntries.put(parentPath, dir); - childrenMap.put(parentPath, parentChildren = new ArrayList()); + childrenMap.put(parentPath, parentChildren = new ArrayList<>()); parentChildren.add(childPath); if (parentPath.segmentCount() == 1) { topJarEntries.add(dir); @@ -132,11 +133,9 @@ private Object[] computeNonJavaResources(ArrayList entryNames) { } } } - Iterator entries = childrenMap.entrySet().iterator(); - while (entries.hasNext()) { - Map.Entry entry = (Map.Entry) entries.next(); - IPath entryPath = (IPath) entry.getKey(); - ArrayList entryValue = (ArrayList) entry.getValue(); + for (Entry> entry: childrenMap.entrySet()) { + IPath entryPath = entry.getKey(); + ArrayList entryValue = entry.getValue(); JarEntryDirectory jarEntryDirectory = (JarEntryDirectory) jarEntries.get(entryPath); int size = entryValue.size(); IJarEntryResource[] children = new IJarEntryResource[size]; @@ -150,7 +149,7 @@ private Object[] computeNonJavaResources(ArrayList entryNames) { jarEntryDirectory.setParent(this); } } - return topJarEntries.toArray(new Object[topJarEntries.size()]); + return topJarEntries.toArray(Object[]::new); } /** * Returns true if this fragment contains at least one java resource. @@ -179,10 +178,8 @@ protected JarPackageFragmentInfo createElementInfo() { */ @Override public IClassFile[] getAllClassFiles() throws JavaModelException { - ArrayList list = getChildrenOfType(CLASS_FILE); - IClassFile[] array= new IClassFile[list.size()]; - list.toArray(array); - return array; + ArrayList list = getChildrenOfType(CLASS_FILE); + return list.toArray(IClassFile[]::new); } /** * A jar package fragment never contains compilation units. diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRoot.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRoot.java index f3fe5abff91..41aa766a993 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRoot.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRoot.java @@ -18,10 +18,13 @@ import java.net.URL; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipException; @@ -41,9 +44,9 @@ import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.core.JarPackageFragmentRootInfo.PackageContent; import org.eclipse.jdt.internal.core.JavaModelManager.PerProjectInfo; import org.eclipse.jdt.internal.core.util.DeduplicationUtil; -import org.eclipse.jdt.internal.core.util.HashtableOfArrayToObject; import org.eclipse.jdt.internal.core.util.Util; /** @@ -56,11 +59,8 @@ * @see org.eclipse.jdt.core.IPackageFragmentRoot * @see org.eclipse.jdt.internal.core.JarPackageFragmentRootInfo */ -@SuppressWarnings({"rawtypes", "unchecked"}) public class JarPackageFragmentRoot extends PackageFragmentRoot { - protected final static ArrayList EMPTY_LIST = new ArrayList(); - /** * The path to the jar file * (a workspace relative path if the jar is internal, @@ -114,12 +114,12 @@ public JarPackageFragmentRoot(IResource resource, IPath externalJarPath, JavaPro */ @Override protected boolean computeChildren(OpenableElementInfo info, IResource underlyingResource) throws JavaModelException { - final HashtableOfArrayToObject rawPackageInfo = new HashtableOfArrayToObject(); - final Map overridden = new HashMap<>(); + Map, PackageContent> rawPackageInfo= new HashMap<>(); + Map overridden = new HashMap<>(); IJavaElement[] children = NO_ELEMENTS; try { // always create the default package - rawPackageInfo.put(CharOperation.NO_STRINGS, new ArrayList[] { EMPTY_LIST, EMPTY_LIST }); + rawPackageInfo.put(new ArrayList<>(), new PackageContent()); Object file = JavaModel.getTarget(this, true); long classLevel = Util.getJdkLevel(file); @@ -165,29 +165,26 @@ protected boolean computeChildren(OpenableElementInfo info, IResource underlying overridden.put(name, versionPath); } } - initRawPackageInfo(rawPackageInfo, name, member.isDirectory(), CompilerOptions.versionFromJdkLevel(classLevel)); + initRawPackageInfo(rawPackageInfo, getClassNameSubFolder(), name, member.isDirectory(), CompilerOptions.versionFromJdkLevel(classLevel)); } } finally { JavaModelManager.getJavaModelManager().closeZipFile(jar); } - // loop through all of referenced packages, creating package fragments if necessary - // and cache the entry names in the rawPackageInfo table - children = new IJavaElement[rawPackageInfo.size()]; - int index = 0; - for (Object[] o : rawPackageInfo.keyTable) { - String[] pkgName = (String[]) o; - if (pkgName == null) continue; - children[index++] = getPackageFragment(pkgName); - } + rawPackageInfo = unmodifiableCopy(rawPackageInfo); + children = createChildren(rawPackageInfo.keySet()); } catch (ZipException zipex) { // malcious ZIP archive, leave the children empty Util.log(zipex, "Invalid ZIP archive: " + toStringWithAncestors()); //$NON-NLS-1$ children = NO_ELEMENTS; + rawPackageInfo= Map.of(); + overridden = Map.of(); } catch (CoreException e) { if (e.getCause() instanceof ZipException zipex) { // not a ZIP archive, leave the children empty Util.log(zipex, "Invalid ZIP archive: " + toStringWithAncestors()); //$NON-NLS-1$ children = NO_ELEMENTS; + rawPackageInfo= Map.of(); + overridden = Map.of(); } else if (e instanceof JavaModelException) { throw (JavaModelException)e; } else { @@ -200,15 +197,16 @@ protected boolean computeChildren(OpenableElementInfo info, IResource underlying return true; } - protected IJavaElement[] createChildren(final HashtableOfArrayToObject rawPackageInfo) { - IJavaElement[] children; + protected IJavaElement[] createChildren(Collection> packagenames) { + // XXX sorting the children is unnecessary by contract - see org.eclipse.jdt.core.IParent#getChildren() + // but some tests like JavaProjectTests rely on a fixed child order + ArrayList keys = new ArrayList<>(packagenames.stream().map(s->s.toArray(String[]::new)).toList() ); + Collections.sort(keys, Arrays::compare); + + IJavaElement[] children = new IJavaElement[packagenames.size()]; // loop through all of referenced packages, creating package fragments if necessary - // and cache the entry names in the rawPackageInfo table - children = new IJavaElement[rawPackageInfo.size()]; int index = 0; - for (Object[] o : rawPackageInfo.keyTable) { - String[] pkgName = (String[]) o; - if (pkgName == null) continue; + for (String[] pkgName : keys) { children[index++] = getPackageFragment(pkgName); } return children; @@ -352,56 +350,56 @@ public IResource getUnderlyingResource() throws JavaModelException { return super.getUnderlyingResource(); } } - protected void initRawPackageInfo(HashtableOfArrayToObject rawPackageInfo, String entryName, boolean isDirectory, String compliance) { + + /** make sure all parent packages exit in rawPackageInfo and adds the given file to the given package + * static implementation to make sure the result can be shared across instances */ + static void initRawPackageInfo(Map, PackageContent> rawPackageInfo, String classNameSubFolder, + String entryName, boolean isDirectory, String compliance) { + String className = entryToClassName(classNameSubFolder, entryName); int lastSeparator; if (isDirectory) { - if (entryName.charAt(entryName.length() - 1) == '/') { - lastSeparator = entryName.length() - 1; + if (className.charAt(className.length() - 1) == '/') { + lastSeparator = className.length() - 1; } else { - lastSeparator = entryName.length(); + lastSeparator = className.length(); } } else { - lastSeparator = entryName.lastIndexOf('/'); + lastSeparator = className.lastIndexOf('/'); } - String[] pkgName = Util.splitOn('/', entryName, 0, lastSeparator); - String[] existing = null; - int length = pkgName.length; - int existingLength = length; - while (existingLength >= 0) { - existing = (String[]) rawPackageInfo.getKey(pkgName, existingLength); - if (existing != null) break; - existingLength--; + ArrayList pkgName = new ArrayList<>(Arrays.asList(Util.splitOn('/', className, 0, lastSeparator))); + PackageContent existing = null; + int length = pkgName.size(); + int existingLength; + for (existingLength = length; existing == null; existingLength--) { + existing = rawPackageInfo.get(pkgName.subList(0, existingLength)); } + existingLength++; for (int i = existingLength; i < length; i++) { // sourceLevel must be null because we know nothing about it based on a jar file - if (Util.isValidFolderNameForPackage(pkgName[i], null, compliance)) { - System.arraycopy(existing, 0, existing = new String[i+1], 0, i); - existing[i] = DeduplicationUtil.intern(pkgName[i]); - rawPackageInfo.put(existing, new ArrayList[] { EMPTY_LIST, EMPTY_LIST }); + if (Util.isValidFolderNameForPackage(pkgName.get(i), null, compliance)) { + List path = pkgName.subList(0, i + 1); + existing = new PackageContent(); + rawPackageInfo.put(path, existing); } else { // non-Java resource folder if (!isDirectory) { - ArrayList[] children = (ArrayList[]) rawPackageInfo.get(existing); - if (children[1/*NON_JAVA*/] == EMPTY_LIST) children[1/*NON_JAVA*/] = new ArrayList(); - children[1/*NON_JAVA*/].add(entryName); + existing.resources().add(className); } return; } } - if (isDirectory) + if (isDirectory) { return; + } // add classfile info amongst children - ArrayList[] children = (ArrayList[]) rawPackageInfo.get(pkgName); - if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(entryName)) { - if (children[0/*JAVA*/] == EMPTY_LIST) children[0/*JAVA*/] = new ArrayList(); - String nameWithoutExtension = entryName.substring(lastSeparator + 1, entryName.length() - 6); - children[0/*JAVA*/].add(nameWithoutExtension); + PackageContent children = rawPackageInfo.get(pkgName); + if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(className)) { + String nameWithoutExtension = className.substring(lastSeparator + 1, className.length() - 6); + children.javaClasses().add(nameWithoutExtension); } else { - if (children[1/*NON_JAVA*/] == EMPTY_LIST) children[1/*NON_JAVA*/] = new ArrayList(); - children[1/*NON_JAVA*/].add(entryName); + children.resources().add(className); } - } /** * @see IPackageFragmentRoot @@ -477,6 +475,32 @@ public Manifest getManifest() { return null; } + /** overridden by JModPackageFragmentRoot */ + protected String getClassNameSubFolder() { + return null; + } + + static String entryToClassName(String classNameSubFolder, String entryName) { + if (classNameSubFolder != null && entryName.startsWith(classNameSubFolder)) { + return entryName.substring(classNameSubFolder.length()); + } else { + return entryName; + } + } + + /** creates a unmodifiable copy that is thread-safe and has it's Strings deduplicated **/ + static Map, PackageContent> unmodifiableCopy(Map, PackageContent> rawPackageInfo) { + Map, PackageContent> deduplicatedPackageInfo = new HashMap<>(); + + for (Entry, PackageContent> e : rawPackageInfo.entrySet()) { + List key = e.getKey(); + PackageContent p = e.getValue(); + PackageContent packageContent = new PackageContent(DeduplicationUtil.intern(p.javaClasses()), DeduplicationUtil.intern(p.resources())); + deduplicatedPackageInfo.put(DeduplicationUtil.intern(key), packageContent); + } + return Map.copyOf(deduplicatedPackageInfo); + } + // @Override // public boolean isModule() { // try { diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRootInfo.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRootInfo.java index fd4e91c48da..d7b36965cfb 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRootInfo.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JarPackageFragmentRootInfo.java @@ -13,16 +13,25 @@ *******************************************************************************/ package org.eclipse.jdt.internal.core; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -import org.eclipse.jdt.internal.core.util.HashtableOfArrayToObject; /** * The element info for JarPackageFragmentRoots. */ class JarPackageFragmentRootInfo extends PackageFragmentRootInfo { - // a map from package name (String[]) to a size-2 array of Array, the first element being the .class file names, and the second element being the non-Java resource names - HashtableOfArrayToObject rawPackageInfo; - Map overriddenClasses; + /** contains .class file names, and non-Java resource names of a package */ + static record PackageContent(List javaClasses, List resources) { + PackageContent() { + this(new ArrayList<>(), new ArrayList<>()); + } + } + /** + * Cache for the the jar's entries names. A unmodifiable map from package name to PackageContent + */ + Map, PackageContent> rawPackageInfo; + Map overriddenClasses; } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JrtPackageFragmentRoot.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JrtPackageFragmentRoot.java index ff29d34d89e..59089486a1d 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JrtPackageFragmentRoot.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JrtPackageFragmentRoot.java @@ -13,12 +13,16 @@ *******************************************************************************/ package org.eclipse.jdt.internal.core; +import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IPath; @@ -28,12 +32,11 @@ import org.eclipse.jdt.core.IJavaModelStatusConstants; import org.eclipse.jdt.core.IModuleDescription; import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.env.IModule; import org.eclipse.jdt.internal.compiler.env.IModulePathEntry; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.util.JRTUtil; -import org.eclipse.jdt.internal.core.util.HashtableOfArrayToObject; +import org.eclipse.jdt.internal.core.JarPackageFragmentRootInfo.PackageContent; import org.eclipse.jdt.internal.core.util.Util; /** @@ -48,6 +51,14 @@ public class JrtPackageFragmentRoot extends JarPackageFragmentRoot implements IM public static final ThreadLocal workingOnOldClasspath = new ThreadLocal<>(); + record JrtModuleKey(File image, String moduleName, String classNameSubFolder) {/** nothing */} + /** + * static cache for org.eclipse.jdt.internal.core.JarPackageFragmentRootInfo.rawPackageInfo across JarPackageFragmentRoot instances per java project + * + * @see org.eclipse.jdt.internal.core.JarPackageFragmentRootInfo#rawPackageInfo + **/ + private static final Map, PackageContent>> childrenCache = new ConcurrentHashMap<>(); + /** * Constructs a package fragment root which represents a module * contained in a JRT. @@ -59,42 +70,52 @@ protected JrtPackageFragmentRoot(IPath jrtPath, String moduleName, JavaProject p @Override protected boolean computeChildren(OpenableElementInfo info, IResource underlyingResource) throws JavaModelException { - final HashtableOfArrayToObject rawPackageInfo = new HashtableOfArrayToObject(); - final String compliance = CompilerOptions.VERSION_1_8; // TODO: Java 9 Revisit + JrtModuleKey key = new JrtModuleKey(this.jarPath.toFile(), this.moduleName, getClassNameSubFolder()); + Map, PackageContent> rawPackageInfo; + rawPackageInfo = childrenCache.computeIfAbsent(key, JrtPackageFragmentRoot::computeChildren); + info.setChildren(createChildren(rawPackageInfo.keySet())); + ((JarPackageFragmentRootInfo) info).rawPackageInfo = rawPackageInfo; + return true; + } + /** static implementation to make sure the result can be shared across instances*/ + private static Map, PackageContent> computeChildren(JrtModuleKey key) { + Map, PackageContent> rawPackageInfo= new HashMap<>(); + File image= key.image(); + String moduleName= key.moduleName(); + String classNameSubFolder= key.classNameSubFolder(); + String compliance = CompilerOptions.VERSION_1_8; // TODO: Java 9 Revisit // always create the default package - rawPackageInfo.put(CharOperation.NO_STRINGS, new ArrayList[] { EMPTY_LIST, EMPTY_LIST }); - + rawPackageInfo.put(List.of(), new PackageContent()); try { - org.eclipse.jdt.internal.compiler.util.JRTUtil.walkModuleImage(this.jarPath.toFile(), + org.eclipse.jdt.internal.compiler.util.JRTUtil.walkModuleImage(image, new org.eclipse.jdt.internal.compiler.util.JRTUtil.JrtFileVisitor() { - @Override - public FileVisitResult visitPackage(Path dir, Path mod, BasicFileAttributes attrs) throws IOException { - initRawPackageInfo(rawPackageInfo, dir.toString(), true, compliance); - return FileVisitResult.CONTINUE; - } + @Override + public FileVisitResult visitPackage(Path dir, Path mod, BasicFileAttributes attrs) + throws IOException { + initRawPackageInfo(rawPackageInfo, classNameSubFolder, dir.toString(), true, compliance); + return FileVisitResult.CONTINUE; + } - @Override - public FileVisitResult visitFile(Path path, Path mod, BasicFileAttributes attrs) throws IOException { - initRawPackageInfo(rawPackageInfo, path.toString(), false, compliance); - return FileVisitResult.CONTINUE; - } + @Override + public FileVisitResult visitFile(Path path, Path mod, BasicFileAttributes attrs) + throws IOException { + initRawPackageInfo(rawPackageInfo, classNameSubFolder, path.toString(), false, compliance); + return FileVisitResult.CONTINUE; + } - @Override - public FileVisitResult visitModule(Path path, String name) throws IOException { - if (!JrtPackageFragmentRoot.this.moduleName.equals(name)) { - return FileVisitResult.SKIP_SUBTREE; - } - return FileVisitResult.CONTINUE; - } - }, JRTUtil.NOTIFY_ALL); + @Override + public FileVisitResult visitModule(Path path, String name) throws IOException { + if (!moduleName.equals(name)) { + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; + } + }, JRTUtil.NOTIFY_ALL); } catch (IOException e) { - Util.log(e, "Error reading modules" + toStringWithAncestors()); //$NON-NLS-1$ + Util.log(e, "Error reading modules" + image); //$NON-NLS-1$ } - - info.setChildren(createChildren(rawPackageInfo)); - ((JarPackageFragmentRootInfo) info).rawPackageInfo = rawPackageInfo; - return true; + return unmodifiableCopy(rawPackageInfo); } @Override SourceMapper createSourceMapper(IPath sourcePath, IPath rootPath) throws JavaModelException { diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/Openable.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/Openable.java index d18a30fded0..7d5a0ae10eb 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/Openable.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/Openable.java @@ -14,6 +14,7 @@ package org.eclipse.jdt.internal.core; import java.util.Enumeration; +import java.util.List; import java.util.Map; import org.eclipse.core.resources.*; @@ -198,7 +199,7 @@ public boolean exists() { } catch (JavaModelException e) { return false; } - return rootInfo.rawPackageInfo.containsKey(((PackageFragment) this).names); + return rootInfo.rawPackageInfo.containsKey(List.of(((PackageFragment) this).names)); } break; case IJavaElement.CLASS_FILE: diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJMod.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJMod.java index 190b080d940..57d38589efe 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJMod.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJMod.java @@ -33,7 +33,7 @@ public class ClasspathJMod extends ClasspathJar { public static char[] CLASSES = "classes".toCharArray(); //$NON-NLS-1$ - public static char[] CLASSES_FOLDER = "classes/".toCharArray(); //$NON-NLS-1$ + public static final String CLASSES_FOLDER = "classes/"; //$NON-NLS-1$ private static int MODULE_DESCRIPTOR_NAME_LENGTH = IModule.MODULE_INFO_CLASS.length(); ClasspathJMod(String zipFilename, long lastModified, AccessRuleSet accessRuleSet, IPath externalAnnotationPath) { @@ -43,7 +43,7 @@ public class ClasspathJMod extends ClasspathJar { IModule initializeModule() { IModule mod = null; try (ZipFile file = new ZipFile(this.zipFilename)) { - String fileName = new String(CLASSES_FOLDER) + IModule.MODULE_INFO_CLASS; + String fileName = CLASSES_FOLDER + IModule.MODULE_INFO_CLASS; ClassFileReader classfile = ClassFileReader.read(file, fileName); if (classfile != null) { mod = classfile.getModuleDeclaration(); @@ -62,7 +62,7 @@ public NameEnvironmentAnswer findClass(String binaryFileName, String qualifiedPa return null; try { - qualifiedBinaryFileName = new String(CharOperation.append(CLASSES_FOLDER, qualifiedBinaryFileName.toCharArray())); + qualifiedBinaryFileName = CLASSES_FOLDER + qualifiedBinaryFileName; IBinaryType reader = ClassFileReader.read(this.zipFile, qualifiedBinaryFileName); if (reader != null) { char[] modName = this.module == null ? null : this.module.name(); diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/DeduplicationUtil.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/DeduplicationUtil.java index 3817764aba3..4bce7c8a8fe 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/DeduplicationUtil.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/DeduplicationUtil.java @@ -15,6 +15,9 @@ package org.eclipse.jdt.internal.core.util; +import java.util.ArrayList; +import java.util.List; + import org.eclipse.jdt.internal.core.JavaElement; /** Utility to provide deduplication by best effort. **/ @@ -68,4 +71,27 @@ public static String[] intern(String[] a) { return a; } } + + /** interns the elements and the list as whole **/ + public static List intern(List a) { + if (a.size() == 0) { + return List.of(); + } + synchronized (objectCache) { + Object existing = objectCache.get(a); + if (existing instanceof List l) { + @SuppressWarnings("unchecked") + List existingList = l; + return existingList; + } + } + + ArrayList result= new ArrayList<>(a.size()); + synchronized (stringSymbols) { + for (String s:a) { + result.add(s == null ? null :stringSymbols.add(s)); + } + } + return internObject(List.copyOf(result)); + } }