/*******************************************************************************
* Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.mappingsmodel.mapping;
import java.text.Collator;
import java.util.*;
import org.eclipse.persistence.tools.workbench.mappingsmodel.MWModel;
import org.eclipse.persistence.tools.workbench.mappingsmodel.ProblemConstants;
import org.eclipse.persistence.tools.workbench.utility.ClassTools;
import org.eclipse.persistence.tools.workbench.utility.CollectionTools;
import org.eclipse.persistence.tools.workbench.utility.iterators.CloneIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.TransformationIterator;
import org.eclipse.persistence.tools.workbench.utility.node.Node;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.internal.helper.ConversionManager;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.mappings.converters.ObjectTypeConverter;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLCompositeCollectionMapping;
public final class MWObjectTypeConverter
extends MWTypeConverter
{
// **************** Fields ************************************************
/** A list of ValuePair objects, mapping data values to attribute values */
private Collection valuePairs;
public final static String VALUE_PAIRS_COLLECTION = "valuePairs";
/** The value in the map that is used by default */
private volatile ValuePair defaultValuePair;
public final static String DEFAULT_ATTRIBUTE_VALUE_PROPERTY = "defaultAttributeProperty";
// **************** Constructors ******************************************
/** Default constructor - for TopLink use only */
private MWObjectTypeConverter() {
super();
}
public MWObjectTypeConverter(MWConverterMapping parent) {
super(parent);
}
//only used for legacy projects
public MWObjectTypeConverter(MWConverterMapping parent, Map legacyValueMap) {
super(parent, legacyValueMap);
}
// **************** Initialization ****************************************
protected void initialize(Node parent) {
super.initialize(parent);
this.valuePairs = new Vector();
}
protected void addChildrenTo(List children) {
super.addChildrenTo(children);
synchronized (this.valuePairs) { children.addAll(this.valuePairs); }
}
// **************** Value Pairs API ***************************************
/** For internal or UI use only */
public Iterator valuePairs() {
return new CloneIterator(this.valuePairs);
}
/** For internal or UI use only */
public int valuePairsSize() {
return this.valuePairs.size();
}
public void addValuePair(String dataValueString, String attributeValueString)
throws ConversionValueException, ConversionException
{
Object attributeValue = this.buildAttributeValue(attributeValueString);
Object dataValue = this.buildDataValue(dataValueString);
this.addValuePair(dataValue, attributeValue);
}
public ValuePair addValuePair(Object dataValue, Object attributeValue)
throws ConversionValueException
{
if (this.duplicateDataValue(dataValue)) {
throw ConversionValueException.duplicateDataValueException(dataValue);
}
else if (this.duplicateAttributeValue(attributeValue)) {
throw ConversionValueException.duplicateAttributeValueException(attributeValue);
}
ValuePair valuePair = new ValuePair(this, dataValue, attributeValue);
this.addValuePairInternal(valuePair);
return valuePair;
}
public void editValuePair(ValuePair valuePair, String dataValueString, String attributeValueString)
throws ConversionValueException, ConversionException
{
Object dataValue = this.buildDataValue(dataValueString);
Object attributeValue = this.buildAttributeValue(attributeValueString);
if (! valuePair.dataValue.equals(dataValue) && this.duplicateDataValue(dataValue)) {
throw ConversionValueException.duplicateDataValueException(dataValue);
}
else if (! valuePair.attributeValue.equals(attributeValue) && this.duplicateAttributeValue(attributeValue)) {
throw ConversionValueException.duplicateAttributeValueException(attributeValue);
}
valuePair.setDataValue(dataValue);
valuePair.setAttributeValue(attributeValue);
}
public void removeValuePair(ValuePair valuePair) {
this.valuePairs.remove(valuePair);
this.fireItemRemoved(VALUE_PAIRS_COLLECTION, valuePair);
if (this.defaultValuePair == valuePair)
this.setDefaultValuePair(null);
}
public void clearValuePairs() {
this.valuePairs.clear();
this.fireCollectionChanged(VALUE_PAIRS_COLLECTION);
setDefaultValuePair(null);
}
// **************** Default Attribute Value API ***************************
public Object getDefaultAttributeValue() {
return this.defaultValuePair == null ? null : this.defaultValuePair.attributeValue;
}
/**
* NOTE: The object specified here *must* be present in the value pairs list,
* otherwise the default attribute value is set to null.
*
* (This should only be a consideration if calling this method while building
* a test project. If called from the UI, the object will *always* be present
* in the value pairs list.)
*/
public void setDefaultAttributeValue(Object defaultAttributeValue) {
this.setDefaultValuePair(this.valuePairForAttributeValue(defaultAttributeValue));
}
public void setDefaultAttributeValue(String newDefaultAttributeValueString)
throws ConversionException
{
this.setDefaultAttributeValue(this.buildAttributeValue(newDefaultAttributeValueString));
}
// ************** MWConverter implementation ************
/** Should ONLY be used in one place - the UI */
public String accessibleNameKey() {
return "ACCESSIBLE_SERIALIZED_MAPPING_NODE";
}
public String getType() {
return OBJECT_TYPE_CONVERTER;
}
public String iconKey() {
return "mapping.objectType";
}
// ************** Value Pairs Internal Behavior *************
private Iterator dataValues() {
return new TransformationIterator(this.valuePairs()) {
protected Object transform(Object next) {
return ((ValuePair) next).dataValue;
}
};
}
private Iterator attributeValues() {
return new TransformationIterator(this.valuePairs()) {
protected Object transform(Object next) {
return ((ValuePair) next).attributeValue;
}
};
}
private void addValuePairInternal(ValuePair valuePair) {
this.valuePairs.add(valuePair);
this.fireItemAdded(VALUE_PAIRS_COLLECTION, valuePair);
}
/**
* This will always clear out the value pairs and rebuild them.
*/
protected void rebuildValuePairs() {
Iterator valuePairsCopy = valuePairs();
this.clearValuePairs();
if (getDataType() == null || getAttributeType() == null) {
return;
}
for (Iterator stream = valuePairsCopy; stream.hasNext(); ) {
ValuePair nextPair = (ValuePair) stream.next();
ValuePair valuePair = null;
try {
valuePair = this.addValuePair(this.buildDataValue(nextPair.dataValue), this.buildAttributeValue(nextPair.attributeValue));
}
catch (ConversionException ce) {
// do nothing - just don't add the value pair
}
catch (ConversionValueException cve) {
// do nothing - just don't add the value pair
}
if (this.defaultValuePair == nextPair) {
this.setDefaultValuePair(valuePair);
}
}
}
private boolean duplicateDataValue(Object dataValue) {
for (Iterator stream = this.dataValues(); stream.hasNext(); ) {
if ((stream.next()).equals(dataValue)) {
return true;
}
}
return false;
}
private boolean duplicateAttributeValue(Object attributeValue) {
for (Iterator stream = this.attributeValues(); stream.hasNext(); ) {
if (stream.next().equals(attributeValue)) {
return true;
}
}
return false;
}
/**
* Return the value pair for the specified attribute value.
*/
private ValuePair valuePairForAttributeValue(Object attributeValue) {
for (Iterator stream = this.valuePairs(); stream.hasNext(); ) {
ValuePair valuePair = (ValuePair) stream.next();
if (valuePair.attributeValue.equals(attributeValue)) {
return valuePair;
}
}
if (attributeValue == null) {
return null;
}
throw new IllegalArgumentException(String.valueOf(attributeValue));
}
// **************** Default Attribute Value Internal Behavior *************
private void setDefaultValuePair(ValuePair newDefaultValuePair) {
Object oldDefaultAttributeValue =
this.defaultValuePair == null ? null : this.defaultValuePair.attributeValue;
this.defaultValuePair = newDefaultValuePair;
Object newDefaultAttributeValue =
this.defaultValuePair == null ? null : this.defaultValuePair.attributeValue;
this.firePropertyChanged(DEFAULT_ATTRIBUTE_VALUE_PROPERTY, oldDefaultAttributeValue, newDefaultAttributeValue);
}
// **************** Miscellaneous Internal Behavior ***********************
private Object buildAttributeValue(Object oldAttributeValue)
throws ConversionException
{
Class javaClass = null;
try {
javaClass = ClassTools.classForTypeDeclaration(getAttributeType().typeName(), getAttributeType().getDimensionality());
} catch (ClassNotFoundException e) {
//this is unlikely to happen, so just throw a runtimeException
throw new RuntimeException(e);
}
return ConversionManager.getDefaultManager().convertObject(oldAttributeValue, javaClass);
}
private Object buildDataValue(Object oldDataValue)
throws ConversionException
{
Class javaClass = null;
try {
javaClass = ClassTools.classForTypeDeclaration(getDataType().typeName(), getDataType().getDimensionality());
} catch (ClassNotFoundException e) {
//this is unlikely to happen, so just throw a runtimeException
throw new RuntimeException(e);
}
return ConversionManager.getDefaultManager().convertObject(oldDataValue, javaClass);
}
//************* Problem Handling ************
protected void addProblemsTo(List currentProblems) {
super.addProblemsTo(currentProblems);
this.checkValuePairs(currentProblems);
//this.databaseTypeMatchesFieldTypeTest(currentProblems);
}
private void checkValuePairs(List currentProblems) {
if (this.valuePairsSize() == 0) {
currentProblems.add(buildProblem(ProblemConstants.MAPPING_VALUE_PAIRS_NOT_SPECIFIED));
}
}
//TODO this only applies for relational directToFieldMappings
//do we need subclasses just for this one thing?
//Will we need this rule for xml ObjectTypeMappings
// private void databaseTypeMatchesFieldTypeTest(Set currentProblems) {
// // if the dictionary is empty then this rule doesn't apply yet
// if (!dataValues().hasNext())
// return;
// MWClass databaseType = getDataType();
// if (getField() == null)
// return;
// MWDatabaseType fieldType = getField().getType();
// if (databaseType == null || fieldType == null)
// return;
// MWDatabaseType fieldTypeShouldBe = getProject().getDatabase().getPlatform().databaseTypeForJavaTypeNamed(databaseType.getName());
// if (fieldTypeShouldBe == null)
// return;
// if(!fieldType.equals(fieldTypeShouldBe)) {
// currentProblems.add(buildProblem(ProblemConstants.XXXXXX_YYYYY));
// }
// }
// **************** Runtime conversion ************************************
public Converter runtimeConverter(DatabaseMapping mapping) {
ObjectTypeConverter converter = new ObjectTypeConverter(mapping);
for (Iterator stream = this.valuePairs(); stream.hasNext(); ) {
ValuePair valuePair = (ValuePair) stream.next();
converter.addConversionValue(valuePair.dataValue, valuePair.attributeValue);
}
converter.setDefaultAttributeValue(this.getDefaultAttributeValue());
return converter;
}
// **************** TopLink Methods ***************************************
public static XMLDescriptor buildDescriptor()
{
XMLDescriptor descriptor = new XMLDescriptor();
descriptor.setJavaClass(MWObjectTypeConverter.class);
descriptor.getInheritancePolicy().setParentClass(MWTypeConverter.class);
// value pairs
XMLCompositeCollectionMapping valuePairsMapping = new XMLCompositeCollectionMapping();
valuePairsMapping.setAttributeName("valuePairs");
valuePairsMapping.setXPath("value-pairs/value-pair");
valuePairsMapping.setGetMethodName("getValuePairsForTopLink");
valuePairsMapping.setSetMethodName("setValuePairsForTopLink");
valuePairsMapping.setReferenceClass(MWObjectTypeConverter.ValuePair.class);
descriptor.addMapping(valuePairsMapping);
// default attribute value
//TODO does this need a null value, look at the getter
descriptor.addDirectMapping("defaultValuePair", "getDefaultAttributeValueForTopLink", "setDefaultAttributeValueForTopLink", "default-attribute-value/text()");
return descriptor;
}
public void postProjectBuild() {
super.postProjectBuild();
for (Iterator stream = this.valuePairs(); stream.hasNext(); ) {
ValuePair nextPair = (ValuePair) stream.next();
nextPair.attributeValue = this.buildAttributeValue(nextPair.attributeValue);
nextPair.dataValue = this.buildDataValue(nextPair.dataValue);
}
}
/**
* sort the mappings for TopLink
*/
private Collection getValuePairsForTopLink() {
return CollectionTools.sort((List) valuePairs);
}
private void setValuePairsForTopLink(Collection valuePairs) {
this.valuePairs = valuePairs;
}
private Object getDefaultAttributeValueForTopLink() {
return this.getDefaultAttributeValue();
}
private void setDefaultAttributeValueForTopLink(Object defaultAttributeValue) {
this.defaultValuePair = this.valuePairForAttributeValue(defaultAttributeValue);
}
// **************** Member classes ****************************************
public static class ValuePair
extends MWModel
implements Comparable
{
Object dataValue;
public final static String DATA_VALUE_PROPERTY = "dataValue";
Object attributeValue;
public final static String ATTRIBUTE_VALUE_PROPERTY = "attributeValue";
public final static String DEFAULT_ATTRIBUTE_VALUE_PROPERTY = "defaultAttributeValue";
private ValuePair() {
super();
}
private ValuePair(MWObjectTypeConverter parent, Object dataValue, Object attributeValue) {
super(parent);
this.dataValue = dataValue;
this.attributeValue = attributeValue;
}
public Object getDataValue() {
return this.dataValue;
}
public String getDataValueAsString() {
return (String) ConversionManager.getDefaultManager().convertObject(this.dataValue, String.class);
}
private void setDataValue(Object newDataValue) {
Object oldDataValue = this.dataValue;
this.dataValue = newDataValue;
this.firePropertyChanged(DATA_VALUE_PROPERTY, oldDataValue, newDataValue);
}
public Object getAttributeValue() {
return this.attributeValue;
}
public String getAttributeValueAsString() {
return (String) ConversionManager.getDefaultManager().convertObject(this.attributeValue, String.class);
}
private void setAttributeValue(Object newAttributeValue) {
Object oldAttributeValue = this.attributeValue;
this.attributeValue = newAttributeValue;
this.firePropertyChanged(ATTRIBUTE_VALUE_PROPERTY, oldAttributeValue, newAttributeValue);
}
public boolean isDefaultAttributeValue() {
return this.attributeValue == getObjectTypeConverter().getDefaultAttributeValue();
}
public void setDefaultAttributeValue(boolean isNewDefaultValue) {
ValuePair oldDefaultAttributeValuePair = this.getObjectTypeConverter().defaultValuePair;
boolean isOldDefaultValue = oldDefaultAttributeValuePair == this;
if (isNewDefaultValue == isOldDefaultValue) {
return;
}
if (isOldDefaultValue) {
this.getObjectTypeConverter().setDefaultAttributeValue(null);
}
else if (isNewDefaultValue) {
// unset the old default value first
if (oldDefaultAttributeValuePair != null) {
oldDefaultAttributeValuePair.setDefaultAttributeValue(false);
}
this.getObjectTypeConverter().setDefaultAttributeValue(this.attributeValue);
}
this.firePropertyChanged(DEFAULT_ATTRIBUTE_VALUE_PROPERTY, isOldDefaultValue, isNewDefaultValue);
}
public MWObjectTypeConverter getObjectTypeConverter() {
return (MWObjectTypeConverter) this.getMWParent();
}
public int compareTo(Object o) {
return Collator.getInstance().compare(this.dataValue.toString(), ((ValuePair) o).dataValue.toString());
}
public static XMLDescriptor buildDescriptor() {
XMLDescriptor descriptor = new XMLDescriptor();
descriptor.setJavaClass(MWObjectTypeConverter.ValuePair.class);
descriptor.addDirectMapping("dataValue", "getDataValueForTopLink", "setDataValueForTopLink", "data-value/text()");
descriptor.addDirectMapping("attributeValue", "getAttributeValueForTopLink", "setAttributeValueForTopLink", "attribute-value/text()");
return descriptor;
}
private Object getDataValueForTopLink() {
return this.getDataValueAsString();
}
private void setDataValueForTopLink(Object dataValue) {
this.dataValue = dataValue;
}
private Object getAttributeValueForTopLink() {
return this.getAttributeValueAsString();
}
private void setAttributeValueForTopLink(Object attributeValue) {
this.attributeValue = attributeValue;
}
public void toString(StringBuffer sb) {
sb.append(this.dataValue);
sb.append(" => ");
sb.append(this.attributeValue);
}
}
public static class ConversionValueException
extends Exception
{
private int error;
private final static int DUPLICATE_DATA_VALUE = 0;
private final static int DUPLICATE_ATTRIBUTE_VALUE = 1;
private Object value;
private ConversionValueException(int error, Object valueObject) {
super();
this.error = error;
this.value = valueObject;
}
public static ConversionValueException duplicateDataValueException(Object value) {
return new ConversionValueException(DUPLICATE_DATA_VALUE, value);
}
public static ConversionValueException duplicateAttributeValueException(Object value) {
return new ConversionValueException(DUPLICATE_ATTRIBUTE_VALUE, value);
}
public boolean isRepeatedDataValue() {
return this.error == DUPLICATE_DATA_VALUE;
}
public boolean isRepeatedAttributeValue() {
return this.error == DUPLICATE_ATTRIBUTE_VALUE;
}
}
}