Skip to content

Commit

Permalink
[#2102][#2107] PropertiesDefaultProvider to load properties from clas…
Browse files Browse the repository at this point in the history
…spath
  • Loading branch information
remkop committed Sep 18, 2023
1 parent 6b047ae commit 6dfd688
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 51 deletions.
3 changes: 3 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ Artifacts in this release are signed by Remko Popma (6601 E5C0 8DCC BB96).

## <a name="4.7.6-new"></a> New and Noteworthy

`PropertiesDefaultProvider` now tries to load properties from the classpath if the file cannot be found in the user.home directory.



## <a name="4.7.6-fixes"></a> Fixed issues

* [#2102][#2107] Enhancement: `PropertiesDefaultProvider` should try to load properties from classpath (last). Thanks to [Lumír Návrat](https://github.com/rimuln) for the pull request.
* [#2058] Bugfix: `defaultValue` should not be applied in addition to user-specified value for options with a custom `IParameterConsumer`. Thanks to [Staffan Arvidsson McShane](https://github.com/StaffanArvidsson) for raising this.
* [#2047] DEP: Bump andymckay/append-gist-action from 1fbfbbce708a39bd45846f0955ed5521f2099c6d to 6e8d64427fe47cbacf4ab6b890411f1d67c07f3e
* [#2091] DEP: Bump actions/checkout from 3.5.2 to 3.6.0
Expand Down
3 changes: 2 additions & 1 deletion docs/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2034,7 +2034,7 @@ If the default provider has no value for that option or positional parameter, th
From picocli 4.1, applications can use the built-in `PropertiesDefaultProvider`
implementation that loads default values from a properties file.

By default, this implementation tries to find a properties file named `.${COMMAND-NAME}.properties` in the user home directory, where `${COMMAND-NAME}` is the name of the command. If a command has aliases in addition to its name, these aliases are also used to try to find the properties file. For example:
By default, this implementation tries to find a properties file named `.${COMMAND-NAME}.properties` in the user home directory or in the classpath, where `${COMMAND-NAME}` is the name of the command. If a command has aliases in addition to its name, these aliases are also used to try to find the properties file. For example:

.Java
[source,java,role="primary"]
Expand All @@ -2056,6 +2056,7 @@ class Git { }

The above will try to load default values from `new File(System.getProperty("user.home"), ".git.properties")`.
The location of the properties file can also be controlled with system property `"picocli.defaults.${COMMAND-NAME}.path"` (`"picocli.defaults.git.path"` in this example), in which case the value of the property must be the path to the file containing the default values.
Finally, picocli will try to load the `.git.properties` file from the classpath.

The location of the properties file may also be specified programmatically. For example:

Expand Down
56 changes: 33 additions & 23 deletions src/main/java/picocli/CommandLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -18887,7 +18887,7 @@ public static class MissingTypeConverterException extends ParameterException {
public static class PropertiesDefaultProvider implements IDefaultValueProvider {

private Properties properties;
private File location;
private String location;

/**
* Default constructor, used when this default value provider is specified in
Expand Down Expand Up @@ -18940,30 +18940,39 @@ public PropertiesDefaultProvider(Properties properties) {
public PropertiesDefaultProvider(File file) {
this(createProperties(file, null));
properties.remove("__picocli_internal_location");
location = file;
location = file.getAbsolutePath();
}

private static Properties createProperties(File file, CommandSpec commandSpec) {
if (file == null) {
throw new NullPointerException("file is null");
}
Tracer tracer = CommandLine.tracer();
Properties result = new Properties();
if (file.exists() && file.canRead()) {
InputStream in = null;
try {
String command = commandSpec == null ? "unknown command" : commandSpec.qualifiedName();
tracer.debug("Reading defaults from %s for %s", file.getAbsolutePath(), command);
in = new FileInputStream(file);
result.load(in);
result.put("__picocli_internal_location", file);
} catch (IOException ioe) {
tracer.warn("could not read defaults from %s: %s", file.getAbsolutePath(), ioe);
} finally {
close(in);
return createProperties(new FileInputStream(file), file.getAbsolutePath(), commandSpec);
} catch (Exception ex) {
tracer().warn("PropertiesDefaultProvider could not read defaults from %s: %s", file.getAbsolutePath(), ex);
}
} else {
tracer.warn("defaults configuration file %s does not exist or is not readable", file.getAbsolutePath());
tracer().warn("PropertiesDefaultProvider: defaults configuration file %s does not exist or is not readable", file.getAbsolutePath());
}
return new Properties();
}

private static Properties createProperties(InputStream in, String locationDescription, CommandSpec commandSpec) {
if (in == null) {
throw new NullPointerException("InputStream is null");
}
Properties result = new Properties();
try {
String command = commandSpec == null ? "unknown command" : commandSpec.qualifiedName();
tracer().debug("PropertiesDefaultProvider reading defaults from %s for %s", locationDescription, command);
result.load(in);
result.put("__picocli_internal_location", locationDescription);
} catch (IOException ioe) {
tracer().warn("PropertiesDefaultProvider could not read defaults from %s: %s", locationDescription, ioe);
} finally {
close(in);
}
return result;
}
Expand All @@ -18976,6 +18985,9 @@ private static Properties loadProperties(CommandSpec commandSpec) {
String path = p.getProperty("picocli.defaults." + name + ".path");
File defaultPath = new File(p.getProperty("user.home"), propertiesFileName);
File file = path == null ? defaultPath : new File(path);
if (path != null) {
tracer().debug("PropertiesDefaultProvider using path from system property %s: %s", "picocli.defaults." + name + ".path", path);
}
if (file.canRead()) {
return createProperties(file, commandSpec);
} else {
Expand All @@ -18984,16 +18996,14 @@ private static Properties loadProperties(CommandSpec commandSpec) {
userObject = commandSpec.commandLine;
}
URL resource = userObject.getClass().getClassLoader().getResource(propertiesFileName);
Tracer tracer = CommandLine.tracer();
if (resource != null) {
file = new File(resource.getFile());
if (file.canRead()) {
return createProperties(file, commandSpec);
} else {
tracer.warn("could not read defaults from %s", file.getAbsolutePath());
try {
return createProperties(resource.openStream(), resource.toString(), commandSpec);
} catch (Exception ex) {
tracer().warn("PropertiesDefaultProvider could not read defaults from %s: %s", resource, ex);
}
} else {
tracer.warn("defaults configuration file %s doesn not exists on classpath", propertiesFileName);
tracer().debug("PropertiesDefaultProvider defaults configuration file %s does not exist on classpath or user home or specified location", propertiesFileName);
}
}
}
Expand All @@ -19003,7 +19013,7 @@ private static Properties loadProperties(CommandSpec commandSpec) {
public String defaultValue(ArgSpec argSpec) throws Exception {
if (properties == null) {
properties = loadProperties(argSpec.command());
location = properties == null ? null : (File) properties.remove("__picocli_internal_location");
location = properties == null ? null : (String) properties.remove("__picocli_internal_location");
}
if (properties == null || properties.isEmpty()) {
return null;
Expand Down
50 changes: 27 additions & 23 deletions src/test/java/picocli/PropertiesDefaultProviderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,9 @@ public class PropertiesDefaultProviderTest {
@Rule
public final SystemErrRule systemErrRule = new SystemErrRule().enableLog().muteForSuccessfulTests();

@Command(name = "providertest", subcommands = Subcommand.class,
defaultValueProvider = PropertiesDefaultProvider.class)
static class MyApp {
@Option(names = "--aaa") int aaa;
@Option(names = "-b", descriptionKey = "bbb") int bbb;
@Parameters(index = "1", paramLabel = "ppp") int ppp;
@Parameters(index = "0", paramLabel = "qqq", descriptionKey = "xxx") int xxx;
}

@Command(name = "providersub",
defaultValueProvider = PropertiesDefaultProvider.class)
static class Subcommand {
@Command(name = "classpathLoadedProvidertest",
defaultValueProvider = PropertiesDefaultProvider.class)
static class ClasspathLoadedDefaultsApp {
@Option(names = "--aaa") int aaa;
@Option(names = "-b", descriptionKey = "bbb") int bbb;
@Parameters(index = "1", paramLabel = "ppp") int ppp;
Expand All @@ -54,23 +45,36 @@ static class Subcommand {

@Test
public void testLoadFromResourceClasspathIfPropertySpecified() throws IOException {
Properties expected = new Properties();
expected.setProperty("aaa", "111");
expected.setProperty("bbb", "222");
expected.setProperty("ppp", "333");
expected.setProperty("xxx", "444");

MyApp myApp = new MyApp();
ClasspathLoadedDefaultsApp myApp = new ClasspathLoadedDefaultsApp();
assertEquals(myApp.aaa, 0);
assertEquals(myApp.bbb, 0);
assertEquals(myApp.ppp, 0);
assertEquals(myApp.xxx, 0);
new CommandLine(myApp).parseArgs();

assertEquals(myApp.aaa, 111);
assertEquals(myApp.bbb, 222);
assertEquals(myApp.ppp, 333);
assertEquals(myApp.xxx, 444);
assertEquals(myApp.aaa, 1001);
assertEquals(myApp.bbb, 2002);
assertEquals(myApp.ppp, 3003);
assertEquals(myApp.xxx, 4004);
}

@Command(name = "providertest", subcommands = Subcommand.class,
defaultValueProvider = PropertiesDefaultProvider.class)
static class MyApp {
@Option(names = "--aaa") int aaa;
@Option(names = "-b", descriptionKey = "bbb") int bbb;
@Parameters(index = "1", paramLabel = "ppp") int ppp;
@Parameters(index = "0", paramLabel = "qqq", descriptionKey = "xxx") int xxx;
}

@Command(name = "providersub",
defaultValueProvider = PropertiesDefaultProvider.class)
static class Subcommand {
@Option(names = "--aaa") int aaa;
@Option(names = "-b", descriptionKey = "bbb") int bbb;
@Parameters(index = "1", paramLabel = "ppp") int ppp;
@Parameters(index = "0", paramLabel = "qqq", descriptionKey = "xxx") int xxx;
}

@Test
Expand Down Expand Up @@ -243,7 +247,7 @@ public void testNullFile() {
public void testNonExistingFile() {
TestUtil.setTraceLevel(CommandLine.TraceLevel.DEBUG);
new PropertiesDefaultProvider(new File("nosuchfile"));
assertTrue(systemErrRule.getLog().startsWith("[picocli WARN] defaults configuration file "));
assertTrue(systemErrRule.getLog().startsWith("[picocli WARN] PropertiesDefaultProvider: defaults configuration file "));
assertTrue(systemErrRule.getLog().endsWith(String.format("nosuchfile does not exist or is not readable%n")));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#exported from test
#Mon Sep 18 12:37:21 CEST 2023
aaa=111
ppp=333
bbb=222
xxx=444
aaa=1001
ppp=3003
bbb=2002
xxx=4004

0 comments on commit 6dfd688

Please sign in to comment.