[ https://issues.apache.org/jira/browse/MNG-8015?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17807623#comment-17807623 ]
ASF GitHub Bot commented on MNG-8015: ------------------------------------- gnodet commented on code in PR #1378: URL: https://github.com/apache/maven/pull/1378#discussion_r1453039589 ########## api/maven-api-core/src/main/java/org/apache/maven/api/DependencyProperties.java: ########## @@ -33,27 +36,226 @@ @Immutable public interface DependencyProperties { /** - * Boolean flag telling that dependency contains all of its dependencies. Value of this key should be parsed with - * {@link Boolean#parseBoolean(String)} to obtain value. + * Keys in the dependency properties map. + * Each key can be associated to values of a specific class. + * + * @param <V> type of value associated to the key + */ + class Key<V> { + /** + * The keys that are defined in this {@code DependencyProperties} map. + * Accesses to this map shall be synchronized on the map. + * + * @see #intern() + */ + private static final Map<String, Key<?>> INTERNS = new HashMap<>(); Review Comment: Can we use a `ConcurrentHashMap` rather than a `HashMap` and synchronizing access to it ? ########## api/maven-api-core/src/main/java/org/apache/maven/api/DependencyProperties.java: ########## @@ -33,27 +36,226 @@ @Immutable public interface DependencyProperties { /** - * Boolean flag telling that dependency contains all of its dependencies. Value of this key should be parsed with - * {@link Boolean#parseBoolean(String)} to obtain value. + * Keys in the dependency properties map. + * Each key can be associated to values of a specific class. + * + * @param <V> type of value associated to the key + */ + class Key<V> { + /** + * The keys that are defined in this {@code DependencyProperties} map. + * Accesses to this map shall be synchronized on the map. + * + * @see #intern() + */ + private static final Map<String, Key<?>> INTERNS = new HashMap<>(); + + /** + * Value returned by {@link #name()}. + */ + @Nonnull + private final String name; + + /** + * Value returned by {@link #valueType()}. + */ + @Nonnull + private final Class<V> valueType; + + /** + * Creates a new key. + * + * @param name name of the key + * @param valueType type of value associated to the key + */ + public Key(@Nonnull final String name, @Nonnull final Class<V> valueType) { + this.name = Objects.requireNonNull(name); + this.valueType = Objects.requireNonNull(valueType); + } + + /** + * If a key exists in the {@linkplain #intern() intern pool} for the given name, returns that key. + * Otherwise, if the {@code defaultType} is non-null, creates a key for values of the specified type. + * Otherwise, returns {@code null}. + * + * @param name name of the key to search or create + * @param defaultType value type of the key to create if none exist for the given name, or {@code null} + * @return key found or created, or {@code null} if no key was found and {@code defaultType} is null + * + * @see #intern() + */ + public static Key<?> forName(String name, Class<?> defaultType) { + Key<?> key; + synchronized (INTERNS) { + key = INTERNS.get(name); + } + if (key == null && defaultType != null) { + key = new Key<>(name, defaultType); + } + return key; + } + + /** + * {@return the name of the key}. + */ + @Nonnull + public String name() { + return name; + } + + /** + * {@return the type of value associated to the key}. + */ + @Nonnull + public Class<V> valueType() { + return valueType; + } + + /** + * {@return a canonical representation of this key}. A pool of keys, initially empty, is maintained privately. + * When the {@code intern()} method is invoked, if the pool already contains a key equal to this {@code Key} + * as determined by the {@link #equals(Object)} method, then the key from the pool is returned. Otherwise, + * if no key exist in the pool for this key {@linkplain #name() name}, then this {@code Key} object is added + * to the pool and {@code this} is returned. Otherwise an {@link IllegalStateException} is thrown. + * + * @throws IllegalStateException if a key exists in the pool for the same name but a different class of values. + * + * @see String#intern() + */ + @SuppressWarnings("unchecked") + public Key<V> intern() { + Key<?> previous; + synchronized (INTERNS) { + previous = INTERNS.putIfAbsent(name, this); + } + if (previous == null) { + return this; + } + if (equals(previous)) { + return (Key<V>) previous; + } + throw new IllegalStateException("Key " + name + " already exists for a different class of values."); + } + + /** + * {@return a string representation of this key}. + * By default, this is the name of this key. + */ + @Override + public String toString() { + return name; + } + + /** + * {@return an hash code value for this key}. + */ + @Override + public int hashCode() { + return 7 + name.hashCode() + 31 * valueType.hashCode(); Review Comment: `Objects.hash(name, valueType)` ########## api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java: ########## @@ -0,0 +1,282 @@ +/* + * 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.api; + +import java.io.File; +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import java.util.StringJoiner; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Nonnull; + +/** + * The option of a Java command-line tool where to place the paths to some dependencies. + * A {@code PathType} can identify the class-path, the module-path, the patches for a specific module, + * or another kind of path. This class is like an enumeration, except that it is extensible: + * plugins can define their own kinds of path. + * + * <p>One path type is handled in a special way: contrarily to other options, + * the paths specified in a {@code --patch-module} Java option is effective only for a specified module. + * This type is created by calls to {@link #patchModule(String)} and a new instance must be created for + * every module to patch.</p> + * + * <p>Path types are often exclusive. For example, a dependency should not be both on the Java class-path + * and on the Java module-path.</p> + * + * @see org.apache.maven.api.services.DependencyResolverResult#getDispatchedPaths() + * + * @since 4.0.0 + */ +@Experimental +public final class JavaPathType extends PathType { + /** + * The path identified by the Java {@code --class-path} option. + * Used for compilation, execution and Javadoc among others. + * + * <h4>Context-sensitive interpretation</h4> + * A dependency with this path type will not necessarily be placed on the class-path. + * There is two circumstances where the dependency may be nevertheless placed somewhere else: + * + * <ul> + * <li>If {@link #MODULES} path type is also set, then the dependency can be placed either on the + * class-path or on the module-path, but only one of those. The choice is up to the plugin, + * possibly using heuristic rules (Maven 3 behavior).</li> + * <li>If a {@link #patchModule(String)} is also set and the main JAR file was placed on the module-path, + * then the test dependency will be placed on the Java {@code --patch-module} option instead of the + * class-path.</li> + * </ul> + */ + public static final JavaPathType CLASSES = new JavaPathType("CLASSES", "--class-path", null); + + /** + * The path identified by the Java {@code --module-path} option. + * Used for compilation, execution and Javadoc among others. + * + * <h4>Context-sensitive interpretation</h4> + * A dependency with this flag will not necessarily be placed on the module-path. + * There is two circumstances where the dependency may be nevertheless placed somewhere else: + * + * <ul> + * <li>If {@link #CLASSES} path type is also set, then the dependency <em>should</em> be placed on the + * module-path, but is nevertheless compatible with placement on the class-path. Compatibility can Review Comment: `nevertheless` -> `also` ########## api/maven-api-core/src/main/java/org/apache/maven/api/DependencyProperties.java: ########## @@ -33,27 +36,226 @@ @Immutable public interface DependencyProperties { /** - * Boolean flag telling that dependency contains all of its dependencies. Value of this key should be parsed with - * {@link Boolean#parseBoolean(String)} to obtain value. + * Keys in the dependency properties map. + * Each key can be associated to values of a specific class. + * + * @param <V> type of value associated to the key + */ + class Key<V> { + /** + * The keys that are defined in this {@code DependencyProperties} map. + * Accesses to this map shall be synchronized on the map. + * + * @see #intern() + */ + private static final Map<String, Key<?>> INTERNS = new HashMap<>(); + + /** + * Value returned by {@link #name()}. + */ + @Nonnull + private final String name; + + /** + * Value returned by {@link #valueType()}. + */ + @Nonnull + private final Class<V> valueType; + + /** + * Creates a new key. + * + * @param name name of the key + * @param valueType type of value associated to the key + */ + public Key(@Nonnull final String name, @Nonnull final Class<V> valueType) { + this.name = Objects.requireNonNull(name); + this.valueType = Objects.requireNonNull(valueType); + } + + /** + * If a key exists in the {@linkplain #intern() intern pool} for the given name, returns that key. + * Otherwise, if the {@code defaultType} is non-null, creates a key for values of the specified type. + * Otherwise, returns {@code null}. + * + * @param name name of the key to search or create + * @param defaultType value type of the key to create if none exist for the given name, or {@code null} + * @return key found or created, or {@code null} if no key was found and {@code defaultType} is null + * + * @see #intern() + */ + public static Key<?> forName(String name, Class<?> defaultType) { + Key<?> key; + synchronized (INTERNS) { + key = INTERNS.get(name); + } + if (key == null && defaultType != null) { + key = new Key<>(name, defaultType); + } + return key; + } + + /** + * {@return the name of the key}. + */ + @Nonnull + public String name() { + return name; + } + + /** + * {@return the type of value associated to the key}. + */ + @Nonnull + public Class<V> valueType() { + return valueType; + } + + /** + * {@return a canonical representation of this key}. A pool of keys, initially empty, is maintained privately. + * When the {@code intern()} method is invoked, if the pool already contains a key equal to this {@code Key} + * as determined by the {@link #equals(Object)} method, then the key from the pool is returned. Otherwise, + * if no key exist in the pool for this key {@linkplain #name() name}, then this {@code Key} object is added + * to the pool and {@code this} is returned. Otherwise an {@link IllegalStateException} is thrown. + * + * @throws IllegalStateException if a key exists in the pool for the same name but a different class of values. + * + * @see String#intern() + */ + @SuppressWarnings("unchecked") + public Key<V> intern() { + Key<?> previous; + synchronized (INTERNS) { + previous = INTERNS.putIfAbsent(name, this); + } + if (previous == null) { + return this; + } + if (equals(previous)) { + return (Key<V>) previous; + } + throw new IllegalStateException("Key " + name + " already exists for a different class of values."); + } + + /** + * {@return a string representation of this key}. + * By default, this is the name of this key. + */ + @Override + public String toString() { + return name; + } + + /** + * {@return an hash code value for this key}. + */ + @Override + public int hashCode() { + return 7 + name.hashCode() + 31 * valueType.hashCode(); + } + + /** + * Compares this key with the given object for equality. + * Two keys are considered equal if they have the same name + * and are associated to values of the same class. + * + * @param obj the object to compare with this key + * @return whether the given object is equal to this key + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj != null && getClass() == obj.getClass()) { + final Key<?> other = (Key<?>) obj; + return name.equals(other.name) && valueType.equals(other.valueType); + } + return false; + } + } + + /** + * The dependency type. The {@linkplain Key#name() name} of this property + * is equal to the {@code ArtifactProperties.TYPE} value. + */ + Key<String> TYPE = new Key<>("type", String.class).intern(); + + /** + * The dependency language. The {@linkplain Key#name() name} of this property + * is equal to the {@code ArtifactProperties.LANGUAGE} value. + */ + Key<String> LANGUAGE = new Key<>("language", String.class).intern(); + + /** + * Types of path (class-path, module-path, …) where the dependency can be placed. + * For most deterministic builds, the array length should be 1. In such case, + * the dependency will be unconditionally placed on the specified type of path + * and no heuristic rule will be involved. + * + * <p>It is nevertheless common to specify two or more types of path. For example, + * a Java library may be compatible with either the class-path or the module-path, + * and the user may have provided no instruction about which type to use. In such + * case, the plugin may apply rules for choosing a path. See for example + * {@link JavaPathType#CLASSES} and {@link JavaPathType#MODULES}.</p> + */ + Key<PathType[]> PATH_TYPES = new Key<>("pathTypes", PathType[].class).intern(); + + /** + * Boolean flag telling that dependency contains all of its dependencies. * <p> * <em>Important: this flag must be kept in sync with resolver! (as is used during collection)</em> */ - String FLAG_INCLUDES_DEPENDENCIES = "includesDependencies"; + Key<Boolean> FLAG_INCLUDES_DEPENDENCIES = new Key<>("includesDependencies", Boolean.class).intern(); /** - * Boolean flag telling that dependency is meant to be placed on class path. Value of this key should be parsed with - * {@link Boolean#parseBoolean(String)} to obtain value. + * Boolean flag telling that dependency is meant to be placed on class path. + * + * @deprecated Use {@link #PATH_TYPES} and {@link JavaPathType#CLASSES} instead. */ - String FLAG_CLASS_PATH_CONSTITUENT = "classPathConstituent"; + @Deprecated + Key<Boolean> FLAG_CLASS_PATH_CONSTITUENT = new Key<>("classPathConstituent", Boolean.class).intern(); /** - * Returns immutable "map view" of all the properties. + * {@return the keys of all properties in this map}. */ - @Nonnull - Map<String, String> asMap(); + Set<Key<?>> keys(); + + /** + * Returns the value associated to the given key. + * + * @param <V> type of value to get + * @param key key of the value to get + * @return value associated to the given key, or {@code null} if none + */ + <V> V get(@Nonnull Key<V> key); + + /** + * Returns the value associated to the given key, or the given default value if none. + * + * @param <V> type of value to get + * @param key key of the value to get + * @param defaultValue the value to return is none is associated to the given key, or {@code null} + * @return value associated to the given key, or {@code null} if none and the default is null + */ + <V> V getOrDefault(@Nonnull Key<V> key, V defaultValue); Review Comment: Missing `@Nullable` on the return value and the `V` parameter to be explicit . ########## api/maven-api-core/src/main/java/org/apache/maven/api/DependencyProperties.java: ########## @@ -33,27 +36,226 @@ @Immutable public interface DependencyProperties { /** - * Boolean flag telling that dependency contains all of its dependencies. Value of this key should be parsed with - * {@link Boolean#parseBoolean(String)} to obtain value. + * Keys in the dependency properties map. + * Each key can be associated to values of a specific class. + * + * @param <V> type of value associated to the key + */ + class Key<V> { + /** + * The keys that are defined in this {@code DependencyProperties} map. + * Accesses to this map shall be synchronized on the map. + * + * @see #intern() + */ + private static final Map<String, Key<?>> INTERNS = new HashMap<>(); + + /** + * Value returned by {@link #name()}. + */ + @Nonnull + private final String name; + + /** + * Value returned by {@link #valueType()}. + */ + @Nonnull + private final Class<V> valueType; + + /** + * Creates a new key. + * + * @param name name of the key + * @param valueType type of value associated to the key + */ + public Key(@Nonnull final String name, @Nonnull final Class<V> valueType) { Review Comment: `final` in parameters is useless, please remove ########## api/maven-api-core/src/main/java/org/apache/maven/api/DependencyProperties.java: ########## @@ -33,27 +36,226 @@ @Immutable public interface DependencyProperties { /** - * Boolean flag telling that dependency contains all of its dependencies. Value of this key should be parsed with - * {@link Boolean#parseBoolean(String)} to obtain value. + * Keys in the dependency properties map. + * Each key can be associated to values of a specific class. + * + * @param <V> type of value associated to the key + */ + class Key<V> { + /** + * The keys that are defined in this {@code DependencyProperties} map. + * Accesses to this map shall be synchronized on the map. + * + * @see #intern() + */ + private static final Map<String, Key<?>> INTERNS = new HashMap<>(); + + /** + * Value returned by {@link #name()}. + */ + @Nonnull + private final String name; + + /** + * Value returned by {@link #valueType()}. + */ + @Nonnull + private final Class<V> valueType; + + /** + * Creates a new key. + * + * @param name name of the key + * @param valueType type of value associated to the key + */ + public Key(@Nonnull final String name, @Nonnull final Class<V> valueType) { + this.name = Objects.requireNonNull(name); + this.valueType = Objects.requireNonNull(valueType); + } + + /** + * If a key exists in the {@linkplain #intern() intern pool} for the given name, returns that key. + * Otherwise, if the {@code defaultType} is non-null, creates a key for values of the specified type. + * Otherwise, returns {@code null}. + * + * @param name name of the key to search or create + * @param defaultType value type of the key to create if none exist for the given name, or {@code null} + * @return key found or created, or {@code null} if no key was found and {@code defaultType} is null + * + * @see #intern() + */ + public static Key<?> forName(String name, Class<?> defaultType) { + Key<?> key; + synchronized (INTERNS) { + key = INTERNS.get(name); + } + if (key == null && defaultType != null) { + key = new Key<>(name, defaultType); + } + return key; + } + + /** + * {@return the name of the key}. + */ + @Nonnull + public String name() { + return name; + } + + /** + * {@return the type of value associated to the key}. + */ + @Nonnull + public Class<V> valueType() { + return valueType; + } + + /** + * {@return a canonical representation of this key}. A pool of keys, initially empty, is maintained privately. + * When the {@code intern()} method is invoked, if the pool already contains a key equal to this {@code Key} + * as determined by the {@link #equals(Object)} method, then the key from the pool is returned. Otherwise, + * if no key exist in the pool for this key {@linkplain #name() name}, then this {@code Key} object is added + * to the pool and {@code this} is returned. Otherwise an {@link IllegalStateException} is thrown. + * + * @throws IllegalStateException if a key exists in the pool for the same name but a different class of values. + * + * @see String#intern() + */ + @SuppressWarnings("unchecked") + public Key<V> intern() { Review Comment: I don't think the whole `intern` thing is needed at all. I would suppose all references to `Key`s will be done using `DependencyProperties.XXX`, so I don't see a good use case for users to create duplicate instances. ########## api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java: ########## @@ -0,0 +1,282 @@ +/* + * 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.api; + +import java.io.File; +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import java.util.StringJoiner; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Nonnull; + +/** + * The option of a Java command-line tool where to place the paths to some dependencies. + * A {@code PathType} can identify the class-path, the module-path, the patches for a specific module, + * or another kind of path. This class is like an enumeration, except that it is extensible: + * plugins can define their own kinds of path. + * + * <p>One path type is handled in a special way: contrarily to other options, + * the paths specified in a {@code --patch-module} Java option is effective only for a specified module. + * This type is created by calls to {@link #patchModule(String)} and a new instance must be created for + * every module to patch.</p> + * + * <p>Path types are often exclusive. For example, a dependency should not be both on the Java class-path + * and on the Java module-path.</p> + * + * @see org.apache.maven.api.services.DependencyResolverResult#getDispatchedPaths() + * + * @since 4.0.0 + */ +@Experimental +public final class JavaPathType extends PathType { + /** + * The path identified by the Java {@code --class-path} option. + * Used for compilation, execution and Javadoc among others. + * + * <h4>Context-sensitive interpretation</h4> + * A dependency with this path type will not necessarily be placed on the class-path. + * There is two circumstances where the dependency may be nevertheless placed somewhere else: Review Comment: `There is` -> `There are` `be nevertheless` -> `nevertheless be` ########## api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java: ########## @@ -0,0 +1,282 @@ +/* + * 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.api; + +import java.io.File; +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import java.util.StringJoiner; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Nonnull; + +/** + * The option of a Java command-line tool where to place the paths to some dependencies. + * A {@code PathType} can identify the class-path, the module-path, the patches for a specific module, + * or another kind of path. This class is like an enumeration, except that it is extensible: + * plugins can define their own kinds of path. + * + * <p>One path type is handled in a special way: contrarily to other options, + * the paths specified in a {@code --patch-module} Java option is effective only for a specified module. + * This type is created by calls to {@link #patchModule(String)} and a new instance must be created for + * every module to patch.</p> + * + * <p>Path types are often exclusive. For example, a dependency should not be both on the Java class-path + * and on the Java module-path.</p> + * + * @see org.apache.maven.api.services.DependencyResolverResult#getDispatchedPaths() + * + * @since 4.0.0 + */ +@Experimental +public final class JavaPathType extends PathType { + /** + * The path identified by the Java {@code --class-path} option. + * Used for compilation, execution and Javadoc among others. + * + * <h4>Context-sensitive interpretation</h4> + * A dependency with this path type will not necessarily be placed on the class-path. + * There is two circumstances where the dependency may be nevertheless placed somewhere else: + * + * <ul> + * <li>If {@link #MODULES} path type is also set, then the dependency can be placed either on the + * class-path or on the module-path, but only one of those. The choice is up to the plugin, + * possibly using heuristic rules (Maven 3 behavior).</li> + * <li>If a {@link #patchModule(String)} is also set and the main JAR file was placed on the module-path, Review Comment: `was placed` -> `is placed` ########## api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java: ########## @@ -0,0 +1,282 @@ +/* + * 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.api; + +import java.io.File; +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import java.util.StringJoiner; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Nonnull; + +/** + * The option of a Java command-line tool where to place the paths to some dependencies. + * A {@code PathType} can identify the class-path, the module-path, the patches for a specific module, + * or another kind of path. This class is like an enumeration, except that it is extensible: + * plugins can define their own kinds of path. + * + * <p>One path type is handled in a special way: contrarily to other options, Review Comment: `contrarily to` -> `unlike` ########## api/maven-api-core/src/main/java/org/apache/maven/api/Type.java: ########## @@ -80,11 +129,16 @@ public interface Type { String getClassifier(); /** - * Specifies if the artifact contains java classes and should be - * added to the classpath. + * Specifies if the artifact contains java classes and can be added to the classpath. + * Whether the artifact <em>should</em> be added to the classpath depends on other + * {@linkplain #getDependencyProperties() dependency properties}. + * + * @return if the artifact <em>can</em> be added to the class path * - * @return if the artifact should be added to the class path + * @deprecated A value of {@code true} does not mean that the dependency <em>should</em> + * be placed on the classpath. See {@link JavaPathType} instead for better analysis. */ + @Deprecated Review Comment: If deprecation is necessary, it should be noted that it will be removed before 4.0.0 GA. ########## api/maven-api-core/src/main/java/org/apache/maven/api/PathType.java: ########## @@ -0,0 +1,143 @@ +/* + * 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.api; + +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Nonnull; + +/** + * The option of a command-line tool where to place the paths to some dependencies. + * A {@code PathType} can identify the Java class-path, the Java module-path, + * or another kind of path for another programming language for example. + * This class is like an enumeration, except that it is extensible: + * plugins can define their own kinds of path. + * + * <p>Path types are often exclusive. For example, a dependency should not be both + * on the Java class-path and on the Java module-path.</p> + * + * @see DependencyProperties#PATH_TYPES + * @see org.apache.maven.api.services.DependencyResolverResult#getDispatchedPaths() + * + * @since 4.0.0 + */ +@Experimental +public abstract class PathType { Review Comment: I'm not convinced we will ever have an implementation other than `JavaPathType`, in which case, both interfaces could be merged. I'd also prefer an interface rather than an abstract class if that's possible. ########## api/maven-api-core/src/main/java/org/apache/maven/api/DependencyProperties.java: ########## @@ -33,27 +36,226 @@ @Immutable public interface DependencyProperties { /** - * Boolean flag telling that dependency contains all of its dependencies. Value of this key should be parsed with - * {@link Boolean#parseBoolean(String)} to obtain value. + * Keys in the dependency properties map. + * Each key can be associated to values of a specific class. + * + * @param <V> type of value associated to the key + */ + class Key<V> { + /** + * The keys that are defined in this {@code DependencyProperties} map. + * Accesses to this map shall be synchronized on the map. + * + * @see #intern() + */ + private static final Map<String, Key<?>> INTERNS = new HashMap<>(); + + /** + * Value returned by {@link #name()}. + */ + @Nonnull + private final String name; + + /** + * Value returned by {@link #valueType()}. + */ + @Nonnull + private final Class<V> valueType; + + /** + * Creates a new key. + * + * @param name name of the key + * @param valueType type of value associated to the key + */ + public Key(@Nonnull final String name, @Nonnull final Class<V> valueType) { + this.name = Objects.requireNonNull(name); + this.valueType = Objects.requireNonNull(valueType); + } + + /** + * If a key exists in the {@linkplain #intern() intern pool} for the given name, returns that key. + * Otherwise, if the {@code defaultType} is non-null, creates a key for values of the specified type. + * Otherwise, returns {@code null}. + * + * @param name name of the key to search or create + * @param defaultType value type of the key to create if none exist for the given name, or {@code null} + * @return key found or created, or {@code null} if no key was found and {@code defaultType} is null + * + * @see #intern() + */ + public static Key<?> forName(String name, Class<?> defaultType) { + Key<?> key; + synchronized (INTERNS) { + key = INTERNS.get(name); + } + if (key == null && defaultType != null) { + key = new Key<>(name, defaultType); + } + return key; + } + + /** + * {@return the name of the key}. + */ + @Nonnull + public String name() { + return name; + } + + /** + * {@return the type of value associated to the key}. + */ + @Nonnull + public Class<V> valueType() { + return valueType; + } + + /** + * {@return a canonical representation of this key}. A pool of keys, initially empty, is maintained privately. + * When the {@code intern()} method is invoked, if the pool already contains a key equal to this {@code Key} + * as determined by the {@link #equals(Object)} method, then the key from the pool is returned. Otherwise, + * if no key exist in the pool for this key {@linkplain #name() name}, then this {@code Key} object is added + * to the pool and {@code this} is returned. Otherwise an {@link IllegalStateException} is thrown. + * + * @throws IllegalStateException if a key exists in the pool for the same name but a different class of values. + * + * @see String#intern() + */ + @SuppressWarnings("unchecked") + public Key<V> intern() { + Key<?> previous; + synchronized (INTERNS) { + previous = INTERNS.putIfAbsent(name, this); + } + if (previous == null) { + return this; + } + if (equals(previous)) { + return (Key<V>) previous; + } + throw new IllegalStateException("Key " + name + " already exists for a different class of values."); + } + + /** + * {@return a string representation of this key}. + * By default, this is the name of this key. + */ + @Override + public String toString() { + return name; + } + + /** + * {@return an hash code value for this key}. + */ + @Override + public int hashCode() { + return 7 + name.hashCode() + 31 * valueType.hashCode(); + } + + /** + * Compares this key with the given object for equality. + * Two keys are considered equal if they have the same name + * and are associated to values of the same class. + * + * @param obj the object to compare with this key + * @return whether the given object is equal to this key + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj != null && getClass() == obj.getClass()) { + final Key<?> other = (Key<?>) obj; + return name.equals(other.name) && valueType.equals(other.valueType); + } + return false; + } + } + + /** + * The dependency type. The {@linkplain Key#name() name} of this property + * is equal to the {@code ArtifactProperties.TYPE} value. + */ + Key<String> TYPE = new Key<>("type", String.class).intern(); + + /** + * The dependency language. The {@linkplain Key#name() name} of this property + * is equal to the {@code ArtifactProperties.LANGUAGE} value. + */ + Key<String> LANGUAGE = new Key<>("language", String.class).intern(); + + /** + * Types of path (class-path, module-path, …) where the dependency can be placed. + * For most deterministic builds, the array length should be 1. In such case, + * the dependency will be unconditionally placed on the specified type of path + * and no heuristic rule will be involved. + * + * <p>It is nevertheless common to specify two or more types of path. For example, + * a Java library may be compatible with either the class-path or the module-path, + * and the user may have provided no instruction about which type to use. In such + * case, the plugin may apply rules for choosing a path. See for example + * {@link JavaPathType#CLASSES} and {@link JavaPathType#MODULES}.</p> + */ + Key<PathType[]> PATH_TYPES = new Key<>("pathTypes", PathType[].class).intern(); Review Comment: `Collection<PathType>` so that we can ensure immutability ? ########## api/maven-api-core/src/main/java/org/apache/maven/api/DependencyProperties.java: ########## @@ -33,27 +36,226 @@ @Immutable public interface DependencyProperties { /** - * Boolean flag telling that dependency contains all of its dependencies. Value of this key should be parsed with - * {@link Boolean#parseBoolean(String)} to obtain value. + * Keys in the dependency properties map. + * Each key can be associated to values of a specific class. + * + * @param <V> type of value associated to the key + */ + class Key<V> { + /** + * The keys that are defined in this {@code DependencyProperties} map. + * Accesses to this map shall be synchronized on the map. + * + * @see #intern() + */ + private static final Map<String, Key<?>> INTERNS = new HashMap<>(); + + /** + * Value returned by {@link #name()}. + */ + @Nonnull + private final String name; + + /** + * Value returned by {@link #valueType()}. + */ + @Nonnull + private final Class<V> valueType; + + /** + * Creates a new key. + * + * @param name name of the key + * @param valueType type of value associated to the key + */ + public Key(@Nonnull final String name, @Nonnull final Class<V> valueType) { + this.name = Objects.requireNonNull(name); + this.valueType = Objects.requireNonNull(valueType); + } + + /** + * If a key exists in the {@linkplain #intern() intern pool} for the given name, returns that key. + * Otherwise, if the {@code defaultType} is non-null, creates a key for values of the specified type. + * Otherwise, returns {@code null}. + * + * @param name name of the key to search or create + * @param defaultType value type of the key to create if none exist for the given name, or {@code null} + * @return key found or created, or {@code null} if no key was found and {@code defaultType} is null + * + * @see #intern() + */ + public static Key<?> forName(String name, Class<?> defaultType) { + Key<?> key; + synchronized (INTERNS) { + key = INTERNS.get(name); + } + if (key == null && defaultType != null) { + key = new Key<>(name, defaultType); + } + return key; + } + + /** + * {@return the name of the key}. + */ + @Nonnull + public String name() { + return name; + } + + /** + * {@return the type of value associated to the key}. + */ + @Nonnull + public Class<V> valueType() { + return valueType; + } + + /** + * {@return a canonical representation of this key}. A pool of keys, initially empty, is maintained privately. + * When the {@code intern()} method is invoked, if the pool already contains a key equal to this {@code Key} + * as determined by the {@link #equals(Object)} method, then the key from the pool is returned. Otherwise, + * if no key exist in the pool for this key {@linkplain #name() name}, then this {@code Key} object is added + * to the pool and {@code this} is returned. Otherwise an {@link IllegalStateException} is thrown. + * + * @throws IllegalStateException if a key exists in the pool for the same name but a different class of values. + * + * @see String#intern() + */ + @SuppressWarnings("unchecked") + public Key<V> intern() { + Key<?> previous; + synchronized (INTERNS) { + previous = INTERNS.putIfAbsent(name, this); + } + if (previous == null) { + return this; + } + if (equals(previous)) { + return (Key<V>) previous; + } + throw new IllegalStateException("Key " + name + " already exists for a different class of values."); + } + + /** + * {@return a string representation of this key}. + * By default, this is the name of this key. + */ + @Override + public String toString() { + return name; + } + + /** + * {@return an hash code value for this key}. + */ + @Override + public int hashCode() { + return 7 + name.hashCode() + 31 * valueType.hashCode(); + } + + /** + * Compares this key with the given object for equality. + * Two keys are considered equal if they have the same name + * and are associated to values of the same class. + * + * @param obj the object to compare with this key + * @return whether the given object is equal to this key + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj != null && getClass() == obj.getClass()) { + final Key<?> other = (Key<?>) obj; + return name.equals(other.name) && valueType.equals(other.valueType); + } + return false; + } + } + + /** + * The dependency type. The {@linkplain Key#name() name} of this property + * is equal to the {@code ArtifactProperties.TYPE} value. + */ + Key<String> TYPE = new Key<>("type", String.class).intern(); + + /** + * The dependency language. The {@linkplain Key#name() name} of this property + * is equal to the {@code ArtifactProperties.LANGUAGE} value. + */ + Key<String> LANGUAGE = new Key<>("language", String.class).intern(); + + /** + * Types of path (class-path, module-path, …) where the dependency can be placed. + * For most deterministic builds, the array length should be 1. In such case, + * the dependency will be unconditionally placed on the specified type of path + * and no heuristic rule will be involved. + * + * <p>It is nevertheless common to specify two or more types of path. For example, + * a Java library may be compatible with either the class-path or the module-path, + * and the user may have provided no instruction about which type to use. In such + * case, the plugin may apply rules for choosing a path. See for example + * {@link JavaPathType#CLASSES} and {@link JavaPathType#MODULES}.</p> + */ + Key<PathType[]> PATH_TYPES = new Key<>("pathTypes", PathType[].class).intern(); + + /** + * Boolean flag telling that dependency contains all of its dependencies. * <p> * <em>Important: this flag must be kept in sync with resolver! (as is used during collection)</em> */ - String FLAG_INCLUDES_DEPENDENCIES = "includesDependencies"; + Key<Boolean> FLAG_INCLUDES_DEPENDENCIES = new Key<>("includesDependencies", Boolean.class).intern(); /** - * Boolean flag telling that dependency is meant to be placed on class path. Value of this key should be parsed with - * {@link Boolean#parseBoolean(String)} to obtain value. + * Boolean flag telling that dependency is meant to be placed on class path. + * + * @deprecated Use {@link #PATH_TYPES} and {@link JavaPathType#CLASSES} instead. */ - String FLAG_CLASS_PATH_CONSTITUENT = "classPathConstituent"; + @Deprecated + Key<Boolean> FLAG_CLASS_PATH_CONSTITUENT = new Key<>("classPathConstituent", Boolean.class).intern(); /** - * Returns immutable "map view" of all the properties. + * {@return the keys of all properties in this map}. */ - @Nonnull - Map<String, String> asMap(); + Set<Key<?>> keys(); + + /** + * Returns the value associated to the given key. + * + * @param <V> type of value to get + * @param key key of the value to get + * @return value associated to the given key, or {@code null} if none + */ + <V> V get(@Nonnull Key<V> key); Review Comment: `Optional<V>` as a return value ? ########## api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java: ########## @@ -0,0 +1,282 @@ +/* + * 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.api; + +import java.io.File; +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import java.util.StringJoiner; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Nonnull; + +/** + * The option of a Java command-line tool where to place the paths to some dependencies. + * A {@code PathType} can identify the class-path, the module-path, the patches for a specific module, + * or another kind of path. This class is like an enumeration, except that it is extensible: + * plugins can define their own kinds of path. + * + * <p>One path type is handled in a special way: contrarily to other options, + * the paths specified in a {@code --patch-module} Java option is effective only for a specified module. + * This type is created by calls to {@link #patchModule(String)} and a new instance must be created for + * every module to patch.</p> + * + * <p>Path types are often exclusive. For example, a dependency should not be both on the Java class-path + * and on the Java module-path.</p> + * + * @see org.apache.maven.api.services.DependencyResolverResult#getDispatchedPaths() + * + * @since 4.0.0 + */ +@Experimental +public final class JavaPathType extends PathType { + /** + * The path identified by the Java {@code --class-path} option. + * Used for compilation, execution and Javadoc among others. + * + * <h4>Context-sensitive interpretation</h4> + * A dependency with this path type will not necessarily be placed on the class-path. + * There is two circumstances where the dependency may be nevertheless placed somewhere else: + * + * <ul> + * <li>If {@link #MODULES} path type is also set, then the dependency can be placed either on the + * class-path or on the module-path, but only one of those. The choice is up to the plugin, + * possibly using heuristic rules (Maven 3 behavior).</li> + * <li>If a {@link #patchModule(String)} is also set and the main JAR file was placed on the module-path, + * then the test dependency will be placed on the Java {@code --patch-module} option instead of the + * class-path.</li> + * </ul> + */ + public static final JavaPathType CLASSES = new JavaPathType("CLASSES", "--class-path", null); + + /** + * The path identified by the Java {@code --module-path} option. + * Used for compilation, execution and Javadoc among others. + * + * <h4>Context-sensitive interpretation</h4> + * A dependency with this flag will not necessarily be placed on the module-path. + * There is two circumstances where the dependency may be nevertheless placed somewhere else: + * + * <ul> + * <li>If {@link #CLASSES} path type is also set, then the dependency <em>should</em> be placed on the + * module-path, but is nevertheless compatible with placement on the class-path. Compatibility can + * be achieved, for example, by repeating in the {@code META-INF/services/} directory the services + * that are declared in the {@code module-info.class} file. In that case, the path type can be chosen + * by the plugin.</li> + * <li>If a {@link #patchModule(String)} is also set and the main JAR file was placed on the module-path, + * then the test dependency will be placed on the Java {@code --patch-module} option instead of the + * module-path.</li> Review Comment: `module-path` -> `{@code --module-path}` for consistency ########## api/maven-api-core/src/main/java/org/apache/maven/api/JavaPathType.java: ########## @@ -0,0 +1,282 @@ +/* + * 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.api; + +import java.io.File; +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import java.util.StringJoiner; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Nonnull; + +/** + * The option of a Java command-line tool where to place the paths to some dependencies. + * A {@code PathType} can identify the class-path, the module-path, the patches for a specific module, + * or another kind of path. This class is like an enumeration, except that it is extensible: + * plugins can define their own kinds of path. + * + * <p>One path type is handled in a special way: contrarily to other options, + * the paths specified in a {@code --patch-module} Java option is effective only for a specified module. + * This type is created by calls to {@link #patchModule(String)} and a new instance must be created for + * every module to patch.</p> + * + * <p>Path types are often exclusive. For example, a dependency should not be both on the Java class-path + * and on the Java module-path.</p> + * + * @see org.apache.maven.api.services.DependencyResolverResult#getDispatchedPaths() + * + * @since 4.0.0 + */ +@Experimental +public final class JavaPathType extends PathType { + /** + * The path identified by the Java {@code --class-path} option. + * Used for compilation, execution and Javadoc among others. + * + * <h4>Context-sensitive interpretation</h4> + * A dependency with this path type will not necessarily be placed on the class-path. + * There is two circumstances where the dependency may be nevertheless placed somewhere else: + * + * <ul> + * <li>If {@link #MODULES} path type is also set, then the dependency can be placed either on the + * class-path or on the module-path, but only one of those. The choice is up to the plugin, + * possibly using heuristic rules (Maven 3 behavior).</li> + * <li>If a {@link #patchModule(String)} is also set and the main JAR file was placed on the module-path, + * then the test dependency will be placed on the Java {@code --patch-module} option instead of the + * class-path.</li> + * </ul> + */ + public static final JavaPathType CLASSES = new JavaPathType("CLASSES", "--class-path", null); + + /** + * The path identified by the Java {@code --module-path} option. + * Used for compilation, execution and Javadoc among others. + * + * <h4>Context-sensitive interpretation</h4> + * A dependency with this flag will not necessarily be placed on the module-path. + * There is two circumstances where the dependency may be nevertheless placed somewhere else: Review Comment: `There is` -> `There are` `be nevertheless` -> `nevertheless be` > Control the type of path where each dependency can be placed > ------------------------------------------------------------ > > Key: MNG-8015 > URL: https://issues.apache.org/jira/browse/MNG-8015 > Project: Maven > Issue Type: Improvement > Components: Core > Affects Versions: 4.0.0-alpha-12 > Reporter: Martin Desruisseaux > Priority: Major > > Make possible to declare where each dependency can be placed: on the > module-path, class-path, agent path, doclet path, taglet path, annotation > processing path, _etc._ The proposed improvement consists in adding a new > {{PATH_TYPES}} property that can be associated to dependencies. The property > value is an array of {{PathType}}, a new enumeration-like class with values > such as {{CLASSES}}, {{MODULES}}, {{DOCLET}}, _etc._ Contrarily to real Java > enumerations, this enumeration-like class is extensible: plugins can add > their own enumeration values. This is required at least for the > {{--patch-module}} option, where a new {{PathType}} enumeration value need to > be created for each module to patch. > Users can control indirectly the {{PathType}} of a dependency by specifying > the dependency type. Note that there is no direct mapping between the > dependency type and where the dependency will be placed, but only an indirect > mapping caused by the fact that using a dependency type implies implicit > values of some properties such as classifier, and (with this proposal) path > types: > * {{<type>jar</type>}} implies {{PathType.CLASSES}} and {{PathType.MODULES}}. > * {{<type>modular-jar</type>}} implies {{PathType.MODULES}} only. > * {{<type>classpath-jar</type>}} implies {{PathType.CLASSES}} only. > * _etc._ > When a plugin requests the paths of dependencies, the plugin specifies the > types of path it is interested in. For example, a Java compiler plugin can > specify that it is interested in {{PathType.CLASSES}} and > {{PathType.MODULES}}, but not {{PathType.DOCLET}}. If a dependency declared > that it can be placed on the class-path or the doclet-path, only the > class-path is left after intersection with plugin's request. This is > important for the next step. > If, after all filtering such as above paragraph are applied, a dependency has > only one {{PathType}} left, then there is no ambiguity and we are done. > Combined with above-cited dependency types like {{modular-jar}} or > {{classpath-jar}}, this rule allows users to control where the dependency > will be placed. But if there are two or more {{PathType}} left after > filtering, then a choice needs to be done. For example if there are both > {{PathType.CLASSES}} and {{PathType.MODULES}} (which may happen when > {{<type>jar</type>}} is used), then an heuristic rule similar to Maven 3 can > be applied: check if a {{module-info.class}} file or an {{Automatic-Name}} > manifest attribute is present, and base the decision on that. > This proposal aims to fix MNG-7855. -- This message was sent by Atlassian Jira (v8.20.10#820010)