Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,7 @@ gradle-app.setting
# JDT-specific (Eclipse Java Development Tools)
.classpath

# Generated Graphs from lx graph
src/main/java/io/openliberty/explore/GraphDisplay/Graph.svg

# End of https://www.toptal.com/developers/gitignore/api/macos,intellij,gradle,java,vim,visualstudiocode
File renamed without changes.
5 changes: 4 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ ext {
}

tasks.named('wrapper') {
jarFile = rootProject.file('.gradle/gradle-wrapper.jar')
jarFile = rootProject.file('.gradle-wrapper/gradle-wrapper.jar')
}

compileJava {
Expand All @@ -27,10 +27,13 @@ repositories {
}

dependencies {
implementation "guru.nidi:graphviz-java:0.18.1"
implementation "org.jgrapht:jgrapht-core:1.5.1"
implementation "org.jgrapht:jgrapht-io:1.5.1"
implementation "info.picocli:picocli:4.6.3"
implementation "org.barfuin.texttree:text-tree:2.1.2"
implementation "org.osgi:osgi.core:8.0.0"
implementation "org.apache.commons:commons-collections4:4.4"
implementation "commons-io:commons-io:2.6"
implementation "org.slf4j:slf4j-nop:1.7.36"
}
2 changes: 1 addition & 1 deletion gradlew
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ case "`uname`" in
;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH=$APP_HOME/.gradle-wrapper/gradle-wrapper.jar


# Determine the Java command to use to start the JVM.
Expand Down
2 changes: 1 addition & 1 deletion gradlew.bat
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ goto fail
:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=%APP_HOME%\.gradle-wrapper\gradle-wrapper.jar


@rem Execute Gradle
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/io/openliberty/explore/DescribeCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* =============================================================================
* Copyright (c) 2022 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* =============================================================================
*/
package io.openliberty.explore;

import picocli.CommandLine.Command;
import static picocli.CommandLine.Help.Ansi.Style.fg_red;

@Command(
name = "describe",
description = "Display description for matching features"
)
public class DescribeCommand extends QueryCommand {

DescribeCommand() {
super(DisplayOption.normal, true);
}

void execute() {
explorer().allResults().stream().map(e -> "" + fg_red.on() + this.displayName(e) + ":" + fg_red.off() + "\n" + e.description() + "\n").
sorted().
forEach(System.out::println);
}
}
28 changes: 27 additions & 1 deletion src/main/java/io/openliberty/explore/GraphCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,35 @@
*/
package io.openliberty.explore;

import guru.nidi.graphviz.engine.Format;
import guru.nidi.graphviz.engine.Graphviz;
import guru.nidi.graphviz.model.MutableGraph;
import guru.nidi.graphviz.parse.Parser;
import io.openliberty.inspect.Bundle;
import io.openliberty.inspect.Element;
import io.openliberty.inspect.feature.Feature;
import org.apache.commons.io.FileUtils;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.nio.Attribute;
import org.jgrapht.nio.dot.DOTExporter;
import picocli.CommandLine.Command;

import java.awt.Desktop;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.jgrapht.nio.DefaultAttribute.createAttribute;
import static picocli.CommandLine.Help.Ansi.Style.fg_blue;
import static picocli.CommandLine.Help.Ansi.Style.underline;

@Command(
name = "graph",
Expand All @@ -44,16 +58,28 @@ String displayName(Element e) {
+ '"';
}

URI generateSVGGraph(String graphDotCode) throws IOException {
MutableGraph g = new Parser().read(graphDotCode);
File svgFile = new File("src/main/java/io/openliberty/explore/GraphDisplay/Graph.svg");
Graphviz.fromGraph(g).render(Format.SVG).toFile(svgFile);
return svgFile.toURI();
}

void execute() {
var exporter = new DOTExporter<Element, DefaultEdge>(this::displayName);
exporter.setVertexAttributeProvider(this::getDotAttributes);
var writer = new StringWriter();
exporter.exportGraph(explorer().subgraph(), writer);
System.out.println(writer);
try {
System.out.println("SVG Graph: " + underline.on() + fg_blue.on() + generateSVGGraph(writer.toString()) + underline.off() + fg_blue.off());
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private static Attribute shape(Element element) {
if (element instanceof Feature) switch(element.visibility()) {
if (element instanceof Feature) switch (element.visibility()) {
case PUBLIC: return createAttribute("tripleoctagon");
case PROTECTED: return createAttribute("doubleoctagon");
case PRIVATE: return createAttribute("octagon");
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/openliberty/explore/LibertyExplorer.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
name = "lx",
description = "Liberty installation eXplorer",
version = "Liberty installation eXplorer 0.5",
subcommands = {ListCommand.class, GraphCommand.class, TreeCommand.class, HelpCommand.class},
subcommands = {DescribeCommand.class, ListCommand.class, GraphCommand.class, TreeCommand.class, HelpCommand.class},
defaultValueProvider = PropertiesDefaultProvider.class
)
public class LibertyExplorer {
Expand Down
15 changes: 10 additions & 5 deletions src/main/java/io/openliberty/inspect/Bundle.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,18 @@ public final class Bundle implements Element {
@Override
public Path path() { return path; }
@Override
public String symbolicName() {
return symbolicName;
}
public String symbolicName() { return symbolicName; }
@Override
public String name() { return symbolicName() + "_" + version; }
@Override
public String name() {
return symbolicName() + "_" + version;
public String description() {
String description = manifest.getMainAttributes().getValue("Bundle-Description");
if (description != null) {
return description;
}
return "No Description found";
}

@Override
public Version version() { return version; }
@Override
Expand Down
1 change: 1 addition & 0 deletions src/main/java/io/openliberty/inspect/Element.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public interface Element extends Comparable<Element> {
default String fileName() { return path().getFileName().toString(); }
default String pathName() { return path().toString(); }
String name();
default String description() { return "No Description found"; }
Version version();
/** Returns a stream of other names for this element */
Stream<String> aka();
Expand Down
86 changes: 64 additions & 22 deletions src/main/java/io/openliberty/inspect/feature/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.stream.Stream;

import static io.openliberty.inspect.Visibility.UNKNOWN;
import static java.util.stream.Collectors.toUnmodifiableList;

public final class Feature implements Element {
Expand All @@ -39,6 +42,7 @@ public final class Feature implements Element {
private final Version version;
private final Visibility visibility;
private final List<ContentSpec> contents;
private final Manifest manifest;
private final boolean isAutoFeature;

public Feature(Path path) {
Expand All @@ -52,11 +56,7 @@ public Feature(Path path) {
Optional<ManifestValueEntry> symbolicName = ManifestKey.SUBSYSTEM_SYMBOLICNAME.parseValues(attributes).findFirst();
this.fullName = symbolicName.orElseThrow(Error::new).id;
this.shortName = ManifestKey.IBM_SHORTNAME.get(attributes).orElse(null);
this.visibility = symbolicName
.map(v -> v.getQualifier("visibility"))
.map(String::toUpperCase)
.map(Visibility::valueOf)
.orElse(Visibility.UNKNOWN);
this.visibility = symbolicName.map(Feature::getVisibility).orElse(UNKNOWN);
this.name = visibility == Visibility.PUBLIC ? shortName().orElse(fullName) : fullName;
this.contents = ManifestKey.SUBSYSTEM_CONTENT.parseValues(attributes)
.map(Feature::createSpec)
Expand All @@ -65,32 +65,51 @@ public Feature(Path path) {
.collect(toUnmodifiableList());
this.isAutoFeature = ManifestKey.IBM_PROVISION_CAPABILITY.isPresent(attributes);
this.version = ManifestKey.SUBSYSTEM_VERSION.get(attributes).map(Version::new).orElse(Version.emptyVersion);
try {
this.manifest = new Manifest(path.toUri().toURL().openStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private static Visibility getVisibility(ManifestValueEntry symbolicName) {
String vis = symbolicName.getQualifier("visibility").toUpperCase();
try {
return Visibility.valueOf(vis);
} catch (IllegalArgumentException e) {
return null;
}
}

@Override
public Path path() { return path; }
@Override
public String symbolicName() { return fullName; }
public Optional<String> shortName() { return Optional.ofNullable(shortName); }
@Override
public Visibility visibility() { return this.visibility; }
@Override
public String name() { return name; }
@Override
public String description() {
if (visibility() == Visibility.PUBLIC) {
try {
return getPublicFeatureDescription();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Attributes attributes = manifest.getMainAttributes();
String symbolicNameAttr = attributes.getValue("Subsystem-SymbolicName").substring(symbolicName().length() + 2);
if(isAutoFeature()) return "" + symbolicNameAttr + "\n" + attributes.getValue("IBM-Provision-Capability");
if(!symbolicNameAttr.isEmpty()) return symbolicNameAttr;
return "No Description found";
}
public Version version() { return version; }
@Override
public Stream<String> aka() { return Stream.of(shortName); }
@Override
public boolean isAutoFeature() { return isAutoFeature; }

@Override
public Stream<Element> findDependencies(Collection<Element> elements) {
return contents.stream()
.map(spec -> spec.findBestMatch(elements))
.flatMap(Optional::stream);
}

@Override
public int compareTo(Element other) {
if (!(other instanceof Feature)) return -1; // Features sort before other element types
Feature that = (Feature) other;
Expand All @@ -104,25 +123,48 @@ public int compareTo(Element other) {
public boolean equals(Object other) {
if (this == other) return true;
if (other == null) return false;
if (! (other instanceof Feature)) return false;
if (!(other instanceof Feature)) return false;
Feature that = (Feature) other;
return this.fullName.equals(that.fullName);
}

@Override
public int hashCode() { return Objects.hash(fullName); }
public int hashCode() {
return Objects.hash(fullName);
}

@Override
public String toString() { return symbolicName(); }
public String toString() {
return symbolicName();
}

static Optional<ContentSpec> createSpec(ManifestValueEntry ve) {
String type = ve.getQualifierOrDefault("type", "bundle");
switch (type) {
case "osgi.subsystem.feature": return Optional.of(ve).map(FeatureSpec::new);
case "bundle": return Optional.of(ve).map(BundleSpec::new);
case "file": return Optional.empty();
case "jar": return Optional.empty();
default: throw new IllegalStateException("Unknown content type: " + type);
case "osgi.subsystem.feature":
return Optional.of(ve).map(FeatureSpec::new);
case "bundle":
return Optional.of(ve).map(BundleSpec::new);
case "file":
return Optional.empty();
case "jar":
return Optional.empty();
default:
throw new IllegalStateException("Unknown content type: " + type);
}
}

private String getPublicFeatureDescription() throws IOException {
Path featuresRoot = path.getParent();
Path propertiesFile = validate(featuresRoot.resolve("l10n/" + symbolicName() + ".properties"));
Properties prop = new Properties();
prop.load(new FileInputStream(propertiesFile.toString()));
return prop.getProperty("description");
}

private static Path validate(Path path) {
if (Files.exists(path)) return path;
throw new Error("No properties file found: " + path.toFile().getAbsolutePath());
}

}