DefaultSourceRoot.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.impl;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.apache.maven.api.Language;
import org.apache.maven.api.ProjectScope;
import org.apache.maven.api.Session;
import org.apache.maven.api.SourceRoot;
import org.apache.maven.api.Version;
import org.apache.maven.api.model.Resource;
import org.apache.maven.api.model.Source;
/**
* A default implementation of {@code SourceRoot} built from the model.
*/
public final class DefaultSourceRoot implements SourceRoot {
private final Path directory;
private final List<String> includes;
private final List<String> excludes;
private final ProjectScope scope;
private final Language language;
private final String moduleName;
private final Version targetVersion;
private final Path targetPath;
private final boolean stringFiltering;
private final boolean enabled;
/**
* Creates a new instance from the given model.
*
* @param session the session of resolving extensible enumerations
* @param baseDir the base directory for resolving relative paths
* @param source a source element from the model
*/
public DefaultSourceRoot(final Session session, final Path baseDir, final Source source) {
includes = source.getIncludes();
excludes = source.getExcludes();
stringFiltering = source.isStringFiltering();
enabled = source.isEnabled();
moduleName = nonBlank(source.getModule());
String value = nonBlank(source.getScope());
scope = (value != null) ? session.requireProjectScope(value) : ProjectScope.MAIN;
value = nonBlank(source.getLang());
language = (value != null) ? session.requireLanguage(value) : Language.JAVA_FAMILY;
value = nonBlank(source.getDirectory());
if (value != null) {
directory = baseDir.resolve(value);
} else {
Path src = baseDir.resolve("src");
if (moduleName != null) {
src = src.resolve(moduleName);
}
directory = src.resolve(scope.id()).resolve(language.id());
}
value = nonBlank(source.getTargetVersion());
targetVersion = (value != null) ? session.parseVersion(value) : null;
value = nonBlank(source.getTargetPath());
targetPath = (value != null) ? baseDir.resolve(value) : null;
}
/**
* Creates a new instance from the given resource.
* This is used for migration from the previous way of declaring resources.
*
* @param baseDir the base directory for resolving relative paths
* @param scope the scope of the resource (main or test)
* @param resource a resource element from the model
*/
public DefaultSourceRoot(final Path baseDir, ProjectScope scope, Resource resource) {
String value = nonBlank(resource.getDirectory());
if (value == null) {
throw new IllegalArgumentException("Source declaration without directory value.");
}
directory = baseDir.resolve(value).normalize();
includes = resource.getIncludes();
excludes = resource.getExcludes();
stringFiltering = Boolean.parseBoolean(resource.getFiltering());
enabled = true;
moduleName = null;
this.scope = scope;
language = Language.RESOURCES;
targetVersion = null;
value = nonBlank(resource.getTargetPath());
targetPath = (value != null) ? baseDir.resolve(value).normalize() : null;
}
/**
* Creates a new instance for the given directory and scope.
*
* @param scope scope of source code (main or test)
* @param language language of the source code
* @param directory directory of the source code
*/
public DefaultSourceRoot(final ProjectScope scope, final Language language, final Path directory) {
this.scope = Objects.requireNonNull(scope);
this.language = Objects.requireNonNull(language);
this.directory = Objects.requireNonNull(directory);
includes = List.of();
excludes = List.of();
moduleName = null;
targetVersion = null;
targetPath = null;
stringFiltering = false;
enabled = true;
}
/**
* Creates a new instance for the given directory and scope.
*
* @param scope scope of source code (main or test)
* @param language language of the source code
* @param directory directory of the source code
* @param includes patterns for the files to include, or {@code null} or empty if unspecified
* @param excludes patterns for the files to exclude, or {@code null} or empty if nothing to exclude
*/
public DefaultSourceRoot(
final ProjectScope scope,
final Language language,
final Path directory,
List<String> includes,
List<String> excludes) {
this.scope = Objects.requireNonNull(scope);
this.language = language;
this.directory = Objects.requireNonNull(directory);
this.includes = includes != null ? List.copyOf(includes) : List.of();
this.excludes = excludes != null ? List.copyOf(excludes) : List.of();
moduleName = null;
targetVersion = null;
targetPath = null;
stringFiltering = false;
enabled = true;
}
/**
* {@return the given value as a trimmed non-blank string, or null otherwise}.
*/
private static String nonBlank(String value) {
if (value != null) {
value = value.trim();
if (value.isBlank()) {
value = null;
}
}
return value;
}
/**
* {@return the root directory where the sources are stored}.
*/
@Override
public Path directory() {
return directory;
}
/**
* {@return the patterns for the files to include}.
*/
@Override
@SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because unmodifiable
public List<String> includes() {
return includes;
}
/**
* {@return the patterns for the files to exclude}.
*/
@Override
@SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because unmodifiable
public List<String> excludes() {
return excludes;
}
/**
* {@return a matcher combining the include and exclude patterns}.
*
* @param defaultIncludes the default includes if unspecified by the user
* @param useDefaultExcludes whether to add the default set of patterns to exclude,
* mostly Source Code Management (<abbr>SCM</abbr>) files
*/
@Override
public PathMatcher matcher(Collection<String> defaultIncludes, boolean useDefaultExcludes) {
Collection<String> actual = includes();
if (actual == null || actual.isEmpty()) {
actual = defaultIncludes;
}
return PathSelector.of(directory(), actual, excludes(), useDefaultExcludes);
}
/**
* {@return in which context the source files will be used}.
*/
@Override
public ProjectScope scope() {
return scope;
}
/**
* {@return the language of the source files}.
*/
@Override
public Language language() {
return language;
}
/**
* {@return the name of the Java module (or other language-specific module) which is built by the sources}.
*/
@Override
public Optional<String> module() {
return Optional.ofNullable(moduleName);
}
/**
* {@return the version of the platform where the code will be executed}.
*/
@Override
public Optional<Version> targetVersion() {
return Optional.ofNullable(targetVersion);
}
/**
* {@return an explicit target path, overriding the default value}.
*/
@Override
public Optional<Path> targetPath() {
return Optional.ofNullable(targetPath);
}
/**
* {@return whether resources are filtered to replace tokens with parameterized values}.
*/
@Override
public boolean stringFiltering() {
return stringFiltering;
}
/**
* {@return whether the directory described by this source element should be included in the build}.
*/
@Override
public boolean enabled() {
return enabled;
}
/**
* {@return a hash code value computed from all properties}.
*/
@Override
public int hashCode() {
return Objects.hash(
directory,
includes,
excludes,
scope,
language,
moduleName,
targetVersion,
targetPath,
stringFiltering,
enabled);
}
/**
* {@return whether the two objects are of the same class with equal property values}.
*
* @param obj the other object to compare with this object, or {@code null}
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof DefaultSourceRoot other) {
return directory.equals(other.directory)
&& includes.equals(other.includes)
&& excludes.equals(other.excludes)
&& Objects.equals(scope, other.scope)
&& Objects.equals(language, other.language)
&& Objects.equals(moduleName, other.moduleName)
&& Objects.equals(targetVersion, other.targetVersion)
&& stringFiltering == other.stringFiltering
&& enabled == other.enabled;
}
return false;
}
}