Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#433] SimpleToString with dep-free execution #446

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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<ToStringArguments> {

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<JType> 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;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
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<ToStringArguments> {

private static final JExpression EQ_EXPR = JExpr.lit("=");
private static final JExpression NULL_EXPR = JExpr.lit("<null>");
private static final JExpression MASKED_EXPR = JExpr.lit("****");
public static final String FIELD_SEPARATOR = ", ";
public static final Map<String, String> DATE_TIME_FORMATTER_BY_PATTERN = new TreeMap<>();
public int dateTimeFormatterIndex = 0;
private final String defaultDateFormatterRef;
private final String defaultDateFormatterPattern;
public ToStringCodeGenerationImplementor(JCodeModel codeModel, String defaultDateFormatterRef, String defaultDateFormatterPattern) {
super(codeModel);
this.defaultDateFormatterRef = defaultDateFormatterRef;
this.defaultDateFormatterPattern = defaultDateFormatterPattern;
}

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) {
valueToString = handlePossibleDateField(arguments, valueToString);
}
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);
}
}

private JExpression handlePossibleDateField(ToStringArguments arguments, JExpression valueToString) {
boolean isDate = getCodeModel().ref(Date.class).isAssignableFrom((JClass) arguments.value().type());
boolean isCalendar = getCodeModel().ref(Calendar.class).isAssignableFrom(((JClass) arguments.value().type()));
boolean isXMLCalendar = getCodeModel().ref(XMLGregorianCalendar.class).isAssignableFrom(((JClass) arguments.value().type()));
boolean isTemporal = getCodeModel().ref(Temporal.class).isAssignableFrom(((JClass) arguments.value().type()));
if (isDate || isCalendar || isXMLCalendar || isTemporal) {
CCustomizations customizations = arguments.customizations();
CPluginCustomization formatDateCustomization = customizations == null ? null
: customizations.find(Customizations.DATE_FORMAT_PATTERN.getNamespaceURI(), Customizations.DATE_FORMAT_PATTERN.getLocalPart());
if (formatDateCustomization != null || defaultDateFormatterRef != null || defaultDateFormatterPattern != 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 = formatDateCustomization == null ?
null : (DateFormatClass) CustomizationUtils.unmarshall(Customizations.getContext(), formatDateCustomization);
String formatRef = dateFormatClass == null ? defaultDateFormatterRef : dateFormatClass.getFormatRef();
String format = dateFormatClass == null ? defaultDateFormatterPattern : dateFormatClass.getFormat();
if (formatRef != null) {
try {
// validate the ref
DateTimeFormatter.class.getField(formatRef);
valueToString = getCodeModel().ref(DateTimeFormatter.class).staticRef(formatRef)
.invoke("format").arg(defaultExpr);
if (formatDateCustomization != null) {
formatDateCustomization.markAsAcknowledged();
}
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
} else if (format != null) {
// validate the pattern
DateTimeFormatter.ofPattern(format);
String staticFieldName = DATE_TIME_FORMATTER_BY_PATTERN.get(format);
if (staticFieldName == null) {
staticFieldName = "DATE_TIME_FORMATTER_" + dateTimeFormatterIndex++;
DATE_TIME_FORMATTER_BY_PATTERN.put(format, 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(format));
}
valueToString = defaultExpr.invoke("format").arg(field);
if (formatDateCustomization != null) {
formatDateCustomization.markAsAcknowledged();
}
}
}
}
return valueToString;
}

@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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.jvnet.jaxb.plugin.simpletostring;

import com.sun.codemodel.JCodeModel;
import org.apache.commons.lang3.Validate;
import org.jvnet.jaxb.plugin.codegenerator.CodeGenerationAbstraction;

import java.time.format.DateTimeFormatter;

public class ToStringCodeGenerator extends
CodeGenerationAbstraction<ToStringArguments> {

public ToStringCodeGenerator(JCodeModel codeModel, String defaultDateFormatterRef, String defaultDateFormatterPattern) {
super(new ToStringCodeGenerationImplementor(Validate.notNull(codeModel), defaultDateFormatterRef, defaultDateFormatterPattern));
}

}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading