diff --git a/spoon-visualisation/src/test/java/spoon/visualisation/instrument/SpoonCodeInstrumentTest.java b/spoon-visualisation/src/test/java/spoon/visualisation/instrument/SpoonCodeInstrumentTest.java index 428f711e95f..1a7415614f2 100644 --- a/spoon-visualisation/src/test/java/spoon/visualisation/instrument/SpoonCodeInstrumentTest.java +++ b/spoon-visualisation/src/test/java/spoon/visualisation/instrument/SpoonCodeInstrumentTest.java @@ -311,7 +311,8 @@ void getSpoonProperties() { new Pair<>("isImplicit(): boolean", "false"), new Pair<>("getReferencedTypes(): Set>", "[]"), new Pair<>("getParent(): CtElement", "null"), - new Pair<>("getComments(): List", "[]"))) + new Pair<>("getComments(): List", "[]"), + new Pair<>("getDeclaringModule(): CtModule", "null"))) ); } diff --git a/src/main/java/spoon/ContractVerifier.java b/src/main/java/spoon/ContractVerifier.java index d75dbc5c0e5..2dd91d604bf 100644 --- a/src/main/java/spoon/ContractVerifier.java +++ b/src/main/java/spoon/ContractVerifier.java @@ -7,8 +7,6 @@ */ package spoon; - -import spoon.reflect.CtModelImpl; import spoon.reflect.code.CtArrayWrite; import spoon.reflect.code.CtAssignment; import spoon.reflect.code.CtExpression; @@ -597,7 +595,7 @@ public void checkJavaIdentifiers() { // checking method JavaIdentifiers.isLegalJavaPackageIdentifier _rootPackage.getElements(new TypeFilter<>(CtPackage.class)).parallelStream().forEach(element -> { // the default package is excluded (called "unnamed package") - if (element instanceof CtModelImpl.CtRootPackage) { + if (element.isUnnamedPackage()) { return; } diff --git a/src/main/java/spoon/reflect/CtModel.java b/src/main/java/spoon/reflect/CtModel.java index 7deb473e5e2..14f7b3ec460 100644 --- a/src/main/java/spoon/reflect/CtModel.java +++ b/src/main/java/spoon/reflect/CtModel.java @@ -39,6 +39,21 @@ public interface CtModel extends Serializable, CtQueryable { */ CtModule getUnnamedModule(); + /** + * searches for a module + */ + CtModule getModule(String name); + + /** + * adds a module to the model. + */ + T addModule(CtModule module); + + /** + * removes a module to the model. + */ + T removeModule(CtModule module); + /** * returns all modules of the model */ diff --git a/src/main/java/spoon/reflect/CtModelImpl.java b/src/main/java/spoon/reflect/CtModelImpl.java index cf1ca981cc3..b66a64e4426 100644 --- a/src/main/java/spoon/reflect/CtModelImpl.java +++ b/src/main/java/spoon/reflect/CtModelImpl.java @@ -10,29 +10,37 @@ import spoon.processing.Processor; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtModule; -import spoon.reflect.declaration.CtNamedElement; import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; -import spoon.reflect.factory.ModuleFactory; +import spoon.reflect.path.CtRole; import spoon.reflect.visitor.Filter; import spoon.reflect.visitor.chain.CtConsumableFunction; import spoon.reflect.visitor.chain.CtFunction; import spoon.reflect.visitor.chain.CtQuery; import spoon.reflect.visitor.filter.TypeFilter; import spoon.support.QueueProcessingManager; -import spoon.support.reflect.declaration.CtPackageImpl; +import spoon.support.reflect.declaration.CtModuleImpl; +import spoon.support.util.internal.ElementNameMap; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; public class CtModelImpl implements CtModel { private static final long serialVersionUID = 1L; - private boolean buildModelFinished = false; + private final CtModule unnamedModule; + private final Modules modules; + private boolean buildModelFinished; + + public CtModelImpl(Factory factory) { + this.unnamedModule = new CtModuleImpl.UnnamedModule(factory); + this.modules = new Modules(); + addModule(unnamedModule); + } @Override public CtQuery filterChildren(Filter filter) { @@ -49,42 +57,15 @@ public CtQuery map(CtConsumableFunction queryStep) { return getUnnamedModule().getFactory().Query().createQuery(this.getAllModules().toArray()).map(queryStep); } - public static class CtRootPackage extends CtPackageImpl { - { - this.setSimpleName(CtPackage.TOP_LEVEL_PACKAGE_NAME); - } - - @Override - public T setSimpleName(String name) { - if (name == null) { - return (T) this; - } - - if (name.equals(CtPackage.TOP_LEVEL_PACKAGE_NAME)) { - return super.setSimpleName(name); - } - - return (T) this; - } - - @Override - public String getQualifiedName() { - return ""; - } - - @Override - public String toString() { - return TOP_LEVEL_PACKAGE_NAME; - } + @Override + public Collection> getAllTypes() { + return getAllPackages().stream().map(CtPackage::getTypes).flatMap(Collection::stream).collect(Collectors.toList()); } - private final CtModule unnamedModule; - public CtModelImpl(Factory f) { - this.unnamedModule = new ModuleFactory.CtUnnamedModule(); - this.unnamedModule.setFactory(f); - this.unnamedModule.setRootPackage(new CtModelImpl.CtRootPackage()); - getRootPackage().setFactory(f); + @Override + public Collection getAllPackages() { + return Collections.unmodifiableCollection(getElements(new TypeFilter<>(CtPackage.class))); } @Override @@ -92,33 +73,21 @@ public CtPackage getRootPackage() { return getUnnamedModule().getRootPackage(); } - - @Override - public Collection> getAllTypes() { - final List> result = new ArrayList<>(); - getAllPackages().forEach(ctPackage -> { - result.addAll(ctPackage.getTypes()); - }); - return result; - } - - @Override - public Collection getAllPackages() { - return Collections.unmodifiableCollection(getElements(new TypeFilter<>(CtPackage.class))); + public CtModule getUnnamedModule() { + return this.unnamedModule; } @Override - public CtModule getUnnamedModule() { - return this.unnamedModule; + public CtModule getModule(String name) { + return modules.get(name); } @Override public Collection getAllModules() { - return ((ModuleFactory.CtUnnamedModule) this.unnamedModule).getAllModules(); + return Collections.unmodifiableCollection(modules.values()); } - @Override public void processWith(Processor processor) { QueueProcessingManager processingManager = new QueueProcessingManager(getUnnamedModule().getFactory()); @@ -137,10 +106,37 @@ public boolean isBuildModelFinished() { return this.buildModelFinished; } + @Override + public T addModule(CtModule module) { + modules.put(module.getSimpleName(), module); + return (T) this; + } + + @Override + public T removeModule(CtModule module) { + modules.remove(module.getSimpleName()); + return (T) this; + } + @Override public T setBuildModelIsFinished(boolean buildModelFinished) { this.buildModelFinished = buildModelFinished; return (T) this; } + public void updateModuleName(CtModule newModule, String oldName) { + modules.updateKey(oldName, newModule.getSimpleName()); + } + + private static class Modules extends ElementNameMap { + @Override + protected CtElement getOwner() { + return null; + } + + @Override + protected CtRole getRole() { + return CtRole.DECLARED_MODULE; + } + } } diff --git a/src/main/java/spoon/reflect/declaration/CtElement.java b/src/main/java/spoon/reflect/declaration/CtElement.java index 85097f22d62..736021bac1c 100644 --- a/src/main/java/spoon/reflect/declaration/CtElement.java +++ b/src/main/java/spoon/reflect/declaration/CtElement.java @@ -411,6 +411,12 @@ List getAnnotatedChildren( */ String toStringDebug(); + /** + * Gets the declaring module. + */ + @DerivedProperty + CtModule getDeclaringModule(); + /** * @return the source code of this element with the pretty-printing rules of Spoon * Warning: this is not side-effect free, this triggers some {@link spoon.reflect.visitor.ImportAnalyzer} which would change the model: add/remove imports, change the value `implicit` of some model elements, etc. diff --git a/src/main/java/spoon/reflect/declaration/CtModule.java b/src/main/java/spoon/reflect/declaration/CtModule.java index 9891298e57f..fd88593b9c9 100644 --- a/src/main/java/spoon/reflect/declaration/CtModule.java +++ b/src/main/java/spoon/reflect/declaration/CtModule.java @@ -43,7 +43,7 @@ * grants access at compile time to types in only those packages which are explicitly exported, * but grants access at run time to types in all its packages, as if all packages had been exported. */ -public interface CtModule extends CtNamedElement { +public interface CtModule extends CtNamedElement, CtShadowable { /** * The name for the top level module. @@ -173,4 +173,10 @@ public interface CtModule extends CtNamedElement { @Override CtModule clone(); + + @DerivedProperty + CtPackage getPackage(String qualifiedName); + + @DerivedProperty + List getAllPackages(); } diff --git a/src/main/java/spoon/reflect/declaration/CtPackage.java b/src/main/java/spoon/reflect/declaration/CtPackage.java index 38549048189..b777e561ee8 100644 --- a/src/main/java/spoon/reflect/declaration/CtPackage.java +++ b/src/main/java/spoon/reflect/declaration/CtPackage.java @@ -35,12 +35,6 @@ public interface CtPackage extends CtNamedElement, CtShadowable { */ String TOP_LEVEL_PACKAGE_NAME = "unnamed package"; - /** - * Gets the declaring module. - */ - @DerivedProperty - CtModule getDeclaringModule(); - /** * Gets the declaring package of the current one. Returns null if the package is not yet in another one. */ diff --git a/src/main/java/spoon/reflect/factory/CoreFactory.java b/src/main/java/spoon/reflect/factory/CoreFactory.java index fd127cb08b3..fc70aadf292 100644 --- a/src/main/java/spoon/reflect/factory/CoreFactory.java +++ b/src/main/java/spoon/reflect/factory/CoreFactory.java @@ -373,6 +373,11 @@ public interface CoreFactory { */ CtPackage createPackage(); + /** + * Creates a package. + */ + CtPackage createPackage(CtModule parent); + /** * Creates a package reference. */ diff --git a/src/main/java/spoon/reflect/factory/EnumFactory.java b/src/main/java/spoon/reflect/factory/EnumFactory.java index e886ad02e59..33ddc723e62 100644 --- a/src/main/java/spoon/reflect/factory/EnumFactory.java +++ b/src/main/java/spoon/reflect/factory/EnumFactory.java @@ -59,7 +59,7 @@ public CtEnum create(String qualifiedName) { @SuppressWarnings("unchecked") public CtEnum get(String qualifiedName) { try { - return (CtEnum) super.get(qualifiedName); + return (CtEnum) super.get(qualifiedName); } catch (Exception e) { return null; } diff --git a/src/main/java/spoon/reflect/factory/Factory.java b/src/main/java/spoon/reflect/factory/Factory.java index 7eb8f863434..7eacb588f49 100644 --- a/src/main/java/spoon/reflect/factory/Factory.java +++ b/src/main/java/spoon/reflect/factory/Factory.java @@ -767,6 +767,11 @@ public interface Factory { */ CtPackage createPackage(); + /** + * @see CoreFactory#createPackage(CtModule) + */ + CtPackage createPackage(CtModule parent); + /** * @see CoreFactory#createTypeParameter() */ diff --git a/src/main/java/spoon/reflect/factory/FactoryImpl.java b/src/main/java/spoon/reflect/factory/FactoryImpl.java index d66296d4084..3683107b13b 100644 --- a/src/main/java/spoon/reflect/factory/FactoryImpl.java +++ b/src/main/java/spoon/reflect/factory/FactoryImpl.java @@ -1010,6 +1010,11 @@ public CtPackage createPackage() { return Core().createPackage(); } + @Override + public CtPackage createPackage(CtModule parent) { + return Core().createPackage(parent); + } + @Override public CtTypeParameter createTypeParameter() { return Core().createTypeParameter(); diff --git a/src/main/java/spoon/reflect/factory/ModuleFactory.java b/src/main/java/spoon/reflect/factory/ModuleFactory.java index dadb41b6862..c27d2df6f70 100644 --- a/src/main/java/spoon/reflect/factory/ModuleFactory.java +++ b/src/main/java/spoon/reflect/factory/ModuleFactory.java @@ -9,113 +9,35 @@ import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import spoon.reflect.CtModelImpl; -import spoon.reflect.declaration.CtElement; + import spoon.reflect.declaration.CtModule; import spoon.reflect.declaration.CtModuleRequirement; -import spoon.reflect.declaration.CtNamedElement; import spoon.reflect.declaration.CtPackageExport; import spoon.reflect.declaration.CtProvidedService; import spoon.reflect.declaration.CtUsedService; -import spoon.reflect.declaration.ParentNotInitializedException; import spoon.reflect.reference.CtModuleReference; import spoon.reflect.reference.CtPackageReference; import spoon.reflect.reference.CtTypeReference; -import spoon.reflect.visitor.CtVisitor; -import spoon.support.reflect.declaration.CtElementImpl; -import spoon.support.reflect.declaration.CtModuleImpl; - public class ModuleFactory extends SubFactory { - - public static class CtUnnamedModule extends CtModuleImpl { - final Set allModules = new HashSet<>(); - final CtElement parent = new CtElementImpl() { - @Override - public void accept(CtVisitor visitor) { - - } - - @Override - public CtElement getParent() throws ParentNotInitializedException { - return null; - } - - @Override - public Factory getFactory() { - return CtUnnamedModule.this.getFactory(); - } - }; - - - { - this.setSimpleName(CtModule.TOP_LEVEL_MODULE_NAME); - this.addModule(this); - } - - public boolean addModule(CtModule module) { - return this.allModules.add(module); - } - - public CtModule getModule(String name) { - for (CtModule module : this.allModules) { - if (module.getSimpleName().equals(name)) { - return module; - } - } - return null; - } - - public Collection getAllModules() { - return Collections.unmodifiableCollection(allModules); - } - - @Override - public T setSimpleName(String name) { - if (name == null) { - return (T) this; - } - - if (name.equals(CtModule.TOP_LEVEL_MODULE_NAME)) { - return super.setSimpleName(name); - } - - return (T) this; - } - - @Override - public String toString() { - return CtModule.TOP_LEVEL_MODULE_NAME; - } - - @Override - public void accept(CtVisitor visitor) { - visitor.visitCtModule(this); - } - - @Override - public CtElement getParent() { - return this.parent; - } - } - public ModuleFactory(Factory factory) { super(factory); } - public CtUnnamedModule getUnnamedModule() { - return (CtUnnamedModule) factory.getModel().getUnnamedModule(); + public CtModule getUnnamedModule() { + return factory.getModel().getUnnamedModule(); } public Collection getAllModules() { - return getUnnamedModule().getAllModules(); + return factory.getModel().getAllModules(); } public CtModule getModule(String moduleName) { - return getUnnamedModule().getModule(moduleName); + if (moduleName == null || moduleName.isEmpty()) { + return getUnnamedModule(); + } + + return factory.getModel().getModule(moduleName); } public CtModule getOrCreate(String moduleName) { @@ -123,19 +45,22 @@ public CtModule getOrCreate(String moduleName) { return getUnnamedModule(); } - CtModule ctModule = getUnnamedModule().getModule(moduleName); - if (ctModule == null) { - ctModule = factory.Core().createModule().setSimpleName(moduleName); - ctModule.setRootPackage(new CtModelImpl.CtRootPackage()); - ctModule.setParent(getUnnamedModule()); - getUnnamedModule().addModule(ctModule); + CtModule known = getModule(moduleName); + if (known != null) { + return known; } - return ctModule; + CtModule fresh = factory.Core().createModule().setSimpleName(moduleName); + factory.getModel().addModule(fresh); + return fresh; } public CtModuleReference createReference(CtModule module) { - return factory.Core().createModuleReference().setSimpleName(module.getSimpleName()); + return createReference(module.getSimpleName()); + } + + public CtModuleReference createReference(String module) { + return factory.Core().createModuleReference().setSimpleName(module); } public CtModuleRequirement createModuleRequirement(CtModuleReference moduleReference) { diff --git a/src/main/java/spoon/reflect/factory/PackageFactory.java b/src/main/java/spoon/reflect/factory/PackageFactory.java index 9b071f931a9..671e9e93f86 100644 --- a/src/main/java/spoon/reflect/factory/PackageFactory.java +++ b/src/main/java/spoon/reflect/factory/PackageFactory.java @@ -7,11 +7,13 @@ */ package spoon.reflect.factory; - -import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; import java.util.Collection; import java.util.List; import java.util.StringTokenizer; +import java.util.stream.Collectors; + import spoon.SpoonException; import spoon.reflect.declaration.CtModule; import spoon.reflect.declaration.CtPackage; @@ -19,13 +21,14 @@ import spoon.reflect.declaration.CtType; import spoon.reflect.reference.CtPackageReference; - /** * The {@link CtPackage} sub-factory. */ public class PackageFactory extends SubFactory { private static final long serialVersionUID = 1L; + private final Map packageModuleCache; + /** * Creates a new package sub-factory. * @@ -34,6 +37,7 @@ public class PackageFactory extends SubFactory { */ public PackageFactory(Factory factory) { super(factory); + this.packageModuleCache = new HashMap<>(); } /** @@ -102,7 +106,7 @@ public CtPackage create(CtPackage parent, String simpleName) { if (parent == null) { return getOrCreate(simpleName); } else { - return getOrCreate(parent.toString() + CtPackage.PACKAGE_SEPARATOR + simpleName); + return getOrCreate(parent + CtPackage.PACKAGE_SEPARATOR + simpleName, parent.getDeclaringModule()); } } @@ -114,7 +118,50 @@ public CtPackage create(CtPackage parent, String simpleName) { * */ public CtPackage getOrCreate(String qualifiedName) { - return this.getOrCreate(qualifiedName, factory.getModel().getUnnamedModule()); + return this.getOrCreate(qualifiedName, findModuleByPackage(qualifiedName)); + } + + private CtModule findModuleByPackage(String qualifiedName) { + String owner = getOwner(qualifiedName); + CtModule known = packageModuleCache.get(owner); + if (known != null) { + return known; + } + + CtModule ctModule = findCtModule(qualifiedName); + packageModuleCache.put(owner, ctModule); + return ctModule; + } + + private CtModule findCtModule(String qualifiedName) { + List modules = findModules(qualifiedName); + if (modules.isEmpty()) { + return factory.getModel().getUnnamedModule(); + } + + if (modules.size() != 1) { + throw new SpoonException( + "Ambiguous package name detected. If you believe the code you analyzed is correct, please" + + " file an issue and reference https://github.com/INRIA/spoon/issues/4051. " + + "Error details: Found " + modules.size() + " modules that contain " + + "'" + qualifiedName + "'" + ); + } + + return factory.Module().getOrCreate(modules.get(0).getName()); + } + + private String getOwner(String qualifiedName) { + int index = qualifiedName.indexOf("."); + if (index == -1) { + return qualifiedName; + } + + return qualifiedName.substring(0, index); + } + + private List findModules(String qualifiedName) { + return ModuleLayer.boot().modules().stream().filter(module -> module.getPackages().contains(qualifiedName)).collect(Collectors.toUnmodifiableList()); } /** @@ -177,7 +224,7 @@ public CtPackage get(String qualifiedName) { CtPackage packageWithTypes = null; CtPackage lastNonNullPackage = null; for (CtModule module : factory.getModel().getAllModules()) { - CtPackage aPackage = getPackageFromModule(qualifiedName, module); + CtPackage aPackage = get(qualifiedName, module); if (aPackage == null) { continue; } @@ -207,7 +254,7 @@ public CtPackage get(String qualifiedName) { * @param ctModule A module in which to search for the package. * @return The package if found in this module, otherwise null. */ - private static CtPackage getPackageFromModule(String qualifiedName, CtModule ctModule) { + public CtPackage get(String qualifiedName, CtModule ctModule) { int index = 0; int nextIndex; CtPackage current = ctModule.getRootPackage(); @@ -242,15 +289,5 @@ public Collection getAll() { public CtPackage getRootPackage() { return factory.getModel().getRootPackage(); } - - private List getSubPackageList(CtPackage pack) { - List packs = new ArrayList<>(); - packs.add(pack); - for (CtPackage p : pack.getPackages()) { - packs.addAll(getSubPackageList(p)); - } - return packs; - } - } diff --git a/src/main/java/spoon/reflect/path/CtElementPathBuilder.java b/src/main/java/spoon/reflect/path/CtElementPathBuilder.java index ddbacb06081..926ff4a9f38 100644 --- a/src/main/java/spoon/reflect/path/CtElementPathBuilder.java +++ b/src/main/java/spoon/reflect/path/CtElementPathBuilder.java @@ -7,7 +7,6 @@ */ package spoon.reflect.path; -import spoon.reflect.CtModelImpl; import spoon.reflect.declaration.CtConstructor; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtExecutable; @@ -41,7 +40,7 @@ public class CtElementPathBuilder { * @return CtPath from model root package to el */ public CtPath fromElement(CtElement el) throws CtPathException { - return fromElement(el, el.getParent(CtModelImpl.CtRootPackage.class)); + return fromElement(el, el.getDeclaringModule().getRootPackage()); } /** * Build path to a CtElement el, from one of its parent. diff --git a/src/main/java/spoon/reflect/visitor/CtInheritanceScanner.java b/src/main/java/spoon/reflect/visitor/CtInheritanceScanner.java index 825d9f3553b..8b53f70c796 100644 --- a/src/main/java/spoon/reflect/visitor/CtInheritanceScanner.java +++ b/src/main/java/spoon/reflect/visitor/CtInheritanceScanner.java @@ -1015,6 +1015,7 @@ public void visitCtModule(CtModule module) { scanCtNamedElement(module); scanCtVisitable(module); scanCtElement(module); + scanCtShadowable(module); } @Override diff --git a/src/main/java/spoon/reflect/visitor/Query.java b/src/main/java/spoon/reflect/visitor/Query.java index 8e6e75ba042..deb9d643d25 100644 --- a/src/main/java/spoon/reflect/visitor/Query.java +++ b/src/main/java/spoon/reflect/visitor/Query.java @@ -13,7 +13,9 @@ import spoon.reflect.visitor.chain.CtFunction; import spoon.reflect.visitor.filter.TypeFilter; +import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; /** * This class provides some useful methods to retrieve program elements and @@ -40,7 +42,7 @@ private Query() { */ public static List getElements(Factory factory, Filter filter) { - return getElements(factory.Package().getRootPackage(), filter); + return factory.getModel().getAllModules().stream().map(ctModule -> getElements(ctModule.getRootPackage(), filter)).flatMap(Collection::stream).collect(Collectors.toUnmodifiableList()); } /** @@ -92,7 +94,6 @@ public static List getReferences( */ public static List getReferences( Factory factory, Filter filter) { - return getReferences(factory.Package().getRootPackage(), filter); + return factory.getModel().getAllModules().stream().map(ctModule -> getReferences(ctModule.getRootPackage(), filter)).flatMap(Collection::stream).collect(Collectors.toUnmodifiableList()); } - } diff --git a/src/main/java/spoon/reflect/visitor/filter/AllMethodsSameSignatureFunction.java b/src/main/java/spoon/reflect/visitor/filter/AllMethodsSameSignatureFunction.java index 4b8112f3285..da71df7317f 100644 --- a/src/main/java/spoon/reflect/visitor/filter/AllMethodsSameSignatureFunction.java +++ b/src/main/java/spoon/reflect/visitor/filter/AllMethodsSameSignatureFunction.java @@ -17,6 +17,7 @@ import spoon.reflect.code.CtLambda; import spoon.reflect.declaration.CtExecutable; import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtModule; import spoon.reflect.declaration.CtType; import spoon.reflect.visitor.Filter; import spoon.reflect.visitor.chain.CtConsumableFunction; @@ -114,62 +115,67 @@ public boolean matches(CtLambda lambda) { targetMethods.add(targetMethod); CtType declaringType = targetMethod.getDeclaringType(); lambdaFilter.addImplementingInterface(declaringType); - //search for all declarations and implementations of this method in sub and super classes and interfaces of all related hierarchies. - class Context { - boolean haveToSearchForSubtypes; - } - final Context context = new Context(); - //at the beginning we know that we have to always search for sub types too. - context.haveToSearchForSubtypes = true; - //Sub inheritance hierarchy function, which remembers visited sub types and does not returns/visits them again - final SubInheritanceHierarchyResolver subHierarchyFnc = new SubInheritanceHierarchyResolver(declaringType.getFactory().getModel().getRootPackage()); - //add hierarchy of `targetMethod` as to be checked for sub types of declaring type - subHierarchyFnc.addSuperType(declaringType); - //unique names of all types whose super inheritance hierarchy was searched for rootType - Set typesCheckedForRootType = new HashSet<>(); - //list of sub types whose inheritance hierarchy has to be checked - final List> toBeCheckedSubTypes = new ArrayList<>(); - //add hierarchy of `targetMethod` as to be checked for super types of declaring type - toBeCheckedSubTypes.add(declaringType); - while (!toBeCheckedSubTypes.isEmpty()) { - for (CtType subType : toBeCheckedSubTypes) { - TypeAdaptor typeAdaptor = new TypeAdaptor(subType); - //search for first target method from the same type inheritance hierarchy - targetMethod = getTargetMethodOfHierarchy(targetMethods, typeAdaptor); - //search for all methods with same signature in inheritance hierarchy of `subType` - forEachOverridenMethod(typeAdaptor, targetMethod, typesCheckedForRootType, new CtConsumer>() { - @Override - public void accept(CtMethod overriddenMethod) { - targetMethods.add(overriddenMethod); - outputConsumer.accept(overriddenMethod); - CtType type = overriddenMethod.getDeclaringType(); - lambdaFilter.addImplementingInterface(type); - subHierarchyFnc.addSuperType(type); - //mark that new super type was added, so we have to search for sub types again - context.haveToSearchForSubtypes = true; + + for (CtModule ctModule : declaringType.getFactory().getModel().getAllModules()) { + //search for all declarations and implementations of this method in sub and super classes and interfaces of all related hierarchies. + class Context { + boolean haveToSearchForSubtypes; + } + final Context context = new Context(); + //at the beginning we know that we have to always search for sub types too. + context.haveToSearchForSubtypes = true; + + + //Sub inheritance hierarchy function, which remembers visited sub types and does not returns/visits them again + final SubInheritanceHierarchyResolver subHierarchyFnc = new SubInheritanceHierarchyResolver(ctModule.getRootPackage()); + //add hierarchy of `targetMethod` as to be checked for sub types of declaring type + subHierarchyFnc.addSuperType(declaringType); + //unique names of all types whose super inheritance hierarchy was searched for rootType + Set typesCheckedForRootType = new HashSet<>(); + //list of sub types whose inheritance hierarchy has to be checked + final List> toBeCheckedSubTypes = new ArrayList<>(); + //add hierarchy of `targetMethod` as to be checked for super types of declaring type + toBeCheckedSubTypes.add(declaringType); + while (!toBeCheckedSubTypes.isEmpty()) { + for (CtType subType : toBeCheckedSubTypes) { + TypeAdaptor typeAdaptor = new TypeAdaptor(subType); + //search for first target method from the same type inheritance hierarchy + targetMethod = getTargetMethodOfHierarchy(targetMethods, typeAdaptor); + //search for all methods with same signature in inheritance hierarchy of `subType` + forEachOverridenMethod(typeAdaptor, targetMethod, typesCheckedForRootType, new CtConsumer>() { + @Override + public void accept(CtMethod overriddenMethod) { + targetMethods.add(overriddenMethod); + outputConsumer.accept(overriddenMethod); + CtType type = overriddenMethod.getDeclaringType(); + lambdaFilter.addImplementingInterface(type); + subHierarchyFnc.addSuperType(type); + //mark that new super type was added, so we have to search for sub types again + context.haveToSearchForSubtypes = true; + } + }); + if (query.isTerminated()) { + return; } - }); - if (query.isTerminated()) { - return; + } + toBeCheckedSubTypes.clear(); + if (context.haveToSearchForSubtypes) { + context.haveToSearchForSubtypes = false; + //there are some new super types, whose sub inheritance hierarchy has to be checked + //search their inheritance hierarchy for sub types + subHierarchyFnc.forEachSubTypeInPackage(new CtConsumer>() { + @Override + public void accept(CtType type) { + toBeCheckedSubTypes.add(type); + } + }); } } - toBeCheckedSubTypes.clear(); - if (context.haveToSearchForSubtypes) { - context.haveToSearchForSubtypes = false; - //there are some new super types, whose sub inheritance hierarchy has to be checked - //search their inheritance hierarchy for sub types - subHierarchyFnc.forEachSubTypeInPackage(new CtConsumer>() { - @Override - public void accept(CtType type) { - toBeCheckedSubTypes.add(type); - } - }); + if (includingLambdas) { + //search for all lambdas implementing any of the found interfaces + lambdaQuery.forEach(outputConsumer); } } - if (includingLambdas) { - //search for all lambdas implementing any of the found interfaces - lambdaQuery.forEach(outputConsumer); - } } /** diff --git a/src/main/java/spoon/reflect/visitor/filter/OverriddenMethodQuery.java b/src/main/java/spoon/reflect/visitor/filter/OverriddenMethodQuery.java index 275dd2f7772..a5d66f35571 100644 --- a/src/main/java/spoon/reflect/visitor/filter/OverriddenMethodQuery.java +++ b/src/main/java/spoon/reflect/visitor/filter/OverriddenMethodQuery.java @@ -8,9 +8,10 @@ package spoon.reflect.visitor.filter; import spoon.reflect.declaration.CtMethod; -import spoon.reflect.declaration.CtPackage; +import spoon.reflect.declaration.CtModule; import spoon.reflect.visitor.chain.CtConsumableFunction; import spoon.reflect.visitor.chain.CtConsumer; +import spoon.reflect.visitor.chain.CtQuery; /** * Gets all overridden method from the method given. @@ -18,7 +19,9 @@ public class OverriddenMethodQuery implements CtConsumableFunction> { @Override public void apply(CtMethod input, CtConsumer outputConsumer) { - CtPackage searchScope = input.getFactory().Package().getRootPackage(); - searchScope.filterChildren(new OverriddenMethodFilter(input)).forEach(outputConsumer); + for (CtModule module : input.getFactory().getModel().getAllModules()) { + CtQuery query = module.getRootPackage().filterChildren(new OverriddenMethodFilter(input)); + query.forEach(outputConsumer); + } } } diff --git a/src/main/java/spoon/reflect/visitor/filter/SubInheritanceHierarchyFunction.java b/src/main/java/spoon/reflect/visitor/filter/SubInheritanceHierarchyFunction.java index 95806015d82..f9c99d8b67b 100644 --- a/src/main/java/spoon/reflect/visitor/filter/SubInheritanceHierarchyFunction.java +++ b/src/main/java/spoon/reflect/visitor/filter/SubInheritanceHierarchyFunction.java @@ -10,6 +10,8 @@ import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeInformation; +import spoon.reflect.declaration.CtModule; +import spoon.reflect.factory.Factory; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.chain.CtConsumableFunction; import spoon.reflect.visitor.chain.CtConsumer; @@ -63,28 +65,27 @@ public SubInheritanceHierarchyFunction failOnClassNotFound(boolean failOnClassNo @Override public void apply(CtTypeInformation input, final CtConsumer outputConsumer) { - final SubInheritanceHierarchyResolver fnc = new SubInheritanceHierarchyResolver(((CtElement) input).getFactory().getModel().getRootPackage()) - .failOnClassNotFound(failOnClassNotFound) - .includingInterfaces(includingInterfaces); - if (includingSelf) { - if (input instanceof CtTypeReference) { - outputConsumer.accept(((CtTypeReference) input).getTypeDeclaration()); - } else { - outputConsumer.accept(((CtType) input)); - } - } - fnc.addSuperType(input); - fnc.forEachSubTypeInPackage(new CtConsumer() { - @Override - public void accept(CtType typeInfo) { - outputConsumer.accept(typeInfo); - if (query.isTerminated()) { - //Cannot terminate, because its support was removed. - //I think there are cases where it might be useful. -// fnc.terminate(); + Factory factory = ((CtElement) input).getFactory(); + for (CtModule ctModule : factory.getModel().getAllModules()) { + final SubInheritanceHierarchyResolver fnc = new SubInheritanceHierarchyResolver(ctModule.getRootPackage()) + .failOnClassNotFound(failOnClassNotFound) + .includingInterfaces(includingInterfaces); + if (includingSelf) { + if (input instanceof CtTypeReference) { + outputConsumer.accept(((CtTypeReference) input).getTypeDeclaration()); + } else { + outputConsumer.accept(input); } } - }); + fnc.addSuperType(input); + fnc.forEachSubTypeInPackage((CtType typeInfo) -> { + outputConsumer.accept(typeInfo); + // Cannot terminate, because its support was removed. + // I think there are cases where it might be useful. + // fnc.terminate(); + query.isTerminated(); + }); + } } @Override diff --git a/src/main/java/spoon/support/DefaultCoreFactory.java b/src/main/java/spoon/support/DefaultCoreFactory.java index 9e73ef96634..f38d97a2b6a 100644 --- a/src/main/java/spoon/support/DefaultCoreFactory.java +++ b/src/main/java/spoon/support/DefaultCoreFactory.java @@ -570,9 +570,13 @@ public CtOperatorAssignment createOperatorAssignment() { @Override public CtPackage createPackage() { - CtPackage e = new CtPackageImpl(); - e.setFactory(getMainFactory()); - e.setParent(getMainFactory().Package().getRootPackage()); + return createPackage(getMainFactory().getModel().getUnnamedModule()); + } + + @Override + public CtPackage createPackage(CtModule parent) { + CtPackage e = new CtPackageImpl(parent); + e.setParent(parent.getRootPackage()); return e; } @@ -1122,10 +1126,7 @@ public CtTypeMemberWildcardImportReference createTypeMemberWildcardImportReferen @Override public CtModule createModule() { - CtModule module = new CtModuleImpl(); - module.setFactory(getMainFactory()); - module.setParent(this.getMainFactory().Module().getUnnamedModule()); - return module; + return new CtModuleImpl(getMainFactory()); } @Override diff --git a/src/main/java/spoon/support/compiler/SnippetCompilationHelper.java b/src/main/java/spoon/support/compiler/SnippetCompilationHelper.java index ef7de714a7a..36d3f470de8 100644 --- a/src/main/java/spoon/support/compiler/SnippetCompilationHelper.java +++ b/src/main/java/spoon/support/compiler/SnippetCompilationHelper.java @@ -22,6 +22,7 @@ import spoon.reflect.declaration.CtCodeSnippet; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtModule; import spoon.reflect.declaration.CtParameter; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.ModifierKind; @@ -98,8 +99,10 @@ public static void compileAndReplaceSnippetsIn(CtType initialClass) { CtType newClass = f.Type().get(initialClass.getQualifiedName()); // we find the snippets that are now ASTs - for (CtPath p : elements2before.keySet()) { - elements2after.put(p, p.evaluateOn(f.getModel().getRootPackage()).iterator().next()); + for (CtModule ctModule : f.getModel().getAllModules()) { + for (CtPath p : elements2before.keySet()) { + elements2after.put(p, p.evaluateOn(ctModule.getRootPackage()).iterator().next()); + } } // and we replace the new class in the factory by the initial one @@ -208,7 +211,7 @@ private static CtStatement internalCompileStatement(CtElement st, CtTypeReferenc if (ret instanceof CtClass) { CtClass klass = (CtClass) ret; - ret.getFactory().Package().getRootPackage().addType(klass); + klass.getDeclaringModule().getRootPackage().addType(klass); klass.setSimpleName(klass.getSimpleName().replaceAll("^[0-9]*", "")); } return ret; diff --git a/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java b/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java index 7fa73ef44a6..e2c8af5864f 100644 --- a/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java +++ b/src/main/java/spoon/support/compiler/jdt/JDTBasedSpoonCompiler.java @@ -31,6 +31,7 @@ import spoon.processing.ProcessingManager; import spoon.processing.Processor; import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtModule; import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; @@ -181,7 +182,9 @@ public void instantiateAndProcess(List processors) { factory.getEnvironment().debugMessage("Loaded processor " + processorName + "."); } - processing.process(factory.Package().getRootPackage()); + for (CtModule ctModule : factory.getModel().getAllModules()) { + processing.process(ctModule.getRootPackage()); + } } @Override @@ -193,7 +196,9 @@ public void process(Collection> processors) { factory.getEnvironment().debugMessage("Loaded processor " + processorName + "."); } - processing.process(factory.Package().getRootPackage()); + for (CtModule ctModule : factory.getModel().getAllModules()) { + processing.process(ctModule.getRootPackage()); + } } @Override diff --git a/src/main/java/spoon/support/gui/SpoonModelTree.java b/src/main/java/spoon/support/gui/SpoonModelTree.java index 66ff9b62011..10868089b26 100644 --- a/src/main/java/spoon/support/gui/SpoonModelTree.java +++ b/src/main/java/spoon/support/gui/SpoonModelTree.java @@ -37,6 +37,7 @@ import spoon.Launcher; import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtModule; import spoon.reflect.factory.Factory; import spoon.support.SerializationModelStreamer; @@ -66,7 +67,10 @@ public class SpoonModelTree extends JFrame implements KeyListener, */ public SpoonModelTree(Factory factory) { SpoonTreeBuilder cst = new SpoonTreeBuilder(); - cst.scan(factory.Package().getRootPackage()); + for (CtModule ctModule : factory.getModel().getAllModules()) { + cst.scan(ctModule.getRootPackage()); + } + this.factory = factory; root = cst.getRoot(); initialize(); diff --git a/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java b/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java index a1829372f3a..b471164caf8 100644 --- a/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java +++ b/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java @@ -20,6 +20,7 @@ import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtNamedElement; import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtModule; import spoon.reflect.declaration.ParentNotInitializedException; import spoon.reflect.factory.Factory; import spoon.reflect.factory.FactoryImpl; @@ -60,6 +61,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.Objects; import java.util.List; import java.util.Map; import java.util.Set; @@ -125,7 +127,7 @@ public boolean equals(Object o) { } boolean ret = EqualsVisitor.equals(this, (CtElement) o); // neat online testing of core Java contract - if (ret && !factory.getEnvironment().checksAreSkipped() && this.hashCode() != o.hashCode()) { + if (ret && !getFactory().getEnvironment().checksAreSkipped() && this.hashCode() != o.hashCode()) { throw new IllegalStateException("violation of equal/hashcode contract between \n" + this.toString() + "\nand\n" + o.toString() + "\n"); } return ret; @@ -251,7 +253,7 @@ public E setDocComment(String docComment) { return (E) this; } } - this.addComment(factory.Code().createComment(docComment, CtComment.CommentType.JAVADOC)); + this.addComment(getFactory().Code().createComment(docComment, CtComment.CommentType.JAVADOC)); return (E) this; } @@ -332,17 +334,17 @@ public List getElements(Filter filter) { @Override public CtQuery map(CtConsumableFunction queryStep) { - return factory.Query().createQuery(this).map(queryStep); + return getFactory().Query().createQuery(this).map(queryStep); } @Override public CtQuery map(CtFunction function) { - return factory.Query().createQuery(this).map(function); + return getFactory().Query().createQuery(this).map(function); } @Override public

CtQuery filterChildren(Filter

predicate) { - return factory.Query().createQuery(this).filterChildren(predicate); + return getFactory().Query().createQuery(this).filterChildren(predicate); } @Override @@ -409,7 +411,7 @@ public E getParent(Filter filter) { @Override public boolean hasParent(CtElement candidate) { try { - return this != getFactory().getModel().getUnnamedModule() && (getParent() == candidate || getParent().hasParent(candidate)); + return !(this instanceof CtModule) && (getParent() == candidate || getParent().hasParent(candidate)); } catch (ParentNotInitializedException e) { return false; } @@ -633,4 +635,9 @@ public void scan(CtElement element) { this.accept(scanner); return directChildren; } + + @Override + public CtModule getDeclaringModule() { + return Objects.requireNonNullElse(getParent(CtModule.class), getFactory().getModel().getUnnamedModule()); + } } diff --git a/src/main/java/spoon/support/reflect/declaration/CtModuleImpl.java b/src/main/java/spoon/support/reflect/declaration/CtModuleImpl.java index 7c019e3dd5c..7dc2f72f012 100644 --- a/src/main/java/spoon/support/reflect/declaration/CtModuleImpl.java +++ b/src/main/java/spoon/support/reflect/declaration/CtModuleImpl.java @@ -7,42 +7,81 @@ */ package spoon.support.reflect.declaration; +import spoon.reflect.CtModelImpl; import spoon.reflect.annotations.MetamodelPropertyField; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtModule; import spoon.reflect.declaration.CtModuleDirective; +import spoon.reflect.declaration.CtModuleRequirement; +import spoon.reflect.declaration.CtNamedElement; import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtPackageExport; import spoon.reflect.declaration.CtProvidedService; -import spoon.reflect.declaration.CtModuleRequirement; +import spoon.reflect.declaration.CtShadowable; import spoon.reflect.declaration.CtUsedService; +import spoon.reflect.declaration.ParentNotInitializedException; +import spoon.reflect.factory.Factory; import spoon.reflect.path.CtRole; import spoon.reflect.reference.CtModuleReference; import spoon.reflect.visitor.CtVisitor; -import spoon.support.DerivedProperty; +import spoon.support.UnsettableProperty; import spoon.support.comparator.CtLineElementComparator; import spoon.support.util.SortedList; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; public class CtModuleImpl extends CtNamedElementImpl implements CtModule { @MetamodelPropertyField(role = CtRole.MODIFIER) private boolean openModule; @MetamodelPropertyField(role = CtRole.MODULE_DIRECTIVE) - private List moduleDirectives = CtElementImpl.emptyList(); + private List moduleDirectives; + + @MetamodelPropertyField(role = CtRole.IS_SHADOW) + private boolean shadow; @MetamodelPropertyField(role = CtRole.SUB_PACKAGE) private CtPackage rootPackage; - public CtModuleImpl() { + public CtModuleImpl(Factory factory) { + this.setFactory(factory); + this.moduleDirectives = CtElementImpl.emptyList(); + this.rootPackage = new CtPackageImpl.RootPackage(this); + } + + @Override + public boolean isShadow() { + return shadow; + } + + @Override + public E setSimpleName(String simpleName) { + String oldName = getSimpleName(); + super.setSimpleName(simpleName); + + CtModelImpl ctModel = (CtModelImpl) factory.getModel(); + if (ctModel != null) { + ctModel.updateModuleName(this, oldName); + } + + return (E) this; + } + + @Override + public E setShadow(boolean shadow) { + getFactory().getEnvironment().getModelChangeListener().onObjectUpdate(this, CtRole.IS_SHADOW, shadow, this.shadow); + this.shadow = shadow; + return (E) this; } @Override public boolean isUnnamedModule() { - return TOP_LEVEL_MODULE_NAME.equals(this.getSimpleName()); + return false; } @Override @@ -424,14 +463,77 @@ public CtModule clone() { } @Override - @DerivedProperty - public T setParent(CtElement parent) { - return (T) this; + public CtModule getDeclaringModule() { + return this; + } + + @Override + public List getAllPackages() { + return getPackages(getRootPackage()); + } + + @Override + public CtElement getParent() throws ParentNotInitializedException { + return null; } @Override - @DerivedProperty - public CtElement getParent() { - return getFactory().getModel().getUnnamedModule(); + @UnsettableProperty + public E setParent(CtElement parent) { + return (E) this; + } + + private List getPackages(CtPackage ctPackage) { + List packages = new ArrayList<>(); + packages.add(ctPackage); + ctPackage.getPackages() + .stream() + .peek(packages::add) + .flatMap(this::getChildPackages) + .forEach(packages::addAll); + return packages; + } + + private Stream> getChildPackages(CtPackage child) { + return child.getPackages() + .stream() + .map(CtPackage::getPackages); + } + + @Override + public CtPackage getPackage(String qualifiedName) { + return factory.Package().get(qualifiedName, this); + } + + public static class UnnamedModule extends CtModuleImpl { + public UnnamedModule(Factory factory) { + super(factory); + this.setSimpleName(CtModuleImpl.TOP_LEVEL_MODULE_NAME); + } + + @Override + public boolean isUnnamedModule() { + return true; + } + + @Override + public T setSimpleName(String name) { + return Objects.equals(name, CtModuleImpl.TOP_LEVEL_MODULE_NAME) ? super.setSimpleName(name) : (T) this; + } + + @Override + public String toString() { + return this.getSimpleName(); + } + + @Override + public void accept(CtVisitor visitor) { + visitor.visitCtModule(this); + } + + @Override + public CtModuleImpl clone() { + return (CtModuleImpl) super.clone(); + } } } diff --git a/src/main/java/spoon/support/reflect/declaration/CtPackageImpl.java b/src/main/java/spoon/support/reflect/declaration/CtPackageImpl.java index 7c72365b018..72f949d14c2 100644 --- a/src/main/java/spoon/support/reflect/declaration/CtPackageImpl.java +++ b/src/main/java/spoon/support/reflect/declaration/CtPackageImpl.java @@ -8,6 +8,7 @@ package spoon.support.reflect.declaration; import java.util.LinkedHashSet; +import java.util.Objects; import java.util.Set; import spoon.reflect.annotations.MetamodelPropertyField; import spoon.reflect.cu.position.NoSourcePosition; @@ -29,52 +30,14 @@ */ public class CtPackageImpl extends CtNamedElementImpl implements CtPackage { private static final long serialVersionUID = 1L; - - @MetamodelPropertyField(role = CtRole.SUB_PACKAGE) - protected ElementNameMap packs = new ElementNameMap() { - private static final long serialVersionUID = 1L; - @Override - protected CtElement getOwner() { - return CtPackageImpl.this; - } - - @Override - protected CtRole getRole() { - return CtRole.SUB_PACKAGE; - } - - @Override - public CtPackage put(String simpleName, CtPackage pack) { - if (pack == null || pack == CtPackageImpl.this) { - return null; - } - - // it already exists - CtPackage ctPackage = get(simpleName); - if (ctPackage != null) { - addAllTypes(pack, ctPackage); - addAllPackages(pack, ctPackage); - return null; - } - - return super.put(simpleName, pack); - } - }; - - @MetamodelPropertyField(role = CtRole.CONTAINED_TYPE) - private final ElementNameMap> types = new ElementNameMap>() { - private static final long serialVersionUID = 1L; - @Override - protected CtElement getOwner() { - return CtPackageImpl.this; - } - @Override - protected CtRole getRole() { - return CtRole.CONTAINED_TYPE; - } - }; - - public CtPackageImpl() { + private final Packages packs; + private final Types types; + private final CtModule declaringModule; + public CtPackageImpl(CtModule declaringModule) { + this.types = new Types(); + this.packs = new Packages(); + this.declaringModule = declaringModule; + this.setFactory(declaringModule.getFactory()); } @Override @@ -91,25 +54,6 @@ public T addPackage(CtPackage pack) { return (T) this; } - /** add all types of "from" in "to" */ - private void addAllTypes(CtPackage from, CtPackage to) { - for (CtType t : from.getTypes()) { - for (CtType t2: to.getTypes()) { - if (t2.getQualifiedName().equals(t.getQualifiedName()) && !t2.equals(t)) { - throw new IllegalStateException("types with same qualified names and different code cannot be merged"); - } - } - to.addType(t); - } - } - - /** add all packages of "from" in "to" */ - private void addAllPackages(CtPackage from, CtPackage to) { - for (CtPackage p : from.getPackages()) { - to.addPackage(p); - } - } - @Override public boolean removePackage(CtPackage pack) { return packs.remove(pack.getSimpleName()) != null; @@ -117,7 +61,7 @@ public boolean removePackage(CtPackage pack) { @Override public CtModule getDeclaringModule() { - return getParent(CtModule.class); + return declaringModule; } @Override @@ -152,7 +96,7 @@ public String getQualifiedName() { if (getDeclaringPackage() == null || getDeclaringPackage().isUnnamedPackage()) { return getSimpleName(); } else { - return getDeclaringPackage().getQualifiedName() + "." + getSimpleName(); + return getDeclaringPackage().getQualifiedName() + CtPackage.PACKAGE_SEPARATOR_CHAR + getSimpleName(); } } @@ -232,7 +176,7 @@ public CtPackage clone() { @Override public boolean isUnnamedPackage() { - return TOP_LEVEL_PACKAGE_NAME.equals(getSimpleName()); + return false; } @Override @@ -262,4 +206,103 @@ void updateTypeName(CtType newType, String oldName) { void updatePackageName(CtPackage newPackage, String oldName) { packs.updateKey(oldName, newPackage.getSimpleName()); } + + private class Packages extends ElementNameMap { + private static final long serialVersionUID = 1L; + @Override + protected CtElement getOwner() { + return CtPackageImpl.this; + } + + @Override + protected CtRole getRole() { + return CtRole.SUB_PACKAGE; + } + + @Override + public CtPackage put(String simpleName, CtPackage pack) { + if (pack == null || pack == CtPackageImpl.this) { + return null; + } + + // it already exists + CtPackage ctPackage = get(simpleName); + if (ctPackage != null) { + addAllTypes(pack, ctPackage); + addAllPackages(pack, ctPackage); + return null; + } + + return super.put(simpleName, pack); + } + + private void addAllTypes(CtPackage from, CtPackage to) { + for (CtType t : from.getTypes()) { + for (CtType t2: to.getTypes()) { + if (t2.getQualifiedName().equals(t.getQualifiedName()) && !t2.equals(t)) { + throw new IllegalStateException("types with same qualified names and different code cannot be merged"); + } + } + to.addType(t); + } + } + + private void addAllPackages(CtPackage from, CtPackage to) { + for (CtPackage p : from.getPackages()) { + to.addPackage(p); + } + } + } + + private class Types extends ElementNameMap> { + private static final long serialVersionUID = 1L; + + @Override + protected CtElement getOwner() { + return CtPackageImpl.this; + } + + @Override + protected CtRole getRole() { + return CtRole.CONTAINED_TYPE; + } + } + + public static class RootPackage extends CtPackageImpl { + public RootPackage(CtModule module) { + super(module); + this.setSimpleName(CtPackage.TOP_LEVEL_PACKAGE_NAME); + this.setParent(module); + } + + @Override + public boolean isUnnamedPackage() { + return true; + } + + @Override + public String getQualifiedName() { + return ""; + } + + @Override + public T setSimpleName(String name) { + return Objects.equals(name, CtPackage.TOP_LEVEL_PACKAGE_NAME) ? super.setSimpleName(name) : (T) this; + } + + @Override + public String toString() { + return this.getSimpleName(); + } + + @Override + public void accept(CtVisitor visitor) { + visitor.visitCtPackage(this); + } + + @Override + public CtPackageImpl clone() { + return (CtPackageImpl) super.clone(); + } + } } diff --git a/src/main/java/spoon/support/util/internal/ElementNameMap.java b/src/main/java/spoon/support/util/internal/ElementNameMap.java index 716c4a0a707..12502bffa10 100644 --- a/src/main/java/spoon/support/util/internal/ElementNameMap.java +++ b/src/main/java/spoon/support/util/internal/ElementNameMap.java @@ -96,8 +96,10 @@ public T put(String key, T e) { return null; } CtElement owner = getOwner(); - linkToParent(owner, e); - getModelChangeListener().onMapAdd(owner, getRole(), map, key, e); + if (owner != null) { + linkToParent(owner, e); + getModelChangeListener().onMapAdd(owner, getRole(), map, key, e); + } // We make sure that then last added type is kept (and previous types overwritten) as client // code expects that @@ -119,13 +121,15 @@ public T remove(Object key) { return null; } - getModelChangeListener().onMapDelete( - getOwner(), - getRole(), - map, - (String) key, - removed - ); + if (getOwner() != null) { + getModelChangeListener().onMapDelete( + getOwner(), + getRole(), + map, + (String) key, + removed + ); + } return removed; } @@ -143,13 +147,15 @@ public void clear() { // Only an approximation as the concurrent map is only weakly consistent var old = toInsertionOrderedMap(); map.clear(); - var current = toInsertionOrderedMap(); - getModelChangeListener().onMapDeleteAll( - getOwner(), - getRole(), - current, - old - ); + if (getOwner() != null) { + var current = toInsertionOrderedMap(); + getModelChangeListener().onMapDeleteAll( + getOwner(), + getRole(), + current, + old + ); + } } private LinkedHashMap toInsertionOrderedMap() { @@ -198,7 +204,7 @@ private Stream> entriesByInsertionOrder() { } private FineModelChangeListener getModelChangeListener() { - return getOwner().getFactory().getEnvironment().getModelChangeListener(); + return Objects.requireNonNull(getOwner(), "Cannot access model change listener without an owner").getFactory().getEnvironment().getModelChangeListener(); } @Override diff --git a/src/main/java/spoon/support/visitor/clone/CloneBuilder.java b/src/main/java/spoon/support/visitor/clone/CloneBuilder.java index f1bf76dd9f6..fa582afdc2c 100644 --- a/src/main/java/spoon/support/visitor/clone/CloneBuilder.java +++ b/src/main/java/spoon/support/visitor/clone/CloneBuilder.java @@ -282,6 +282,7 @@ public void visitCtJavaDocTag(spoon.reflect.code.CtJavaDocTag e) { @java.lang.Override public void visitCtModule(spoon.reflect.declaration.CtModule module) { ((spoon.reflect.declaration.CtModule) (other)).setIsOpenModule(module.isOpenModule()); + ((spoon.reflect.declaration.CtModule) (other)).setShadow(module.isShadow()); super.visitCtModule(module); } diff --git a/src/main/java/spoon/support/visitor/java/JavaReflectionTreeBuilder.java b/src/main/java/spoon/support/visitor/java/JavaReflectionTreeBuilder.java index d15a4fb8c74..d19fccdd542 100644 --- a/src/main/java/spoon/support/visitor/java/JavaReflectionTreeBuilder.java +++ b/src/main/java/spoon/support/visitor/java/JavaReflectionTreeBuilder.java @@ -35,24 +35,32 @@ import spoon.reflect.declaration.CtInterface; import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtModifiable; +import spoon.reflect.declaration.CtModule; +import spoon.reflect.declaration.CtModuleRequirement; import spoon.reflect.declaration.CtPackage; +import spoon.reflect.declaration.CtPackageExport; import spoon.reflect.declaration.CtParameter; +import spoon.reflect.declaration.CtProvidedService; import spoon.reflect.declaration.CtRecord; import spoon.reflect.declaration.CtRecordComponent; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeParameter; +import spoon.reflect.declaration.CtUsedService; import spoon.reflect.declaration.ModifierKind; import spoon.reflect.factory.Factory; import spoon.reflect.path.CtRole; import spoon.reflect.reference.CtArrayTypeReference; import spoon.reflect.reference.CtExecutableReference; import spoon.reflect.reference.CtFieldReference; +import spoon.reflect.reference.CtModuleReference; +import spoon.reflect.reference.CtPackageReference; import spoon.reflect.reference.CtTypeParameterReference; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.reference.CtWildcardReference; import spoon.support.util.RtHelper; import spoon.support.visitor.java.internal.AnnotationRuntimeBuilderContext; import spoon.support.visitor.java.internal.ExecutableRuntimeBuilderContext; +import spoon.support.visitor.java.internal.ModuleRuntimeBuilderContext; import spoon.support.visitor.java.internal.PackageRuntimeBuilderContext; import spoon.support.visitor.java.internal.RecordComponentRuntimeBuilderContext; import spoon.support.visitor.java.internal.RuntimeBuilderContext; @@ -61,6 +69,11 @@ import spoon.support.visitor.java.internal.VariableRuntimeBuilderContext; import spoon.support.visitor.java.reflect.RtMethod; import spoon.support.visitor.java.reflect.RtParameter; + +import java.lang.module.ModuleDescriptor; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; /** * Builds Spoon model from class file using the reflection api. The Spoon model * contains only the declaration part (type, field, method, etc.). Everything @@ -71,11 +84,14 @@ * element comes from the reflection api, use {@link spoon.reflect.declaration.CtShadowable#isShadow()}. */ public class JavaReflectionTreeBuilder extends JavaReflectionVisitorImpl { - private Deque contexts = new ArrayDeque<>(); - private Factory factory; + private static final Set attributedModules = new HashSet<>(); + + private final Deque contexts; + private final Factory factory; public JavaReflectionTreeBuilder(Factory factory) { this.factory = factory; + this.contexts = new ArrayDeque<>(); } private void enter(RuntimeBuilderContext context) { @@ -88,38 +104,25 @@ private RuntimeBuilderContext exit() { /** transforms a java.lang.Class into a CtType (ie a shadow type in Spoon's parlance) */ public > R scan(Class clazz) { - CtPackage ctPackage; - CtType ctEnclosingClass; if (clazz.getEnclosingClass() != null && !clazz.isAnonymousClass()) { - ctEnclosingClass = factory.Type().get(clazz.getEnclosingClass()); + CtType ctEnclosingClass = factory.Type().get(clazz.getEnclosingClass()); return ctEnclosingClass.getNestedType(clazz.getSimpleName()); } else { - if (clazz.getPackage() == null) { - ctPackage = factory.Package().getRootPackage(); - } else { - ctPackage = factory.Package().getOrCreate(clazz.getPackage().getName()); - } + CtPackage ctPackage = clazz.getPackage() != null ? factory.Package().getOrCreate(clazz.getPackage().getName()) + : factory.Package().getRootPackage(); if (contexts.isEmpty()) { - enter(new PackageRuntimeBuilderContext(ctPackage)); + contexts.add(new PackageRuntimeBuilderContext(ctPackage)); } - boolean visited = false; + if (clazz.isAnnotation()) { - visited = true; visitAnnotationClass((Class) clazz); - } - if (clazz.isInterface() && !visited) { - visited = true; + } else if (clazz.isInterface()) { visitInterface(clazz); - } - if (clazz.isEnum() && !visited) { - visited = true; + } else if (clazz.isEnum()) { visitEnum(clazz); - } - if (MethodHandleUtils.isRecord(clazz) && !visited) { - visited = true; + } else if (MethodHandleUtils.isRecord(clazz)) { visitRecord(clazz); - } - if (!visited) { + } else { visitClass(clazz); } exit(); @@ -131,6 +134,95 @@ public > R scan(Class clazz) { } } + @Override + public void visitModule(Module module) { + CtModule know = factory.Module().getModule(module.getName()); + if (know != null && isAttributed(know)) { + return; + } + + CtModule fresh = know != null ? know : factory.Module().getOrCreate(module.getName()); + ModuleDescriptor descriptor = module.getDescriptor(); + if (descriptor != null) { + createModuleData(fresh, descriptor); + } + + setAttributed(fresh); + enter(new ModuleRuntimeBuilderContext(fresh)); + super.visitModule(module); + exit(); + } + + private boolean isAttributed(CtModule know) { + return attributedModules.contains(know.getSimpleName()); + } + + private static void setAttributed(CtModule fresh) { + attributedModules.add(fresh.getSimpleName()); + } + + private void createModuleData(CtModule fresh, ModuleDescriptor descriptor) { + fresh.setIsOpenModule(descriptor.isOpen()); + + List requires = descriptor.requires().stream().map(this::createRequires).collect(Collectors.toUnmodifiableList()); + fresh.setRequiredModules(requires); + + List exports = descriptor.exports().stream().map(instruction -> createExport(instruction.source(), instruction.targets(), false)).collect(Collectors.toUnmodifiableList()); + fresh.setExportedPackages(exports); + + List opens = descriptor.opens().stream().map(instruction -> createExport(instruction.source(), instruction.targets(), true)).collect(Collectors.toUnmodifiableList()); + fresh.setOpenedPackages(opens); + + List provides = descriptor.provides().stream().map(this::createProvides).collect(Collectors.toUnmodifiableList()); + fresh.setProvidedServices(provides); + + List uses = descriptor.uses().stream().map(this::createUses).collect(Collectors.toUnmodifiableList()); + fresh.setUsedServices(uses); + } + + private CtModuleRequirement createRequires(ModuleDescriptor.Requires instruction) { + CtModuleReference requiredModule = factory.Module().createReference(instruction.name()); + Set modifiers = new HashSet<>(); + if (instruction.modifiers().contains(ModuleDescriptor.Requires.Modifier.STATIC)) { + modifiers.add(CtModuleRequirement.RequiresModifier.STATIC); + } + + if (instruction.modifiers().contains(ModuleDescriptor.Requires.Modifier.TRANSITIVE)) { + modifiers.add(CtModuleRequirement.RequiresModifier.TRANSITIVE); + } + + CtModuleRequirement requires = factory.Core().createModuleRequirement(); + requires.setModuleReference(requiredModule); + requires.setRequiresModifiers(modifiers); + return requires; + } + + private CtUsedService createUses(String used) { + CtTypeReference usedType = factory.Type().createReference(used); + CtUsedService usedService = factory.Core().createUsedService(); + usedService.setServiceType(usedType); + return usedService; + } + + private CtProvidedService createProvides(ModuleDescriptor.Provides instruction) { + CtTypeReference serviceType = factory.Type().createReference(instruction.service()); + List serviceImplementations = instruction.providers().stream().map(factory.Type()::createReference).collect(Collectors.toUnmodifiableList()); + CtProvidedService export = factory.Core().createProvidedService(); + export.setServiceType(serviceType); + export.setImplementationTypes(serviceImplementations); + return export; + } + + private CtPackageExport createExport(String instruction, Set instructions, boolean openedPackage) { + CtPackageReference exported = factory.Package().createReference(instruction); + List targets = instructions.stream().map(factory.Module()::createReference).collect(Collectors.toUnmodifiableList()); + CtPackageExport export = factory.Core().createPackageExport(); + export.setOpenedPackage(openedPackage); + export.setPackageReference(exported); + export.setTargetExport(targets); + return export; + } + @Override public void visitPackage(Package aPackage) { CtPackage ctPackage = factory.Package().get(aPackage.getName()); diff --git a/src/main/java/spoon/support/visitor/java/JavaReflectionVisitor.java b/src/main/java/spoon/support/visitor/java/JavaReflectionVisitor.java index 00728fa7185..afe3f502473 100644 --- a/src/main/java/spoon/support/visitor/java/JavaReflectionVisitor.java +++ b/src/main/java/spoon/support/visitor/java/JavaReflectionVisitor.java @@ -26,6 +26,9 @@ * Client code should not rely on it. */ interface JavaReflectionVisitor { + /** Visits a {@link java.lang.Module} */ + void visitModule(Module module); + /** Visits a {@link java.lang.Package} */ void visitPackage(Package aPackage); diff --git a/src/main/java/spoon/support/visitor/java/JavaReflectionVisitorImpl.java b/src/main/java/spoon/support/visitor/java/JavaReflectionVisitorImpl.java index 023793d8159..b654c83a1ac 100644 --- a/src/main/java/spoon/support/visitor/java/JavaReflectionVisitorImpl.java +++ b/src/main/java/spoon/support/visitor/java/JavaReflectionVisitorImpl.java @@ -28,7 +28,14 @@ import spoon.support.visitor.java.reflect.RtParameter; class JavaReflectionVisitorImpl implements JavaReflectionVisitor { - private static Class recordClass = getRecordClass(); + private static final Class recordClass = getRecordClass(); + + @Override + public void visitModule(Module module) { + for (Annotation annotation : module.getAnnotations()) { + visitAnnotation(annotation); + } + } @Override public void visitPackage(Package aPackage) { @@ -39,6 +46,7 @@ public void visitPackage(Package aPackage) { @Override public void visitClass(Class clazz) { + visitModule(clazz.getModule()); if (clazz.getPackage() != null) { visitPackage(clazz.getPackage()); } @@ -125,6 +133,7 @@ protected final void visitType(Class aClass) { @Override public void visitInterface(Class clazz) { assert clazz.isInterface(); + visitModule(clazz.getModule()); if (clazz.getPackage() != null) { visitPackage(clazz.getPackage()); } @@ -183,6 +192,7 @@ public void visitInterface(Class clazz) { @Override public void visitEnum(Class clazz) { assert clazz.isEnum(); + visitModule(clazz.getModule()); if (clazz.getPackage() != null) { visitPackage(clazz.getPackage()); } @@ -257,6 +267,7 @@ public void visitEnum(Class clazz) { @Override public void visitAnnotationClass(Class clazz) { assert clazz.isAnnotation(); + visitModule(clazz.getModule()); if (clazz.getPackage() != null) { visitPackage(clazz.getPackage()); } @@ -493,6 +504,9 @@ public void visitArrayReference(CtRole role, Type typeArray) { @Override public void visitTypeReference(CtRole role, Class clazz) { + if (clazz.getEnclosingClass() == null) { + visitModule(clazz.getModule()); + } if (clazz.getPackage() != null && clazz.getEnclosingClass() == null) { visitPackage(clazz.getPackage()); } diff --git a/src/main/java/spoon/support/visitor/java/internal/ModuleRuntimeBuilderContext.java b/src/main/java/spoon/support/visitor/java/internal/ModuleRuntimeBuilderContext.java new file mode 100644 index 00000000000..29e7deb0d15 --- /dev/null +++ b/src/main/java/spoon/support/visitor/java/internal/ModuleRuntimeBuilderContext.java @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: (MIT OR CECILL-C) + * + * Copyright (C) 2006-2019 INRIA and contributors + * + * Spoon is available either under the terms of the MIT License (see LICENSE-MIT.txt) of the Cecill-C License (see LICENSE-CECILL-C.txt). You as the user are entitled to choose the terms under which to adopt Spoon. + */ +package spoon.support.visitor.java.internal; + +import spoon.reflect.declaration.CtModule; + +public class ModuleRuntimeBuilderContext extends AbstractRuntimeBuilderContext { + private final CtModule ctModule; + + public ModuleRuntimeBuilderContext(CtModule ctModule) { + super(ctModule); + this.ctModule = ctModule; + } + + /** + * Returns the module belonging to this context. + * + * @return the package of this context + */ + public CtModule getModule() { + return ctModule; + } +} diff --git a/src/test/java/spoon/test/api/MetamodelTest.java b/src/test/java/spoon/test/api/MetamodelTest.java index 07b49e6e22c..69da734128d 100644 --- a/src/test/java/spoon/test/api/MetamodelTest.java +++ b/src/test/java/spoon/test/api/MetamodelTest.java @@ -177,7 +177,8 @@ public void testGetterSetterForRole() { "lineSeparatorPositions", "rootFragment", "originalSourceCode", - "myPartialSourcePosition")); + "myPartialSourcePosition", + "declaringModule")); @Test public void testRoleOnField() { diff --git a/src/test/java/spoon/test/compilation/CompilationTest.java b/src/test/java/spoon/test/compilation/CompilationTest.java index 605306bba22..6e212d240da 100644 --- a/src/test/java/spoon/test/compilation/CompilationTest.java +++ b/src/test/java/spoon/test/compilation/CompilationTest.java @@ -269,7 +269,7 @@ public void testModuleResolution() throws InterruptedException { launcher.addInputResource("./src/test/resources/simple-module"); launcher.buildModel(); - assertTrue(launcher.getModel().getAllModules().iterator().next().getSimpleName().equals("spoonmod")); + assertTrue(launcher.getModel().getAllModules().stream().anyMatch(module -> module.getSimpleName().equals("spoonmod"))); } @Test diff --git a/src/test/java/spoon/test/parent/SetParentTest.java b/src/test/java/spoon/test/parent/SetParentTest.java index 79f4abeb1c3..e1296804f0e 100644 --- a/src/test/java/spoon/test/parent/SetParentTest.java +++ b/src/test/java/spoon/test/parent/SetParentTest.java @@ -18,16 +18,15 @@ import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; -import spoon.reflect.CtModelImpl; import spoon.reflect.code.BinaryOperatorKind; import spoon.reflect.code.CtBinaryOperator; import spoon.reflect.code.CtTypePattern; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.ParentNotInitializedException; import spoon.reflect.factory.Factory; -import spoon.reflect.factory.ModuleFactory; import spoon.reflect.reference.CtReference; import spoon.test.SpoonTestHelpers; @@ -77,10 +76,11 @@ private void testSetParentDoesNotAlterState(CtType toTest) throws Throwable { || "CtPackage".equals(toTest.getSimpleName()) ) { // contract: root package is the parent for those classes - assertTrue(receiver.getParent() instanceof CtModelImpl.CtRootPackage); + assertTrue(receiver.getParent() instanceof CtPackage && ((CtPackage) receiver.getParent()).isUnnamedPackage()); } else if ("CtModule".equals(toTest.getSimpleName())) { - // contract: module parent is necessarily the unnamedmodule - assertTrue(receiver.getParent() instanceof ModuleFactory.CtUnnamedModule); + // Previously, the parent of a module always was the unnamed module + // This test has been revisited to better suit the definition of a module according to the JLS + assertNull(receiver.getParent()); } else if ("CtCompilationUnit".equals(toTest.getSimpleName())) { // contract: CtCompilationUnit parent is null assertNull(receiver.getParent()); diff --git a/src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java b/src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java index c4f0f5569c8..b00cf7b0d4b 100644 --- a/src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java +++ b/src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java @@ -37,6 +37,7 @@ import spoon.reflect.declaration.CtImport; import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtModifiable; +import spoon.reflect.declaration.CtModule; import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.ModifierKind; @@ -1052,8 +1053,8 @@ public void testToStringWithSniperPrinter(String inputSourcePath) throws Excepti ops.stream() - .filter(el -> !(el instanceof spoon.reflect.CtModelImpl.CtRootPackage) - && !(el instanceof spoon.reflect.factory.ModuleFactory.CtUnnamedModule) + .filter(el -> !(el instanceof CtPackage && ((CtPackage) el).isUnnamedPackage()) + && !(el instanceof CtModule && ((CtModule) el).isUnnamedModule()) ).forEach(el -> { try { sp.reset();