Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

W-17457827: JPMS support for the RevAPI plugin. #172

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
  •  
  •  
  •  
23 changes: 18 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,17 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.11.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>


<dependencies>
<dependency>
<groupId>org.mule.runtime</groupId>
Expand All @@ -133,11 +141,6 @@
</dependency>

<!-- test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
Expand All @@ -149,6 +152,16 @@
<type>pom</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
182 changes: 104 additions & 78 deletions src/main/java/org/mule/tools/revapi/ExportPackageFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@
*/
package org.mule.tools.revapi;

import static org.mule.tools.revapi.JavaModuleSystemExportedPackages.findJpmsModuleReference;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.module.ModuleReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

Expand All @@ -40,12 +43,10 @@
*/
public final class ExportPackageFilter implements ElementFilter {

private static final String EXPORTED_CLASS_PACKAGES_PROPERTY = "artifact.export.classPackages";
private static final String PRIVILEGED_EXPORTED_CLASS_PACKAGES_PROPERTY = "artifact.privileged.classPackages";
private static final Logger LOG = LoggerFactory.getLogger(ExportPackageFilter.class);

private Map<API, Set<String>> exportedPackages;
private Map<Element, Boolean> exportedElements = new HashMap();
private final Map<API, Set<ExportedPackages>> apiModulesExportedPackages = new HashMap<>();
private final Map<Element<?>, Boolean> apiExportedElements = new HashMap<>();

@Override
public void close() {}
Expand All @@ -54,6 +55,10 @@ private static boolean isVerboseLogging() {
return System.getProperty("mule.revapi.verbose") != null;
}

private static String getModuleSystemMode() {
return System.getProperty("mule.revapi.moduleSystem.mode", "MULE");
}

@Override
public String getExtensionId() {
return "mule.module.filter";
Expand All @@ -66,84 +71,133 @@ public Reader getJSONSchema() {

@Override
public void initialize(AnalysisContext analysisContext) {
exportedPackages = new HashMap<>();
Function<API, Set<String>> getExportedPackages = api -> {
Set<String> exportedPackages = new HashSet<>();
api.getArchives().forEach(a -> addExportedPackages(a, exportedPackages));
return exportedPackages;

};

exportedPackages.computeIfAbsent(analysisContext.getOldApi(), getExportedPackages);
exportedPackages.computeIfAbsent(analysisContext.getNewApi(), getExportedPackages);
apiModulesExportedPackages.computeIfAbsent(analysisContext.getOldApi(), this::getExportedPackages);
apiModulesExportedPackages.computeIfAbsent(analysisContext.getNewApi(), this::getExportedPackages);
}

@Override
public boolean applies(Element element) {
boolean exported;

if (element instanceof JavaTypeElement) {
exported = isExported(element);
} else {
TypeElement ownerJavaTypeElement = findOwnerJavaTypeElement(element);

exported = isExported(ownerJavaTypeElement);
}

if (isVerboseLogging()) {
LOG.info(exported + " : applies to " + element);
logIsExported(element, exported);
}

return exported;
}

@Override
public boolean shouldDescendInto(Object element) {
boolean descendInto = element instanceof Element ? isExported((Element) element) : false;

boolean descendInto = element instanceof Element && isExported((Element<?>) element);
if (isVerboseLogging()) {
LOG.info(descendInto + ": should descend into " + element);
LOG.info("{}: should descend into {}", descendInto, element);
}

return descendInto;
}

private TypeElement findOwnerJavaTypeElement(Element element) {
private Set<ExportedPackages> getExportedPackages(API api) {
Set<ExportedPackages> exportedPackages = new HashSet<>();
api.getArchives().forEach(archive -> {
if (!addJavaModuleSystemExportedPackages(api, archive, exportedPackages)) {
if (!addMuleModuleSystemExportedPackages(archive, exportedPackages)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use && instead of nested ifs

LOG.debug("No exported packages found for the archive {}.", archive.getName());
}
}
});
if (exportedPackages.isEmpty()) {
LOG.debug("No exported packages found for the API {}.", api);
}
return exportedPackages;
}

private boolean addMuleModuleSystemExportedPackages(Archive archive, Set<ExportedPackages> exportedPackages) {
if (isMixedMode() || isMuleMode()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't isMuleMode checked for mixed mode as well?

try (JarInputStream jarFile = new JarInputStream(archive.openStream())) {
JarEntry entry;
while ((entry = jarFile.getNextJarEntry()) != null) {
String name = entry.getName();
if (name.equals("META-INF/mule-module.properties")) {
Properties properties = getProperties(jarFile);
ExportedPackages muleModuleSystemExportedPackages = new MuleModuleSystemExportedPackages(properties);
if (isVerboseLogging()) {
muleModuleSystemExportedPackages.logExportedPackages();
}
exportedPackages.add(muleModuleSystemExportedPackages);
return true;
}
}
} catch (IOException e) {
LOG.debug("Failed to open the archive {} as a jar.", archive.getName(), e);
}
LOG.debug("No Mule Module System descriptor found for the archive {}.", archive.getName());
return false;
}
return false;
}

private boolean addJavaModuleSystemExportedPackages(API api, Archive archive, Set<ExportedPackages> exportedPackages) {
if (isJavaMode() || isMixedMode()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't isJavaMode checked for mixed mode as well?

Optional<ModuleReference> optionalModuleReference = findJpmsModuleReference(archive);
if (optionalModuleReference.isPresent()) {
ModuleReference moduleReference = optionalModuleReference.get();
if (!moduleReference.descriptor().isAutomatic()
// Automatic modules must prioritize MuleModuleSystem descriptors when mode is MIXED.
|| (isMixedMode() && !addMuleModuleSystemExportedPackages(archive, exportedPackages))) {
ExportedPackages javaModuleSystemExportedPackages = new JavaModuleSystemExportedPackages(moduleReference);
if (isVerboseLogging()) {
javaModuleSystemExportedPackages.logExportedPackages();
}
exportedPackages.add(javaModuleSystemExportedPackages);
}
return true;
} else {
LOG.debug("No Java Module System descriptor found for the archive: {}.", archive.getName());
return false;
}
}
return false;
}

private TypeElement findOwnerJavaTypeElement(Element<?> element) {
while (!(element instanceof JavaTypeElement) || element.getParent() instanceof TypeElement) {
element = element.getParent();
}

if (!(element instanceof JavaTypeElement)) {
throw new IllegalStateException("Cannot find the parent type element for: " + element);
throw new IllegalStateException("Cannot find the parent type element for: " + element.getFullHumanReadableString());
}

return (TypeElement) element;
}

private boolean isExported(Element element) {
if (exportedElements.containsKey(element)) {
return exportedElements.get(element);
private boolean isExported(Element<?> element) {
if (apiExportedElements.containsKey(element)) {
return apiExportedElements.get(element);
}

boolean exported;
if (!(element instanceof TypeElement)) {
exported = false;
} else {
String packageName = getPackageName((TypeElement) element);
Set<String> exportDefinitions = exportedPackages.get(element.getApi());
if (exportDefinitions == null || exportDefinitions.isEmpty()) {
exported = false;
} else {
exported = exportDefinitions.contains(packageName);
}
exported = apiModulesExportedPackages.get(element.getApi()).stream()
.anyMatch(exportedPackages -> exportedPackages.isExported(packageName));
}
if (isVerboseLogging()) {
LOG.info(exported + " : applies to " + element);
logIsExported(element, exported);
}
exportedElements.put(element, exported);
apiExportedElements.put(element, exported);
return exported;
}

private Properties getProperties(JarInputStream propertiesFile) throws IOException {
Properties properties = new Properties();
byte[] bytes = getBytes(new BufferedInputStream(propertiesFile));
properties.load(new ByteArrayInputStream(bytes));
return properties;
}

private String getPackageName(TypeElement element) {
String canonicalName = findOwnerJavaTypeElement(element).getCanonicalName();
int index = canonicalName.lastIndexOf(".");
Expand All @@ -161,47 +215,19 @@ private byte[] getBytes(InputStream is)
return outputStream.toByteArray();
}

private void addExportedPackages(Archive archive, Set<String> exportedPackages) {
try (JarInputStream jarFile = new JarInputStream(archive.openStream())) {
JarEntry entry;

while ((entry = jarFile.getNextJarEntry()) != null) {
String name = entry.getName();

if (name.equals("META-INF/mule-module.properties")) {
Properties properties = new Properties();
byte bytes[] = getBytes(new BufferedInputStream(jarFile));
properties.load(new ByteArrayInputStream(bytes));

Set<String> standardPackages = getPackagesFromProperty(properties, EXPORTED_CLASS_PACKAGES_PROPERTY);
exportedPackages.addAll(standardPackages);
Set<String> privilegedPackages = getPackagesFromProperty(properties, PRIVILEGED_EXPORTED_CLASS_PACKAGES_PROPERTY);
exportedPackages.addAll(privilegedPackages);
private boolean isMixedMode() {
return getModuleSystemMode().equals("MIXED");
}

if (isVerboseLogging()) {
LOG.info("Adding exported packages from: " + jarFile + "\nstandard: " + standardPackages + "\nprivileged: "
+ privilegedPackages);
}
}
}
} catch (IOException e) {
LOG.debug("Failed to open the archive " + archive + " as a jar.", e);
}
private boolean isMuleMode() {
return getModuleSystemMode().equals("MULE");
}

private Set<String> getPackagesFromProperty(Properties properties, String propertyName) {
Set<String> result = new HashSet<>();
String property = properties.getProperty(propertyName);
if (property != null) {
String[] packages = property.split(",");
for (String packageName : packages) {
String name = packageName.trim();
if (!"".equals(name)) {
result.add(name);
}
}
}
private boolean isJavaMode() {
return getModuleSystemMode().equals("JAVA");
}

return result;
private void logIsExported(Element<?> element, boolean exported) {
LOG.info("{} : applies to {}", exported, element);
}
}
14 changes: 14 additions & 0 deletions src/main/java/org/mule/tools/revapi/ExportedPackages.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright 2023 Salesforce, Inc. All rights reserved.
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.tools.revapi;

public interface ExportedPackages {

boolean isExported(String packageName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add javadocs


void logExportedPackages();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this is weird... by the name this seems to be a value object... why does it have logging behaviour?

}
Loading