Skip to content

Commit

Permalink
feat(core): Add command to validate package names
Browse files Browse the repository at this point in the history
  • Loading branch information
aalmiray committed Feb 23, 2023
1 parent c122c5a commit 92f927f
Show file tree
Hide file tree
Showing 6 changed files with 321 additions and 1 deletion.
29 changes: 29 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Set<String>> {
private static final String VERSIONED = "META-INF/versions/";

private static final Set<String> 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<JarFileResult<Set<String>>> getResult() throws JarvizException {
Set<JarFileResult<Set<String>>> 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<JarFile, Set<String>> e : packageTracker.packages.entrySet()) {
Set<String> 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<JarFile, Set<String>> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Main> {
@CommandLine.Spec
public CommandLine.Model.CommandSpec spec;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ private void output(Set<JarProcessor.JarFileResult<Set<String>>> results) {

private void output(JarProcessor.JarFileResult<Set<String>> result) {
parent().getOut().println($$("output.subject", result.getJarFileName()));
parent().getOut().println($$("output.total", result.getResult().size()));
result.getResult().forEach(parent().getOut()::println);
}

Expand All @@ -77,6 +78,7 @@ private void report(Set<JarProcessor.JarFileResult<Set<String>>> results) {

private void buildReport(Format format, Node root, JarProcessor.JarFileResult<Set<String>> 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()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Packages> {
@Override
protected int execute() {
JarFileResolver jarFileResolver = createJarFileResolver();
ValidatePackageJarProcessor processor = new ValidatePackageJarProcessor(jarFileResolver);

Set<JarProcessor.JarFileResult<Set<String>>> results = processor.getResult();

output(results);
report(results);

return 0;
}

private void output(Set<JarProcessor.JarFileResult<Set<String>>> results) {
Node root = createRootNode();
for (JarProcessor.JarFileResult<Set<String>> 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<Set<String>> 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<JarProcessor.JarFileResult<Set<String>>> results) {
if (null == reportPath) return;

for (Format format : validateReportFormats()) {
Node root = createRootNode();
for (JarProcessor.JarFileResult<Set<String>> result : results) {
buildReport(format, root, result);
}
writeReport(resolveFormatter(format).write(root), format);
}
}

private void buildReport(Format format, Node root, JarProcessor.JarFileResult<Set<String>> 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();
}
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
###############################################################################
Expand All @@ -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}
Expand Down

0 comments on commit 92f927f

Please sign in to comment.