Config import: move responsibilities

This commit is contained in:
Roel Spilker
2020-01-20 14:10:33 +01:00
parent 1444d6dfbf
commit bb4fb7d220
9 changed files with 239 additions and 36 deletions
@@ -26,6 +26,7 @@ import java.util.Collections;
import lombok.core.configuration.BubblingConfigurationResolver;
import lombok.core.configuration.ConfigurationKey;
import lombok.core.configuration.ConfigurationParser;
import lombok.core.configuration.ConfigurationProblemReporter;
import lombok.core.configuration.ConfigurationResolver;
import lombok.core.configuration.ConfigurationResolverFactory;
@@ -74,7 +75,7 @@ public class LombokConfiguration {
private static ConfigurationResolverFactory createFileSystemBubblingResolverFactory() {
return new ConfigurationResolverFactory() {
@Override public ConfigurationResolver createResolver(URI sourceLocation) {
return new BubblingConfigurationResolver(cache.sourcesForJavaFile(sourceLocation, ConfigurationProblemReporter.CONSOLE));
return new BubblingConfigurationResolver(cache.sourcesForJavaFile(sourceLocation, new ConfigurationParser(ConfigurationProblemReporter.CONSOLE)));
}
};
}
@@ -43,6 +43,7 @@ public class BubblingConfigurationResolver implements ConfigurationResolver {
List<List<ListModification>> listModificationsList = null;
outer:
for (ConfigurationSource source : sources) {
source.imports();
Result result = source.resolve(key);
if (result == null) continue;
if (isList) {
@@ -22,6 +22,7 @@
package lombok.core.configuration;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URI;
import java.util.ArrayList;
@@ -188,6 +189,7 @@ public class ConfigurationApp extends LombokApp {
};
FileSystemSourceCache cache = new FileSystemSourceCache();
ConfigurationParser parser = new ConfigurationParser(reporter);
boolean first = true;
for (Entry<URI, Set<String>> entry : sharedDirectories.entrySet()) {
if (!first) {
@@ -202,7 +204,7 @@ public class ConfigurationApp extends LombokApp {
out.println();
}
URI directory = entry.getKey();
ConfigurationResolver resolver = new BubblingConfigurationResolver(cache.sourcesForDirectory(directory, reporter));
ConfigurationResolver resolver = new BubblingConfigurationResolver(cache.sourcesForDirectory(directory, parser));
Map<ConfigurationKey<?>, ? extends Collection<String>> traces = trace(keys, directory);
boolean printed = false;
for (ConfigurationKey<?> key : keys) {
@@ -257,7 +259,7 @@ public class ConfigurationApp extends LombokApp {
if (!configFile.exists() || !configFile.isFile()) continue;
ConfigurationFile context = ConfigurationFile.fromFile(configFile);
Map<ConfigurationKey<?>, List<String>> traces = trace(context.contents(), context, keys);
Map<ConfigurationKey<?>, List<String>> traces = trace(context, keys);
stopBubbling = stopBubbling(traces.get(ConfigurationKeys.STOP_BUBBLING));
for (ConfigurationKey<?> key : keys) {
@@ -286,10 +288,13 @@ public class ConfigurationApp extends LombokApp {
return result;
}
private Map<ConfigurationKey<?>, List<String>> trace(String content, ConfigurationFile context, final Collection<ConfigurationKey<?>> keys) {
private Map<ConfigurationKey<?>, List<String>> trace(ConfigurationFile context, final Collection<ConfigurationKey<?>> keys) throws IOException {
final Map<ConfigurationKey<?>, List<String>> result = new HashMap<ConfigurationKey<?>, List<String>>();
Collector collector = new Collector() {
@Override public void addImport(ConfigurationFile importFile, ConfigurationFile context, int lineNumber) {
// TODO Trace imports
}
@Override public void clear(ConfigurationKey<?> key, ConfigurationFile context, int lineNumber) {
trace(key, "clear " + key.getKeyName(), lineNumber);
}
@@ -315,8 +320,9 @@ public class ConfigurationApp extends LombokApp {
}
traces.add(String.format("%4d: %s", lineNumber, message));
}
};
new ConfigurationParser(VOID).parse(content, context, collector);
new ConfigurationParser(VOID).parse(context, collector);
return result;
}
@@ -0,0 +1,164 @@
/*
* Copyright (C) 2020 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.core.configuration;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public abstract class ConfigurationFile {
private static final ThreadLocal<byte[]> buffers = new ThreadLocal<byte[]>() {
protected byte[] initialValue() {
return new byte[65536];
}
};
private final String identifier;
public static ConfigurationFile fromFile(File file) {
return new RegularConfigurationFile(file);
}
public static ConfigurationFile fromCharSequence(String identifier, CharSequence contents, long lastModified) {
return new CharSequenceConfigurationFile(identifier, contents, lastModified);
}
private ConfigurationFile(String identifier) {
this.identifier = identifier;
}
abstract long getLastModifiedOrMissing();
abstract boolean exists();
abstract CharSequence contents() throws IOException;
public abstract ConfigurationFile resolve(String path);
final String description() {
return identifier;
}
@Override public boolean equals(Object obj) {
if (!(obj instanceof ConfigurationFile)) return false;
return identifier.equals(((ConfigurationFile)obj).identifier);
}
@Override public int hashCode() {
return identifier.hashCode();
}
public static long getLastModifiedOrMissing(File file) {
if (!fileExists(file)) return FileSystemSourceCache.MISSING;
return file.lastModified();
}
private static boolean fileExists(File file) {
return file.exists() && file.isFile();
}
private static String read(InputStream is) throws IOException {
byte[] b = buffers.get();
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
while (true) {
int r = is.read(b);
if (r == -1) break;
out.write(b, 0, r);
}
return new String(out.toByteArray(), "UTF-8");
} finally {
is.close();
}
}
private static class RegularConfigurationFile extends ConfigurationFile {
private final File file;
private RegularConfigurationFile(File file) {
super(file.getPath());
this.file = file;
}
@Override boolean exists() {
return fileExists(file);
}
public ConfigurationFile resolve(String path) {
File file = resolveFile(path);
return file == null ? null : fromFile(file);
}
private File resolveFile(String path) {
boolean absolute = false;
int colon = path.indexOf(':');
if (colon != -1) {
if (colon != 1 || path.indexOf(':', colon + 1) != -1) return null;
char firstCharacter = Character.toLowerCase(path.charAt(0));
if (firstCharacter < 'a' || firstCharacter > 'z') return null;
absolute = true;
}
if (path.charAt(0) == '/') absolute = true;
try {
return absolute ? new File(path) : new File(file.toURI().resolve(path));
} catch (Exception e) {
return null;
}
}
@Override
long getLastModifiedOrMissing() {
return getLastModifiedOrMissing(file);
}
@Override
CharSequence contents() throws IOException {
return read(new FileInputStream(file));
}
}
private static class CharSequenceConfigurationFile extends ConfigurationFile {
private final CharSequence contents;
private final long lastModified;
private CharSequenceConfigurationFile(String identifier, CharSequence contents, long lastModified) {
super(identifier);
this.contents = contents;
this.lastModified = lastModified;
}
@Override long getLastModifiedOrMissing() {
return lastModified;
}
@Override CharSequence contents() throws IOException {
return contents;
}
@Override boolean exists() {
return true;
}
@Override public ConfigurationFile resolve(String path) {
return null;
}
}
}
@@ -21,6 +21,7 @@
*/
package lombok.core.configuration;
import java.io.IOException;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -37,13 +38,17 @@ public class ConfigurationParser {
this.reporter = reporter;
}
public void parse(CharSequence content, ConfigurationFile context, Collector collector) {
public void parse(ConfigurationFile context, Collector collector) {
CharSequence contents = contents(context);
if (contents == null) {
return;
}
Map<String, ConfigurationKey<?>> registeredKeys = ConfigurationKey.registeredKeys();
int lineNumber = 0;
Matcher lineMatcher = NEWLINE_FINDER.matcher(content);
Matcher lineMatcher = NEWLINE_FINDER.matcher(contents);
boolean importsAllowed = true;
while (lineMatcher.find()) {
CharSequence line = content.subSequence(lineMatcher.start(1), lineMatcher.end(1));
CharSequence line = contents.subSequence(lineMatcher.start(1), lineMatcher.end(1));
lineNumber++;
if (line.length() == 0 || line.charAt(0) == '#') continue;
@@ -54,9 +59,16 @@ public class ConfigurationParser {
continue;
}
String imported = importMatcher.group(1);
if (!context.importResolves(imported)) {
reporter.report(context.description(), "Imported file does not exist", lineNumber, line);
ConfigurationFile importFile = context.resolve(imported);
if (importFile == null) {
reporter.report(context.description(), "Import is not valid", lineNumber, line);
continue;
}
if (!importFile.exists()) {
reporter.report(context.description(), "Imported file does not exist", lineNumber, line);
continue;
}
collector.addImport(importFile, context, lineNumber);
continue;
}
@@ -116,7 +128,17 @@ public class ConfigurationParser {
}
}
private CharSequence contents(ConfigurationFile context) {
try {
return context.contents();
} catch (IOException e) {
reporter.report(context.description(), "Exception while reading file: " + e.getMessage(), 0, null);
}
return null;
}
public interface Collector {
void addImport(ConfigurationFile importFile, ConfigurationFile context, int lineNumber);
void clear(ConfigurationKey<?> key, ConfigurationFile context, int lineNumber);
void set(ConfigurationKey<?> key, Object value, ConfigurationFile context, int lineNumber);
void add(ConfigurationKey<?> key, Object value, ConfigurationFile context, int lineNumber);
@@ -21,9 +21,12 @@
*/
package lombok.core.configuration;
import java.util.List;
public interface ConfigurationSource {
Result resolve(ConfigurationKey<?> key);
List<ConfigurationFile> imports();
public static final class Result {
private final Object value;
@@ -57,7 +57,7 @@ public class FileSystemSourceCache {
}
}
public Iterable<ConfigurationSource> sourcesForJavaFile(URI javaFile, ConfigurationProblemReporter reporter) {
public Iterable<ConfigurationSource> sourcesForJavaFile(URI javaFile, ConfigurationParser parser) {
if (javaFile == null) return Collections.emptyList();
cacheClear();
File dir = uriCache.get(javaFile);
@@ -86,7 +86,7 @@ public class FileSystemSourceCache {
if (dir != null) {
try {
return sourcesForDirectory(dir, reporter);
return sourcesForDirectory(dir, parser);
} catch (Exception e) {
// Especially for eclipse's sake, exceptions here make eclipse borderline unusable, so let's play nice.
ProblemReporter.error("Can't resolve config stack for dir: " + dir.getAbsolutePath(), e);
@@ -96,11 +96,11 @@ public class FileSystemSourceCache {
return Collections.emptyList();
}
public Iterable<ConfigurationSource> sourcesForDirectory(URI directory, ConfigurationProblemReporter reporter) {
return sourcesForJavaFile(directory, reporter);
public Iterable<ConfigurationSource> sourcesForDirectory(URI directory, ConfigurationParser parser) {
return sourcesForJavaFile(directory, parser);
}
private Iterable<ConfigurationSource> sourcesForDirectory(final File directory, final ConfigurationProblemReporter reporter) {
private Iterable<ConfigurationSource> sourcesForDirectory(final File directory, final ConfigurationParser parser) {
return new Iterable<ConfigurationSource>() {
@Override
public Iterator<ConfigurationSource> iterator() {
@@ -127,7 +127,7 @@ public class FileSystemSourceCache {
private ConfigurationSource findNext() {
while (currentDirectory != null && next == null) {
next = getSourceForDirectory(currentDirectory, reporter);
next = getSourceForDirectory(currentDirectory, parser);
currentDirectory = currentDirectory.getParentFile();
}
if (next != null) {
@@ -146,11 +146,11 @@ public class FileSystemSourceCache {
};
}
ConfigurationSource getSourceForDirectory(File directory, ConfigurationProblemReporter reporter) {
return getSourceForConfigFile(ConfigurationFile.fromFile(new File(directory, LOMBOK_CONFIG_FILENAME)), reporter);
ConfigurationSource getSourceForDirectory(File directory,ConfigurationParser parser) {
return getSourceForConfigFile(ConfigurationFile.fromFile(new File(directory, LOMBOK_CONFIG_FILENAME)), parser);
}
private ConfigurationSource getSourceForConfigFile(ConfigurationFile context, ConfigurationProblemReporter reporter) {
private ConfigurationSource getSourceForConfigFile(ConfigurationFile context, ConfigurationParser parser) {
long now = System.currentTimeMillis();
Content content = ensureContent(context);
synchronized (content) {
@@ -160,7 +160,7 @@ public class FileSystemSourceCache {
content.lastChecked = now;
long previouslyModified = content.lastModified;
content.lastModified = context.getLastModifiedOrMissing();
if (content.lastModified != previouslyModified) content.source = content.lastModified == MISSING ? null : parse(context, reporter);
if (content.lastModified != previouslyModified) content.source = content.lastModified == MISSING ? null : parse(context, parser);
return content.source;
}
}
@@ -174,13 +174,8 @@ public class FileSystemSourceCache {
return fileCache.get(context);
}
private ConfigurationSource parse(ConfigurationFile context, ConfigurationProblemReporter reporter) {
try {
return StringConfigurationSource.forString(context.contents(), reporter, context);
} catch (Exception e) {
reporter.report(context.description(), "Exception while reading file: " + e.getMessage(), 0, null);
return null;
}
private ConfigurationSource parse(ConfigurationFile context, ConfigurationParser parser) {
return SingleConfigurationSource.parse(context, parser);
}
private static class Content {
@@ -30,13 +30,18 @@ import java.util.Map.Entry;
import lombok.core.configuration.ConfigurationParser.Collector;
public class StringConfigurationSource implements ConfigurationSource {
public final class SingleConfigurationSource implements ConfigurationSource {
private final Map<ConfigurationKey<?>, Result> values;
private final List<ConfigurationFile> imports;
public static ConfigurationSource forString(CharSequence content, ConfigurationProblemReporter reporter, ConfigurationFile context) {
public static ConfigurationSource parse(ConfigurationFile context, ConfigurationParser parser) {
final Map<ConfigurationKey<?>, Result> values = new HashMap<ConfigurationKey<?>, Result>();
final List<ConfigurationFile> imports = new ArrayList<ConfigurationFile>();
Collector collector = new Collector() {
@Override public void addImport(ConfigurationFile importFile, ConfigurationFile context, int lineNumber) {
imports.add(importFile);
}
@Override public void clear(ConfigurationKey<?> key, ConfigurationFile context, int lineNumber) {
values.put(key, new Result(null, true));
}
@@ -66,11 +71,11 @@ public class StringConfigurationSource implements ConfigurationSource {
list.add(new ListModification(value, add));
}
};
new ConfigurationParser(reporter).parse(content, context, collector);
return new StringConfigurationSource(values);
parser.parse(context, collector);
return new SingleConfigurationSource(values, imports);
}
private StringConfigurationSource(Map<ConfigurationKey<?>, Result> values) {
private SingleConfigurationSource(Map<ConfigurationKey<?>, Result> values, List<ConfigurationFile> imports) {
this.values = new HashMap<ConfigurationKey<?>, Result>();
for (Entry<ConfigurationKey<?>, Result> entry : values.entrySet()) {
Result result = entry.getValue();
@@ -80,10 +85,16 @@ public class StringConfigurationSource implements ConfigurationSource {
this.values.put(entry.getKey(), result);
}
}
this.imports = Collections.unmodifiableList(imports);
}
@Override
public Result resolve(ConfigurationKey<?> key) {
return values.get(key);
}
@Override
public List<ConfigurationFile> imports() {
return imports;
}
}
+3 -3
View File
@@ -42,9 +42,10 @@ import org.junit.Assert;
import lombok.core.LombokImmutableList;
import lombok.core.configuration.BubblingConfigurationResolver;
import lombok.core.configuration.ConfigurationFile;
import lombok.core.configuration.ConfigurationParser;
import lombok.core.configuration.ConfigurationProblemReporter;
import lombok.core.configuration.ConfigurationResolver;
import lombok.core.configuration.StringConfigurationSource;
import lombok.core.configuration.SingleConfigurationSource;
public class LombokTestSource {
private final File file;
@@ -244,8 +245,7 @@ public class LombokTestSource {
Assert.fail("Problem on directive line: " + problem + " at conf line #" + lineNumber + " (" + line + ")");
}
};
this.configuration = new BubblingConfigurationResolver(Collections.singleton(StringConfigurationSource.forString(conf, reporter, ConfigurationFile.fromFile(file))));
this.configuration = new BubblingConfigurationResolver(Collections.singleton(SingleConfigurationSource.parse(ConfigurationFile.fromCharSequence(file.getAbsoluteFile().getPath(), conf, ConfigurationFile.getLastModifiedOrMissing(file)), new ConfigurationParser(reporter))));
this.formatPreferences = Collections.unmodifiableMap(formats);
}