Add auto-trimming support to configtree sources

Update `ConfigTreePropertySource` with an option to automatically trim
trailing new-line characters.

Closes gh-23826
pull/28993/head
Phillip Webb 4 years ago
parent cf673cee55
commit 2e2b371679

@ -21,6 +21,7 @@ import java.nio.file.Path;
import java.util.Collections; import java.util.Collections;
import org.springframework.boot.env.ConfigTreePropertySource; import org.springframework.boot.env.ConfigTreePropertySource;
import org.springframework.boot.env.ConfigTreePropertySource.Option;
/** /**
* {@link ConfigDataLoader} for config tree locations. * {@link ConfigDataLoader} for config tree locations.
@ -37,7 +38,7 @@ public class ConfigTreeConfigDataLoader implements ConfigDataLoader<ConfigTreeCo
Path path = resource.getPath(); Path path = resource.getPath();
ConfigDataResourceNotFoundException.throwIfDoesNotExist(resource, path); ConfigDataResourceNotFoundException.throwIfDoesNotExist(resource, path);
String name = "Config tree '" + path + "'"; String name = "Config tree '" + path + "'";
ConfigTreePropertySource source = new ConfigTreePropertySource(name, path); ConfigTreePropertySource source = new ConfigTreePropertySource(name, path, Option.AUTO_TRIM_TRAILING_NEW_LINE);
return new ConfigData(Collections.singletonList(source)); return new ConfigData(Collections.singletonList(source));
} }

@ -145,7 +145,12 @@ public class ConfigTreePropertySource extends EnumerablePropertySource<Path> imp
/** /**
* Convert file and directory names to lowercase. * Convert file and directory names to lowercase.
*/ */
USE_LOWERCASE_NAMES USE_LOWERCASE_NAMES,
/**
* Automatically attempt trim trailing new-line characters.
*/
AUTO_TRIM_TRAILING_NEW_LINE
} }
@ -173,17 +178,22 @@ public class ConfigTreePropertySource extends EnumerablePropertySource<Path> imp
private final PropertyFileContent cachedContent; private final PropertyFileContent cachedContent;
private final boolean autoTrimTrailingNewLine;
private PropertyFile(Path path, Set<Option> options) { private PropertyFile(Path path, Set<Option> options) {
this.path = path; this.path = path;
this.resource = new PathResource(path); this.resource = new PathResource(path);
this.origin = new TextResourceOrigin(this.resource, START_OF_FILE); this.origin = new TextResourceOrigin(this.resource, START_OF_FILE);
this.autoTrimTrailingNewLine = options.contains(Option.AUTO_TRIM_TRAILING_NEW_LINE);
this.cachedContent = options.contains(Option.ALWAYS_READ) ? null this.cachedContent = options.contains(Option.ALWAYS_READ) ? null
: new PropertyFileContent(path, this.resource, this.origin, true); : new PropertyFileContent(path, this.resource, this.origin, true, this.autoTrimTrailingNewLine);
} }
PropertyFileContent getContent() { PropertyFileContent getContent() {
return (this.cachedContent != null) ? this.cachedContent if (this.cachedContent != null) {
: new PropertyFileContent(this.path, this.resource, this.origin, false); return this.cachedContent;
}
return new PropertyFileContent(this.path, this.resource, this.origin, false, this.autoTrimTrailingNewLine);
} }
Origin getOrigin() { Origin getOrigin() {
@ -247,17 +257,21 @@ public class ConfigTreePropertySource extends EnumerablePropertySource<Path> imp
private final Resource resource; private final Resource resource;
private final Origin origin;
private final boolean cacheContent; private final boolean cacheContent;
private volatile byte[] content; private final boolean autoTrimTrailingNewLine;
private final Origin origin; private volatile byte[] content;
private PropertyFileContent(Path path, Resource resource, Origin origin, boolean cacheContent) { private PropertyFileContent(Path path, Resource resource, Origin origin, boolean cacheContent,
boolean autoTrimTrailingNewLine) {
this.path = path; this.path = path;
this.resource = resource; this.resource = resource;
this.origin = origin; this.origin = origin;
this.cacheContent = cacheContent; this.cacheContent = cacheContent;
this.autoTrimTrailingNewLine = autoTrimTrailingNewLine;
} }
@Override @Override
@ -282,7 +296,28 @@ public class ConfigTreePropertySource extends EnumerablePropertySource<Path> imp
@Override @Override
public String toString() { public String toString() {
return new String(getBytes()); String string = new String(getBytes());
if (this.autoTrimTrailingNewLine) {
string = autoTrimTrailingNewLine(string);
}
return string;
}
private String autoTrimTrailingNewLine(String string) {
if (!string.endsWith("\n")) {
return string;
}
int numberOfLines = 0;
for (char ch : string.toCharArray()) {
if (ch == '\n') {
numberOfLines++;
}
}
if (numberOfLines > 1) {
return string;
}
return (string.endsWith("\r\n")) ? string.substring(0, string.length() - 2)
: string.substring(0, string.length() - 1);
} }
@Override @Override

@ -50,7 +50,7 @@ public class ConfigTreeConfigDataLoaderTests {
void loadReturnsConfigDataWithPropertySource() throws IOException { void loadReturnsConfigDataWithPropertySource() throws IOException {
File file = this.directory.resolve("hello").toFile(); File file = this.directory.resolve("hello").toFile();
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
FileCopyUtils.copy("world".getBytes(StandardCharsets.UTF_8), file); FileCopyUtils.copy("world\n".getBytes(StandardCharsets.UTF_8), file);
ConfigTreeConfigDataResource location = new ConfigTreeConfigDataResource(this.directory.toString()); ConfigTreeConfigDataResource location = new ConfigTreeConfigDataResource(this.directory.toString());
ConfigData configData = this.loader.load(this.loaderContext, location); ConfigData configData = this.loader.load(this.loaderContext, location);
assertThat(configData.getPropertySources().size()).isEqualTo(1); assertThat(configData.getPropertySources().size()).isEqualTo(1);

@ -199,6 +199,30 @@ class ConfigTreePropertySourceTests {
assertThat(propertySource.getProperty("spring")).hasToString("boot"); assertThat(propertySource.getProperty("spring")).hasToString("boot");
} }
@Test
void getPropertyAsStringWhenMultiLinePropertyReturnsNonTrimmed() throws Exception {
addProperty("a", "a\nb\n");
ConfigTreePropertySource propertySource = new ConfigTreePropertySource("test", this.directory,
Option.AUTO_TRIM_TRAILING_NEW_LINE);
assertThat(propertySource.getProperty("a").toString()).isEqualTo("a\nb\n");
}
@Test
void getPropertyAsStringWhenPropertyEndsWithNewLineReturnsTrimmed() throws Exception {
addProperty("a", "a\n");
ConfigTreePropertySource propertySource = new ConfigTreePropertySource("test", this.directory,
Option.AUTO_TRIM_TRAILING_NEW_LINE);
assertThat(propertySource.getProperty("a").toString()).isEqualTo("a");
}
@Test
void getPropertyAsStringWhenPropertyEndsWithWindowsNewLineReturnsTrimmed() throws Exception {
addProperty("a", "a\r\n");
ConfigTreePropertySource propertySource = new ConfigTreePropertySource("test", this.directory,
Option.AUTO_TRIM_TRAILING_NEW_LINE);
assertThat(propertySource.getProperty("a").toString()).isEqualTo("a");
}
private ConfigTreePropertySource getFlatPropertySource() throws IOException { private ConfigTreePropertySource getFlatPropertySource() throws IOException {
addProperty("a", "A"); addProperty("a", "A");
addProperty("b", "B"); addProperty("b", "B");

Loading…
Cancel
Save