generated from lengors/maven-java-template
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
246 additions
and
0 deletions.
There are no files selected for viewing
197 changes: 197 additions & 0 deletions
197
core/src/main/java/io/github/lengors/js2pets/rules/ConstructorRule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
package io.github.lengors.js2pets.rules; | ||
|
||
import org.apache.commons.collections4.IteratorUtils; | ||
import org.apache.commons.lang3.reflect.FieldUtils; | ||
import org.apache.commons.lang3.reflect.MethodUtils; | ||
import org.apache.maven.model.Plugin; | ||
import org.apache.maven.plugin.descriptor.PluginDescriptor; | ||
import org.checkerframework.checker.nullness.qual.Nullable; | ||
import org.codehaus.plexus.util.xml.Xpp3Dom; | ||
import org.jsonschema2pojo.Schema; | ||
import org.jsonschema2pojo.rules.Rule; | ||
import org.jsonschema2pojo.rules.RuleFactory; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.sun.codemodel.JDefinedClass; | ||
import com.sun.codemodel.JMethod; | ||
|
||
import io.github.lengors.js2pets.annotators.EnhancedAnnotator; | ||
import io.github.lengors.js2pets.rules.exceptions.ConfigurationPropertyMissingException; | ||
import lombok.AllArgsConstructor; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
import java.lang.reflect.Field; | ||
import java.lang.reflect.InvocationTargetException; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Modifier; | ||
|
||
/** | ||
* Constructor rule wrapper that removes the no-args constructor if the respective flag is enabled. This rule also | ||
* notifies the annotator if it supports constructor callbacks. | ||
* | ||
* This class extends the functionality provided by the jsonschema2pojo library by adding a customizable rule for | ||
* generating or omitting no-argument constructors based on configuration settings. | ||
* | ||
* @author lengors | ||
*/ | ||
@AllArgsConstructor | ||
public class ConstructorRule implements Rule<JDefinedClass, JDefinedClass> { | ||
/** | ||
* Rule factory from where we get generation configuration among other utilities. | ||
*/ | ||
private final RuleFactory ruleFactory; | ||
|
||
/** | ||
* Flag determining whether the no-args constructor should be included in the resulting set of constructors or not. | ||
* Leaving the flag null will lead the rule to try to infer it from the plugin's configuration. | ||
*/ | ||
private final @Nullable Boolean includeNoArgsConstructor; | ||
|
||
/** | ||
* The constructor rule that must be obtained from the super rule's factory. | ||
*/ | ||
private final Rule<JDefinedClass, JDefinedClass> superConstructorRule; | ||
|
||
/** | ||
* Applies this rule to the given {@link JDefinedClass}, potentially removing the no-args constructor and notifying | ||
* the annotator if applicable. | ||
* | ||
* @param nodeName The name of the JSON node being processed. | ||
* @param node The JSON node to which the rule is being applied. | ||
* @param parent The parent JSON node, or null if there isn't one. | ||
* @param type The Java class that is being generated from the JSON | ||
* schema. | ||
* @param currentSchema The current schema being processed. | ||
* @return The {@link JDefinedClass} after applying the rule. | ||
*/ | ||
@Override | ||
public JDefinedClass apply( | ||
final String nodeName, | ||
final JsonNode node, | ||
final JsonNode parent, | ||
final JDefinedClass type, | ||
final Schema currentSchema) { | ||
final var clazz = superConstructorRule.apply(nodeName, node, parent, type, currentSchema); | ||
|
||
if (!isIncludeNoArgsConstructor()) { | ||
removeConstructors(clazz, ruleFactory); | ||
} | ||
|
||
if (ruleFactory.getAnnotator() instanceof EnhancedAnnotator annotator) { | ||
IteratorUtils.forEach(clazz.constructors(), annotator::constructor); | ||
} | ||
|
||
return clazz; | ||
} | ||
|
||
private boolean isIncludeNoArgsConstructor() { | ||
return Optional | ||
.ofNullable(includeNoArgsConstructor) | ||
.orElseGet(() -> { | ||
final var generationConfig = ruleFactory.getGenerationConfig(); | ||
final var method = MethodUtils.getAccessibleMethod(generationConfig.getClass(), "getPluginContext"); | ||
if (method == null) { | ||
throw new ConfigurationPropertyMissingException(INCLUDE_NO_ARGS_CONSTRUCTOR_KEY); | ||
} | ||
|
||
final var pluginContext = invokeMethod(method, generationConfig, Map.class) | ||
.orElseGet(Collections::emptyMap); | ||
final var optPlugin = Optional | ||
.ofNullable(pluginContext.get("pluginDescriptor")) | ||
.filter(PluginDescriptor.class::isInstance) | ||
.map(PluginDescriptor.class::cast) | ||
.map(PluginDescriptor::getPlugin); | ||
|
||
final var executionsCount = optPlugin | ||
.map(Plugin::getExecutions) | ||
.map(List::size) | ||
.orElse(0); | ||
|
||
if (executionsCount > 1) { | ||
throw new ConfigurationPropertyMissingException(INCLUDE_NO_ARGS_CONSTRUCTOR_KEY); | ||
} | ||
|
||
return optPlugin | ||
.map(Plugin::getConfiguration) | ||
.filter(Xpp3Dom.class::isInstance) | ||
.map(Xpp3Dom.class::cast) | ||
.map(configuration -> configuration.getChild(INCLUDE_NO_ARGS_CONSTRUCTOR_KEY)) | ||
.map(Xpp3Dom::getValue) | ||
.map(Boolean::parseBoolean) | ||
.orElse(DEFAULT_INCLUDE_NO_ARGS_CONSTRUCTOR); | ||
}); | ||
} | ||
|
||
private static <T> Optional<T> invokeMethod( | ||
final Method method, | ||
final Object target, | ||
final Class<? extends T> targetType) { | ||
try { | ||
return Optional | ||
.ofNullable(method.invoke(target)) | ||
.filter(targetType::isInstance) | ||
.map(targetType::cast); | ||
} catch (final IllegalAccessException | InvocationTargetException exception) { | ||
return Optional.empty(); | ||
} | ||
} | ||
|
||
private static @Nullable Object readFieldValue( | ||
final JDefinedClass target, | ||
final Field field, | ||
final RuleFactory ruleFactory) { | ||
if (Modifier.isStatic(field.getModifiers())) { | ||
return null; | ||
} | ||
|
||
try { | ||
return FieldUtils.readField(field, target, true); | ||
} catch (final IllegalAccessException exception) { | ||
ruleFactory | ||
.getLogger() | ||
.warn(String.format("Could not access JDefinedClass{} field Field{name=%s}", field.getName()), exception); | ||
} | ||
return null; | ||
} | ||
|
||
private static void removeConstructors(final JDefinedClass clazz, final RuleFactory ruleFactory) { | ||
final var knownConstructors = IteratorUtils.toList(clazz.constructors()); | ||
for (final var field : FieldUtils.getAllFields(clazz.getClass())) { | ||
if (readFieldValue(clazz, field, ruleFactory) instanceof Collection<?> collection) { | ||
final var noArgsConstructors = new ArrayList<JMethod>(); | ||
for (final var element : collection) { | ||
if (element instanceof JMethod method | ||
&& method.listParams().length == 0 | ||
&& knownConstructors.contains(method)) { | ||
noArgsConstructors.add(method); | ||
} | ||
} | ||
noArgsConstructors.forEach(method -> remove(collection, method)); | ||
} | ||
} | ||
} | ||
|
||
private static boolean remove(final Collection<?> collection, final Object value) { | ||
try { | ||
return collection.remove(value); | ||
} catch (final UnsupportedOperationException exception) { | ||
return false; | ||
} | ||
} | ||
|
||
/** | ||
* The key used to retrieve the "includeNoArgsConstructor" configuration property. | ||
*/ | ||
private static final String INCLUDE_NO_ARGS_CONSTRUCTOR_KEY = "includeNoArgsConstructor"; | ||
|
||
/** | ||
* The default value for whether the no-args constructor should be included. | ||
*/ | ||
private static final boolean DEFAULT_INCLUDE_NO_ARGS_CONSTRUCTOR = true; | ||
} |
31 changes: 31 additions & 0 deletions
31
...ava/io/github/lengors/js2pets/rules/exceptions/ConfigurationPropertyMissingException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package io.github.lengors.js2pets.rules.exceptions; | ||
|
||
import lombok.Getter; | ||
|
||
/** | ||
* Exception thrown when a required configuration property is missing and its value cannot be inferred. | ||
* | ||
* This exception is a specific type of {@link NullPointerException} that includes the name of the missing configuration | ||
* property for easier debugging and context understanding. | ||
* | ||
* @author lengors | ||
*/ | ||
public class ConfigurationPropertyMissingException extends NullPointerException { | ||
/** | ||
* The name of the configuration property that is missing. | ||
*/ | ||
@Getter | ||
private final String configurationPropertyName; | ||
|
||
/** | ||
* Constructs a new {@code ConfigurationPropertyMissingException} with the specified property name. | ||
* | ||
* @param configurationPropertyName The name of the missing configuration property. | ||
*/ | ||
public ConfigurationPropertyMissingException(final String configurationPropertyName) { | ||
super(String.format( | ||
"Configuration property {name=%s} missing and value could not be inferred from context", | ||
configurationPropertyName)); | ||
this.configurationPropertyName = configurationPropertyName; | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
core/src/main/java/io/github/lengors/js2pets/rules/exceptions/package-info.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/** | ||
* This package contains custom exceptions used in the JS2Pets library. | ||
* | ||
* The exceptions in this package are designed to handle specific error cases related to configuration and rule | ||
* processing within the framework, making it easier to diagnose and respond to issues during runtime. | ||
* | ||
* @author lengors | ||
*/ | ||
package io.github.lengors.js2pets.rules.exceptions; |
9 changes: 9 additions & 0 deletions
9
core/src/main/java/io/github/lengors/js2pets/rules/package-info.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/** | ||
* This package contains the rules that extend the functionality of the JS2Pets library. | ||
* | ||
* These rules are used to customize the behavior of jsonschema2pojo, specifically in handling constructor generation | ||
* and related tasks within the context of JSON Schema to POJO conversion. | ||
* | ||
* @author lengors | ||
*/ | ||
package io.github.lengors.js2pets.rules; |