diff --git a/README.adoc b/README.adoc index cc83e35..f7ea418 100644 --- a/README.adoc +++ b/README.adoc @@ -216,6 +216,35 @@ uses: === Packages +*validate* + +Validate package names + +[source] +---- +$ jarviz packages validate --gav dev.gradleplugins:gradle-api:8.0.1 +subject: gradle-api-8.0.1.jar +total: 72 +org.gradle.internal.impldep.META-INF.versions.11.org.bouncycastle.jcajce.provider.asymmetric.edec +org.gradle.internal.impldep.META-INF.versions.15.org.bouncycastle.jcajce.provider.asymmetric.edec +org.gradle.internal.impldep.META-INF.versions.9 +org.gradle.internal.impldep.META-INF.versions.9.com.sun.istack.logging +org.gradle.internal.impldep.META-INF.versions.9.jakarta.xml.bind +org.gradle.internal.impldep.META-INF.versions.9.org.bouncycastle.asn1 +org.gradle.internal.impldep.META-INF.versions.9.org.bouncycastle.asn1.anssi +org.gradle.internal.impldep.META-INF.versions.9.org.bouncycastle.asn1.bc +org.gradle.internal.impldep.META-INF.versions.9.org.bouncycastle.asn1.cryptlib +org.gradle.internal.impldep.META-INF.versions.9.org.bouncycastle.asn1.cryptopro +org.gradle.internal.impldep.META-INF.versions.9.org.bouncycastle.asn1.gm +org.gradle.internal.impldep.META-INF.versions.9.org.bouncycastle.asn1.isara +org.gradle.internal.impldep.META-INF.versions.9.org.bouncycastle.asn1.nist +org.gradle.internal.impldep.META-INF.versions.9.org.bouncycastle.asn1.oiw +org.gradle.internal.impldep.META-INF.versions.9.org.bouncycastle.asn1.pkcs +org.gradle.internal.impldep.META-INF.versions.9.org.bouncycastle.asn1.rosstandart +org.gradle.internal.impldep.META-INF.versions.9.org.bouncycastle.asn1.sec +... +---- + *split* Display split packages diff --git a/core/jarviz-core/src/main/java/org/kordamp/jarviz/core/processors/ValidatePackageJarProcessor.java b/core/jarviz-core/src/main/java/org/kordamp/jarviz/core/processors/ValidatePackageJarProcessor.java new file mode 100644 index 0000000..8ba6279 --- /dev/null +++ b/core/jarviz-core/src/main/java/org/kordamp/jarviz/core/processors/ValidatePackageJarProcessor.java @@ -0,0 +1,188 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2022-2023 The Jarviz authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.kordamp.jarviz.core.processors; + +import org.kordamp.jarviz.bundle.RB; +import org.kordamp.jarviz.core.JarFileResolver; +import org.kordamp.jarviz.core.JarProcessor; +import org.kordamp.jarviz.core.JarvizException; +import org.kordamp.jarviz.util.StringUtils; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import static org.kordamp.jarviz.util.StringUtils.isBlank; +import static org.kordamp.jarviz.util.StringUtils.isNotBlank; + +/** + * @author Andres Almiray + * @since 0.3.0 + */ +public class ValidatePackageJarProcessor implements JarProcessor> { + private static final String VERSIONED = "META-INF/versions/"; + + private static final Set RESERVED_KEYWORDS = Set.of( + "abstract", + "assert", + "boolean", + "break", + "byte", + "case", + "catch", + "char", + "class", + "const", + "continue", + "default", + "do", + "double", + "else", + "enum", + "extends", + "final", + "finally", + "float", + "for", + "goto", + "if", + "implements", + "import", + "instanceof", + "int", + "interface", + "long", + "native", + "new", + "package", + "private", + "protected", + "public", + "return", + "short", + "static", + "strictfp", + "super", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "try", + "void", + "volatile", + "while", + "true", + "false", + "null", + "_"); + + private final JarFileResolver jarFileResolver; + + public ValidatePackageJarProcessor(JarFileResolver jarFileResolver) { + this.jarFileResolver = jarFileResolver; + } + + @Override + public Set>> getResult() throws JarvizException { + Set>> set = new TreeSet<>(); + + PackageTracker packageTracker = new PackageTracker(); + + for (JarFile jarFile : jarFileResolver.resolveJarFiles()) { + try (jarFile) { + jarFile.stream() + .map(JarEntry::getName) + .filter(entryName -> entryName.endsWith(".class")) + .map(this::asPackage) + .distinct() + .filter(StringUtils::isNotBlank) + .filter(this::isInvalid) + .forEach(thePackage -> packageTracker.add(jarFile, thePackage)); + } catch (IOException e) { + throw new JarvizException(RB.$("ERROR_OPENING_JAR", jarFile.getName())); + } + } + + for (Map.Entry> e : packageTracker.packages.entrySet()) { + Set splitPackages = e.getValue(); + if (!splitPackages.isEmpty()) { + set.add(JarFileResult.of(e.getKey(), new TreeSet<>(splitPackages))); + } + } + + return set; + } + + private String asPackage(String name) { + if (name.startsWith(VERSIONED)) { + name = name.substring(VERSIONED.length()); + int p = name.indexOf("/"); + name = name.substring(p + 1); + } + int i = name.lastIndexOf('/'); + return i != -1 ? name.substring(0, i).replace('/', '.') : ""; + } + + private boolean isInvalid(String thePackage) { + for (String part : thePackage.split("\\.")) { + if (!isJavaIdentifier(part)) { + return true; + } + } + return false; + } + + private static class PackageTracker { + private final Map> packages = new LinkedHashMap<>(); + + private void add(JarFile jarFile, String thePackage) { + if (isNotBlank(thePackage)) { + packages.computeIfAbsent(jarFile, k -> new TreeSet<>()) + .add(thePackage); + } + } + } + + private boolean isJavaIdentifier(String str) { + if (isBlank(str) || RESERVED_KEYWORDS.contains(str)) { + return false; + } + + int first = Character.codePointAt(str, 0); + if (!Character.isJavaIdentifierStart(first)) { + return false; + } + + int i = Character.charCount(first); + while (i < str.length()) { + int cp = Character.codePointAt(str, i); + if (!Character.isJavaIdentifierPart(cp)) { + return false; + } + i += Character.charCount(cp); + } + + return true; + } +} diff --git a/plugins/jarviz-cli/src/main/java/org/kordamp/jarviz/cli/packages/Packages.java b/plugins/jarviz-cli/src/main/java/org/kordamp/jarviz/cli/packages/Packages.java index 81883e8..5100226 100644 --- a/plugins/jarviz-cli/src/main/java/org/kordamp/jarviz/cli/packages/Packages.java +++ b/plugins/jarviz-cli/src/main/java/org/kordamp/jarviz/cli/packages/Packages.java @@ -26,7 +26,7 @@ * @since 0.3.0 */ @CommandLine.Command(name = "packages", - subcommands = PackagesSplit.class) + subcommands = {PackagesSplit.class, PackagesValidate.class}) public class Packages extends AbstractJarvizCommand
{ @CommandLine.Spec public CommandLine.Model.CommandSpec spec; diff --git a/plugins/jarviz-cli/src/main/java/org/kordamp/jarviz/cli/packages/PackagesSplit.java b/plugins/jarviz-cli/src/main/java/org/kordamp/jarviz/cli/packages/PackagesSplit.java index 7869f5e..89dcbe3 100644 --- a/plugins/jarviz-cli/src/main/java/org/kordamp/jarviz/cli/packages/PackagesSplit.java +++ b/plugins/jarviz-cli/src/main/java/org/kordamp/jarviz/cli/packages/PackagesSplit.java @@ -60,6 +60,7 @@ private void output(Set>> results) { private void output(JarProcessor.JarFileResult> result) { parent().getOut().println($$("output.subject", result.getJarFileName())); + parent().getOut().println($$("output.total", result.getResult().size())); result.getResult().forEach(parent().getOut()::println); } @@ -77,6 +78,7 @@ private void report(Set>> results) { private void buildReport(Format format, Node root, JarProcessor.JarFileResult> result) { appendSubject(root, result.getJarPath(), "packages split", resultNode -> { + resultNode.node($("report.key.total")).value(result.getResult().size()).end(); Node packages = resultNode.array($("report.key.packages")); for (String thePackage : result.getResult()) { diff --git a/plugins/jarviz-cli/src/main/java/org/kordamp/jarviz/cli/packages/PackagesValidate.java b/plugins/jarviz-cli/src/main/java/org/kordamp/jarviz/cli/packages/PackagesValidate.java new file mode 100644 index 0000000..f8f5767 --- /dev/null +++ b/plugins/jarviz-cli/src/main/java/org/kordamp/jarviz/cli/packages/PackagesValidate.java @@ -0,0 +1,93 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2022-2023 The Jarviz authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.kordamp.jarviz.cli.packages; + +import org.kordamp.jarviz.cli.internal.AbstractJarvizSubcommand; +import org.kordamp.jarviz.core.JarFileResolver; +import org.kordamp.jarviz.core.JarProcessor; +import org.kordamp.jarviz.core.processors.ValidatePackageJarProcessor; +import org.kordamp.jarviz.reporting.Format; +import org.kordamp.jarviz.reporting.Node; +import picocli.CommandLine; + +import java.util.Set; + +/** + * @author Andres Almiray + * @since 0.3.0 + */ +@CommandLine.Command(name = "validate") +public class PackagesValidate extends AbstractJarvizSubcommand { + @Override + protected int execute() { + JarFileResolver jarFileResolver = createJarFileResolver(); + ValidatePackageJarProcessor processor = new ValidatePackageJarProcessor(jarFileResolver); + + Set>> results = processor.getResult(); + + output(results); + report(results); + + return 0; + } + + private void output(Set>> results) { + Node root = createRootNode(); + for (JarProcessor.JarFileResult> result : results) { + if (null == outputFormat) { + output(result); + } else { + buildReport(outputFormat, root, result); + } + } + if (null != outputFormat) writeOutput(resolveFormatter(outputFormat).write(root)); + } + + private void output(JarProcessor.JarFileResult> result) { + parent().getOut().println($$("output.subject", result.getJarFileName())); + parent().getOut().println($$("output.total", result.getResult().size())); + result.getResult().forEach(parent().getOut()::println); + } + + private void report(Set>> results) { + if (null == reportPath) return; + + for (Format format : validateReportFormats()) { + Node root = createRootNode(); + for (JarProcessor.JarFileResult> result : results) { + buildReport(format, root, result); + } + writeReport(resolveFormatter(format).write(root), format); + } + } + + private void buildReport(Format format, Node root, JarProcessor.JarFileResult> result) { + appendSubject(root, result.getJarPath(), "packages validate", resultNode -> { + resultNode.node($("report.key.total")).value(result.getResult().size()).end(); + Node packages = resultNode.array($("report.key.packages")); + + for (String thePackage : result.getResult()) { + if (format != Format.XML) { + packages.node(thePackage); + } else { + packages.node($("report.key.package")).value(thePackage).end(); + } + } + }); + } +} diff --git a/plugins/jarviz-cli/src/main/resources/org/kordamp/jarviz/cli/internal/Messages.properties b/plugins/jarviz-cli/src/main/resources/org/kordamp/jarviz/cli/internal/Messages.properties index 0335f4b..cff46d0 100644 --- a/plugins/jarviz-cli/src/main/resources/org/kordamp/jarviz/cli/internal/Messages.properties +++ b/plugins/jarviz-cli/src/main/resources/org/kordamp/jarviz/cli/internal/Messages.properties @@ -146,6 +146,13 @@ jarviz.packages.usage.header = Commands for packages. jarviz.packages.split.usage.headerHeading = jarviz.packages.split.usage.header = Display split packages. +############################################################################### +# Packages.validate +############################################################################### +# header +jarviz.packages.validate.usage.headerHeading = +jarviz.packages.validate.usage.header = Validate package names. + ############################################################################### # Services ############################################################################### @@ -172,6 +179,7 @@ jarviz.services.show.usage.header = Display service implementations. ############################################################################### output.subject = @|yellow subject|@: {0} +output.total = @|yellow total|@: @|cyan {0}|@ manifest.query.attribute = @|yellow {0}|@: {1} services.show.service = @|yellow service|@: {0} bytecode.version.attribute = @|yellow Bytecode-Version|@: {0}