Skip to content

Commit

Permalink
[highsource#433] SimpleToString with dep-free execution
Browse files Browse the repository at this point in the history
  • Loading branch information
laurentschoelens committed Oct 20, 2023
1 parent 7085316 commit febc5b0
Show file tree
Hide file tree
Showing 19 changed files with 713 additions and 225 deletions.

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

0 comments on commit febc5b0

Please sign in to comment.