From 8271a11f32e129b3722b8743ee39e7af4b392198 Mon Sep 17 00:00:00 2001 From: Laurent SCHOELENS <61973605+laurentschoelens@users.noreply.github.com> Date: Tue, 10 Oct 2023 18:42:24 +0200 Subject: [PATCH] [#433] SimpleToString with dep-free execution --- .../simpletostring/SimpleToStringPlugin.java | 360 ++++++++---------- .../simpletostring/ToStringArguments.java | 136 +++++++ .../ToStringCodeGenerationImplementor.java | 182 +++++++++ .../simpletostring/ToStringCodeGenerator.java | 14 + .../jaxb/plugin/tostring/Customizations.java | 23 +- .../jaxb/plugin/tostring/DateFormatClass.java | 38 ++ .../jaxb/plugin/tostring/ObjectFactory.java | 11 + .../jaxb/plugin/tostring/ToStringPlugin.java | 1 + .../jvnet/jaxb/plugin/util/ArrayUtils.java | 17 + .../jaxb/plugin/util/FieldOutlineUtils.java | 12 + .../services/com.sun.tools.xjc.Plugin | 1 + jaxb-plugins-parent/tests/qa-simple/pom.xml | 7 +- .../qa-simple/src/main/resources/binding.xjb | 2 +- .../qa-simple/src/main/resources/schema.xsd | 34 +- .../jaxb/tests/qa/simple/ToStringTest.java | 21 + .../src/test/resources/log4j.properties | 5 - .../tests/qa-simple/src/test/samples/1.xml | 11 + .../tests/qa-simple/src/test/samples/2.xml | 4 + .../tests/qa-simple/src/test/samples/3.xml | 4 + 19 files changed, 658 insertions(+), 225 deletions(-) create mode 100644 jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/ToStringArguments.java create mode 100644 jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/ToStringCodeGenerationImplementor.java create mode 100644 jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/ToStringCodeGenerator.java create mode 100644 jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/DateFormatClass.java create mode 100644 jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/ObjectFactory.java create mode 100644 jaxb-plugins-parent/tests/qa-simple/src/test/java/org/jvnet/jaxb/tests/qa/simple/ToStringTest.java delete mode 100644 jaxb-plugins-parent/tests/qa-simple/src/test/resources/log4j.properties create mode 100644 jaxb-plugins-parent/tests/qa-simple/src/test/samples/1.xml create mode 100644 jaxb-plugins-parent/tests/qa-simple/src/test/samples/2.xml create mode 100644 jaxb-plugins-parent/tests/qa-simple/src/test/samples/3.xml diff --git a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/SimpleToStringPlugin.java b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/SimpleToStringPlugin.java index 8c80316bf..119766901 100644 --- a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/SimpleToStringPlugin.java +++ b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/SimpleToStringPlugin.java @@ -2,23 +2,26 @@ import java.util.Arrays; import java.util.Collection; +import java.util.Iterator; +import java.util.List; import javax.xml.namespace.QName; -import org.jvnet.jaxb.lang.JAXBToStringStrategy; -import org.jvnet.jaxb.lang.ToString; -import org.jvnet.jaxb.lang.ToStringStrategy; -import org.jvnet.jaxb.locator.ObjectLocator; -import org.jvnet.jaxb.plugin.AbstractParameterizablePlugin; +import com.sun.codemodel.JType; +import com.sun.tools.xjc.model.CCustomizations; +import com.sun.tools.xjc.outline.Aspect; +import org.jvnet.jaxb.plugin.ComposedIgnoring; import org.jvnet.jaxb.plugin.CustomizedIgnoring; import org.jvnet.jaxb.plugin.Ignoring; +import org.jvnet.jaxb.plugin.codegenerator.AbstractCodeGeneratorPlugin; +import org.jvnet.jaxb.plugin.codegenerator.CodeGenerator; import org.jvnet.jaxb.plugin.util.FieldOutlineUtils; import org.jvnet.jaxb.plugin.util.StrategyClassUtils; -import org.jvnet.jaxb.util.ClassUtils; +import org.jvnet.jaxb.util.CustomizationUtils; import org.jvnet.jaxb.util.FieldAccessorFactory; +import org.jvnet.jaxb.util.FieldUtils; import org.jvnet.jaxb.util.PropertyFieldAccessorFactory; import org.jvnet.jaxb.xjc.outline.FieldAccessorEx; -import org.xml.sax.ErrorHandler; import com.sun.codemodel.JBlock; import com.sun.codemodel.JCodeModel; @@ -28,213 +31,150 @@ import com.sun.codemodel.JMethod; import com.sun.codemodel.JMod; import com.sun.codemodel.JVar; -import com.sun.tools.xjc.Options; import com.sun.tools.xjc.outline.ClassOutline; import com.sun.tools.xjc.outline.FieldOutline; -import com.sun.tools.xjc.outline.Outline; -public class SimpleToStringPlugin extends AbstractParameterizablePlugin { - - @Override - public String getOptionName() { - return "XsimpleToString"; - } - - @Override - public String getUsage() { - // TODO - return "TBD"; - } - - private FieldAccessorFactory fieldAccessorFactory = PropertyFieldAccessorFactory.INSTANCE; - - public FieldAccessorFactory getFieldAccessorFactory() { - return fieldAccessorFactory; - } - - public void setFieldAccessorFactory( - FieldAccessorFactory fieldAccessorFactory) { - this.fieldAccessorFactory = fieldAccessorFactory; - } - - private String toStringStrategyClass = JAXBToStringStrategy.class.getName(); - - public void setToStringStrategyClass(String toStringStrategy) { - this.toStringStrategyClass = toStringStrategy; - } - - public String getToStringStrategyClass() { - return toStringStrategyClass; - } - - public JExpression createToStringStrategy(JCodeModel codeModel) { - return StrategyClassUtils.createStrategyInstanceExpression(codeModel, - ToStringStrategy.class, getToStringStrategyClass()); - } - - private Ignoring ignoring = new CustomizedIgnoring( - org.jvnet.jaxb.plugin.tostring.Customizations.IGNORED_ELEMENT_NAME, - org.jvnet.jaxb.plugin.tostring.LegacyCustomizations.IGNORED_ELEMENT_NAME, - org.jvnet.jaxb.plugin.Customizations.IGNORED_ELEMENT_NAME, +public class SimpleToStringPlugin extends + AbstractCodeGeneratorPlugin { + + @Override + public String getOptionName() { + return "XsimpleToString"; + } + + @Override + public String getUsage() { + // TODO + return "TBD"; + } + + private FieldAccessorFactory fieldAccessorFactory = PropertyFieldAccessorFactory.INSTANCE; + + public FieldAccessorFactory getFieldAccessorFactory() { + return fieldAccessorFactory; + } + + public void setFieldAccessorFactory(FieldAccessorFactory fieldAccessorFactory) { + this.fieldAccessorFactory = fieldAccessorFactory; + } + + @Override + protected QName getSpecialIgnoredElementName() { + return org.jvnet.jaxb.plugin.tostring.Customizations.IGNORED_ELEMENT_NAME; + } + + private Ignoring ignoring = new ComposedIgnoring( + logger, + new CustomizedIgnoring( + org.jvnet.jaxb.plugin.tostring.Customizations.IGNORED_ELEMENT_NAME, + org.jvnet.jaxb.plugin.Customizations.IGNORED_ELEMENT_NAME, + org.jvnet.jaxb.plugin.Customizations.GENERATED_ELEMENT_NAME), + new CustomizedIgnoring( + org.jvnet.jaxb.plugin.tostring.LegacyCustomizations.IGNORED_ELEMENT_NAME, + org.jvnet.jaxb.plugin.LegacyCustomizations.IGNORED_ELEMENT_NAME, + org.jvnet.jaxb.plugin.LegacyCustomizations.GENERATED_ELEMENT_NAME)); + + @Override + public Ignoring getIgnoring() { + return ignoring; + } + + @Override + public void setIgnoring(Ignoring ignoring) { + this.ignoring = ignoring; + } + + @Override + public Collection getCustomizationElementNames() { + return Arrays.asList( + org.jvnet.jaxb.plugin.tostring.Customizations.IGNORED_ELEMENT_NAME, + org.jvnet.jaxb.plugin.tostring.Customizations.MASKED_ELEMENT_NAME, + org.jvnet.jaxb.plugin.tostring.Customizations.DATE_FORMAT_PATTERN, + org.jvnet.jaxb.plugin.tostring.LegacyCustomizations.IGNORED_ELEMENT_NAME, + org.jvnet.jaxb.plugin.Customizations.IGNORED_ELEMENT_NAME, org.jvnet.jaxb.plugin.Customizations.GENERATED_ELEMENT_NAME, org.jvnet.jaxb.plugin.LegacyCustomizations.IGNORED_ELEMENT_NAME, org.jvnet.jaxb.plugin.LegacyCustomizations.GENERATED_ELEMENT_NAME); - - public Ignoring getIgnoring() { - return ignoring; - } - - public void setIgnoring(Ignoring ignoring) { - this.ignoring = ignoring; - } - - @Override - public Collection getCustomizationElementNames() { - return Arrays - .asList(org.jvnet.jaxb.plugin.tostring.Customizations.IGNORED_ELEMENT_NAME, - org.jvnet.jaxb.plugin.tostring.LegacyCustomizations.IGNORED_ELEMENT_NAME, - org.jvnet.jaxb.plugin.Customizations.IGNORED_ELEMENT_NAME, - org.jvnet.jaxb.plugin.Customizations.GENERATED_ELEMENT_NAME, - org.jvnet.jaxb.plugin.LegacyCustomizations.IGNORED_ELEMENT_NAME, - org.jvnet.jaxb.plugin.LegacyCustomizations.GENERATED_ELEMENT_NAME); - } - - @Override - public boolean run(Outline outline, Options opt, ErrorHandler errorHandler) { - for (final ClassOutline classOutline : outline.getClasses()) - if (!getIgnoring().isIgnored(classOutline)) { - processClassOutline(classOutline); - } - return true; - } - - protected void processClassOutline(ClassOutline classOutline) { - final JDefinedClass theClass = classOutline.implClass; - ClassUtils._implements(theClass, theClass.owner().ref(ToString.class)); - - @SuppressWarnings("unused") - final JMethod object$toString = generateObject$toString(classOutline, - theClass); - @SuppressWarnings("unused") - final JMethod toString$append = generateToString$append(classOutline, - theClass); - @SuppressWarnings("unused") - final JMethod toString$appendFields = generateToString$appendFields( - classOutline, theClass); - } - - protected JMethod generateObject$toString(final ClassOutline classOutline, - final JDefinedClass theClass) { - final JCodeModel codeModel = theClass.owner(); - final JMethod object$toString = theClass.method(JMod.PUBLIC, - codeModel.ref(String.class), "toString"); - object$toString.annotate(Override.class); - { - final JBlock body = object$toString.body(); - - final JVar toStringStrategy = - - body.decl(JMod.FINAL, codeModel.ref(ToStringStrategy.class), - "strategy", createToStringStrategy(codeModel)); - - final JVar buffer = body.decl(JMod.FINAL, - codeModel.ref(StringBuilder.class), "buffer", - JExpr._new(codeModel.ref(StringBuilder.class))); - body.invoke("append").arg(JExpr._null()).arg(buffer) - .arg(toStringStrategy); - body._return(buffer.invoke("toString")); - } - return object$toString; - } - - protected JMethod generateToString$append(final ClassOutline classOutline, - final JDefinedClass theClass) { - final JCodeModel codeModel = theClass.owner(); - final JMethod toString$append = theClass.method(JMod.PUBLIC, - codeModel.ref(StringBuilder.class), "append"); - toString$append.annotate(Override.class); - { - - final JVar locator = toString$append.param(ObjectLocator.class, - "locator"); - final JVar buffer = toString$append.param(StringBuilder.class, - "buffer"); - final JVar toStringStrategy = toString$append.param( - ToStringStrategy.class, "strategy"); - - final JBlock body = toString$append.body(); - - body.invoke(toStringStrategy, "appendStart").arg(locator) - .arg(JExpr._this()).arg(buffer); - body.invoke("appendFields").arg(locator).arg(buffer) - .arg(toStringStrategy); - body.invoke(toStringStrategy, "appendEnd").arg(locator) - .arg(JExpr._this()).arg(buffer); - body._return(buffer); - } - return toString$append; - } - - protected JMethod generateToString$appendFields(ClassOutline classOutline, - final JDefinedClass theClass) { - final JCodeModel codeModel = theClass.owner(); - - final JMethod toString$appendFields = theClass.method(JMod.PUBLIC, - codeModel.ref(StringBuilder.class), "appendFields"); - toString$appendFields.annotate(Override.class); - { - final JVar locator = toString$appendFields.param( - ObjectLocator.class, "locator"); - final JVar buffer = toString$appendFields.param( - StringBuilder.class, "buffer"); - final JVar toStringStrategy = toString$appendFields.param( - ToStringStrategy.class, "strategy"); - final JBlock body = toString$appendFields.body(); - - final Boolean superClassImplementsToString = StrategyClassUtils - .superClassImplements(classOutline, ignoring, - ToString.class); - - if (superClassImplementsToString == null) { - // No superclass - } else if (superClassImplementsToString.booleanValue()) { - body.invoke(JExpr._super(), "appendFields").arg(locator) - .arg(buffer).arg(toStringStrategy); - } else { - // Superclass does not implement ToString - } - - final FieldOutline[] declaredFields = FieldOutlineUtils.filter( - classOutline.getDeclaredFields(), getIgnoring()); - - if (declaredFields.length > 0) { - - for (final FieldOutline fieldOutline : declaredFields) { - final JBlock block = body.block(); - final FieldAccessorEx fieldAccessor = getFieldAccessorFactory() - .createFieldAccessor(fieldOutline, JExpr._this()); - final JVar theValue = block.decl( - fieldAccessor.getType(), - "the" - + fieldOutline.getPropertyInfo().getName( - true)); - final JExpression valueIsSet = (fieldAccessor.isAlwaysSet() || fieldAccessor - .hasSetValue() == null) ? JExpr.TRUE - : fieldAccessor.hasSetValue(); - - fieldAccessor.toRawValue(block, theValue); - - block.invoke(toStringStrategy, "appendField") - .arg(locator) - .arg(JExpr._this()) - .arg(JExpr.lit(fieldOutline.getPropertyInfo() - .getName(false))).arg(buffer).arg(theValue) - .arg(valueIsSet); - } - } - body._return(buffer); - } - return toString$appendFields; - } - + } + + @Override + protected CodeGenerator createCodeGenerator(JCodeModel codeModel) { + return new ToStringCodeGenerator(codeModel); + } + + @Override + protected void generate(ClassOutline classOutline, JDefinedClass theClass) { + final JCodeModel codeModel = theClass.owner(); + final JMethod object$toString = theClass.method(JMod.PUBLIC, + codeModel.ref(String.class), "toString"); + object$toString.annotate(Override.class); + { + final JBlock body = object$toString.body(); + + final JVar buffer = body.decl(JMod.FINAL, + codeModel.ref(StringBuilder.class), "buffer", + JExpr._new(codeModel.ref(StringBuilder.class))); + + body.invoke(buffer, "append").arg(theClass.dotclass().invoke("getSimpleName")); + body.invoke(buffer, "append").arg("@"); + body.invoke(buffer, "append").arg(codeModel.ref(Integer.class).staticInvoke("toHexString").arg(JExpr._this().invoke("hashCode"))); + body.invoke(buffer, "append").arg("["); + + List fields = FieldOutlineUtils.filterToList(classOutline.getDeclaredFields(), getIgnoring()); + + appendFieldsIn(fields, classOutline, theClass, body, buffer); + + body.invoke(buffer, "append").arg("]"); + + body._return(buffer.invoke("toString")); + } + } + + private void appendFieldsIn(List declaredFields, ClassOutline classOutline, JDefinedClass theClass, JBlock body, JVar buffer) { + Boolean superClassNotIgnored = StrategyClassUtils.superClassNotIgnored(classOutline, ignoring); + ClassOutline superClass = classOutline.getSuperClass(); + if (Boolean.TRUE.equals(superClassNotIgnored) && superClass != null) { + final JBlock block = body.block(); + block.invoke(buffer, "append").arg(JExpr._super().invoke("toString")); + if (declaredFields.size() > 0) { + block.invoke(buffer, "append").arg(ToStringCodeGenerationImplementor.FIELD_SEPARATOR); + } + } + + final JCodeModel codeModel = theClass.owner(); + if (declaredFields.size() > 0) { + Iterator fieldIterator = declaredFields.iterator(); + while (fieldIterator.hasNext()) { + final FieldOutline fieldOutline = fieldIterator.next(); + final String privateFieldName = fieldOutline.getPropertyInfo().getName(false); + final JBlock block = body.block(); + final FieldAccessorEx fieldAccessor = getFieldAccessorFactory().createFieldAccessor(fieldOutline, JExpr._this()); + // declare var and affect it with field value + final JVar theValue = block.decl( + fieldAccessor.getType(), + "the" + fieldOutline.getPropertyInfo().getName(true)); + fieldAccessor.toRawValue(block, theValue); + final JType exposedType = fieldAccessor.getType(); + + final Collection possibleTypes = FieldUtils.getPossibleTypes(fieldOutline, Aspect.EXPOSED); + final boolean isAlwaysSet = fieldAccessor.isAlwaysSet(); + + final JExpression hasSetValue = (fieldAccessor.isAlwaysSet() || fieldAccessor + .hasSetValue() == null) ? JExpr.TRUE + : fieldAccessor.hasSetValue(); + + CCustomizations customizations = CustomizationUtils.getCustomizations(fieldOutline); + getCodeGenerator().generate( + block, + exposedType, + possibleTypes, + isAlwaysSet, + new ToStringArguments(codeModel, classOutline, buffer, + privateFieldName, theValue, hasSetValue, customizations)); + if (fieldIterator.hasNext()) { + block.invoke(buffer, "append").arg(ToStringCodeGenerationImplementor.FIELD_SEPARATOR); + } + } + } + } } diff --git a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/ToStringArguments.java b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/ToStringArguments.java new file mode 100644 index 000000000..e2534c0c4 --- /dev/null +++ b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/ToStringArguments.java @@ -0,0 +1,136 @@ +package org.jvnet.jaxb.plugin.simpletostring; + +import com.sun.codemodel.JBlock; +import com.sun.codemodel.JCodeModel; +import com.sun.codemodel.JConditional; +import com.sun.codemodel.JExpr; +import com.sun.codemodel.JExpression; +import com.sun.codemodel.JMod; +import com.sun.codemodel.JType; +import com.sun.codemodel.JVar; +import com.sun.tools.xjc.model.CCustomizations; +import com.sun.tools.xjc.outline.ClassOutline; +import org.apache.commons.lang3.Validate; +import org.jvnet.jaxb.plugin.codegenerator.Arguments; + +import java.util.Collection; +import java.util.ListIterator; + +public class ToStringArguments implements Arguments { + + private final JCodeModel codeModel; + private final ClassOutline classOutline; + private final JVar buffer; + private final String fieldName; + private final JVar value; + private final JExpression hasSetValue; + private final CCustomizations customizations; + + public ToStringArguments(JCodeModel codeModel, ClassOutline classOutline, JVar buffer, String fieldName, JVar value, JExpression hasSetValue, CCustomizations customizations) { + this.codeModel = Validate.notNull(codeModel); + this.classOutline = Validate.notNull(classOutline); + this.buffer = Validate.notNull(buffer); + this.fieldName = Validate.notNull(fieldName); + this.value = Validate.notNull(value); + this.hasSetValue = Validate.notNull(hasSetValue); + this.customizations = customizations; + } + + private JCodeModel getCodeModel() { + return codeModel; + } + + public JVar buffer() { + return buffer; + } + + public ClassOutline classOutline() { + return classOutline; + } + + public String fieldName() { + return fieldName; + } + + public JVar value() { + return value; + } + + public JExpression hasSetValue() { + return hasSetValue; + } + + public CCustomizations customizations() { + return customizations; + } + + private ToStringArguments spawn(String fieldName, JVar value, JExpression hasSetValue, CCustomizations customizations) { + return new ToStringArguments(getCodeModel(), classOutline(), buffer(), fieldName, value, hasSetValue, customizations); + } + + public ToStringArguments property(JBlock block, String propertyName, + String propertyMethod, JType declarablePropertyType, + JType propertyType, Collection possiblePropertyTypes) { + final JVar propertyValue = block.decl(JMod.FINAL, + declarablePropertyType, value().name() + propertyName, value().invoke(propertyMethod)); + // We assume that primitive properties are always set + boolean isAlwaysSet = propertyType.isPrimitive(); + final JExpression propertyHasSetValue = isAlwaysSet ? JExpr.TRUE : propertyValue.ne(JExpr._null()); + return spawn(propertyName, propertyValue, propertyHasSetValue, null); + } + + public ToStringArguments iterator(JBlock block, JType elementType) { + final JVar listIterator = block + .decl(JMod.FINAL, getCodeModel().ref(ListIterator.class) + .narrow(elementType), value().name() + "ListIterator", + value().invoke("listIterator")); + + return spawn(fieldName(), listIterator, JExpr.TRUE, customizations()); + } + + public ToStringArguments element(JBlock subBlock, JType elementType) { + final JVar elementValue = subBlock.decl(JMod.FINAL, elementType, + value().name() + "Element", value().invoke("next")); + final boolean isElementAlwaysSet = elementType.isPrimitive(); + final JExpression elementHasSetValue = isElementAlwaysSet ? JExpr.TRUE + : elementValue.ne(JExpr._null()); + return spawn(fieldName(), elementValue, elementHasSetValue, customizations()); + + } + + public JExpression _instanceof(JType type) { + return value()._instanceof(type); + } + + public ToStringArguments cast(String suffix, JBlock block, + JType jaxbElementType, boolean suppressWarnings) { + final JVar castedValue = block.decl(JMod.FINAL, jaxbElementType, + value().name() + suffix, JExpr.cast(jaxbElementType, value())); + if (suppressWarnings) { + castedValue.annotate(SuppressWarnings.class).param("value", "unchecked"); + } + return spawn(fieldName(), castedValue, JExpr.TRUE, customizations()); + } + + public JBlock ifHasSetValue(JBlock block, boolean isAlwaysSet, boolean checkForNullRequired) { + if (isAlwaysSet || !checkForNullRequired) { + return block; + } else { + return block._if(hasSetValue())._then(); + } + } + + public JConditional ifConditionHasSetValue(JBlock block, boolean isAlwaysSet, boolean checkForNullRequired) { + if (isAlwaysSet || !checkForNullRequired) { + return null; + } else { + return block._if(hasSetValue()); + } + } + + public JBlock _while(JBlock block) { + final JBlock subBlock = block._while(value().invoke("hasNext")).body(); + return subBlock; + } + +} diff --git a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/ToStringCodeGenerationImplementor.java b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/ToStringCodeGenerationImplementor.java new file mode 100644 index 000000000..f313be4f4 --- /dev/null +++ b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/ToStringCodeGenerationImplementor.java @@ -0,0 +1,182 @@ +package org.jvnet.jaxb.plugin.simpletostring; + +import com.sun.codemodel.JBlock; +import com.sun.codemodel.JClass; +import com.sun.codemodel.JCodeModel; +import com.sun.codemodel.JConditional; +import com.sun.codemodel.JExpr; +import com.sun.codemodel.JExpression; +import com.sun.codemodel.JFieldVar; +import com.sun.codemodel.JMod; +import com.sun.tools.xjc.model.CCustomizations; +import com.sun.tools.xjc.model.CPluginCustomization; +import org.jvnet.jaxb.plugin.codegenerator.AbstractCodeGenerationImplementor; +import org.jvnet.jaxb.plugin.tostring.Customizations; +import org.jvnet.jaxb.plugin.tostring.DateFormatClass; +import org.jvnet.jaxb.util.CustomizationUtils; + +import javax.xml.datatype.XMLGregorianCalendar; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.Temporal; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.Map; +import java.util.TreeMap; + +public class ToStringCodeGenerationImplementor extends + AbstractCodeGenerationImplementor { + + private static final JExpression EQ_EXPR = JExpr.lit("="); + private static final JExpression NULL_EXPR = JExpr.lit(""); + private static final JExpression MASKED_EXPR = JExpr.lit("****"); + public static final String FIELD_SEPARATOR = ", "; + public static final Map DATE_TIME_FORMATTER_BY_PATTERN = new TreeMap<>(); + public int dateTimeFormatterIndex = 0; + public ToStringCodeGenerationImplementor(JCodeModel codeModel) { + super(codeModel); + } + + private void ifHasSetValueAppendToStringElseAppendNull( + ToStringArguments arguments, JBlock block, + JExpression valueToString, boolean isAlwaysSet, + boolean checkForNullRequired) { + block.invoke(arguments.buffer(), "append").arg(arguments.fieldName()); + block.invoke(arguments.buffer(), "append").arg(EQ_EXPR); + + JConditional conditionalHasSetValue = arguments.ifConditionHasSetValue(block, isAlwaysSet, checkForNullRequired); + CCustomizations customizations = arguments.customizations(); + + // date / temporal specific treatments + if (arguments.value().type() instanceof JClass) { + boolean isDate = ((JClass) arguments.value().type()).isAssignableFrom(getCodeModel().ref(Date.class)); + boolean isCalendar = ((JClass) arguments.value().type()).isAssignableFrom(getCodeModel().ref(Calendar.class)); + boolean isXMLCalendar = ((JClass) arguments.value().type()).isAssignableFrom(getCodeModel().ref(XMLGregorianCalendar.class)); + boolean isTemporal = ((JClass) arguments.value().type()).isAssignableFrom(getCodeModel().ref(Temporal.class)); + if (isDate || isCalendar || isXMLCalendar || isTemporal) { + CPluginCustomization formatDateCustomization = customizations == null ? null + : customizations.find(Customizations.DATE_FORMAT_PATTERN.getNamespaceURI(), Customizations.DATE_FORMAT_PATTERN.getLocalPart()); + if (formatDateCustomization != null) { + JExpression defaultExpr = arguments.value(); + if (isXMLCalendar) { + defaultExpr = defaultExpr.invoke("toGregorianCalendar"); + } + if (!isTemporal) { + defaultExpr = getCodeModel().ref(ZonedDateTime.class) + .staticInvoke("ofInstant") + .arg(defaultExpr.invoke("toInstant")) + .arg(getCodeModel().ref(ZoneId.class).staticInvoke("systemDefault")); + } + DateFormatClass dateFormatClass = (DateFormatClass) CustomizationUtils.unmarshall(Customizations.getContext(), formatDateCustomization); + // validate the pattern + if (dateFormatClass.getFormat() != null) { + DateTimeFormatter.ofPattern(dateFormatClass.getFormat()); + String staticFieldName = DATE_TIME_FORMATTER_BY_PATTERN.get(dateFormatClass.getFormat()); + if (staticFieldName == null) { + staticFieldName = "DATE_TIME_FORMATTER_" + dateTimeFormatterIndex++; + DATE_TIME_FORMATTER_BY_PATTERN.put(dateFormatClass.getFormat(), staticFieldName); + } + JFieldVar field = arguments.classOutline().ref.fields().get(staticFieldName); + if (field == null) { + field = arguments.classOutline().ref.field(JMod.STATIC | JMod.FINAL | JMod.PRIVATE, + getCodeModel().ref(DateTimeFormatter.class), + staticFieldName, + getCodeModel().ref(DateTimeFormatter.class).staticInvoke("ofPattern").arg(dateFormatClass.getFormat())); + } + valueToString = defaultExpr.invoke("format").arg(field); + formatDateCustomization.markAsAcknowledged(); + } else if (dateFormatClass.getFormatRef() != null) { + try { + DateTimeFormatter.class.getField(dateFormatClass.getFormatRef()); + valueToString = getCodeModel().ref(DateTimeFormatter.class).staticRef(dateFormatClass.getFormatRef()) + .invoke("format").arg(defaultExpr); + formatDateCustomization.markAsAcknowledged(); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + } + } + } + boolean isMasked = false; + CPluginCustomization maskedCustomization = customizations == null ? null + : customizations.find(Customizations.MASKED_ELEMENT_NAME.getNamespaceURI(), Customizations.MASKED_ELEMENT_NAME.getLocalPart()); + + if (maskedCustomization != null) { + isMasked = true; + maskedCustomization.markAsAcknowledged(); + } + if (conditionalHasSetValue == null) { + block.invoke(arguments.buffer(), "append").arg(isMasked ? MASKED_EXPR : valueToString); + } else { + conditionalHasSetValue._then().invoke(arguments.buffer(), "append").arg(isMasked ? MASKED_EXPR : valueToString); + conditionalHasSetValue._else().invoke(arguments.buffer(), "append").arg(NULL_EXPR); + } + } + + @Override + public void onArray(JBlock block, boolean isAlwaysSet, ToStringArguments arguments) { + ifHasSetValueAppendToStringElseAppendNull( + arguments, + block, + getCodeModel().ref(Arrays.class).staticInvoke("toString") + .arg(arguments.value()), isAlwaysSet, true); + } + + @Override + public void onBoolean(ToStringArguments arguments, JBlock block, boolean isAlwaysSet) { + ifHasSetValueAppendToStringElseAppendNull(arguments, block, + arguments.value(), isAlwaysSet, true); + } + + @Override + public void onByte(ToStringArguments arguments, JBlock block, boolean isAlwaysSet) { + ifHasSetValueAppendToStringElseAppendNull(arguments, block, + arguments.value(), isAlwaysSet, true); + } + + @Override + public void onChar(ToStringArguments arguments, JBlock block, boolean isAlwaysSet) { + ifHasSetValueAppendToStringElseAppendNull(arguments, block, + arguments.value(), isAlwaysSet, true); + } + + @Override + public void onDouble(ToStringArguments arguments, JBlock block, boolean isAlwaysSet) { + ifHasSetValueAppendToStringElseAppendNull(arguments, block, + arguments.value(), isAlwaysSet, true); + } + + @Override + public void onFloat(ToStringArguments arguments, JBlock block, boolean isAlwaysSet) { + ifHasSetValueAppendToStringElseAppendNull(arguments, block, + arguments.value(), isAlwaysSet, true); + } + + @Override + public void onInt(ToStringArguments arguments, JBlock block, + boolean isAlwaysSet) { + ifHasSetValueAppendToStringElseAppendNull(arguments, block, + arguments.value(), isAlwaysSet, true); + } + + @Override + public void onLong(ToStringArguments arguments, JBlock block, boolean isAlwaysSet) { + ifHasSetValueAppendToStringElseAppendNull(arguments, block, + arguments.value(), isAlwaysSet, true); + } + + @Override + public void onShort(ToStringArguments arguments, JBlock block, boolean isAlwaysSet) { + ifHasSetValueAppendToStringElseAppendNull(arguments, block, + arguments.value(), isAlwaysSet, true); + } + + @Override + public void onObject(ToStringArguments arguments, JBlock block, boolean isAlwaysSet) { + ifHasSetValueAppendToStringElseAppendNull(arguments, block, + arguments.value(), isAlwaysSet, true); + } +} diff --git a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/ToStringCodeGenerator.java b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/ToStringCodeGenerator.java new file mode 100644 index 000000000..37a814d43 --- /dev/null +++ b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/simpletostring/ToStringCodeGenerator.java @@ -0,0 +1,14 @@ +package org.jvnet.jaxb.plugin.simpletostring; + +import com.sun.codemodel.JCodeModel; +import org.apache.commons.lang3.Validate; +import org.jvnet.jaxb.plugin.codegenerator.CodeGenerationAbstraction; + +public class ToStringCodeGenerator extends + CodeGenerationAbstraction { + + public ToStringCodeGenerator(JCodeModel codeModel) { + super(new ToStringCodeGenerationImplementor(Validate.notNull(codeModel))); + } + +} diff --git a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/Customizations.java b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/Customizations.java index 61b5a2561..6f678b687 100644 --- a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/Customizations.java +++ b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/Customizations.java @@ -1,11 +1,32 @@ package org.jvnet.jaxb.plugin.tostring; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import org.glassfish.jaxb.runtime.v2.ContextFactory; + import javax.xml.namespace.QName; public class Customizations { - public static String NAMESPACE_URI = "urn:jaxb.jvnet.org:plugin:toString"; + public static final String NAMESPACE_URI = "urn:jaxb.jvnet.org:plugin:toString"; public static QName IGNORED_ELEMENT_NAME = new QName(NAMESPACE_URI, "ignored"); + public static QName MASKED_ELEMENT_NAME = new QName(NAMESPACE_URI, "masked"); + public static final QName DATE_FORMAT_PATTERN = new QName(NAMESPACE_URI, "date-format"); + + private static final JAXBContext context; + static { + try { + context = ContextFactory.createContext( + ObjectFactory.class.getPackage().getName(), + ObjectFactory.class.getClassLoader(), + null); + } catch (JAXBException e) { + throw new ExceptionInInitializerError(e); + } + } + public static JAXBContext getContext() { + return context; + } } diff --git a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/DateFormatClass.java b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/DateFormatClass.java new file mode 100644 index 000000000..779aadec1 --- /dev/null +++ b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/DateFormatClass.java @@ -0,0 +1,38 @@ +package org.jvnet.jaxb.plugin.tostring; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlRootElement; +import jakarta.xml.bind.annotation.XmlValue; +import jakarta.xml.bind.annotation.adapters.CollapsedStringAdapter; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +@XmlRootElement(namespace = Customizations.NAMESPACE_URI, name = "date-format") +@XmlAccessorType(XmlAccessType.PROPERTY) +public class DateFormatClass { + + private String format; + + private String formatRef; + + @XmlAttribute + @XmlJavaTypeAdapter(value = CollapsedStringAdapter.class) + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + @XmlAttribute + @XmlJavaTypeAdapter(value = CollapsedStringAdapter.class) + public String getFormatRef() { + return formatRef; + } + + public void setFormatRef(String formatRef) { + this.formatRef = formatRef; + } +} diff --git a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/ObjectFactory.java b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/ObjectFactory.java new file mode 100644 index 000000000..068fb293f --- /dev/null +++ b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/ObjectFactory.java @@ -0,0 +1,11 @@ +package org.jvnet.jaxb.plugin.tostring; + +import jakarta.xml.bind.annotation.XmlRegistry; + +@XmlRegistry +public class ObjectFactory { + + public DateFormatClass createDateFormatClass() { + return new DateFormatClass(); + } +} diff --git a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/ToStringPlugin.java b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/ToStringPlugin.java index 2fea6d847..f82fb44cc 100644 --- a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/ToStringPlugin.java +++ b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/tostring/ToStringPlugin.java @@ -95,6 +95,7 @@ public void setIgnoring(Ignoring ignoring) { public Collection getCustomizationElementNames() { return Arrays .asList(org.jvnet.jaxb.plugin.tostring.Customizations.IGNORED_ELEMENT_NAME, + org.jvnet.jaxb.plugin.tostring.Customizations.MASKED_ELEMENT_NAME, org.jvnet.jaxb.plugin.tostring.LegacyCustomizations.IGNORED_ELEMENT_NAME, org.jvnet.jaxb.plugin.Customizations.IGNORED_ELEMENT_NAME, org.jvnet.jaxb.plugin.Customizations.GENERATED_ELEMENT_NAME, diff --git a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/util/ArrayUtils.java b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/util/ArrayUtils.java index 088cbf6bf..a786fb39a 100644 --- a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/util/ArrayUtils.java +++ b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/util/ArrayUtils.java @@ -29,4 +29,21 @@ public static T[] filter(T[] array, Predicate predicate, } + public static List filterToList(T[] array, Predicate predicate, + Class theClass) { + + if (array == null) { + return null; + } else { + final List list = new LinkedList(); + for (T item : array) { + if (predicate.evaluate(item)) { + list.add(item); + } + } + return list; + } + + } + } diff --git a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/util/FieldOutlineUtils.java b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/util/FieldOutlineUtils.java index f84e98170..7a18d0ced 100644 --- a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/util/FieldOutlineUtils.java +++ b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/util/FieldOutlineUtils.java @@ -4,6 +4,8 @@ import com.sun.tools.xjc.outline.FieldOutline; +import java.util.List; + public class FieldOutlineUtils { private FieldOutlineUtils() { @@ -19,4 +21,14 @@ public boolean evaluate(FieldOutline fieldOutline) { } }, FieldOutline.class); } + + public static List filterToList(final FieldOutline[] fieldOutlines, + final Ignoring ignoring) { + return ArrayUtils.filterToList(fieldOutlines, new Predicate() { + public boolean evaluate(FieldOutline fieldOutline) { + return !ignoring.isIgnored(fieldOutline); + + } + }, FieldOutline.class); + } } diff --git a/jaxb-plugins-parent/jaxb-plugins/src/main/resources/META-INF/services/com.sun.tools.xjc.Plugin b/jaxb-plugins-parent/jaxb-plugins/src/main/resources/META-INF/services/com.sun.tools.xjc.Plugin index 150129a09..c4bb112a6 100644 --- a/jaxb-plugins-parent/jaxb-plugins/src/main/resources/META-INF/services/com.sun.tools.xjc.Plugin +++ b/jaxb-plugins-parent/jaxb-plugins/src/main/resources/META-INF/services/com.sun.tools.xjc.Plugin @@ -12,6 +12,7 @@ org.jvnet.jaxb.plugin.elementwrapper.ElementWrapperPlugin org.jvnet.jaxb.plugin.simplify.SimplifyPlugin org.jvnet.jaxb.plugin.simplehashcode.SimpleHashCodePlugin org.jvnet.jaxb.plugin.simpleequals.SimpleEqualsPlugin +org.jvnet.jaxb.plugin.simpletostring.SimpleToStringPlugin org.jvnet.jaxb.plugin.enumvalue.EnumValuePlugin org.jvnet.jaxb.plugin.fixjaxb1058.FixJAXB1058Plugin org.jvnet.jaxb.plugin.customizations.CustomizationsPlugin diff --git a/jaxb-plugins-parent/tests/qa-simple/pom.xml b/jaxb-plugins-parent/tests/qa-simple/pom.xml index e2c347b82..8502dedf3 100644 --- a/jaxb-plugins-parent/tests/qa-simple/pom.xml +++ b/jaxb-plugins-parent/tests/qa-simple/pom.xml @@ -17,8 +17,8 @@ test - org.jvnet.jaxb - jaxb-plugins-runtime + org.slf4j + slf4j-simple @@ -30,8 +30,7 @@ true - - + -XsimpleToString -XsimpleEquals -XsimpleHashCode diff --git a/jaxb-plugins-parent/tests/qa-simple/src/main/resources/binding.xjb b/jaxb-plugins-parent/tests/qa-simple/src/main/resources/binding.xjb index dad44fa73..d8c4242d8 100644 --- a/jaxb-plugins-parent/tests/qa-simple/src/main/resources/binding.xjb +++ b/jaxb-plugins-parent/tests/qa-simple/src/main/resources/binding.xjb @@ -15,7 +15,7 @@ - + diff --git a/jaxb-plugins-parent/tests/qa-simple/src/main/resources/schema.xsd b/jaxb-plugins-parent/tests/qa-simple/src/main/resources/schema.xsd index e547ac9ba..19939baef 100644 --- a/jaxb-plugins-parent/tests/qa-simple/src/main/resources/schema.xsd +++ b/jaxb-plugins-parent/tests/qa-simple/src/main/resources/schema.xsd @@ -1,7 +1,9 @@ + xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb" + xmlns:toString="urn:jaxb.jvnet.org:plugin:toString" + jaxb:version="3.0" + jaxb:extensionBindingPrefixes="toString"> @@ -56,6 +58,11 @@ + + + + + @@ -71,7 +78,13 @@ - + + + + + + + @@ -94,7 +107,13 @@ - + + + + + + + @@ -122,6 +141,13 @@ + + + + + + + diff --git a/jaxb-plugins-parent/tests/qa-simple/src/test/java/org/jvnet/jaxb/tests/qa/simple/ToStringTest.java b/jaxb-plugins-parent/tests/qa-simple/src/test/java/org/jvnet/jaxb/tests/qa/simple/ToStringTest.java new file mode 100644 index 000000000..fa192d711 --- /dev/null +++ b/jaxb-plugins-parent/tests/qa-simple/src/test/java/org/jvnet/jaxb/tests/qa/simple/ToStringTest.java @@ -0,0 +1,21 @@ +package org.jvnet.jaxb.tests.qa.simple; + +import jakarta.xml.bind.JAXBElement; +import org.jvnet.jaxb.test.AbstractSamplesTest; + +import java.io.File; + +public class ToStringTest extends AbstractSamplesTest { + + @Override + protected void checkSample(File sample) throws Exception { + + final Object object = createContext().createUnmarshaller().unmarshal(sample); + if (object instanceof JAXBElement) { + System.out.println(((JAXBElement) object).getValue().toString()); + } else { + System.out.println(object.toString()); + } + } + +} diff --git a/jaxb-plugins-parent/tests/qa-simple/src/test/resources/log4j.properties b/jaxb-plugins-parent/tests/qa-simple/src/test/resources/log4j.properties deleted file mode 100644 index ca4ee5e2c..000000000 --- a/jaxb-plugins-parent/tests/qa-simple/src/test/resources/log4j.properties +++ /dev/null @@ -1,5 +0,0 @@ -log4j.rootCategory=DEBUG, stdout -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.target=system.out -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n diff --git a/jaxb-plugins-parent/tests/qa-simple/src/test/samples/1.xml b/jaxb-plugins-parent/tests/qa-simple/src/test/samples/1.xml new file mode 100644 index 000000000..56bd94578 --- /dev/null +++ b/jaxb-plugins-parent/tests/qa-simple/src/test/samples/1.xml @@ -0,0 +1,11 @@ + + a + b + c + d + e + + 0 + 1 + + diff --git a/jaxb-plugins-parent/tests/qa-simple/src/test/samples/2.xml b/jaxb-plugins-parent/tests/qa-simple/src/test/samples/2.xml new file mode 100644 index 000000000..286906526 --- /dev/null +++ b/jaxb-plugins-parent/tests/qa-simple/src/test/samples/2.xml @@ -0,0 +1,4 @@ + + a + 2001-01-01T00:00:00 + diff --git a/jaxb-plugins-parent/tests/qa-simple/src/test/samples/3.xml b/jaxb-plugins-parent/tests/qa-simple/src/test/samples/3.xml new file mode 100644 index 000000000..0c95afe1d --- /dev/null +++ b/jaxb-plugins-parent/tests/qa-simple/src/test/samples/3.xml @@ -0,0 +1,4 @@ + + 2001-01-01 + myPassword +