package com.sun.tools.xjc.addon.xew;
import static com.sun.tools.xjc.addon.xew.CommonUtils.generableToString;
import static com.sun.tools.xjc.addon.xew.CommonUtils.getAnnotation;
import static com.sun.tools.xjc.addon.xew.CommonUtils.getAnnotationMemberExpression;
import static com.sun.tools.xjc.addon.xew.CommonUtils.getXsdDeclaration;
import static com.sun.tools.xjc.addon.xew.CommonUtils.isHiddenClass;
import static com.sun.tools.xjc.addon.xew.XmlElementWrapperPlugin.FACTORY_CLASS_NAME;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.tools.xjc.model.CClassInfo;
import com.sun.tools.xjc.model.CPropertyInfo;
import com.sun.xml.xsom.XSDeclaration;
/**
* Describes the collection container class - a candidate for removal. This class class has only one field - collection
* of objects.
*/
public final class Candidate {
private final JDefinedClass candidateClass;
private final JFieldVar field;
private final CPropertyInfo fieldPropertyInfo;
private final String fieldTargetNamespace;
private final JDefinedClass fieldParametrisationClass;
private final JDefinedClass fieldParametrisationImpl;
// Order matters (value Object Factory is first):
private final Map<String, JDefinedClass> objectFactoryClasses = new LinkedHashMap<String, JDefinedClass>();
private final boolean valueObjectDisabled;
private final Map<String, ScopedElementInfo> scopedElementInfos = new HashMap<String, ScopedElementInfo>();
/**
* By default the candidate is marked for removal unless something prevents it from being removed.
*/
private boolean markedForRemoval = true;
/**
* Number of times this candidate has been substituted in the model.
*/
private int substitutionsCount;
Candidate(JDefinedClass candidateClass, CClassInfo candidateClassInfo, JFieldVar field,
JDefinedClass fieldParametrizationClass, JDefinedClass fieldParametrisationImpl,
JClass xmlElementDeclModelClass, JClass xmlSchemaModelClass) {
this.candidateClass = candidateClass;
this.field = field;
this.fieldPropertyInfo = candidateClassInfo.getProperty(field.name());
this.fieldParametrisationClass = fieldParametrizationClass;
this.fieldParametrisationImpl = fieldParametrisationImpl;
this.valueObjectDisabled = addObjectFactoryForClass(candidateClass);
this.fieldTargetNamespace = getTargetNamespace(candidateClassInfo, xmlSchemaModelClass);
collectScopedElementInfos(xmlElementDeclModelClass);
}
private String getTargetNamespace(CClassInfo candidateClassInfo, JClass xmlSchemaModelClass) {
XSDeclaration xsdDeclaration = getXsdDeclaration(candidateClassInfo.getProperty(field.name()));
if (xsdDeclaration != null && !xsdDeclaration.getTargetNamespace().isEmpty()) {
return xsdDeclaration.getTargetNamespace();
}
else {
// Default (mostly used) namespace is generated as annotation for the package,
// see com.sun.tools.xjc.generator.bean.PackageOutlineImpl#calcDefaultValues()
for (JDefinedClass objectFactoryClass : objectFactoryClasses.values()) {
JAnnotationUse schemaAnnotation = getAnnotation(objectFactoryClass.getPackage(), xmlSchemaModelClass);
JExpression elementFormDefault = getAnnotationMemberExpression(schemaAnnotation, "elementFormDefault");
if (elementFormDefault != null && generableToString(elementFormDefault).endsWith(".QUALIFIED")) {
return generableToString(getAnnotationMemberExpression(schemaAnnotation, "namespace"));
}
}
}
return null;
}
private void collectScopedElementInfos(JClass xmlElementDeclModelClass) {
String dotClazz = candidateClass.fullName() + ".class";
// Only value Object Factory methods are inspected:
for (JMethod method : objectFactoryClasses.values().iterator().next().methods()) {
JAnnotationUse xmlElementDeclAnnotation = getAnnotation(method, xmlElementDeclModelClass);
JExpression scope = getAnnotationMemberExpression(xmlElementDeclAnnotation, "scope");
if (scope == null || !dotClazz.equals(generableToString(scope))) {
continue;
}
scopedElementInfos.put(method.name(),
new ScopedElementInfo(getAnnotationMemberExpression(xmlElementDeclAnnotation, "name"),
getAnnotationMemberExpression(xmlElementDeclAnnotation, "namespace"),
method.params().get(0).type()));
}
}
/**
* Container class
*/
public JDefinedClass getClazz() {
return candidateClass;
}
/**
* Container class name
*/
public String getClassName() {
return candidateClass.fullName();
}
/**
* The only field in container class (collection property).
*/
public JFieldVar getField() {
return field;
}
/**
* The name of the only field in container class.
*/
public String getFieldName() {
return field.name();
}
/**
* The class of the only field in container class (collection interface or concrete implementation).
*/
public JClass getFieldClass() {
return (JClass) field.type();
}
/**
* The corresponding property info of the only field in container class.
*/
public CPropertyInfo getFieldPropertyInfo() {
return fieldPropertyInfo;
}
/**
* The XSD namespace of the property associated with a field.
*/
public String getFieldTargetNamespace() {
return fieldTargetNamespace;
}
/**
* The only parametrisation class of the field (collection type). In case of basic parametrisation like {@link 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}.
*/
public JDefinedClass getFieldParametrisationImpl() {
return fieldParametrisationImpl;
}
/**
* Return information about scoped elements, that have this candidate as a scope.
*
* @return object factory method name -to- element info map
*/
public Map<String, ScopedElementInfo> getScopedElementInfos() {
return scopedElementInfos;
}
/**
* Object Factory classes for value (implementation) classes, interface classes and extra packages. Cannot be empty.
*/
public Collection<JDefinedClass> getObjectFactoryClasses() {
return objectFactoryClasses.values();
}
/**
* For the given class locate and add Object Factory classes to the map.
*
* @return {@code true} if value class generation is enabled
*/
public boolean addObjectFactoryForClass(JDefinedClass clazz) {
JDefinedClass valueObjectFactoryClass = clazz._package()._getClass(FACTORY_CLASS_NAME);
if (objectFactoryClasses.containsKey(valueObjectFactoryClass.fullName())) {
return false;
}
objectFactoryClasses.put(valueObjectFactoryClass.fullName(), valueObjectFactoryClass);
JDefinedClass objectFactoryClass = null;
// If class has a non-hidden interface, then there is object factory in another package.
for (Iterator<JClass> iter = clazz._implements(); iter.hasNext();) {
JClass interfaceClass = iter.next();
if (!isHiddenClass(interfaceClass)) {
objectFactoryClass = interfaceClass._package()._getClass(FACTORY_CLASS_NAME);
if (objectFactoryClass != null) {
objectFactoryClasses.put(objectFactoryClass.fullName(), objectFactoryClass);
}
}
}
return objectFactoryClass != null;
}
/**
* Returns {@code true} if the setting {@code <jaxb:globalBindings generateValueClass="false">} is active.
*/
public boolean isValueObjectDisabled() {
return valueObjectDisabled;
}
/**
* Has given candidate green light to be removed?
*/
public boolean canBeRemoved() {
return markedForRemoval && substitutionsCount > 0;
}
/**
* Increments number of substitutions for this candidate.
*/
public void incrementSubstitutions() {
substitutionsCount++;
}
/**
* Signal that this candidate should not be removed from model on some reason.
*/
public void unmarkForRemoval() {
this.markedForRemoval = false;
}
@Override
public String toString() {
return "Candidate[" + getClassName() + " in field " + getFieldClass().name() + " " + getFieldName() + "]";
}
}