net.codesup.jaxb.plugins.delegate.DelegatePlugin Maven / Gradle / Ivy
/*
* MIT License
*
* Copyright (c) 2014 Klemm Software Consulting, Mirko Klemm
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package net.codesup.jaxb.plugins.delegate;
import java.lang.reflect.Modifier;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import com.kscs.util.plugins.xjc.base.AbstractPlugin;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JConditional;
import com.sun.codemodel.JDeclaration;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JGenericInvocation;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JType;
import com.sun.codemodel.JTypeVar;
import com.sun.codemodel.JVar;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.model.CPluginCustomization;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.Outline;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
import static java.lang.Thread.currentThread;
/**
* XJC plugin to generate a "toString"-like method by generating an invocation of a delegate object formatter class. Delegate class, method names, method return types and modifiers can be customized
* on the XJC command line or as binding customizations.
*
* @author Mirko Klemm 2015-01-22
*/
public class DelegatePlugin extends AbstractPlugin {
private static final JAXBContext JAXB_CONTEXT;
private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(DelegatePlugin.class.getName());
private static final String OPTION_NAME = "-Xdelegate";
private static final String CUSTOMIZATION_NS = "http://www.codesup.net/jaxb/plugins/delegate";
private static final String DELEGATES_CUSTOMIZATION_NAME = "delegates";
private static final String DELEGATE_CUSTOMIZATION_NAME = "delegate";
private static final String DELEGATE_REF_CUSTOMIZATION_NAME = "delegate-ref";
private static final String METHOD_CUSTOMIZATION_NAME = "method";
private static final String PARAM_CUSTOMIZATION_NAME = "param";
private static final String TYPE_PARAM_CUSTOMIZATION_NAME = "type-param";
private static final String TYPE_ARG_CUSTOMIZATION_NAME = "type-arg";
private static final String DOCUMENTATION_CUSTOMIZATION_NAME = "documentation";
private static final String ANNOTATE_CUSTOMIZATION_NAME = "annotate";
private static final List CUSTOM_ELEMENTS = Arrays.asList(
DelegatePlugin.DELEGATES_CUSTOMIZATION_NAME,
DelegatePlugin.DELEGATE_CUSTOMIZATION_NAME,
DelegatePlugin.DELEGATE_REF_CUSTOMIZATION_NAME,
DelegatePlugin.METHOD_CUSTOMIZATION_NAME,
DelegatePlugin.PARAM_CUSTOMIZATION_NAME,
DelegatePlugin.TYPE_PARAM_CUSTOMIZATION_NAME,
DelegatePlugin.TYPE_ARG_CUSTOMIZATION_NAME,
DelegatePlugin.DOCUMENTATION_CUSTOMIZATION_NAME,
DelegatePlugin.ANNOTATE_CUSTOMIZATION_NAME);
private static final String DEFAULT_DELEGATE_FIELD_PATTERN = "__delegate%s";
static {
try {
JAXB_CONTEXT = JAXBContext.newInstance(Delegates.class, Delegate.class);
} catch (final JAXBException e) {
throw new RuntimeException(e);
}
}
@Override
public String getOptionName() {
return DelegatePlugin.OPTION_NAME.substring(1);
}
@Override
public List getCustomizationURIs() {
return Collections.singletonList(DelegatePlugin.CUSTOMIZATION_NS);
}
@Override
public boolean isCustomizationTagName(final String nsUri, final String localName) {
return DelegatePlugin.CUSTOMIZATION_NS.equals(nsUri) && DelegatePlugin.CUSTOM_ELEMENTS.contains(localName);
}
@Override
public String getUsage() {
return DelegatePlugin.RESOURCE_BUNDLE.getString("usageText");
}
@Override
public boolean run(final Outline outline, final Options opt, final ErrorHandler errorHandler) throws SAXException {
try {
final Unmarshaller unmarshaller = DelegatePlugin.JAXB_CONTEXT.createUnmarshaller();
final Map delegateCache = new HashMap<>();
for (final ClassOutline classOutline : outline.getClasses()) {
final CPluginCustomization delegatesCustomization = getCustomizationElement(classOutline, DelegatePlugin.DELEGATES_CUSTOMIZATION_NAME);
if (delegatesCustomization != null) {
delegatesCustomization.markAsAcknowledged();
final JAXBElement delegatesElement = unmarshaller.unmarshal(delegatesCustomization.element, Delegates.class);
delegatesElement.getValue().getDelegateOrDelegateRef().stream()
.filter(Delegate.class::isInstance)
.map(Delegate.class::cast)
.forEach(delegate -> {
if (delegate.getId() != null) {
delegateCache.put(delegate.getId(), delegate);
}
if(delegate.getAnnotate() == null) {
delegate.setAnnotate(delegatesElement.getValue().getAnnotate());
}
generateDelegateCode(outline, errorHandler, classOutline, delegate, delegatesCustomization.locator);
});
} else {
final CPluginCustomization delegateCustomization = getCustomizationElement(classOutline, DelegatePlugin.DELEGATE_CUSTOMIZATION_NAME);
if (delegateCustomization != null) {
delegateCustomization.markAsAcknowledged();
final JAXBElement delegateElement = unmarshaller.unmarshal(delegateCustomization.element, Delegate.class);
final Delegate delegate = delegateElement.getValue();
if (delegate.getId() != null) {
delegateCache.put(delegate.getId(), delegate);
}
generateDelegateCode(outline, errorHandler, classOutline, delegate, delegateCustomization.locator);
}
}
}
for (final ClassOutline classOutline : outline.getClasses()) {
final CPluginCustomization delegatesCustomization = getCustomizationElement(classOutline, DelegatePlugin.DELEGATES_CUSTOMIZATION_NAME);
if (delegatesCustomization != null) {
delegatesCustomization.markAsAcknowledged();
final JAXBElement delegatesElement = unmarshaller.unmarshal(delegatesCustomization.element, Delegates.class);
delegatesElement.getValue().getDelegateOrDelegateRef().stream()
.filter(DelegateRef.class::isInstance)
.map(DelegateRef.class::cast)
.forEach(delegateRef -> {
final String delegateRefId = delegateRef.getRefid();
final Delegate delegate = delegateCache.get(delegateRefId);
if (delegate != null) {
generateDelegateCode(outline, errorHandler, classOutline, delegate, delegatesCustomization.locator);
} else {
try {
errorHandler.error(new SAXParseException(getMessage("error.invalidRef", delegateRef.getRefid()), delegatesCustomization.locator));
} catch (final SAXException e) {
}
}
}
);
} else {
final CPluginCustomization delegateRefCustomization = getCustomizationElement(classOutline, DelegatePlugin.DELEGATE_REF_CUSTOMIZATION_NAME);
if (delegateRefCustomization != null) {
delegateRefCustomization.markAsAcknowledged();
final JAXBElement delegateRef = unmarshaller.unmarshal(delegateRefCustomization.element, DelegateRef.class);
final String delegateRefId = delegateRef.getValue().getRefid();
final Delegate delegate = delegateCache.get(delegateRefId);
if (delegate != null) {
generateDelegateCode(outline, errorHandler, classOutline, delegate, delegateRefCustomization.locator);
} else {
try {
errorHandler.error(new SAXParseException(getMessage("error.invalidRef", delegateRef.getValue().getRefid()), delegateRefCustomization.locator));
} catch (final SAXException e) {
}
}
}
}
}
return true;
} catch (final JAXBException e) {
errorHandler.error(new SAXParseException(e.getMessage(), outline.getModel().getLocator()));
}
return true;
}
private void generateDelegateCode(final Outline outline, final ErrorHandler errorHandler, final ClassOutline classOutline, final Delegate delegateAnnotation, final Locator rootLocator) {
if (delegateAnnotation.getTarget() == null) {
generateDelegateCode(outline, errorHandler, classOutline.implClass, delegateAnnotation, rootLocator);
} else {
generateDelegateCode(outline, errorHandler, Stream.of(classOutline.implClass.listClasses())
.filter(JDefinedClass.class::isInstance)
.map(JDefinedClass.class::cast)
.filter(c -> c.name().equals(delegateAnnotation.getTarget()))
.findFirst().orElseThrow(() -> new IllegalArgumentException(getMessage("error.noSuchNestedClass", delegateAnnotation.getTarget(), classOutline.implClass.name()))), delegateAnnotation, rootLocator);
}
}
private void generateDelegateCode(final Outline outline, final ErrorHandler errorHandler, final JDefinedClass definedClass, final Delegate delegateAnnotation, final Locator rootLocator) {
if (delegateAnnotation.getClazz() == null) {
try {
errorHandler.error(new SAXParseException(getMessage("error.classRequired"), rootLocator));
} catch (final SAXException e) {
// do nothing
}
return;
}
final JCodeModel model = outline.getCodeModel();
final Map envTypes = new HashMap<>();
JClass delegateClass = model.ref(delegateAnnotation.getClazz());
envTypes.put(TypeRef.DELEGEE_CLASS, definedClass);
envTypes.put(TypeRef.DELEGATE_CLASS, delegateClass);
envTypes.put(TypeRef.OUTLINE_CLASS, definedClass.outer() == null ? definedClass : definedClass.outer());
final TypeParser typeParser = new TypeParser(model, envTypes);
final Delegate delegate = gatherRuntimeInformation(delegateAnnotation, delegateClass);
final boolean staticDelegate = coalesce(delegate.isStatic(), Boolean.FALSE);
final boolean lazyDelegate = coalesce(delegate.isLazy(), Boolean.FALSE);
final var delegateTypeParams = delegate.getTypeParamOrTypeArg().stream().map(typeParamElement -> {
final TypeParameterType typeParameterType = typeParamElement.getValue();
final JClass extendsType = typeParameterType.getExtends() == null ? null : (JClass)typeParser.parse(typeParameterType.getExtends());
if (typeParamElement.getName().getLocalPart().equals(DelegatePlugin.TYPE_PARAM_CUSTOMIZATION_NAME)) {
definedClass.generify(typeParameterType.getName(), extendsType);
}
envTypes.put(TypeRef.DELEGATE_CLASS, definedClass);
return (JClass)typeParser.parse(typeParameterType.getName());
}).collect(Collectors.toList());
delegateClass = delegateClass.narrow(delegateTypeParams);
final String delegateFieldName = String.format(DelegatePlugin.DEFAULT_DELEGATE_FIELD_PATTERN, delegateClass.erasure().name());
final JFieldVar delegateField = staticDelegate || !lazyDelegate ? null : definedClass.field(JMod.PRIVATE | JMod.TRANSIENT, delegateClass, delegateFieldName, JExpr._null());
if (delegate.getDocumentation() != null) {
delegateField.javadoc().append(delegate.getDocumentation());
}
for (final Method method : delegate.getMethod()) {
final boolean staticMethod = coalesce(method.isStatic(), Boolean.FALSE);
final int modifiers = parseModifiers(coalesce(method.getModifiers(), "public"));
final JType returnType = typeParser.parse(method.getType());
final JMethod implMethod = definedClass.method(staticMethod ? JMod.STATIC | modifiers : modifiers, returnType, method.getName());
if (method.getDocumentation() != null) {
implMethod.javadoc().append(method.getDocumentation());
}
final List typeParams = new ArrayList<>();
for (final TypeParameterType param : method.getTypeParam()) {
final JClass extendsType = param.getExtends() == null ? null : (JClass)typeParser.parse(param.getExtends());
final JTypeVar implParam = implMethod.generify(param.getName(), extendsType);
typeParams.add(implParam);
if (param.getDocumentation() != null) {
implMethod.javadoc().addParam(param.getName()).append(param.getDocumentation());
}
}
final List params = new ArrayList<>();
int i = 0;
for (final MethodParameterType param : method.getParam()) {
final JType paramType = typeParser.parse(param.getType());
final JVar implParam = implMethod.param(paramType, coalesce(param.getName(), "p" + i++));
params.add(implParam);
if (param.getDocumentation() != null) {
implMethod.javadoc().addParam(implParam).append(param.getDocumentation());
}
if(param.isNullable() != null && delegate.getAnnotate() != null) {
if (param.isNullable()) {
implParam.annotate(model.ref(delegate.getAnnotate().getNullable()));
} else {
implParam.annotate(model.ref(delegate.getAnnotate().getNonNull()));
}
}
}
if(method.isNullable() != null && delegate.getAnnotate() != null) {
if (method.isNullable()) {
implMethod.annotate(model.ref(delegate.getAnnotate().getNullable()));
} else {
implMethod.annotate(model.ref(delegate.getAnnotate().getNonNull()));
}
}
final JInvocation invoke;
if (staticDelegate) {
invoke = delegateClass.staticInvoke(method.getName());
if (!staticMethod) {
invoke.arg(JExpr._this());
}
} else if (lazyDelegate) {
if (staticMethod) {
invoke = delegateClass.staticInvoke(method.getName());
} else {
final JConditional ifStatement = implMethod.body()._if(delegateField.eq(JExpr._null()));
ifStatement._then().assign(delegateField, parameterizedNew(delegateClass).arg(JExpr._this()));
invoke = delegateField.invoke(method.getName());
}
} else {
if (staticMethod) {
invoke = delegateClass.staticInvoke(method.getName());
} else {
invoke = parameterizedNew(delegateClass).arg(JExpr._this()).invoke(method.getName());
}
}
for (final JVar param : params) {
invoke.arg(param);
}
// for (final JTypeVar typeParam:typeParams) {
// invoke.arg(typeParam);
// }
if (returnType.compareTo(JType.parse(model, "void")) == 0) {
implMethod.body().add(invoke);
} else {
implMethod.body()._return(invoke);
}
}
}
private JGenericInvocation parameterizedNew(JClass jClass) {
return new JGenericInvocation(jClass);
}
private Delegate gatherRuntimeInformation(final Delegate delegateAnnotation, final JClass delegateClass) {
if (delegateClass instanceof JDeclaration) {
try {
final Class> referencedClass = findRuntimeClass(delegateClass);
if (delegateAnnotation.getMethod().isEmpty()) {
for (final java.lang.reflect.Method runtimeMethod : referencedClass.getMethods()) {
delegateAnnotation.getMethod().add(createMethodDescriptor(delegateAnnotation, runtimeMethod));
}
} else {
for (final Method method : delegateAnnotation.getMethod()) {
try {
final java.lang.reflect.Method runtimeMethod = findRuntimeMethod(referencedClass, method);
extendMethodDescriptor(delegateAnnotation, method, runtimeMethod);
} catch (final NoSuchMethodException nmx) {
// fall through
}
}
}
} catch (final ClassNotFoundException cnf) {
// fall through
}
}
return delegateAnnotation;
}
private java.lang.reflect.Method findRuntimeMethod(final Class> referencedClass, final Method method) throws NoSuchMethodException {
if (method.getParam().isEmpty()) {
try {
return referencedClass.getMethod(method.getName());
} catch (final NoSuchMethodException nsmx) {
for (final java.lang.reflect.Method runtimeMethod : referencedClass.getMethods()) {
if (runtimeMethod.getName().equals(method.getName())) {
return runtimeMethod;
}
}
throw new NoSuchMethodException("Method " + referencedClass.getName() + "#" + method.getName() + "() not found");
}
} else {
final Class>[] runtimeParameterTypes = new Class>[method.getParam().size()];
int paramIndex = 0;
for (final MethodParameterType param : method.getParam()) {
final int currentIndex = paramIndex++;
try {
runtimeParameterTypes[currentIndex] = Class.forName(param.getType());
} catch (final ClassNotFoundException cnfe) {
runtimeParameterTypes[currentIndex] = Object.class;
}
}
return referencedClass.getMethod(method.getName(), runtimeParameterTypes);
}
}
private Method createMethodDescriptor(final Delegate delegateAnnotation, final java.lang.reflect.Method runtimeMethod) {
final boolean staticDelegate = coalesce(delegateAnnotation.isStatic(), Boolean.FALSE);
final Method method = new Method();
method.setModifiers(Modifier.toString(runtimeMethod.getModifiers() & ~Modifier.STATIC));
method.setName(runtimeMethod.getName());
method.setStatic((runtimeMethod.getModifiers() & Modifier.STATIC) != 0 && !staticDelegate);
method.setType(runtimeMethod.getReturnType().getName());
inferParameters(method, runtimeMethod);
return method;
}
private Method extendMethodDescriptor(final Delegate delegateAnnotation, final Method method, final java.lang.reflect.Method runtimeMethod) {
if (method.getModifiers() == null) {
method.setModifiers(Modifier.toString(runtimeMethod.getModifiers() & ~Modifier.STATIC));
}
if (method.getType() == null) {
method.setType(runtimeMethod.getReturnType().getName());
}
if (method.getParam().isEmpty()) {
inferParameters(method, runtimeMethod);
} else {
int paramIndex = 0;
for (final MethodParameterType param : method.getParam()) {
if (param.getType() == null) {
param.setType(runtimeMethod.getParameterTypes()[paramIndex++].getName());
}
}
}
return method;
}
private void inferParameters(final Method method, final java.lang.reflect.Method runtimeMethod) {
int i = 0;
for (final Class> paramType : runtimeMethod.getParameterTypes()) {
final MethodParameterType param = new MethodParameterType();
param.setName("p" + i++);
param.setType(paramType.getName());
method.getParam().add(param);
}
}
private Class> findRuntimeClass(final JClass jClass) throws ClassNotFoundException {
try {
final ClassLoader contextClassLoader;
// try the context class loader first
if (System.getSecurityManager() == null) {
contextClassLoader = Thread.currentThread().getContextClassLoader();
} else {
contextClassLoader = (ClassLoader)java.security.AccessController.doPrivileged(
(PrivilegedAction)currentThread()::getContextClassLoader);
}
return contextClassLoader.loadClass(jClass.binaryName());
} catch (final ClassNotFoundException e) {
// then the default mechanism.
return Class.forName(jClass.binaryName());
}
}
private int parseModifiers(final String modifiers) {
int mod = JMod.NONE;
for (final String token : modifiers.split("\\s+")) {
switch (token.toLowerCase()) {
case "public":
mod |= JMod.PUBLIC;
break;
case "protected":
mod |= JMod.PROTECTED;
break;
case "private":
mod |= JMod.PRIVATE;
break;
case "final":
mod |= JMod.FINAL;
break;
case "static":
mod |= JMod.STATIC;
break;
case "abstract":
mod |= JMod.ABSTRACT;
break;
case "native":
mod |= JMod.NATIVE;
break;
case "synchronized":
mod |= JMod.SYNCHRONIZED;
break;
case "transient":
mod |= JMod.TRANSIENT;
break;
case "volatile":
mod |= JMod.VOLATILE;
break;
}
}
return mod;
}
private CPluginCustomization getCustomizationElement(final ClassOutline classOutline, final String elementName) {
return classOutline.target.getCustomizations().find(DelegatePlugin.CUSTOMIZATION_NS, elementName);
}
private T coalesce(final T... vals) {
for (final T val : vals) {
if (val != null)
return val;
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy