io.takari.builder.internal.BuilderInputsBuilder Maven / Gradle / Ivy
package io.takari.builder.internal;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import io.takari.builder.GeneratedSourcesDirectory;
import io.takari.builder.IArtifactMetadata;
import io.takari.builder.internal.BuilderInputs.ArtifactResourcesValue;
import io.takari.builder.internal.BuilderInputs.CollectionValue;
import io.takari.builder.internal.BuilderInputs.CompositeValue;
import io.takari.builder.internal.BuilderInputs.DependencyMapValue;
import io.takari.builder.internal.BuilderInputs.DependencyValue;
import io.takari.builder.internal.BuilderInputs.GeneratedResourcesDirectoryValue;
import io.takari.builder.internal.BuilderInputs.GeneratedSourcesDirectoryValue;
import io.takari.builder.internal.BuilderInputs.InputDirectoryValue;
import io.takari.builder.internal.BuilderInputs.InputFileValue;
import io.takari.builder.internal.BuilderInputs.InputFilesValue;
import io.takari.builder.internal.BuilderInputs.InstanceFactory;
import io.takari.builder.internal.BuilderInputs.ListArtifactResourcesValue;
import io.takari.builder.internal.BuilderInputs.MapValue;
import io.takari.builder.internal.BuilderInputs.OutputDirectoryValue;
import io.takari.builder.internal.BuilderInputs.OutputFileValue;
import io.takari.builder.internal.BuilderInputs.StringValue;
import io.takari.builder.internal.BuilderInputs.Value;
import io.takari.builder.internal.Reflection.ReflectionField;
import io.takari.builder.internal.Reflection.ReflectionType;
import io.takari.builder.internal.model.AbstractFileParameter;
import io.takari.builder.internal.model.AbstractParameter;
import io.takari.builder.internal.model.AbstractResourceSelectionParameter;
import io.takari.builder.internal.model.ArtifactResourcesParameter;
import io.takari.builder.internal.model.BuilderClass;
import io.takari.builder.internal.model.BuilderMetadataVisitor;
import io.takari.builder.internal.model.BuilderMethod;
import io.takari.builder.internal.model.BuilderValidationVisitor;
import io.takari.builder.internal.model.CompositeParameter;
import io.takari.builder.internal.model.DependenciesParameter;
import io.takari.builder.internal.model.DependencyResourcesParameter;
import io.takari.builder.internal.model.GeneratedResourcesDirectoryParameter;
import io.takari.builder.internal.model.GeneratedSourcesDirectoryParameter;
import io.takari.builder.internal.model.InputDirectoryFilesParameter;
import io.takari.builder.internal.model.InputDirectoryParameter;
import io.takari.builder.internal.model.InputFileParameter;
import io.takari.builder.internal.model.MapParameter;
import io.takari.builder.internal.model.MultivalueParameter;
import io.takari.builder.internal.model.OutputDirectoryParameter;
import io.takari.builder.internal.model.OutputFileParameter;
import io.takari.builder.internal.model.SimpleParameter;
import io.takari.builder.internal.model.TypeAdapter;
import io.takari.builder.internal.model.UnsupportedCollectionParameter;
import io.takari.builder.internal.pathmatcher.FileMatcher;
public class BuilderInputsBuilder implements BuilderMetadataVisitor {
static final String XML_CONFIG_LOCATION = "location";
static final String XML_CONFIG_INCLUDES = "includes";
static final String XML_CONFIG_INCLUDE = "include";
static final String XML_CONFIG_EXCLUDES = "excludes";
static final String XML_CONFIG_EXCLUDE = "exclude";
static final String SOURCE_ROOTS_EXPR = "${project.compileSourceRoots}";
static final String TEST_SOURCE_ROOTS_EXPR = "${project.testCompileSourceRoots}";
private final ProjectModelProvider projectModelProvider;
private final DependencyResolver dependencyResolver;
private final ExpressionEvaluator expressionEvaluator;
private final Xpp3Dom builderConfiguration;
private final Map> forcedParameters;
private final String goal;
private final BuilderWorkspace workspace;
private boolean isNonDeterministic;
Context context;
final Map> parameters = new LinkedHashMap<>();
BuilderInputsBuilder(String goal, ProjectModelProvider projectModelProvider,
DependencyResolver dependencyResolver, ExpressionEvaluator expressionEvaluator,
Xpp3Dom configuration, Map> forcedParameters, BuilderWorkspace workspace) {
this.goal = goal;
this.projectModelProvider = projectModelProvider;
this.dependencyResolver = dependencyResolver;
this.expressionEvaluator = expressionEvaluator;
this.builderConfiguration = configuration;
this.forcedParameters = forcedParameters;
this.workspace = workspace;
}
public static BuilderInputs build(String goal, ProjectModelProvider projectModelProvider,
DependencyResolver dependencyResolver, ExpressionEvaluator expressionEvaluator,
Class> clazz, Xpp3Dom configuration, Map> forcedParameters,
BuilderWorkspace builderWorkspace) throws IOException {
BuilderClass metadata = Reflection.createBuilderClass(clazz);
return build(goal, projectModelProvider, dependencyResolver, expressionEvaluator, clazz,
configuration, forcedParameters, metadata, builderWorkspace);
}
static BuilderInputs build(String goal, ProjectModelProvider mavenModelProvider,
DependencyResolver dependencyResolver, ExpressionEvaluator expressionEvaluator,
Class> clazz, Xpp3Dom configuration, Map> forcedParameters,
BuilderClass metadata, BuilderWorkspace workspace) {
BuilderValidationVisitor vv = new BuilderValidationVisitor() {
@Override
protected void error(AbstractParameter parameter, String message) {
throw new InvalidModelException();
}
@Override
protected void error(BuilderMethod builder, String message) {
throw new InvalidModelException();
}
};
metadata.accept(vv);
BuilderInputsBuilder v = new BuilderInputsBuilder(goal, mavenModelProvider, dependencyResolver,
expressionEvaluator, configuration, forcedParameters, workspace);
metadata.accept(v);
return new BuilderInputs(clazz, v.parameters, v.isNonDeterministic);
}
//
//
//
static boolean isEmpty(String str) {
return str == null || str.trim().isEmpty();
}
@SuppressWarnings("serial")
static class InvalidConfigurationException extends RuntimeException {
public final Context context;
public InvalidConfigurationException(Context context, String message) {
super(context.getBacktrace() + ": " + message);
this.context = context;
}
public InvalidConfigurationException(Context context, String message, Throwable cause) {
super(context.getBacktrace() + ": " + message, cause);
this.context = context;
}
}
@SuppressWarnings("serial")
static class InvalidModelException extends RuntimeException {
}
class Evaluator {
private final Context context;
private String[] value;
private Xpp3Dom configuration;
private String[] defaultValue;
public Evaluator(Context context) {
this.context = context;
}
public Evaluator withValue(String[] value) {
assert this.value == null;
this.value = value;
return this;
}
public Evaluator withConfiguration(Xpp3Dom configuration) {
assert this.configuration == null;
this.configuration = configuration;
return this;
}
public Evaluator withDefaultValue(String[] defaultValue) {
assert this.defaultValue == null;
this.defaultValue = defaultValue;
return this;
}
private Stream configuration() {
if (configuration == null) {
return Stream.empty();
}
// TODO assert both value and children are not present simultaneously
Stream result;
if (configuration.getChildCount() > 0) {
result = Stream.of(configuration.getChildren());
} else {
result = Stream.of(configuration);
}
return result //
.map(xml -> xml != null ? xml.getValue() : null) //
.filter(str -> !isEmpty(str));
}
public Stream asStrings() {
return asStrings(false);
}
public Stream asStrings(boolean allowSourceRoots) {
if (value != null && value.length > 0) {
if (configuration().count() > 0) {
throw new InvalidConfigurationException(context, "configuration is not allowed");
}
String[] valueToEvaluate = parseSourceRoots(value, allowSourceRoots);
return Stream.of(valueToEvaluate).map(s -> evaluate(s));
}
List configuration = configuration().collect(Collectors.toList());
if (!configuration.isEmpty()) {
return configuration.stream();
}
if (defaultValue != null && defaultValue.length > 0) {
String[] valueToEvaluate = parseSourceRoots(defaultValue, allowSourceRoots);
return Stream.of(valueToEvaluate).map(s -> {
try {
return evaluate(s);
} catch (InvalidConfigurationException e) {
return null;
}
}).filter(s -> s != null);
}
return Stream.empty();
}
private String[] parseSourceRoots(String[] initialValue, boolean allowSourceRoots) {
List values = Arrays.asList(initialValue);
String expr = null;
Iterator valIter = values.iterator();
String prevVal = null;
while (valIter.hasNext()) {
String currentVal = valIter.next();
if (expr != null) {
throw new InvalidConfigurationException(context,
String.format("%s can not have other values provided along with it", expr));
}
if (currentVal.equals(SOURCE_ROOTS_EXPR) || currentVal.equals(TEST_SOURCE_ROOTS_EXPR)) {
if (prevVal != null) {
throw new InvalidConfigurationException(context,
String.format("%s can not have other values provided along with it", currentVal));
}
if (!allowSourceRoots) {
throw new InvalidConfigurationException(context,
String.format("%s expression is not allowed here", currentVal));
}
expr = currentVal;
}
prevVal = currentVal;
}
if (SOURCE_ROOTS_EXPR.equals(expr)) {
List sourceRoots = projectModelProvider.getCompileSourceRoots();
return sourceRoots.toArray(new String[sourceRoots != null ? sourceRoots.size() : 0]);
}
if (TEST_SOURCE_ROOTS_EXPR.equals(expr)) {
List sourceRoots = projectModelProvider.getTestCompileSourceRoots();
return sourceRoots.toArray(new String[sourceRoots != null ? sourceRoots.size() : 0]);
}
return initialValue;
}
public Stream asPaths() {
return asStrings().map(s -> toPath(s));
}
public Stream asPathsWithSourceRoots() {
return asStrings(true).map(s -> toPath(s));
}
public Path asPath() {
// TODO validate value has no more than one element
if (value != null && value.length > 0) {
return toPath(evaluate(value[0]));
}
// TODO validate configuration does not have children
if (configuration != null) {
if (configuration.getValue().equals(SOURCE_ROOTS_EXPR)
|| configuration.getValue().equals(TEST_SOURCE_ROOTS_EXPR)) {
throw new InvalidConfigurationException(context, String
.format("%s expression is not allowed in configuration", configuration.getValue()));
}
return toPath(evaluate(configuration.getValue()));
}
// TODO validate defaultValue has no more than one element
if (defaultValue != null && defaultValue.length > 0) {
try {
return toPath(evaluate(defaultValue[0]));
} catch (InvalidConfigurationException e) {
// could not evaluate, there is no default value
}
}
return null;
}
private Path toPath(String path) {
Path file = Paths.get(path);
if (!file.isAbsolute()) {
file = projectModelProvider.getBasedir().toAbsolutePath().resolve(path);
}
try {
return file.toFile().getCanonicalFile().toPath();
} catch (IOException e) {
return file.normalize();
}
}
private String evaluate(String str) {
if (isEmpty(str)) {
return null;
}
try {
return expressionEvaluator.evaluate(str.trim());
} catch (ExpressionEvaluationException e) {
throw new InvalidConfigurationException(context, e.getMessage(), e);
}
}
}
abstract class Context {
public final Context parent;
public final String name;
public final Xpp3Dom configuration;
protected Context(Context parent, String name, Xpp3Dom configuration) {
this.parent = parent;
this.name = name;
this.configuration = configuration;
}
public abstract void accept(Value> value);
public abstract List> values();
public abstract Value> value();
public abstract Stream configuration();
public MultivalueContext multivalueSubcontext(boolean required) {
return new MultivalueContext(this, name, configuration, required);
}
public SingletonContext singletonSubcontext(String name, boolean required) {
Xpp3Dom subConfiguration = configuration != null ? configuration.getChild(name) : null;
return new SingletonContext(this, "." + name, subConfiguration, required);
}
public Evaluator evaluator() {
return new Evaluator(this);
}
public String getBacktrace() {
StringBuilder sb = new StringBuilder();
for (Context ctx = this; ctx != null; ctx = ctx.parent) {
sb.insert(0, ctx.name);
}
return sb.toString();
}
}
class SingletonContext extends Context {
private final boolean required;
public SingletonContext(Context parent, String name, Xpp3Dom configuration, boolean required) {
super(parent, name, configuration);
this.required = required;
}
private Value> value;
@Override
public void accept(Value> value) {
assert this.value == null; // TODO validate to make sure this is always the case
this.value = value;
}
@Override
public List> values() {
throw new UnsupportedOperationException();
}
@Override
public Value> value() {
if (required && value == null) {
throw new InvalidConfigurationException(context, "is required");
}
return value;
}
@Override
public Stream configuration() {
return configuration != null ? Stream.of(configuration) : Stream.empty();
}
}
class MultivalueContext extends Context {
private final List> values = new ArrayList<>();
private final boolean required;
public MultivalueContext(Context parent, String name, Xpp3Dom configuration, boolean required) {
super(parent, name, configuration);
this.required = required;
}
@Override
public void accept(Value> value) {
this.values.add(value);
}
@Override
public List> values() {
if (values.isEmpty()) {
if (required) {
throw new InvalidConfigurationException(context, "is required");
}
return null;
}
return values;
}
@Override
public Value> value() {
throw new UnsupportedOperationException();
}
@Override
public Stream configuration() {
return configuration != null ? Stream.of(configuration.getChildren()) : Stream.empty();
}
public SingletonContext elementSubcontext(Xpp3Dom configuration) {
String name = "[" + values.size() + "]";
return new SingletonContext(this, name, configuration, required);
}
}
static class ResourceSelection {
final B bucket;
final Path location;
final List includes;
final List excludes;
public ResourceSelection(B bucket, Path location) {
this(bucket, location, null, null);
}
public ResourceSelection(B bucket, Path location, List includes,
List excludes) {
this.bucket = bucket;
this.location = location;
this.includes = includes;
this.excludes = excludes;
}
}
abstract class ResourceSelector, B> {
final String xmlMultivalue;
final String xmlLocationPlural;
final String xmlLocationSingular;
final AbstractResourceSelectionParameter parameter;
protected ResourceSelector(String multivalue, String locationPlural, String locationSingular,
AbstractResourceSelectionParameter parameter) {
this.xmlMultivalue = multivalue;
this.xmlLocationPlural = locationPlural;
this.xmlLocationSingular = locationSingular;
this.parameter = parameter;
}
abstract Map.Entry evaluateLocation(Xpp3Dom location) throws UncheckedIOException;
abstract Map.Entry evaluateLocation(String location) throws UncheckedIOException;
abstract V newBucketValue(ResourceSelection selection, List paths);
abstract Value> newCollectionResourceValue(TypeAdapter type,
Map, List> resources, TypeAdapter parameterType);
protected boolean shouldAllowSourceRoots() {
return false;
}
final void select() {
List> selections = null;
if (parameter.value().length > 0) {
selections = parseLocations(parameter.value());
}
if (selections == null) {
selections = evaluateConfiguration();
}
if (selections == null && parameter.defaultValue().length > 0) {
selections = parseLocations(parameter.defaultValue());
}
if (selections == null || selections.isEmpty()) {
return;
}
Map, List> resources = new LinkedHashMap<>();
for (ResourceSelection selection : selections) {
List paths = select(selection.location, selection.includes, selection.excludes);
if (!parameter.required() || !paths.isEmpty()) {
resources.put(selection, paths);
}
}
if (resources.isEmpty()) {
return;
}
TypeAdapter type = parameter.originatingElement().getType();
if (type.isArray() || type.isIterable()) {
TypeAdapter parameterType = parameter.type();
if (parameterType.isSameType(URL.class) || parameterType.isSameType(File.class)
|| parameterType.isSameType(Path.class)) {
context.accept(newCollectionResourceValue(type, resources, parameterType));
} else {
context.accept(newCollectionValue(type, toListBucketValue(resources)));
}
} else if (resources.size() == 1) {
Map.Entry, List> entry = resources.entrySet().iterator().next();
context.accept(newBucketValue(entry.getKey(), entry.getValue()));
} else {
throw new InvalidConfigurationException(context, "too many values");
}
}
private List> toListBucketValue(Map, List> resources) {
return resources.entrySet().stream() //
.map(e -> newBucketValue(e.getKey(), e.getValue())) //
.collect(Collectors.toList());
}
final String relativePath(Path basedir, Path path) {
return basedir.relativize(path).toString();
}
private List select(Path basedir, List includes, List excludes) {
if (includes != null && includes.isEmpty()) {
includes = null;
}
if (excludes != null && excludes.isEmpty()) {
excludes = null;
}
if (workspace.isRegularFile(basedir)) {
if (this instanceof InputFileSelector) {
throw new InvalidConfigurationException(context, basedir + " is a regular file");
}
// TODO resource delta support, see takari BuildContext registerAndProcessInputs
FileMatcher matcher = FileMatcher.absoluteMatcher(Paths.get("/"), includes, excludes);
return selectFromJar(basedir, matcher);
} else if (workspace.isDirectory(basedir)) {
// TODO resource delta support, see takari BuildContext registerAndProcessInputs
return selectFromDirectory(FileMatcher.subdirMatchers(basedir, includes, excludes));
} else {
return Collections.emptyList();
}
}
private List selectFromJar(Path basedir, FileMatcher matcher) {
try (JarFile jarFile = new JarFile(basedir.toFile())) {
Enumeration entries = jarFile.entries();
List matchedPaths = new ArrayList<>();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (!entry.isDirectory()) {
if (matcher.matches("/" + entry.getName())) {
matchedPaths.add(Paths.get(entry.getName()));
}
}
}
return matchedPaths;
} catch (IOException e) {
throw new InvalidConfigurationException(context, "could not list jar entries", e);
}
}
private List selectFromDirectory(Map matchers) {
List resources = new ArrayList<>();
matchers.forEach((subdir, matcher) -> {
try (Stream paths = workspace.walk(subdir)) {
paths //
.filter(path -> matcher.matches(path)) //
.forEach(resources::add);
} catch (IOException e) {
throw new InvalidConfigurationException(context, "could not list directory files", e);
}
});
return resources;
}
List> evaluateConfiguration() throws UncheckedIOException {
Xpp3Dom configuration = context.configuration;
if (configuration == null) {
return null;
}
Xpp3Dom[] multivalue = configuration.getChildren(xmlMultivalue);
if (multivalue.length > 0) {
// TODO validate no other elements
// TODO validate no value
List> buckets = new ArrayList<>();
for (Xpp3Dom files : multivalue) {
buckets.addAll(evaluateConfiguration(files, true));
}
return buckets;
}
return evaluateConfiguration(configuration, false);
}
final List> evaluateConfiguration(Xpp3Dom configuration,
boolean required) {
List includes = includes(configuration);
List excludes = excludes(configuration);
if (configuration.getChildCount() == 0 && !isEmpty(configuration.getValue())) {
Map.Entry e = evaluateLocation(configuration.getValue());
return Collections
.singletonList(new ResourceSelection<>(e.getKey(), e.getValue(), includes, excludes));
}
Xpp3Dom[] locations = getXmlList(configuration, xmlLocationPlural, xmlLocationSingular);
if (locations != null && locations.length > 0) {
return Stream.of(locations) //
.map(this::evaluateLocation) //
.map(p -> new ResourceSelection<>(p.getKey(), p.getValue(), includes, excludes)) //
.collect(Collectors.toList());
}
if (required) {
throw new InvalidConfigurationException(context,
"<" + xmlLocationSingular + "> element is required");
}
return null;
}
final List> parseLocations(String[] locations) {
List includes = includes(context.configuration);
List excludes = excludes(context.configuration);
return context.evaluator().withValue(locations).asStrings(shouldAllowSourceRoots()) //
.map(this::evaluateLocation) //
.map(f -> new ResourceSelection<>(f.getKey(), f.getValue(), includes, excludes)) //
.collect(Collectors.toList());
}
private List evaluate(String[] strings) {
return context.evaluator() //
.withValue(strings) //
.asStrings().collect(Collectors.toList());
}
/**
* Returns elements of either nested or flat xml element list.
*/
final Xpp3Dom[] getXmlList(Xpp3Dom element, String plural, String single) {
if (element == null) {
return null;
}
Xpp3Dom[] list = element.getChildren(plural);
if (list.length > 1) {
throw new InvalidConfigurationException(context, "only one <" + plural + "> is allowed");
}
Xpp3Dom[] flatlist = element.getChildren(single);
if (flatlist.length > 0 && list.length > 0) {
throw new InvalidConfigurationException(context,
"Use <" + plural + "> or <" + single + ">");
}
if (list.length > 0) {
flatlist = list[0].getChildren(single);
}
// TODO validate list has no other elements
// TODO validate list has no value
return flatlist;
}
/**
* Evaluates and returns string list from three configuration sources
*
* - list of hardcoded values
*
- xml configuration in either nested or flat list format
*
- list of defaut values
*
*/
final List mergeStringList(String[] hardcoded, String[] defaults, Xpp3Dom xml,
String xmlPlural, String xmlSingle) {
if (hardcoded.length > 0) {
// TODO validate no configuration
// TODO validate no defaultIncludes
return evaluate(hardcoded);
}
Xpp3Dom[] flatlist = getXmlList(xml, xmlPlural, xmlSingle);
if (flatlist != null && flatlist.length > 0) {
// TODO validate configuration has no value
return Stream.of(flatlist) //
.map(x -> context.evaluator().evaluate(x.getValue())) //
.collect(Collectors.toList());
}
if (defaults.length > 0) {
return evaluate(defaults);
}
return null;
}
final List includes(Xpp3Dom configuration) {
List includes = mergeStringList(parameter.includes(), parameter.defaultIncludes(),
configuration, XML_CONFIG_INCLUDES, XML_CONFIG_INCLUDE);
if (includes == null || includes.isEmpty()) {
throw new InvalidConfigurationException(context, " is required");
}
return includes;
}
final List excludes(Xpp3Dom configuration) {
return mergeStringList(parameter.excludes(), parameter.defaultExcludes(), configuration,
XML_CONFIG_EXCLUDES, XML_CONFIG_EXCLUDE);
}
final boolean containsOnly(Xpp3Dom element, String... children) {
boolean contains = false;
Set invalid = new LinkedHashSet<>();
for (Xpp3Dom child : element.getChildren()) {
if (Stream.of(children).anyMatch(n -> n.equals(child.getName()))) {
contains = true;
} else {
invalid.add(child.getName());
}
}
if (contains && !invalid.isEmpty()) {
throw new InvalidConfigurationException(context,
"invalid configuration elements: " + invalid);
}
return contains;
}
}
class InputFileSelector extends ResourceSelector {
public InputFileSelector(InputDirectoryFilesParameter parameter) {
super("files", "locations", "location", parameter);
}
@Override
protected boolean shouldAllowSourceRoots() {
return true;
}
@Override
SimpleEntry evaluateLocation(String location) throws UncheckedIOException {
Path path = context.evaluator().toPath(location);
return new SimpleEntry<>(path, path);
}
@Override
SimpleEntry evaluateLocation(Xpp3Dom location) throws UncheckedIOException {
if (location.getChildCount() > 0) {
throw new InvalidConfigurationException(context, "only value is allowed");
}
String value = location.getValue();
if (value.equals(SOURCE_ROOTS_EXPR) || value.equals(TEST_SOURCE_ROOTS_EXPR)) {
throw new InvalidConfigurationException(context,
String.format("%s expression is not allowed in configuration", value));
}
return evaluateLocation(location.getValue());
}
@Override
InputDirectoryValue newBucketValue(ResourceSelection selection, List paths) {
Class> type = ((ReflectionType) parameter.type()).adaptee();
Path basedir = selection.bucket;
List includes = selection.includes;
List excludes = selection.excludes;
TreeSet files = new TreeSet<>();
TreeSet filenames = new TreeSet<>();
if (paths != null) {
paths.forEach(path -> {
files.add(path);
filenames.add(relativePath(selection.location, path));
});
}
return new InputDirectoryValue(type, basedir, includes, excludes, files, filenames);
}
@Override
Value> newCollectionResourceValue(TypeAdapter type,
Map, List> resources, TypeAdapter parameterType) {
List files = resources.values().stream() //
.flatMap(list -> list.stream()) //
.collect(Collectors.toList());
return new InputFilesValue(((ReflectionType) type).multivalueFactory(), files,
((ReflectionType) parameterType));
}
}
static class ArtifactLocation {
final IArtifactMetadata metadata;
/**
* The artifact jar file or {@code null} for exploded directory-based artifacts.
*/
final Path jar;
public ArtifactLocation(IArtifactMetadata artifact, Path jar) {
this.metadata = artifact;
this.jar = jar;
}
}
class BaseArtifactResourceSelector
extends ResourceSelector {
public BaseArtifactResourceSelector(String multivalue, String locationPlural,
String locationSingular, AbstractResourceSelectionParameter parameter) {
super(multivalue, locationPlural, locationSingular, parameter);
}
private String evaluate(Xpp3Dom element, String childName, boolean required) {
Xpp3Dom child = element.getChild(childName);
if (child == null) {
if (required) {
throw new InvalidConfigurationException(context, "<" + childName + "> is required");
}
return null;
}
if (child.getChildCount() > 0) {
throw new InvalidConfigurationException(context,
"<" + childName + "> must not have child elements");
}
if (isEmpty(child.getValue())) {
if (required) {
throw new InvalidConfigurationException(context, "<" + childName + "> value is required");
}
return null;
}
return context.evaluator().evaluate(child.getValue());
}
@Override
Map.Entry evaluateLocation(Xpp3Dom location)
throws UncheckedIOException {
if (location.getChildCount() > 0 && !isEmpty(location.getValue())) {
throw new InvalidConfigurationException(context, "both elements and value are not allowed");
}
if (!isEmpty(location.getValue())) {
return evaluateLocation(location.getValue());
}
// TODO validate no other elements
String groupId = evaluate(location, "groupId", true);
String artifactId = evaluate(location, "artifactId", true);
String classifier = evaluate(location, "classifier", false);
return evaluateLocation(groupId, artifactId, classifier);
}
private Map.Entry evaluateLocation(String groupId, String artifactId,
String classifier) {
// TODO Use artifact resolver instead of a dependency resolver to support any artifacts not
// just
// dependency artifacts.
Map.Entry dependency =
dependencyResolver.getProjectDependency(groupId, artifactId, classifier);
if (dependency == null || dependency.getValue() == null
|| !(workspace.isRegularFile(dependency.getValue())
|| workspace.isDirectory(dependency.getValue()))) {
String gac = groupId + ":" + artifactId;
if (classifier != null) {
gac = gac + ":" + classifier;
}
throw new InvalidConfigurationException(context, "dependency " + gac + " does not exist");
}
return toArtifactLocation(dependency);
}
protected Map.Entry toArtifactLocation(
Map.Entry dependency) {
Path location = dependency.getValue();
IArtifactMetadata metadata = dependency.getKey();
ArtifactLocation artifact =
new ArtifactLocation(metadata, workspace.isRegularFile(location) ? location : null);
return new SimpleEntry<>(artifact, location);
}
@Override
Map.Entry evaluateLocation(String location)
throws UncheckedIOException {
StringTokenizer st = new StringTokenizer(location, ":");
String groupId = st.nextToken();
String artifactId = st.nextToken();
String classifier = st.hasMoreTokens() ? st.nextToken() : null;
return evaluateLocation(groupId, artifactId, classifier);
}
@Override
Value> newCollectionResourceValue(TypeAdapter type,
Map, List> resources, TypeAdapter parameterType) {
List values = resources.entrySet().stream() //
.map(e -> newBucketValue(e.getKey(), e.getValue())) //
.collect(Collectors.toList());
return new ListArtifactResourcesValue(((ReflectionType) type).multivalueFactory(), values);
}
@Override
ArtifactResourcesValue newBucketValue(ResourceSelection selection,
List paths) {
// notes on file handle leaks and performance
// * each FileSystems.newFileSystem(jar) creates new open file handle (OSX, Java 1.8.0_102)
// * need to explicitly close open filesystems to avoid file handle leak
// * it is not possible to use Path from a closed filesystem
// to avoid resource leaks, the filesystem instances are closed as soon as possible and the
// clients will effectively get jar:file URL instances
// by default JDK caches URLConnection, so performance will be comparable with
// open filesystem access (reading all junit-4.12.jar entries 1000 times using filesystem
// takes ~5.5 seconds, using URL ~6.8 seconds).
// if URLConnection cache is disabled, reading all junit-4.12.jar entries 1000 times takes
// ~22.2 seconds. this is still acceptable assuming only a small number of jar file entries
// will be used through URL API.
try {
ArtifactLocation artifact = selection.bucket;
List urls = new ArrayList<>();
for (Path resource : paths) {
URL url;
String relpath;
if (workspace.isRegularFile(selection.location)) {
relpath = resource.toString();
url = (new URI("jar:" + selection.location.toUri() + "!/" + relpath)).toURL();
} else {
relpath = relativePath(selection.location, resource);
url = resource.toUri().toURL();
}
urls.add(ArtifactResourceURLStreamHandler.newURL(artifact.metadata, relpath, url));
}
Set files = new TreeSet<>();
if (artifact.jar != null) {
files.add(artifact.jar);
} else {
paths.stream().forEach(p -> files.add(p));
}
return new ArtifactResourcesValue(files, artifact.metadata, urls);
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
class DependencyResourceSelector extends BaseArtifactResourceSelector {
public DependencyResourceSelector(AbstractResourceSelectionParameter parameter) {
super("resources", "dependencies", "dependency", parameter);
}
@Override
List> evaluateConfiguration() throws UncheckedIOException {
List> buckets = super.evaluateConfiguration();
if (buckets == null) {
List includes = includes(context.configuration);
List excludes = excludes(context.configuration);
buckets = dependencyResolver.getProjectDependencies(true).entrySet().stream() //
.map(this::toArtifactLocation) //
.map(e -> new ResourceSelection<>(e.getKey(), e.getValue(), includes, excludes)) //
.collect(Collectors.toList());
}
return buckets;
}
}
class ArtifactResourceSelector extends BaseArtifactResourceSelector {
public ArtifactResourceSelector(ArtifactResourcesParameter parameter) {
super("resources", "artifacts", "artifact", parameter);
}
}
@Override
public boolean enterMultivalue(MultivalueParameter metadata) {
context = context.multivalueSubcontext(metadata.elements.required());
if (context.configuration == null) {
metadata.elements.accept(this);
} else {
Xpp3Dom configuration = context.configuration;
// TODO validate configuration does not have value
// TODO validate configuration has children
for (Xpp3Dom subConfiguration : configuration.getChildren()) {
context = ((MultivalueContext) context).elementSubcontext(subConfiguration);
metadata.elements.accept(this);
Value> element = context.value();
context = context.parent;
if (element != null) {
context.accept(element);
}
}
}
List> elements = context.values();
context = context.parent;
if (elements != null && !elements.isEmpty()) {
context.accept(newCollectionValue(metadata.type(), elements));
}
return false;
}
private CollectionValue newCollectionValue(TypeAdapter type, List> elements) {
return new CollectionValue(((ReflectionType) type).multivalueFactory(), elements);
}
@Override
public void visitUnsupportedCollection(UnsupportedCollectionParameter metadata) {
throw new IllegalArgumentException(); // should have been reported during validation
}
@SuppressWarnings("unchecked")
@Override
public void visitMap(MapParameter metadata) {
if (context.configuration == null) {
if (metadata.required()) {
throw new InvalidConfigurationException(context,
"configuration is required for a Map parameter");
}
return;
}
if (!isEmpty(context.configuration.getValue())) {
throw new InvalidConfigurationException(context, "configuration value is not allowed");
}
if (context.configuration.getChildCount() < 1) {
throw new InvalidConfigurationException(context, "configuration requires at least one child");
}
Xpp3Dom configuration = context.configuration;
Map elements = new LinkedHashMap<>();
for (Xpp3Dom subConfiguration : configuration.getChildren()) {
elements.put(subConfiguration.getName(), subConfiguration.getValue());
}
if (!elements.isEmpty()) {
Class> type = ((ReflectionType) metadata.type()).adaptee();
Function converter =
SimpleParameter.getConverter(metadata.originatingElement().getParameterTypes().get(1));
InstanceFactory
© 2015 - 2025 Weber Informatics LLC | Privacy Policy