blob: ab28074aec0c486f1167c64ef0d0652061e951cd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.common.internal.emf.resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.impl.EStructuralFeatureImpl;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.wst.common.internal.emf.utilities.ExtendedEcoreUtil;
import org.eclipse.wst.common.internal.emf.utilities.FeatureValueConverter;
import org.w3c.dom.Node;
public class Translator {
public final static int NO_STYLE = 0;
public final static int DOM_ATTRIBUTE = 1;
public final static int EMPTY_TAG = 1 << 1;
public final static int CDATA_CONTENT = 1 << 2;
/**
* Style bit that indicates that the end tag should NOT be indented; by default it is.
*/
public final static int END_TAG_NO_INDENT = 1 << 3;
/**
* Style bit that indicates that booleans should NOT be converted as "True" and "False"; default
* is that they are
*/
public final static int BOOLEAN_LOWERCASE = 1 << 4;
/**
* Style bit that indicates an enum value contains hyphens If this is true, then internally the
* hyphens are replaced with underscores
*/
public final static int ENUM_FEATURE_WITH_HYPHENS = 1 << 5;
protected final static int OBJECT_MAP = 1 << 6;
protected final static int BOOLEAN_FEATURE = 1 << 7;
protected final static int SHARED_REFERENCE = 1 << 8;
/**
* Indicates that the feature may be significant even if it is empty
*/
public final static int EMPTY_CONTENT_IS_SIGNIFICANT = 1 << 9;
/**
* Used to indicate that feature is associated with a comment node
*/
protected final static int COMMENT_FEATURE = 1 << 10;
/**
* If the value is null, then an eUnset() will be invoked on the feature
*/
public final static int UNSET_IF_NULL = 1 << 11;
/**
* Return the element contents as a String if the feature is unresolveable (Used by the
* SourceLinkTranslator)
*/
public final static int STRING_RESULT_OK = 1 << 12;
protected String[] fDOMNames;
protected String fDOMPath = ""; //$NON-NLS-1$
protected Map readAheadNames;
protected int fStyle = NO_STYLE;
protected EStructuralFeature feature;
protected TranslatorPath[] fTranslatorPaths;
protected EClass emfClass;
protected String fNameSpace = ""; //$NON-NLS-1$
// added by MDE
protected String domNameAndPath = null;
/**
* Indicates if any of the children of this Translator are themselves DependencyTranslators
*/
protected Boolean isDependencyParent;
protected EStructuralFeature dependencyFeature;
protected static EcorePackage ECORE_PACKAGE = EcorePackage.eINSTANCE;
// Use this identifier for the DOMName when the attribute
// value is to be extracted directly from the text of the node.
// This is rare, but occurs in the web.xml in the case of a
// WelcomeFile.
static final public String TEXT_ATTRIBUTE_VALUE = "$TEXT_ATTRIBUTE_VALUE"; //$NON-NLS-1$
static final public EStructuralFeature CONTAINER_FEATURE = new ContainerFeature();
static final public EStructuralFeature ROOT_FEATURE = new RootFeature();
protected static class ContainerFeature extends EStructuralFeatureImpl {
protected ContainerFeature() {
super();
}
}
protected static class RootFeature extends EStructuralFeatureImpl {
protected RootFeature() {
super();
}
}
public Translator findChild(String tagName, Object target, int versionID) {
Translator result = null;
Translator[] maps = getChildren(target, versionID);
if (maps != null) {
for (int i = 0; i < maps.length; i++) {
Translator map = maps[i];
if (map.isMapFor(tagName)) {
result = map;
break;
}
}
}
if (result == null) {
VariableTranslatorFactory factory = getVariableTranslatorFactory();
if (factory != null && factory.accepts(tagName)) {
result = factory.create(tagName);
}
}
return result;
}
/**
* Utility method to string together arrays of children
*/
public static Object[] concat(Object[] array1, Object[] array2) {
Object[] result = (Object[]) java.lang.reflect.Array.newInstance(array1.getClass().getComponentType(), array1.length + array2.length);
System.arraycopy(array1, 0, result, 0, array1.length);
System.arraycopy(array2, 0, result, array1.length, array2.length);
return result;
}
public static Object[] concat(Object[] array1, Object object2) {
Object[] newArray = new Object[]{object2};
return concat(array1, newArray);
}
public static Translator createParentAndTextAttributeTranslator(String domName, EStructuralFeature parentFeature, EStructuralFeature childFeature) {
GenericTranslator parent = new GenericTranslator(domName, parentFeature, END_TAG_NO_INDENT);
parent.setChildren(new Translator[]{new Translator(TEXT_ATTRIBUTE_VALUE, childFeature)});
return parent;
}
public Translator(String domNameAndPath, EClass eClass) {
initializeDOMNameAndPath(domNameAndPath);
setEMFClass(eClass);
}
public Translator(String domNameAndPath, EStructuralFeature aFeature) {
initializeDOMNameAndPath(domNameAndPath);
setFeature(aFeature);
}
public Translator(String domNameAndPath, EStructuralFeature aFeature, EClass eClass) {
this(domNameAndPath, aFeature);
setEMFClass(eClass);
}
public Translator(String domNameAndPath, EStructuralFeature aFeature, TranslatorPath path) {
this(domNameAndPath, aFeature, new TranslatorPath[]{path});
}
public Translator(String domNameAndPath, EStructuralFeature aFeature, TranslatorPath[] paths) {
initializeDOMNameAndPath(domNameAndPath);
fTranslatorPaths = paths;
setFeature(aFeature);
}
public Translator(String domNameAndPath, EStructuralFeature aFeature, int style) {
initializeDOMNameAndPath(domNameAndPath);
fStyle = style;
setFeature(aFeature);
}
public static EcorePackage getEcorePackage() {
return EcorePackage.eINSTANCE;
}
public String getDOMName(Object value) {
return fDOMNames[0];
}
public String[] getDOMNames() {
return fDOMNames;
}
public String getDOMPath() {
return fDOMPath;
}
public boolean hasDOMPath() {
return fDOMPath != null && fDOMPath.length() != 0;
}
public EStructuralFeature getFeature() {
return feature;
}
/**
* Parse the DOM names and path out of <domNameAndPath>and set the appropriate fields.
*/
protected void initializeDOMNameAndPath(String domNameAndPathArg) {
if (domNameAndPathArg == null)
return;
int inx = domNameAndPathArg.lastIndexOf('/');
if (inx != -1) {
fDOMNames = parseDOMNames(domNameAndPathArg.substring(inx + 1));
fDOMPath = domNameAndPathArg.substring(0, inx);
} else {
fDOMNames = parseDOMNames(domNameAndPathArg);
fDOMPath = ""; //$NON-NLS-1$
}
// added by MDE
this.domNameAndPath = domNameAndPathArg;
}
/**
* Indicates whether the node should be written as an empty tag; eg, <distributable/>
*/
public boolean isCDATAContent() {
return (fStyle & CDATA_CONTENT) != 0;
}
/**
* Indicates whether the DOMName represents a sub element name or an attribute name
*
* @return boolean True if the DOMName is an attribute name.
*/
public boolean isDOMAttribute() {
return (fStyle & DOM_ATTRIBUTE) != 0;
}
/**
* Indicates whether the node should be written as an empty tag; eg, <distributable/>
*/
public boolean isEmptyTag() {
return (fStyle & EMPTY_TAG) != 0;
}
public boolean isBooleanUppercase() {
return (fStyle & BOOLEAN_FEATURE) != 0 && (fStyle & BOOLEAN_LOWERCASE) == 0;
}
public boolean isBooleanFeature() {
return (fStyle & BOOLEAN_FEATURE) != 0;
}
public boolean shouldIndentEndTag() {
return (fStyle & END_TAG_NO_INDENT) == 0;
}
public boolean shouldIndentEndTag(Node node) {
return shouldIndentEndTag();
}
public boolean isEmptyContentSignificant() {
return ((fStyle & EMPTY_TAG) != 0) || ((fStyle & EMPTY_CONTENT_IS_SIGNIFICANT) != 0);
}
/**
* Returns true if this map is to another MOF object (not a primitive)
*/
public boolean isObjectMap() {
return (fStyle & OBJECT_MAP) != 0;
}
/**
* Returns true if this map is for a shared reference
*/
public boolean isShared() {
return (fStyle & SHARED_REFERENCE) != 0;
}
public boolean isEnumWithHyphens() {
return (fStyle & ENUM_FEATURE_WITH_HYPHENS) != 0;
}
/**
* Indicates whether the map represents a case where the text of the DOMNode represents the
* objects one and only attribute value. An example of this case is a <welcome-file>file.txt
* </welcome-file>.
*/
public boolean isDOMTextValue() {
return fDOMNames[0] == TEXT_ATTRIBUTE_VALUE;
}
/**
* Indicates whether the id is the mof attribute that should be set.
*/
public boolean isIDMap() {
return false;
}
/**
* Indicates whether the id is the mof attribute that should be set.
*/
public boolean isLinkMap() {
return fTranslatorPaths != null;
}
public boolean isTargetLinkMap() {
return isLinkMap() && !isObjectMap();
}
/**
* Return true if this map is the one representing a node with the name <domName>. By default
* this method simply compares the DOM name of the map against the <domName>parameter
*
* @return boolean
* @param domName
* java.lang.String
*/
public boolean isMapFor(String domName) {
if (domName.equals(getDOMPath()))
return true;
for (int i = 0; i < fDOMNames.length; i++) {
if (domName.equals(fDOMNames[i]))
return true;
}
return false;
}
public boolean isMapFor(Object aFeature, Object oldValue, Object newValue) {
return feature == aFeature;
}
/**
* Indicates whether feature being mapped is a collection.
*
* @return boolean True if the feature is multi valued.
*/
public boolean isMultiValued() {
if (feature != null)
return feature.isMany();
return false;
}
/**
* Parses comma separated names from <domNamesString>. Returns an array containing the names.
*
* @return java.lang.String[]
* @param domNamesString
* java.lang.String
*/
protected String[] parseDOMNames(String domNamesString) {
int startInx = 0;
int inx = domNamesString.indexOf(',');
ArrayList results = new ArrayList(1);
while (inx != -1) {
results.add(domNamesString.substring(startInx, inx));
startInx = inx + 1;
inx = domNamesString.indexOf(',', startInx);
}
if (startInx == 0)
results.add(domNamesString);
else
results.add(domNamesString.substring(startInx));
return (String[]) results.toArray(new String[results.size()]);
}
public String toString() {
StringBuffer sb = new StringBuffer();
String cn = getClass().getName();
int i = cn.lastIndexOf('.');
cn = cn.substring(++i, cn.length());
sb.append(cn);
sb.append('(');
sb.append(fDOMNames[0]);
for (int j = 1; j < fDOMNames.length; j++) {
sb.append('|');
sb.append(fDOMNames[j]);
}
sb.append(',');
sb.append(hashCode());
sb.append(')');
return sb.toString();
}
/**
* Gets the TranslatorPath.
*
* @return Returns a TranslatorPath
*/
public TranslatorPath[] getTranslatorPaths() {
return fTranslatorPaths;
}
/*
* @see Object#equals(Object)
*/
public boolean equals(Object object) {
if (!(object instanceof Translator))
return false;
Translator mapInfo = (Translator) object;
return fDOMNames.equals(mapInfo.getDOMNames()) && (feature == null && mapInfo.getFeature() == null || feature.equals(mapInfo.getFeature()));
}
/**
* Returns the isManagedByParent.
*
* @return boolean
*/
public boolean isManagedByParent() {
return getChildren(null, -1) == null;
}
/*
* In the 99% case there is only one node name to be concerned with, but subclasses can override
* for the cases where multiple dom names map to one feature
*/
public EObject createEMFObject(String nodeName, String readAheadName) {
if (emfClass == null) {
if (feature == null)
return null;
if (isObjectMap())
return createEMFObject(feature);
}
return createEMFObject(emfClass);
}
public static EObject createEMFObject(EStructuralFeature aFeature) {
if (aFeature == null)
return null;
return createEMFObject(((EReference) aFeature).getEReferenceType());
}
public static EObject createEMFObject(EClass anEClass) {
if (anEClass == null)
return null;
return anEClass.getEPackage().getEFactoryInstance().create(anEClass);
}
public void setTextValueIfNecessary(String textValue, Notifier owner, int versionId) {
Translator textTranslator = this.findChild(Translator.TEXT_ATTRIBUTE_VALUE, owner, versionId);
if (textTranslator != null) {
Object objectValue = textTranslator.convertStringToValue(textValue, (EObject) owner);
textTranslator.setMOFValue(owner, objectValue);
}
}
/**
* Check to see if feature is valid on a particular mofObject.
*
* @return boolean Return true if the feature specified exists on the MOF object.
* @param emfObject
* org.eclipse.emf.ecore.EObject
*/
public boolean featureExists(EObject emfObject) {
if (feature == null)
return false;
return emfObject.eClass().getEStructuralFeature(feature.getName()) != null;
}
/**
* Translators which do not have a feature should override this method with custom behavior.
*/
public String extractStringValue(EObject emfObject) {
if (isEmptyTag() && feature == null)
return ""; //Fake it out with a value //$NON-NLS-1$
return null;
}
public Object convertStringToValue(String nodeName, String readAheadName, String value, Notifier owner) {
Object result = null;
try {
if (!this.isManagedByParent()) {
result = createEMFObject(nodeName, readAheadName);
} else {
result = convertStringToValue(value, (EObject) owner);
}
} catch (ClassCastException cce) {
}
return result;
}
/**
* Converts a string value to the appropriate type.
*
* @return java.lang.Object The converted value
* @param strValue
* java.lang.String The string to convert.
*/
public Object convertStringToValue(String strValue, EObject owner) {
if (feature == null)
return strValue;
if (strValue != null) {
if (isEnumWithHyphens())
strValue = strValue.replace('-', '_');
if (!isCDATAContent()) {
strValue = strValue.trim();
}
}
Object value = FeatureValueConverter.DEFAULT.convertValue(strValue, feature);
if (value == null) {
if (isEmptyTag() && !isDOMAttribute() && !isDOMTextValue() && isBooleanFeature())
return Boolean.TRUE;
EObject convertToType = feature.getEType();
if (convertToType == null)
value = strValue;
else if (convertToType.equals(getEcorePackage().getEString())) {
value = ""; //$NON-NLS-1$
}
}
return value;
}
/**
* Converts a value of a specified type to a string value. Subclasses may override for special
* cases where special conversion needs to occur based on the feature and or object type.
*
* @return String The converted value
* @param value
* java.lang.Object The object to convert.
*/
public String convertValueToString(Object value, EObject owner) {
if (isEmptyTag() || value == null)
return null;
else if (isEnumWithHyphens())
return value.toString().replace('_', '-');
else if (isBooleanUppercase())
return ((Boolean) value).booleanValue() ? "True" : "False"; //$NON-NLS-1$ //$NON-NLS-2$
return value.toString();
}
public Translator[] getVariableChildren(Notifier target, int version) {
Translator[] results = null;
VariableTranslatorFactory factory = getVariableTranslatorFactory();
if (factory != null) {
List variableTranslators = factory.create(target);
if (variableTranslators != null && variableTranslators.size() > 0) {
Object[] vtoa = variableTranslators.toArray();
results = new Translator[vtoa.length];
for (int i = 0; i < results.length; i++)
results[i] = (Translator) vtoa[i];
}
}
if (results == null)
results = new Translator[0];
return results;
}
/**
* Returns null by default; subclasses should override to return specific children
*/
public Translator[] getChildren(Object target, int versionID) {
return getChildren();
}
protected Translator[] getChildren() {
return null;
}
/**
* Return the list of MOF children that currently exist for the values of an attribute.
*/
public List getMOFChildren(EObject mofObject) {
if (feature == null)
return Collections.EMPTY_LIST;
Object value = getMOFValue(mofObject);
List result = Collections.EMPTY_LIST;
if (isMultiValued())
result = (List) value;
else if (value != null)
result = Collections.singletonList(value);
return result;
}
public Object getMOFValue(EObject mofObject) {
if (feature == null)
return null;
return mofObject.eGet(feature);
}
/**
* Sets a value of a feature in a mof object.
*/
public void setMOFValue(Notifier owner, Object value, int newIndex) {
if (feature != null) {
if ((fStyle & UNSET_IF_NULL) != 0 && value == null)
ExtendedEcoreUtil.eUnsetOrRemove((EObject) owner, feature, value);
else
ExtendedEcoreUtil.eSetOrAdd((EObject) owner, feature, value, newIndex);
}
}
public void setMOFValue(Notifier owner, Object value) {
if (owner instanceof EObject) {
setMOFValue((EObject) owner, value);
} else if (owner instanceof Resource) {
setMOFValue((Resource) owner, value);
}
}
public void setMOFValue(EObject emfObject, Object value) {
// if (feature != null)
// emfObject.eSet(feature, value);
setMOFValue(emfObject, value, -1);
}
/*
* (non-Javadoc)
*
* @see com.ibm.etools.emf2xml.impl.Translator#setMOFValue(org.eclipse.emf.ecore.EObject,
* java.lang.Object)
*/
public void setMOFValue(Resource res, Object value) {
if (res != null && value != null)
res.getContents().add((EObject)value);
}
public void removeMOFValue(Notifier owner, Object value) {
if (feature != null)
ExtendedEcoreUtil.eUnsetOrRemove((EObject) owner, feature, value);
}
public boolean isSetMOFValue(EObject emfObject) {
boolean isSet = feature != null && emfObject.eIsSet(feature);
if (isEmptyTag())
return isSet && ((Boolean) emfObject.eGet(feature)).booleanValue();
return isSet;
}
public void unSetMOFValue(EObject emfObject) {
if (feature != null)
emfObject.eUnset(feature);
}
public void clearList(EObject mofObject) {
if (feature != null)
((List) mofObject.eGet(feature)).clear();
}
protected void setFeature(EStructuralFeature aFeature) {
this.feature = aFeature;
if (feature == null)
return;
//This way an instance check happens only once
if (aFeature instanceof EReference) {
fStyle |= OBJECT_MAP;
if (!((EReference) aFeature).isContainment())
fStyle |= SHARED_REFERENCE;
}
if (getEcorePackage().getEBoolean() == feature.getEType())
fStyle |= BOOLEAN_FEATURE;
}
protected void setEMFClass(EClass anEClass) {
this.emfClass = anEClass;
if (anEClass != null)
fStyle |= OBJECT_MAP;
}
public boolean hasReadAheadNames() {
return readAheadNames != null && !readAheadNames.isEmpty();
}
/**
* Return the read ahead names, if they are defined, for a given parent node name. This is used
* when creation of a specific EMF object is dependent on the value of a child node.
*/
public ReadAheadHelper getReadAheadHelper(String parentName) {
if (readAheadNames == null)
return null;
return (ReadAheadHelper) readAheadNames.get(parentName);
}
public void addReadAheadHelper(ReadAheadHelper helper) {
if (readAheadNames == null)
readAheadNames = new HashMap(3);
readAheadNames.put(helper.getParentDOMName(), helper);
}
public boolean isDependencyChild() {
return false;
}
/**
* @return
*/
public boolean isDependencyParent() {
if (isDependencyParent == null) {
isDependencyParent = Boolean.FALSE;
Translator[] theChildren = getChildren(null, -1);
if (theChildren != null) {
for (int i = 0; i < theChildren.length; i++) {
//For now we assume one
if (theChildren[i].isDependencyChild()) {
isDependencyParent = Boolean.TRUE;
dependencyFeature = theChildren[i].getDependencyFeature();
}
}
}
}
return isDependencyParent.booleanValue();
}
/**
* @return
*/
public EStructuralFeature getDependencyFeature() {
return dependencyFeature;
}
public EObject basicGetDependencyObject(EObject parent) {
return (EObject) parent.eGet(dependencyFeature);
}
/**
* Use when the DOM path is not null, and there are no children. Default is false, but
* subclasses may wish to override
*/
public boolean shouldRenderEmptyDOMPath(EObject eObject) {
return isEmptyContentSignificant();
}
/**
* Use when the translator tolerates parent nodes that relate to the DOM path, and no children;
* default is do nothing
*/
public void setMOFValueFromEmptyDOMPath(EObject eObject) {
}
/**
* Namespace for the attributes
*
* @return
*/
public String getNameSpace() {
return fNameSpace;
}
/**
* Set the namespace for the dom attribute
*
* @param string
*/
public void setNameSpace(String string) {
fNameSpace = string;
}
public VariableTranslatorFactory getVariableTranslatorFactory() {
if (isObjectMap())
return DefaultTranslatorFactory.INSTANCE;
return null;
}
public boolean isEnumFeature() {
return feature != null && ECORE_PACKAGE.getEEnum().isInstance(feature.getEType());
}
public boolean isUnsettable() {
return feature != null && feature.isUnsettable();
}
public boolean isDataType() {
return feature != null && feature.getEType() instanceof EDataType;
}
/**
* @return
*/
public boolean isComment() {
return (fStyle & COMMENT_FEATURE) != 0;
}
}