Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .mvn/checkstyle/suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
<suppressions>
</suppressions>
<suppress files="Datatypes_SomeBrokenModel.java" checks=".+"/>
</suppressions>
6 changes: 6 additions & 0 deletions protobuf-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.immutables</groupId>
<artifactId>datatype</artifactId>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>org.immutables</groupId>
<artifactId>value</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@

import io.github.ascopes.protobufmavenplugin.utils.DeadCodeGenerated;
import java.util.Set;
import org.immutables.datatype.Data;
import org.immutables.value.Value.Immutable;
import org.immutables.value.Value.Modifiable;
import org.jspecify.annotations.Nullable;


Expand All @@ -28,13 +28,13 @@
* @author Ashley Scopes
* @since 3.3.1
*/
@Data
@Immutable
@Modifiable
public abstract class MavenDependency extends MavenArtifact {

public abstract @Nullable DependencyResolutionDepth getDependencyResolutionDepth();

public abstract Set<MavenExclusionBean> getExclusions();
public abstract Set<MavenExclusion> getExclusions();

@DeadCodeGenerated(reason = "Must be overridden to keep immutables happy.")
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
*/
package io.github.ascopes.protobufmavenplugin.dependencies;

import org.immutables.value.Value.Modifiable;
import org.immutables.datatype.Data;
import org.immutables.value.Value.Immutable;

/**
* Marker to exclude a specific transitive dependency.
Expand All @@ -26,7 +27,8 @@
* @author Ashley Scopes
* @since 2.12.0
*/
@Modifiable
@Data
@Immutable

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add checks to project structure for @Modifiable annotations if possible so we can fail builds when they are used.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verify any changes rebased into this PR do not contain @Modifiable types.

public interface MavenExclusion {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import static java.util.function.Predicate.not;

import io.github.ascopes.protobufmavenplugin.dependencies.DependencyResolutionDepth;
import io.github.ascopes.protobufmavenplugin.dependencies.MavenDependencyBean;
import io.github.ascopes.protobufmavenplugin.dependencies.MavenDependency;
import io.github.ascopes.protobufmavenplugin.digests.Digest;
import io.github.ascopes.protobufmavenplugin.generation.GenerationResult;
import io.github.ascopes.protobufmavenplugin.generation.ImmutableGenerationRequest;
Expand Down Expand Up @@ -301,7 +301,7 @@ public AbstractGenerateMojo() {
* @since 1.2.0
*/
@Parameter
@Nullable List<MavenDependencyBean> importDependencies;
@Nullable List<MavenDependency> importDependencies;

/**
* Specify additional paths to import protobuf sources from on the local file system.
Expand Down Expand Up @@ -743,7 +743,7 @@ public AbstractGenerateMojo() {
* @since 1.2.0
*/
@Parameter
@Nullable List<MavenDependencyBean> sourceDependencies;
@Nullable List<MavenDependency> sourceDependencies;

/**
* Protobuf Descriptor files to compile.
Expand Down Expand Up @@ -777,7 +777,7 @@ public AbstractGenerateMojo() {
* @since 3.1.0
*/
@Parameter
@Nullable List<MavenDependencyBean> sourceDescriptorDependencies;
@Nullable List<MavenDependency> sourceDescriptorDependencies;

/**
* The source directories to compile protobuf sources from.
Expand Down Expand Up @@ -813,6 +813,10 @@ public AbstractGenerateMojo() {
@Parameter
@Nullable List<Path> sourceDescriptorPaths;

/*
* Implementation-specific details.
*/

/**
* Provides the default source directory to read protobuf sources from.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
*/
@NullMarked
@Style(
beanFriendlyModifiables = true,
create = "new",
defaults = @Immutable(copy = false),
defaultAsDefault = true,
get = {"get*", "is*"},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright (C) 2023 Ashley Scopes
*
* Licensed 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 io.github.ascopes.protobufmavenplugin.plexus;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import javax.inject.Named;
import javax.inject.Singleton;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
import org.codehaus.plexus.component.configurator.converters.ParameterizedConfigurationConverter;
import org.codehaus.plexus.component.configurator.converters.basic.AbstractBasicConverter;
import org.codehaus.plexus.component.configurator.converters.lookup.ConverterLookup;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.immutables.datatype.Datatype;
import org.jspecify.annotations.Nullable;

/**
* Converter for Plexus components that can build a generated "immutables" value type, based
* on the interface it was derived from.
*
* <p>Note that all objects must be annotated with both {@link org.immutables.value.Value}
* <strong>and</strong> {@link org.immutables.datatype.Data}, otherwise deserialization will fail at
* runtime.
*
* <p>This will not store any references to the datatypes, to allow for sharing across classloaders
* safely (see GH-974 and GH-976).
*
* @author Ashley Scopes
* @since TBC

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO(ascopes): pin this to the next minor version.

*/
@Named
@Singleton
final class ImmutablesDataPlexusConverter extends AbstractBasicConverter {
@Override
public boolean canConvert(Class<?> cls) {
return datatypeFor(cls).isPresent();
}

@Override
public Object fromConfiguration(
ConverterLookup lookup,
PlexusConfiguration configuration,
Class<?> type,
@Nullable Class<?> enclosingType,
@Nullable ClassLoader loader,
ExpressionEvaluator evaluator,
@Nullable ConfigurationListener listener
) throws ComponentConfigurationException {
var datatype = datatypeFor(type)
.orElseThrow(() -> new NoSuchElementException("No datatype converter " + type.getName()));

var builder = datatype.builder();
for (var child : configuration.getChildren()) {
consumeChild(builder, child, lookup, datatype, loader, evaluator, listener);
}

return builder.build();
}

private Optional<Datatype<Object>> datatypeFor(Class<?> cls) {
if (cls.isPrimitive() || cls.getClassLoader() == null) {
return Optional.empty();
}

var loader = cls.getClassLoader();
var outerClsName = cls.getPackageName() + ".Datatypes_" + cls.getSimpleName();

try {
var outerCls = loader.loadClass(outerClsName);
var method = outerCls.getMethod("_" + cls.getSimpleName());

@SuppressWarnings("unchecked")
var result = (Datatype<Object>) method.invoke(null);

return Optional.of(result);
} catch (ClassNotFoundException ex) {
return Optional.empty();
} catch (ReflectiveOperationException ex) {
throw new IllegalStateException(
"Failed to find datatype for " + cls.getName() + ": " + ex,
ex
);
}
}

private void consumeChild(
Datatype.Builder<Object> builder,
PlexusConfiguration child,
ConverterLookup lookup,
Datatype<?> datatype,
@Nullable ClassLoader loader,
ExpressionEvaluator evaluator,
@Nullable ConfigurationListener listener
) throws ComponentConfigurationException {
try {
@SuppressWarnings("unchecked")
var feature = (Datatype.Feature<Object, Object>) datatype.feature(child.getName());
var valueType = feature.type();
var rawType = rawTypeOf(valueType);

var converter = lookup.lookupConverterForType(rawType);

Object value;

if (converter instanceof ParameterizedConfigurationConverter parameterizedConverter) {
var parameterizedType = (ParameterizedType) valueType;
value = parameterizedConverter.fromConfiguration(
lookup,
child,
rawType,
parameterizedType.getActualTypeArguments(),
rawType.getEnclosingClass(),
loader,
evaluator,
listener
);
} else {
value = converter.fromConfiguration(
lookup,
child,
rawType,
rawType.getEnclosingClass(),
loader,
evaluator,
listener
);
}

builder.set(feature, value);

} catch (NoSuchElementException ex) {
throw new ComponentConfigurationException(
"No attribute " + child.getName() + " exists for " + datatype.name(),
ex
);
}
}

private static Class<?> rawTypeOf(Type type) {
return type instanceof ParameterizedType parameterizedType
? rawTypeOf(parameterizedType.getRawType())
// Assumption: this cannot ever be a wildcard or union type.
: (Class<?>) type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
/**
* Marker to advise the "kind" of the implementation when used with a sealed-type hierarchy.
*
* <p>When this marker is spotted, the {@code ConverterLookup} will be consulted to produce
* an instance of that type.
*
* @author Ashley Scopes
* @since 4.1.0
*/
Expand All @@ -38,20 +41,5 @@
*
* @return the kind.
*/
String kind();

/**
* The implementation that the kind should point to.
*
* <p>This is needed until
* <a href="https://github.com/ascopes/protobuf-maven-plugin/pull/880">GH-880</a>
* can be merged, as we cannot easily infer the immutable implementation class from the base
* when working with the Immutables library. GH-880 will enable better integration with that
* library based on compile-time metadata that avoids this issue. We may just need to include
* this annotation in {@link Style#passAnnotations()} to achieve this correctly closer to the
* time.
*
* @return the implementation type.
*/
Class<?> implementation();
String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,21 +183,22 @@ private Object parseFromObject(
private Optional<? extends Class<?>> findRequestedImplementation(Class<?> type, String kind) {
return listKindedImplementations(type)
.filter(kindPair -> kindPair.kind().equals(kind))
.map(KindHint::implementation)
.map(KindPair::implementation)
.findFirst();
}

private String nameValidKinds(Class<?> type) {
return listKindedImplementations(type)
.map(KindHint::kind)
.map(KindPair::kind)
.sorted()
.map(s -> "\"" + s + "\"")
.collect(Collectors.joining(", "));
}

private Stream<KindHint> listKindedImplementations(Class<?> type) {
private Stream<KindPair> listKindedImplementations(Class<?> type) {
var thisKindHint = AnnotationProxy.findAnnotation(KindHint.class, type)
.stream();
.stream()
.map(annotation -> new KindPair(annotation.value(), type));

var kindedSubtypes = Optional.ofNullable(type.getPermittedSubclasses())
.stream()
Expand All @@ -217,4 +218,6 @@ private ComponentConfigurationException missingKindAttribute(
+ nameValidKinds(type) + "."
);
}

private record KindPair(String kind, Class<?> implementation) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
import io.github.ascopes.protobufmavenplugin.plexus.KindHint;
import io.github.ascopes.protobufmavenplugin.utils.DeadCodeGenerated;
import org.immutables.value.Value.Immutable;
import org.immutables.value.Value.Modifiable;


/**
* Implementation independent descriptor for a protoc plugin that can be resolved from a Maven
Expand All @@ -30,8 +28,7 @@
* @since 4.1.0
*/
@Immutable
@Modifiable
@KindHint(kind = "binary-maven", implementation = BinaryMavenProtocPluginBean.class)
@KindHint("binary-maven")
public abstract non-sealed class BinaryMavenProtocPlugin
extends MavenArtifact
implements ProtocPlugin {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
import io.github.ascopes.protobufmavenplugin.plexus.KindHint;
import io.github.ascopes.protobufmavenplugin.utils.DeadCodeGenerated;
import java.util.List;
import org.immutables.value.Value.Modifiable;
import org.immutables.datatype.Data;
import org.immutables.value.Value.Immutable;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

Expand All @@ -31,8 +32,9 @@
* @author Ashley Scopes
* @since 2.0.0
*/
@Modifiable
@KindHint(kind = "jvm-maven", implementation = JvmMavenProtocPluginBean.class)
@Data
@Immutable
@KindHint("jvm-maven")
public abstract non-sealed class JvmMavenProtocPlugin
extends MavenArtifact
implements ProtocPlugin {
Expand Down
Loading
Loading