Skip to content

Commit

Permalink
Considering also types from @XmlElements annotation which are potenti…
Browse files Browse the repository at this point in the history
…al candidates to be moved one level up (fixes #62).
  • Loading branch information
dmak committed Jan 23, 2023
1 parent 6fd1fd7 commit 1d2dcc1
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 73 deletions.
48 changes: 18 additions & 30 deletions src/main/java/com/sun/tools/xjc/addon/xew/Candidate.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,44 +25,41 @@
* of objects.
*/
public final class Candidate {
private final JDefinedClass candidateClass;
private final JDefinedClass candidateClass;

private final JFieldVar field;
private final JFieldVar field;

private final CPropertyInfo fieldPropertyInfo;
private final CPropertyInfo fieldPropertyInfo;

private final String fieldTargetNamespace;
private final String fieldTargetNamespace;

private final JDefinedClass fieldParametrisationClass;

private final JDefinedClass fieldParametrisationImpl;
private final Collection<ParametrisationInfo> parametrisationInfos;

// Order matters (value Object Factory is first):
private final Map<String, JDefinedClass> objectFactoryClasses = new LinkedHashMap<>();
private final Map<String, JDefinedClass> objectFactoryClasses = new LinkedHashMap<>();

private final boolean valueObjectDisabled;
private final boolean valueObjectDisabled;

// Order matters as it affects the order of generated methods in Object Factory:
private final Map<JMethod, ScopedMethodInfo> scopedFactoryMethods = new LinkedHashMap<>();
private final Map<JMethod, ScopedMethodInfo> scopedFactoryMethods = new LinkedHashMap<>();

/**
* By default the candidate is marked for removal unless something prevents it from being removed.
*/
private boolean markedForRemoval = true;
private boolean markedForRemoval = true;

/**
* Number of times this candidate has been substituted in the model.
*/
private int substitutionsCount;
private int substitutionsCount;

Candidate(JDefinedClass candidateClass, CClassInfo candidateClassInfo, JFieldVar field,
JDefinedClass fieldParametrizationClass, JDefinedClass fieldParametrisationImpl,
JClass xmlElementDeclModelClass, JClass xmlSchemaModelClass) {
Collection<ParametrisationInfo> parametrisationInfos, JClass xmlElementDeclModelClass,
JClass xmlSchemaModelClass) {
this.candidateClass = candidateClass;
this.field = field;
this.fieldPropertyInfo = candidateClassInfo.getProperty(field.name());
this.fieldParametrisationClass = fieldParametrizationClass;
this.fieldParametrisationImpl = fieldParametrisationImpl;
this.parametrisationInfos = parametrisationInfos;
this.valueObjectDisabled = addObjectFactoryForClass(candidateClass);
this.fieldTargetNamespace = getTargetNamespace(candidateClassInfo, xmlSchemaModelClass);
collectScopedFactoryMethods(xmlElementDeclModelClass);
Expand Down Expand Up @@ -138,7 +135,7 @@ public String getFieldName() {
}

/**
* The class of the only field in container class (collection interface or concrete implementation).
* The class of the only field in container class (collection interface or particular implementation).
*/
public JClass getFieldClass() {
return (JClass) field.type();
Expand All @@ -159,20 +156,11 @@ public String getFieldTargetNamespace() {
}

/**
* The only parametrisation class of the field (collection type). In case of basic parametrisation like
* {@code List<String>} this property is {@code null}.
*/
public JDefinedClass getFieldParametrisationClass() {
return fieldParametrisationClass;
}

/**
* If {@link #getFieldParametrisationClass()} is an interface, then this holds the same value. Otherwise it holds
* the implementation (value object) of {@link #getFieldParametrisationClass()}. In case of basic parametrisation
* like {@code List<String>} this property is {@code null}.
* The list of parametrisation classes of the field (collection types). In case of basic parametrisation like
* {@code List<String>} this collection is empty.
*/
public JDefinedClass getFieldParametrisationImpl() {
return fieldParametrisationImpl;
public Collection<ParametrisationInfo> getParametrisationInfos() {
return parametrisationInfos;
}

/**
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/com/sun/tools/xjc/addon/xew/ParametrisationInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.sun.tools.xjc.addon.xew;

import com.sun.codemodel.JDefinedClass;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

/**
* Container for parametrisation class and corresponding implementation. If value objects are enabled, then class and
* implementation refer the same class for simplicity.
*/
public class ParametrisationInfo {

public final JDefinedClass parametrisationClass;

public JDefinedClass parametrisationImpl;

public ParametrisationInfo(JDefinedClass parametrisationClass) {
this.parametrisationClass = parametrisationClass;
}

@Override
public String toString() {
return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import static com.sun.tools.xjc.addon.xew.CommonUtils.isHiddenClass;
import static com.sun.tools.xjc.addon.xew.CommonUtils.isListedAsParametrisation;
import static com.sun.tools.xjc.addon.xew.CommonUtils.setPrivateField;
import static java.util.Collections.singletonList;

import java.io.IOException;
import java.util.ArrayList;
Expand Down Expand Up @@ -153,7 +154,7 @@ protected void runInternal(Outline outline) throws ClassNotFoundException, IOExc
// Write information on candidate classes to summary file.
writeSummary("Candidates:");

for (Candidate candidate : findCandidateClasses(outline, xmlElementDeclModelClass)) {
for (Candidate candidate : findCandidateClasses(outline, xmlElementsModelClass, xmlElementDeclModelClass)) {
if (globalConfiguration.isClassIncluded(candidate.getClassName())) {
if (globalConfiguration.isClassUnmarkedForRemoval(candidate.getClassName())) {
candidate.unmarkForRemoval();
Expand Down Expand Up @@ -496,34 +497,37 @@ else if (candidateFieldPropertyInfo instanceof CReferencePropertyInfo) {
*/
private boolean moveInnerClassToParent(Outline outline, Candidate candidate) {
// Skip basic parametrisations like "List<String>":
if (candidate.getFieldParametrisationClass() == null) {
if (candidate.getParametrisationInfos().isEmpty()) {
return false;
}

JDefinedClass fieldParametrisationImpl = candidate.getFieldParametrisationImpl();
boolean wasAnyClassMoved = false;

if (candidate.getClazz() != fieldParametrisationImpl.parentContainer()) {
// Field parametrisation class is not inner class of the candidate:
return false;
}
for (ParametrisationInfo parametrisationInfo : candidate.getParametrisationInfos()) {
if (candidate.getClazz() != parametrisationInfo.parametrisationImpl.parentContainer()) {
// Field parametrisation class is not inner class of the candidate, hence skipped:
continue;
}

JDefinedClass fieldParametrisationClass = candidate.getFieldParametrisationClass();
wasAnyClassMoved = true;

String oldMethodName = fieldParametrisationClass.outer().name() + fieldParametrisationClass.name();
String oldMethodName = parametrisationInfo.parametrisationClass.outer().name()
+ parametrisationInfo.parametrisationClass.name();

moveClassLevelUp(outline, fieldParametrisationImpl);
moveClassLevelUp(outline, parametrisationInfo.parametrisationImpl);

renameFactoryMethod(fieldParametrisationImpl._package()._getClass(FACTORY_CLASS_NAME), oldMethodName,
fieldParametrisationClass.name());
renameFactoryMethod(parametrisationInfo.parametrisationImpl._package()._getClass(FACTORY_CLASS_NAME),
oldMethodName, parametrisationInfo.parametrisationClass.name());

if (candidate.isValueObjectDisabled()) {
moveClassLevelUp(outline, fieldParametrisationClass);
if (candidate.isValueObjectDisabled()) {
moveClassLevelUp(outline, parametrisationInfo.parametrisationClass);

renameFactoryMethod(fieldParametrisationClass._package()._getClass(FACTORY_CLASS_NAME), oldMethodName,
fieldParametrisationClass.name());
renameFactoryMethod(parametrisationInfo.parametrisationClass._package()._getClass(FACTORY_CLASS_NAME),
oldMethodName, parametrisationInfo.parametrisationClass.name());
}
}

return true;
return wasAnyClassMoved;
}

/**
Expand Down Expand Up @@ -609,14 +613,18 @@ private int createScopedFactoryMethods(JCodeModel codeModel, JDefinedClass facto
/**
* Locate the candidates classes for substitution/removal.
*
* @return a map className -> Candidate
* @return list of potential candidates
*/
private Collection<Candidate> findCandidateClasses(Outline outline, JClass xmlElementDeclModelClass) {
private Collection<Candidate> findCandidateClasses(Outline outline, JClass xmlElementsModelClass,
JClass xmlElementDeclModelClass) {
Map<String, JDefinedClass> classes = new HashMap<>();
Map<String, ClassOutline> interfaceImplementations = new HashMap<>();

// Visit all classes to create a map "interfaceName -> ClassOutline".
// This map is later used to resolve implementations from interfaces.
for (ClassOutline classOutline : outline.getClasses()) {
classes.put(classOutline.implClass.fullName() + ".class", classOutline.implClass);

for (Iterator<JClass> iter = classOutline.implClass._implements(); iter.hasNext();) {
JClass interfaceClass = iter.next();

Expand Down Expand Up @@ -676,30 +684,55 @@ private Collection<Candidate> findCandidateClasses(Outline outline, JClass xmlEl
// FIXME: All known collections have exactly one parametrisation type.
assert fieldParametrisations.size() == 1;

JDefinedClass fieldParametrisationClass = null;
JDefinedClass fieldParametrisationImpl = null;
List<ParametrisationInfo> parametrisationInfos = new ArrayList<>();

// Parametrisations like "List<String>" or "List<Serialazable>" are not considered.
// They are substituted as is and do not require moving of classes.
if (fieldParametrisations.get(0) instanceof JDefinedClass) {
fieldParametrisationClass = (JDefinedClass) fieldParametrisations.get(0);
parametrisationInfos = singletonList(
new ParametrisationInfo((JDefinedClass) fieldParametrisations.get(0)));
}
else {
parametrisationInfos = new ArrayList<>();

// Parametrisations like "List<String>" or "List<Serialazable>" require types in @XmlElements to be considered.
JAnnotationUse annotation = getAnnotation(field, xmlElementsModelClass);

if (annotation != null) {
JAnnotationArrayMember annotationArrayMember = (JAnnotationArrayMember) getAnnotationMember(
annotation, "value");

if (annotationArrayMember != null) {
for (JAnnotationUse subAnnotation : annotationArrayMember.annotations()) {
String typeClassName = getAnnotationMemberValue(subAnnotation, "type");
if (typeClassName != null) {
JDefinedClass typeClass = classes.get(typeClassName);

if (typeClass != null) {
parametrisationInfos.add(new ParametrisationInfo(typeClass));
}
}
}
}
}
}

for (ParametrisationInfo parametrisationInfo : parametrisationInfos) {
ClassOutline fieldParametrisationClassOutline = interfaceImplementations
.get(fieldParametrisationClass.fullName());
.get(parametrisationInfo.parametrisationClass.fullName());

if (fieldParametrisationClassOutline != null) {
assert fieldParametrisationClassOutline.ref == fieldParametrisationClass;
assert fieldParametrisationClassOutline.ref == parametrisationInfo.parametrisationClass;

fieldParametrisationImpl = fieldParametrisationClassOutline.implClass;
parametrisationInfo.parametrisationImpl = fieldParametrisationClassOutline.implClass;
}
else {
fieldParametrisationImpl = fieldParametrisationClass;
// Set to the same class for simplicity:
parametrisationInfo.parametrisationImpl = parametrisationInfo.parametrisationClass;
}
}

// We have a candidate class:
Candidate candidate = new Candidate(candidateClass, classOutline.target, field, fieldParametrisationClass,
fieldParametrisationImpl, xmlElementDeclModelClass, xmlSchemaModelClass);
Candidate candidate = new Candidate(candidateClass, classOutline.target, field, parametrisationInfos,
xmlElementDeclModelClass, xmlSchemaModelClass);
candidates.add(candidate);

logger.debug("Found " + candidate);
Expand Down Expand Up @@ -758,7 +791,8 @@ private int deleteCandidates(Outline outline, Collection<Candidate> candidates)
/**
* Rename methods in factory class: {@code createABC() -> createAC()}.
*/
private void renameFactoryMethod(JDefinedClass factoryClass, String oldMethodNameSuffix, String newMethodNameSuffix) {
private void renameFactoryMethod(JDefinedClass factoryClass, String oldMethodNameSuffix,
String newMethodNameSuffix) {
for (JMethod method : factoryClass.methods()) {
String methodName = method.name();

Expand Down Expand Up @@ -834,6 +868,7 @@ private void moveClassLevelUp(Outline outline, JDefinedClass clazz) {
// Modify the container so it now refers the class. Container can be a class or package.
JDefinedClass parent = (JDefinedClass) clazz.parentContainer();
JClassContainer grandParent = parent.parentContainer();
// Allows to track class name collisions:
Map<String, JDefinedClass> classes;

// FIXME: Pending https://java.net/jira/browse/JAXB-957
Expand Down
2 changes: 1 addition & 1 deletion src/test/generated_resources/element_mixed/AnyText.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public class AnyText {
@XmlElements({
@XmlElement(name = "i", type = I.class, namespace = "http://foo.org/"),
@XmlElement(name = "b", type = B.class, namespace = "http://foo.org/"),
@XmlElement(name = "br", type = Br.class, namespace = "http://foo.org/")
@XmlElement(name = "br", type = FormattedTextBr.class, namespace = "http://foo.org/")
})
protected List<Object> formattedText = new ArrayList<Object>();
@XmlElementWrapper(name = "fixed-text", required = true)
Expand Down
28 changes: 28 additions & 0 deletions src/test/generated_resources/element_mixed/Br.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlType;


Expand All @@ -15,6 +16,7 @@
* &lt;complexType name="br"&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;attribute name="mode" type="{http://www.w3.org/2001/XMLSchema}string" /&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
Expand All @@ -26,5 +28,31 @@
@XmlType(name = "br")
public class Br {

@XmlAttribute(name = "mode")
protected String mode;

/**
* Gets the value of the mode property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getMode() {
return mode;
}

/**
* Sets the value of the mode property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setMode(String value) {
this.mode = value;
}

}
30 changes: 30 additions & 0 deletions src/test/generated_resources/element_mixed/FormattedTextBr.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

package element_mixed;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlType;


/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType&gt;
* &lt;complexContent&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType"&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
* &lt;/complexType&gt;
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "")
public class FormattedTextBr {


}
8 changes: 8 additions & 0 deletions src/test/generated_resources/element_mixed/ObjectFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ public Br createBr() {
return new Br();
}

/**
* Create an instance of {@link FormattedTextBr }
*
*/
public FormattedTextBr createFormattedTextBr() {
return new FormattedTextBr();
}

/**
* Create an instance of {@link JAXBElement }{@code <}{@link B }{@code >}
*
Expand Down
Loading

0 comments on commit 1d2dcc1

Please sign in to comment.