diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/rewrite/ImportRewrite.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/rewrite/ImportRewrite.java index 5078e4711a9..0523b58762e 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/rewrite/ImportRewrite.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/rewrite/ImportRewrite.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2021 IBM Corporation and others. + * Copyright (c) 2000, 2025 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -280,6 +280,7 @@ public IAnnotationBinding[] removeRedundantTypeAnnotations(IAnnotationBinding[] private static final char STATIC_PREFIX= 's'; private static final char NORMAL_PREFIX= 'n'; + private static final char MODULE_PREFIX= 'm'; /** @deprecated using deprecated code */ private static final int JLS8_INTERNAL = AST.JLS8; @@ -291,6 +292,7 @@ public IAnnotationBinding[] removeRedundantTypeAnnotations(IAnnotationBinding[] private final boolean restoreExistingImports; private final List existingImports; + private final Map> moduleEntries; private final Map importsKindMap; private String[] importOrder; @@ -314,6 +316,7 @@ public IAnnotationBinding[] removeRedundantTypeAnnotations(IAnnotationBinding[] private String[] createdImports; private String[] createdStaticImports; + private String[] createdModuleImports; private boolean filterImplicitImports; private boolean useContextToFilterImplicitImports; @@ -337,15 +340,64 @@ public static ImportRewrite create(ICompilationUnit cu, boolean restoreExistingI throw new IllegalArgumentException("Compilation unit must not be null"); //$NON-NLS-1$ } List existingImport= null; + Map> moduleEntries= null; if (restoreExistingImports) { existingImport= new ArrayList(); + moduleEntries= new HashMap<>(); IImportDeclaration[] imports= cu.getImports(); + CompilationUnit compilationUnit= null; for (IImportDeclaration curr : imports) { - char prefix= Flags.isStatic(curr.getFlags()) ? STATIC_PREFIX : NORMAL_PREFIX; - existingImport.add(prefix + curr.getElementName()); + char prefix= Flags.isStatic(curr.getFlags()) ? STATIC_PREFIX : + Flags.isModule(curr.getFlags()) ? MODULE_PREFIX : NORMAL_PREFIX; + String currName= curr.getElementName(); + if (currName.endsWith(".*")) { //$NON-NLS-1$ + currName= currName.substring(0, currName.length() - 2); + } + existingImport.add(prefix + currName); + if (Flags.isModule(curr.getFlags())) { + List packageNames= new ArrayList<>(); + if (compilationUnit == null) { + compilationUnit= convertICUtoCU(cu); + } + if (compilationUnit != null) { + List astImports= compilationUnit.imports(); + ImportDeclaration foundModuleImport= null; + for (ImportDeclaration astImport : astImports) { + if (astImport.getName().getFullyQualifiedName().equals(currName)) { + foundModuleImport= astImport; + break; + } + } + if (foundModuleImport != null) { + IBinding moduleImportBinding= foundModuleImport.resolveBinding(); + if (moduleImportBinding instanceof IModuleBinding moduleBinding) { + packageNames= getPackageNames(moduleBinding); + } + } + } + moduleEntries.put(curr.getElementName(), packageNames); + } } } - return new ImportRewrite(cu, null, existingImport); + return new ImportRewrite(cu, null, existingImport, moduleEntries); + } + + private static List getPackageNames(IModuleBinding binding) { + List result= new ArrayList<>(); + IPackageBinding[] packageBindings= binding.getExportedPackages(); + for (IPackageBinding packageBinding : packageBindings) { + result.add(packageBinding.getName()); + } + return result; + } + + private static CompilationUnit convertICUtoCU(ICompilationUnit compilationUnit) { + ASTParser parser= ASTParser.newParser(AST.getJLSLatest()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + parser.setSource(compilationUnit); + parser.setResolveBindings(true); + + return (CompilationUnit) parser.createAST(null); } /** @@ -371,27 +423,42 @@ public static ImportRewrite create(CompilationUnit astRoot, boolean restoreExist throw new IllegalArgumentException("AST must have been constructed from a Java element"); //$NON-NLS-1$ } List existingImport= null; + Map> moduleEntries= null; if (restoreExistingImports) { existingImport= new ArrayList(); + moduleEntries= new HashMap<>(); List imports= astRoot.imports(); for (int i= 0; i < imports.size(); i++) { ImportDeclaration curr= (ImportDeclaration) imports.get(i); StringBuilder buf= new StringBuilder(); - buf.append(curr.isStatic() ? STATIC_PREFIX : NORMAL_PREFIX).append(curr.getName().getFullyQualifiedName()); + buf.append(curr.isStatic() ? STATIC_PREFIX : Flags.isModule(curr.getFlags()) ? MODULE_PREFIX : NORMAL_PREFIX).append(curr.getName().getFullyQualifiedName()); if (curr.isOnDemand()) { if (buf.length() > 1) buf.append('.'); buf.append('*'); } + if (Flags.isModule(curr.getFlags())) { + List packageList= new ArrayList<>(); + IBinding binding= curr.resolveBinding(); + if (binding instanceof IModuleBinding moduleBinding) { + packageList= getPackageNames(moduleBinding); + } + moduleEntries.put(curr.getName().getFullyQualifiedName(), packageList); + } existingImport.add(buf.toString()); } } - return new ImportRewrite((ICompilationUnit) typeRoot, astRoot, existingImport); + return new ImportRewrite((ICompilationUnit) typeRoot, astRoot, existingImport, moduleEntries); } - private ImportRewrite(ICompilationUnit cu, CompilationUnit astRoot, List existingImports) { + private ImportRewrite(ICompilationUnit cu, CompilationUnit astRoot, List existingImports, Map> moduleEntries) { this.compilationUnit= cu; this.astRoot= astRoot; // might be null + if (moduleEntries != null) { + this.moduleEntries= moduleEntries; + } else { + this.moduleEntries= new HashMap<>(); + } if (existingImports != null) { this.existingImports= existingImports; this.restoreExistingImports= !existingImports.isEmpty(); @@ -415,6 +482,7 @@ public int findInContext(String qualifier, String name, int kind) { this.staticExplicitSimpleNames = new HashSet<>(); this.createdImports= null; this.createdStaticImports= null; + this.createdModuleImports= null; this.importOrder= CharOperation.NO_STRINGS; this.importOnDemandThreshold= 99; @@ -527,7 +595,18 @@ public void setUseContextToFilterImplicitImports(boolean useContextToFilterImpli this.useContextToFilterImplicitImports = useContextToFilterImplicitImports; } - private static int compareImport(char prefix, String qualifier, String name, String curr) { + private static int compareImport(char prefix, String qualifier, String name, String curr, Map> moduleExportsMap) { + if (curr.charAt(0) == MODULE_PREFIX) { + List exportedPackageList= moduleExportsMap.get(curr.substring(1)); + if (exportedPackageList != null) { + for (String exportedPackage : exportedPackageList) { + if (exportedPackage.equals(qualifier)) { + return ImportRewriteContext.RES_NAME_FOUND; + } + } + } + return ImportRewriteContext.RES_NAME_UNKNOWN; + } if (curr.charAt(0) != prefix || !curr.endsWith(name)) { return ImportRewriteContext.RES_NAME_UNKNOWN; } @@ -562,7 +641,7 @@ private static int compareImport(char prefix, String qualifier, String name, Str for (int i= imports.size() - 1; i >= 0 ; i--) { String curr= (String) imports.get(i); - int res= compareImport(prefix, qualifier, name, curr); + int res= compareImport(prefix, qualifier, name, curr, this.moduleEntries); if (res != ImportRewriteContext.RES_NAME_UNKNOWN) { if (!allowAmbiguity || res == ImportRewriteContext.RES_NAME_FOUND) { if (prefix != STATIC_PREFIX) { @@ -917,6 +996,18 @@ private static ITypeBinding normalizeTypeBinding(ITypeBinding binding) { return null; } + + /** + * Adds a new module import to the rewriter's record. + * @since 3.43 + * + */ + + public void addModuleImport(String name, List packageList) { + addEntry(MODULE_PREFIX + name); + this.moduleEntries.put(name, packageList); + } + /** * Adds a new import to the rewriter's record and returns a {@link Type} that can be used * in the code. The type binding can be an array binding, type variable or wildcard. @@ -1274,6 +1365,7 @@ public final TextEdit rewriteImports(IProgressMonitor monitor) throws CoreExcept if (!hasRecordedChanges()) { this.createdImports= CharOperation.NO_STRINGS; this.createdStaticImports= CharOperation.NO_STRINGS; + this.createdModuleImports= CharOperation.NO_STRINGS; return new MultiTextEdit(); } @@ -1293,14 +1385,16 @@ public final TextEdit rewriteImports(IProgressMonitor monitor) throws CoreExcept for (String addedImport : this.addedImports) { boolean isStatic = STATIC_PREFIX == addedImport.charAt(0); + boolean isModule = MODULE_PREFIX == addedImport.charAt(0); String qualifiedName = addedImport.substring(1); - computer.addImport(isStatic, qualifiedName); + computer.addImport(isStatic, isModule, qualifiedName); } for (String removedImport : this.removedImports) { boolean isStatic = STATIC_PREFIX == removedImport.charAt(0); + boolean isModule = MODULE_PREFIX == removedImport.charAt(0); String qualifiedName = removedImport.substring(1); - computer.removeImport(isStatic, qualifiedName); + computer.removeImport(isStatic, isModule, qualifiedName); } for (String typeExplicitSimpleName : this.typeExplicitSimpleNames) { @@ -1315,6 +1409,7 @@ public final TextEdit rewriteImports(IProgressMonitor monitor) throws CoreExcept this.createdImports= result.getCreatedImports(); this.createdStaticImports= result.getCreatedStaticImports(); + this.createdModuleImports= result.getCreatedModuleImports(); return result.getTextEdit(); } @@ -1369,6 +1464,20 @@ public String[] getCreatedStaticImports() { return this.createdStaticImports; } + /** + * Returns all new module imports created by the last invocation of {@link #rewriteImports(IProgressMonitor)} + * or null if these methods have not been called yet. + *

+ * Note that this list doesn't need to be the same as the added static imports ({@link #getAddedStaticImports()}) as + * implicit imports are not created and some imports are represented by on-demand imports instead. + *

+ * @return the created imports + * @since 3.43 + */ + public String[] getCreatedModuleImports() { + return this.createdModuleImports; + } + /** * Returns all non-static imports that are recorded to be added. * @@ -1387,6 +1496,26 @@ public String[] getAddedStaticImports() { return filterFromList(this.addedImports, STATIC_PREFIX); } + /** + * Returns all module imports that are recorded to be added. + * + * @return the module imports recorded to be added. + * @since 3.43 + */ + public String[] getAddedModuleImports() { + return filterFromList(this.addedImports, MODULE_PREFIX); + } + + /** + * Returns all the exported packages registeted for an import module. + * @param moduleName name of module to get exports + * @return list of exported package names + * @since 3.43 + */ + public List getAddedModuleExportedPackages(String moduleName) { + return this.moduleEntries.get(moduleName); + } + /** * Returns all non-static imports that are recorded to be removed. * @@ -1405,6 +1534,16 @@ public String[] getRemovedStaticImports() { return filterFromList(this.removedImports, STATIC_PREFIX); } + /** + * Returns all static imports that are recorded to be removed. + * + * @return the static imports recorded to be removed. + * @since 3.43 + */ + public String[] getRemovedModuleImports() { + return filterFromList(this.removedImports, MODULE_PREFIX); + } + /** * Returns true if imports have been recorded to be added or removed. * @return boolean returns if any changes to imports have been recorded. diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportDeclarationWriter.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportDeclarationWriter.java index 6e38d372994..20446d769e3 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportDeclarationWriter.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportDeclarationWriter.java @@ -32,6 +32,10 @@ String writeImportDeclaration(ImportName importName) { sb.append("static "); //$NON-NLS-1$ } + if (importName.isModule) { + sb.append("module "); //$NON-NLS-1$ + } + sb.append(importName.qualifiedName); if (this.insertSpaceBeforeSemicolon) { diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportName.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportName.java index 0539f1a646d..72db1ce7dad 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportName.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportName.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015 Google Inc and others. + * Copyright (c) 2015, 2025 Google Inc and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -15,6 +15,7 @@ import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.Modifier; /** * Encapsulates an import's fully qualified name, whether it is on-demand, and whether it is static. @@ -28,29 +29,34 @@ public final class ImportName { static ImportName createFor(ImportDeclaration importDeclaration) { String declName = importDeclaration.getName().getFullyQualifiedName(); - if (importDeclaration.isOnDemand()) { + if (Modifier.isModule(importDeclaration.getModifiers())) { + return createFor(importDeclaration.isStatic(), Modifier.isModule(importDeclaration.getModifiers()), declName); + } else if (importDeclaration.isOnDemand()) { return createOnDemand(importDeclaration.isStatic(), declName); } - return createFor(importDeclaration.isStatic(), declName); + return createFor(importDeclaration.isStatic(), Modifier.isModule(importDeclaration.getModifiers()), declName); } static ImportName createOnDemand(boolean isStatic, String containerName) { - return new ImportName(isStatic, containerName, "*"); //$NON-NLS-1$ + return new ImportName(isStatic, false, containerName, "*"); //$NON-NLS-1$ } - public static ImportName createFor(boolean isStatic, String qualifiedName) { + public static ImportName createFor(boolean isStatic, boolean isModule, String qualifiedName) { String containerName = Signature.getQualifier(qualifiedName); String simpleName = Signature.getSimpleName(qualifiedName); - return new ImportName(isStatic, containerName, simpleName); + return new ImportName(isStatic, isModule, containerName, simpleName); } public final boolean isStatic; + public final boolean isModule; public final String containerName; public final String simpleName; public final String qualifiedName; - private ImportName(boolean isStatic, String containerName, String simpleName) { + private ImportName(boolean isStatic, boolean isModule, String containerName, String simpleName) { this.isStatic = isStatic; + this.isModule = isModule; + assert(!(this.isStatic && this.isModule)); this.containerName = containerName; this.simpleName = simpleName; @@ -59,14 +65,14 @@ private ImportName(boolean isStatic, String containerName, String simpleName) { @Override public String toString() { - String template = this.isStatic ? "staticImport(%s)" : "typeImport(%s)"; //$NON-NLS-1$ //$NON-NLS-2$ + String template = this.isStatic ? "staticImport(%s)" : this.isModule ? "moduleImport(%s)" : "typeImport(%s)"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ return String.format(template, this.qualifiedName); } @Override public int hashCode() { int result = this.qualifiedName.hashCode(); - result = 31 * result + (this.isStatic ? 1 : 0); + result = 31 * result + (this.isStatic ? 1 : 0) + (this.isModule ? 3 : 0); return result; } @@ -78,7 +84,7 @@ public boolean equals(Object obj) { ImportName other = (ImportName) obj; - return this.qualifiedName.equals(other.qualifiedName) && this.isStatic == other.isStatic; + return this.qualifiedName.equals(other.qualifiedName) && this.isStatic == other.isStatic && this.isModule == other.isModule; } public boolean isOnDemand() { diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportRewriteAnalyzer.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportRewriteAnalyzer.java index 151e5674a8e..28204113d0d 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportRewriteAnalyzer.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/ImportRewriteAnalyzer.java @@ -75,17 +75,21 @@ public TextEdit getTextEdit() { } public String[] getCreatedImports() { - return extractQualifiedNames(false, this.createdImports); + return extractQualifiedNames(false, false, this.createdImports); } public String[] getCreatedStaticImports() { - return extractQualifiedNames(true, this.createdImports); + return extractQualifiedNames(true, false, this.createdImports); } - private String[] extractQualifiedNames(boolean b, Collection imports) { + public String[] getCreatedModuleImports() { + return extractQualifiedNames(false, true, this.createdImports); + } + + private String[] extractQualifiedNames(boolean b, boolean m, Collection imports) { List names = new ArrayList<>(imports.size()); for (ImportName importName : imports) { - if (importName.isStatic == b) { + if (importName.isStatic == b && importName.isModule == m) { names.add(importName.qualifiedName); } } @@ -505,8 +509,8 @@ public ImportRewriteAnalyzer( *

* Overrides any previous corresponding call to {@link #removeImport}. */ - public void addImport(boolean isStatic, String qualifiedName) { - ImportName importToAdd = ImportName.createFor(isStatic, qualifiedName); + public void addImport(boolean isStatic, boolean isModule, String qualifiedName) { + ImportName importToAdd = ImportName.createFor(isStatic, isModule, qualifiedName); this.importsToAdd.add(importToAdd); this.importsToRemove.remove(importToAdd); } @@ -519,8 +523,8 @@ public void addImport(boolean isStatic, String qualifiedName) { *

* Overrides any previous corresponding call to {@link #addImport}. */ - public void removeImport(boolean isStatic, String qualifiedName) { - ImportName importToRemove = ImportName.createFor(isStatic, qualifiedName); + public void removeImport(boolean isStatic, boolean isModule, String qualifiedName) { + ImportName importToRemove = ImportName.createFor(isStatic, isModule, qualifiedName); this.importsToAdd.remove(importToRemove); this.importsToRemove.add(importToRemove); } @@ -604,10 +608,14 @@ private Set determineTouchedContainers() { this.importsToAdd.size() + this.importsToRemove.size()); for (ImportName addedImport : this.importsToAdd) { - touchedContainers.add(addedImport.getContainerOnDemand()); + if (!addedImport.isModule) { + touchedContainers.add(addedImport.getContainerOnDemand()); + } } for (ImportName removedImport : this.importsToRemove) { - touchedContainers.add(removedImport.getContainerOnDemand()); + if (!removedImport.isModule) { + touchedContainers.add(removedImport.getContainerOnDemand()); + } } return Collections.unmodifiableSet(new HashSet<>(touchedContainers)); diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OnDemandComputer.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OnDemandComputer.java index f762ba4c68a..d7214b6cfd6 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OnDemandComputer.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/imports/OnDemandComputer.java @@ -86,15 +86,17 @@ Collection identifyPossibleReductions( private Map> mapByContainer(Collection imports) { Map> importsByContainer = new HashMap<>(); for (ImportName importName : imports) { - ImportName containerOnDemand = importName.getContainerOnDemand(); + if (!importName.isModule) { + ImportName containerOnDemand = importName.getContainerOnDemand(); - Collection containerImports = importsByContainer.get(containerOnDemand); - if (containerImports == null) { - containerImports = new ArrayList<>(); - importsByContainer.put(containerOnDemand, containerImports); - } + Collection containerImports = importsByContainer.get(containerOnDemand); + if (containerImports == null) { + containerImports = new ArrayList<>(); + importsByContainer.put(containerOnDemand, containerImports); + } - containerImports.add(importName); + containerImports.add(importName); + } } return importsByContainer;