Skip to content

Commit

Permalink
A concept of "app name" for help purposes #93
Browse files Browse the repository at this point in the history
* a better algorithm that detects jars
  • Loading branch information
andrus committed Sep 9, 2016
1 parent f6e09af commit d588907
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 53 deletions.
@@ -0,0 +1,85 @@
package io.bootique.cli.meta;

import io.bootique.Bootique;

import java.net.URL;
import java.util.Map;

/**
* A helper class to determine the app main class and user-freindly name.
*/
public class ApplicationIntrospector {

/**
* Returns application name that is the name of the main class derived from runtime stack.
*
* @return application name that is the name of the main class derived from runtime stack.
*/
static String appNameFromRuntime() {
Class<?> main = mainClass();
String name = appNameFromJar(main);
return name != null ? name : appNameFromClassName(main);
}

private static String appNameFromClassName(Class<?> mainClass) {
// use full class name as app name...
return mainClass.getName();
}

private static String appNameFromJar(Class<?> mainClass) {

URL url;
try {
url = mainClass.getProtectionDomain().getCodeSource().getLocation();
} catch (Exception e) {
return null;
}

String urlString = url.toExternalForm();

// e.g. the app is started from an unpacked .jar
if (!urlString.endsWith(".jar")) {
return null;
}

int slash = urlString.lastIndexOf('/');
return slash < 0 && slash >= urlString.length() - 1 ? urlString : urlString.substring(slash + 1);
}


/**
* Returns the name of the app main class. If it can't be found, return 'io.bootique.Bootique'.
*
* @return the name of the app main class.
*/
static Class<?> mainClass() {

for (Map.Entry<Thread, StackTraceElement[]> stackEntry : Thread.getAllStackTraces().entrySet()) {

// thread must be called "main"
if ("main".equals(stackEntry.getKey().getName())) {

StackTraceElement[] stack = stackEntry.getValue();
StackTraceElement bottom = stack[stack.length - 1];

// method must be called main
if ("main".equals(bottom.getMethodName())) {

try {
return Class.forName(bottom.getClassName());
} catch (ClassNotFoundException e) {
// can't load class for some reason...
return Bootique.class;
}

} else {
// no other ideas where else to look for main...
return Bootique.class;
}
}
}

// failover...
return Bootique.class;
}
}
45 changes: 1 addition & 44 deletions bootique/src/main/java/io/bootique/cli/meta/CliApplication.java
@@ -1,10 +1,7 @@
package io.bootique.cli.meta;

import io.bootique.Bootique;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

/**
* Metadata for the app application command invocation structure.
Expand Down Expand Up @@ -33,46 +30,6 @@ public static Builder builder(String name, String description) {
return new Builder().name(name).description(description);
}

/**
* Returns application name that is the name of the main class derived from runtime stack.
*
* @return application name that is the name of the main class derived from runtime stack.
*/
static String appNameFromRuntime() {

String main = mainClass();
int dot = main.lastIndexOf('.');
return dot >= 0 && dot < main.length() - 1 ? main.substring(dot + 1) : main;
}

/**
* Returns the name of the app main class. If it can't be found, return 'io.bootique.Bootique'.
*
* @return the name of the app main class.
*/
static String mainClass() {

for (Map.Entry<Thread, StackTraceElement[]> stackEntry : Thread.getAllStackTraces().entrySet()) {

// thread must be called "main"
if ("main".equals(stackEntry.getKey().getName())) {

StackTraceElement[] stack = stackEntry.getValue();
StackTraceElement bottom = stack[stack.length - 1];

// method must be called main
if ("main".equals(bottom.getMethodName())) {
return bottom.getClassName();
} else {
// no other ideas where else to look for main...
return Bootique.class.getName();
}
}
}

// failover...
return Bootique.class.getName();
}

public Collection<CliCommand> getCommands() {
return commands;
Expand Down Expand Up @@ -100,7 +57,7 @@ public Builder name(String name) {
}

public Builder defaultName() {
return name(CliApplication.appNameFromRuntime());
return name(ApplicationIntrospector.appNameFromRuntime());
}

public Builder description(String description) {
Expand Down
Expand Up @@ -4,32 +4,29 @@
import org.junit.Assert;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class CliApplicationTest {
public class ApplicationIntrospectorTest {

@Test
public void testMainClass() {

String mainClassName = CliApplication.mainClass();
Class<?> mainClass = ApplicationIntrospector.mainClass();

// TODO: until https://github.com/bootique/bootique/issues/52 is available,
// we can't make an exact assertion here, as tests can be started from different IDEs, etc.

Assert.assertNotNull(mainClassName);
Assert.assertNotEquals(Bootique.class.getName(), mainClassName);
Assert.assertNotNull(mainClass);
Assert.assertNotEquals(Bootique.class, mainClass);
}

@Test
public void testAppNameFromRuntime() {

String name = CliApplication.appNameFromRuntime();
String name = ApplicationIntrospector.appNameFromRuntime();

// TODO: until https://github.com/bootique/bootique/issues/52 is available,
// we can't make an exact assertion here, as tests can be started from different IDEs, etc.

Assert.assertNotNull(name);
assertEquals(-1, name.indexOf('.'));
Assert.assertNotEquals(Bootique.class.getSimpleName(), name);
Assert.assertNotEquals("Bootique", name);
}
}

0 comments on commit d588907

Please sign in to comment.