/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.transformation.ui.wizards.xmlfile;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
import org.teiid.core.designer.util.CoreArgCheck;
import org.teiid.core.designer.util.StringConstants;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.query.IQueryFactory;
import org.teiid.designer.query.IQueryService;
import org.teiid.designer.query.proc.ITeiidXmlColumnInfo;
import org.teiid.designer.query.sql.symbol.IElementSymbol;
import org.teiid.designer.transformation.ui.Messages;
import org.teiid.designer.transformation.ui.UiConstants;
/**
* @since 8.0
*/
public class TeiidXmlColumnInfo implements ITeiidXmlColumnInfo {
private static final String TEXT_SEGMENT = "text()"; //$NON-NLS-1$
private static final String AT_SIGN = "@"; //$NON-NLS-1$
private static final String DOT_DOT = ".."; //$NON-NLS-1$
/**
* The unique column name (never <code>null</code> or empty).
*/
private IElementSymbol nameSymbol;
/**
* The unique column datatype (never <code>null</code> or empty).
*/
private String datatype;
/**
* The column width value
*/
private int width = DEFAULT_WIDTH;
/**
* The unique column datatype (never <code>null</code> or empty).
*/
private boolean forOrdinality;
/**
* The unique column datatype (never <code>null</code> or empty).
*/
private String defaultValue = StringConstants.EMPTY_STRING;
/**
* The full xml path
*/
private String relativePath = StringConstants.EMPTY_STRING;
/**
* The root xml path
*/
private String rootXmlPath = StringConstants.EMPTY_STRING;
/**
* The xml element
*/
private XmlElement xmlElement;
/**
* The xml attribute
*/
private XmlAttribute xmlAttribute;
/**
* Current <code>IStatus</code> representing the state of the input values for this instance of
* <code>TeiidColumnInfo</code>
*/
private IStatus status;
private boolean pathOverriden = false;
boolean initializing = false;
/**
*
* @param name the column name (never <code>null</code> or empty).
*/
public TeiidXmlColumnInfo(XmlElement element, String rootPath) {
this(element, rootPath, DEFAULT_DATATYPE);
}
/**
*
* @param name the column name (never <code>null</code> or empty).
*/
public TeiidXmlColumnInfo(XmlAttribute attribute, String rootPath) {
this(attribute, rootPath, DEFAULT_DATATYPE);
}
/**
*
* @param name the column name (never <code>null</code> or empty).
* @param datatype the column datatype (never <code>null</code> or empty).
*/
public TeiidXmlColumnInfo(XmlAttribute attribute, String rootPath, String datatype) {
super();
CoreArgCheck.isNotNull(attribute, "attribute is null"); //$NON-NLS-1$
CoreArgCheck.isNotEmpty(datatype, "datatype is null"); //$NON-NLS-1$
initializing = true;
this.xmlAttribute = attribute;
this.xmlElement = attribute.getElement();
setRootPath(rootPath);
initNameSymbol(xmlAttribute.getName());
setRelativePathInternal(attribute);
this.datatype = datatype;
this.defaultValue = StringConstants.EMPTY_STRING;
validate();
initializing = false;
}
/**
*
* @param name the column name (never <code>null</code> or empty).
* @param datatype the column datatype (never <code>null</code> or empty).
*/
public TeiidXmlColumnInfo(XmlElement element, String rootPath, String datatype) {
super();
CoreArgCheck.isNotNull(element, "element is null"); //$NON-NLS-1$
CoreArgCheck.isNotEmpty(datatype, "datatype is null"); //$NON-NLS-1$
initializing = true;
this.xmlElement = element;
setRootPath(rootPath);
setRelativePathInternal(element);
initNameSymbol(element.getName());
this.datatype = datatype;
this.defaultValue = StringConstants.EMPTY_STRING;
validate();
initializing = false;
}
/**
*
* @param name the column name (never <code>null</code> or empty).
* @param datatype the column datatype (never <code>null</code> or empty).
*/
public TeiidXmlColumnInfo(
XmlElement element,
XmlAttribute attribute,
String name,
boolean ordinality,
String datatype,
String defaultValue,
String rootPath,
String fullXmlPath ) {
super();
initializing = true;
this.xmlElement = element;
this.xmlAttribute = attribute;
setRootPath(rootPath);
setRelativePathInternal(element);
initNameSymbol(name);
this.datatype = datatype;
this.forOrdinality = ordinality;
if( defaultValue == null ) {
this.defaultValue = StringConstants.EMPTY_STRING;
} else {
this.defaultValue = defaultValue;
}
initializing = false;
}
/**
* Initialise the {@link ElementSymbol} to hold the
* name. This validates the symbol's character composition.
*
* The '.' character is the only punctuation symbol that will cause
* problems for an element symbol so these are replaced these with '_'.
*/
private void initNameSymbol(final String name) {
IQueryService queryService = ModelerCore.getTeiidQueryService();
IQueryFactory factory = queryService.createQueryFactory();
nameSymbol = factory.createElementSymbol(name.replaceAll("\\.", "_")); //$NON-NLS-1$//$NON-NLS-2$
}
/**
* Get the fully validated column name. This should be used in SQL string
* generation.
*
* @return name the column name
*/
public String getSymbolName() {
return this.nameSymbol.toString();
}
/**
* Get the column name for display in the UI. This removes any quotes for
* aesthetic reasons. Use {@link #getSymbolName()} for retrieving the
* fully validated column name.
*
* @return the column name sans quotes.
*/
public String getName() {
String name = this.nameSymbol.toString();
return name.replaceAll("\"", ""); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
*
* @param name the column name (never <code>null</code> or empty).
*/
public void setName(String name) {
CoreArgCheck.isNotNull(name, "name is null"); //$NON-NLS-1$
initNameSymbol(name);
validate();
}
/**
*
* @return datatype the column datatype
*/
public String getDatatype() {
return this.datatype;
}
/**
*
* @param datatype the column datatype (never <code>null</code> or empty).
*/
public void setDatatype(String datatype) {
CoreArgCheck.isNotNull(datatype, "datatype is null"); //$NON-NLS-1$
this.datatype = datatype;
validate();
}
/**
*
* @return name the column name
*/
public int getWidth() {
return this.width;
}
/**
*
* @param name the column name (never <code>null</code> or empty).
*/
public void setWidth(int width) {
CoreArgCheck.isPositive(width, "width is less than 1"); //$NON-NLS-1$
this.width = width;
validate();
}
/**
*
* @return defaultValue the column defaultValue
*/
public String getDefaultValue() {
return this.defaultValue;
}
/**
*
* @param defaultValue the column defaultValue
*/
public void setDefaultValue(String defaultValue) {
if( defaultValue == null ) {
this.defaultValue = StringConstants.EMPTY_STRING;
} else {
this.defaultValue = defaultValue;
}
validate();
}
/**
*
* @param xmlPath the column xmlPath
*/
public void setRelativePath(String relativePath) {
this.relativePath = relativePath;
validate();
}
private void setRelativePathInternal(Object obj) {
String rootPath = this.rootXmlPath;
if( obj instanceof XmlElement ) {
XmlElement element = (XmlElement)obj;
String fullPath = element.getFullPath();
StringBuffer relativePathBuff = new StringBuffer(getRelativePath(fullPath,rootPath));
if(!relativePathBuff.toString().isEmpty() && !relativePathBuff.toString().endsWith(XmlElement.SEPARATOR)) {
relativePathBuff.append(XmlElement.SEPARATOR);
}
relativePathBuff.append(TEXT_SEGMENT);
setRelativePath(relativePathBuff.toString());
return;
}
if( obj instanceof XmlAttribute ) {
XmlAttribute attr = (XmlAttribute)obj;
XmlElement element = attr.getElement();
String fullPath = element.getFullPath();
StringBuffer relativePathBuff = new StringBuffer(getRelativePath(fullPath,rootPath));
if(!relativePathBuff.toString().isEmpty() && !relativePathBuff.toString().endsWith(XmlElement.SEPARATOR)) {
relativePathBuff.append(XmlElement.SEPARATOR);
}
relativePathBuff.append( AT_SIGN + attr.getName());
setRelativePath(relativePathBuff.toString());
return;
}
}
/**
*
* @return xmlPath the column xmlPath
*/
public String getRelativePath() {
return this.relativePath.toString();
}
public void setXmlElement(XmlElement element) {
this.xmlElement = element;
}
public void setXmlAttribute(XmlAttribute attribute) {
this.xmlAttribute = attribute;
this.xmlElement = attribute.getElement();
}
/**
*
* @return xmlPath the column xmlPath
*/
public String getFullXmlPath() {
// Return ROOT PATH + relative path
return this.rootXmlPath + XmlElement.SEPARATOR + this.relativePath;
}
public void setRootPath(String thePath) {
boolean rootPathChanged = false;
String newRootPath = null;
if( thePath != null && thePath.length() > 0 ) {
newRootPath = thePath;
} else {
newRootPath = StringConstants.EMPTY_STRING;
}
if( !this.rootXmlPath.equalsIgnoreCase(newRootPath) ) {
rootPathChanged = true;
}
if( rootPathChanged ) {
// Only re-calculate the relative path if it is VALID
if(!initializing && fullPathExists() ) {
// Recalculate the relative path?
// ROOT PATH should always be VALID and be a part of the backing XmlElement.getFullXmlPath() value
if( isXmlAttribute() ) {
String fullPath = this.xmlElement.getFullPath() + XmlElement.SEPARATOR + AT_SIGN + getXmlAttribute().getName();
String relativePath = getRelativePath(fullPath,newRootPath);
setRelativePath(relativePath);
} else {
String fullPath = this.xmlElement.getFullPath();
if( this.relativePath != null && !this.relativePath.isEmpty() && TEXT_SEGMENT.equalsIgnoreCase(getLastSegment(this.relativePath))) {
String shortPath = fullPath;
String newFullPath = shortPath + XmlElement.SEPARATOR + TEXT_SEGMENT;
String relativePath = getRelativePath(newFullPath,newRootPath);
setRelativePath(relativePath);
} else {
String theFullPath = this.xmlElement.getFullPath();
String relativePath = getRelativePath(theFullPath,newRootPath);
setRelativePath(relativePath);
}
}
}
this.rootXmlPath = newRootPath;
}
if( !initializing ) {
validate();
}
}
/**
*
* @return xmlElement the xmlElement
*/
public XmlElement getXmlElement() {
return this.xmlElement;
}
/**
*
* @return xmlElement the xmlAttribute
*/
public XmlAttribute getXmlAttribute() {
return this.xmlAttribute;
}
public boolean isXmlAttribute() {
return this.xmlAttribute != null;
}
/**r
*
* @return forOrdinality the column forOrdinality
*/
public boolean getOrdinality() {
return this.forOrdinality;
}
/**
*
* @param forOrdinality the column forOrdinality
*/
public void setOrdinality(boolean value) {
this.forOrdinality = value;
validate();
}
/**
*
* @return forOrdinality the column forOrdinality
*/
public boolean isPathOverridden() {
return this.pathOverriden;
}
/**
*
* @param forOrdinality the column forOrdinality
*/
public void setPathOverridden(boolean value) {
this.pathOverriden = value;
validate();
}
/**
*
* @return status the <code>IStatus</code> representing the validity of the data in this info object
*/
public IStatus getStatus() {
return this.status;
}
/**
*
* @param status the <code>IStatus</code> representing the validity of the data in this info object
*/
public void setStatus(IStatus status) {
this.status = status;
}
private void validate() {
boolean pathOK = fullPathExists();
if( !pathOK ) {
setStatus(new Status(IStatus.WARNING, UiConstants.PLUGIN_ID,
NLS.bind(Messages.InvalidPathWarning, getRelativePath(), getName())) );
return;
}
setStatus(Status.OK_STATUS);
}
private boolean fullPathExists() {
// Validate that the relative path exists in the full xml path
String fullXmlElementPath = this.xmlElement.getFullPath();
boolean pathOK = true;
if( isXmlAttribute() ) {
String thisFullPath = getFullXmlPath();
thisFullPath = collapseDotDots(thisFullPath);
String shortPath = removeLastSegment(thisFullPath);
int nSegs = getPathSegments(shortPath).size();
if( getMatchingSegmentCount(fullXmlElementPath,shortPath) != nSegs ) {
pathOK = false;
}
} else {
String thisFullPath = getFullXmlPath();
if( TEXT_SEGMENT.equalsIgnoreCase(getLastSegment(thisFullPath))) {
thisFullPath = collapseDotDots(thisFullPath);
String shortPath = removeLastSegment(thisFullPath);
int nSegs = getPathSegments(shortPath).size();
if( getMatchingSegmentCount(fullXmlElementPath,shortPath) != nSegs ) {
pathOK = false;
}
} else {
pathOK = thisFullPath.equals(fullXmlElementPath);
}
}
return pathOK;
}
/**
* Form the relative path from the full path and the root path
* @param fullPath the full path
* @param rootPath the root path
* @return the relative path
*/
private String getRelativePath(String fullPath, String rootPath) {
StringBuffer resultBuff = new StringBuffer();
// Get number of matching segments
int nMatch = getMatchingSegmentCount(fullPath,rootPath);
// Extra segments (may need '..')
int extraSegs = getPathSegments(rootPath).size()-nMatch;
List<String> segments = getPathSegments(fullPath);
for(int i=0; i<segments.size(); i++) {
if(i>=nMatch) {
// add separator after first matching segment
if(i!=nMatch) resultBuff.append(XmlElement.SEPARATOR);
// add segment
resultBuff.append( segments.get(i) );
}
}
// Add ../ for extra segments
for(int i=0; i<extraSegs; i++) {
resultBuff.insert(0, DOT_DOT+XmlElement.SEPARATOR);
}
return resultBuff.toString();
}
/**
* Remove the last segment from the supplied path
* @param path the path
* @return the path, minus last segment
*/
private String removeLastSegment(String path) {
StringBuffer resultBuff = new StringBuffer();
List<String> segments = getPathSegments(path);
for(int i=0; i<segments.size()-1; i++) {
resultBuff.append(XmlElement.SEPARATOR+segments.get(i));
}
return resultBuff.toString();
}
/**
* Get the last segment of the supplied path
* @param path the path
* @return the last path segment
*/
private String getLastSegment(String path) {
String lastSegment = StringConstants.EMPTY_STRING;
List<String> segments = getPathSegments(path);
if(segments.size()>0) {
lastSegment = segments.get(segments.size()-1);
}
return lastSegment;
}
/**
* Get the list of path segments (separated by XmlElement.SEPARATOR)
* @param path the path
* @return the list of segments
*/
private List<String> getPathSegments(String path) {
List<String> segments = new ArrayList<String>();
StringTokenizer st = new StringTokenizer(path,XmlElement.SEPARATOR);
while(st.hasMoreTokens()) {
segments.add(st.nextToken());
}
return segments;
}
/**
* Get the number of segments of the two path strings that match
* @param path1 the first path
* @param path2 the second path
* @return number of matching segments
*/
private int getMatchingSegmentCount(String path1, String path2) {
int matchingSegs = 0;
List<String> path1Segs = getPathSegments(path1);
List<String> path2Segs = getPathSegments(path2);
int path1Size = path1Segs.size();
int path2Size = path2Segs.size();
if(path1Size<=path2Size) {
for(int i=0; i<path1Segs.size(); i++) {
String path1Seg = path1Segs.get(i);
String path2Seg = path2Segs.get(i);
if(path1Seg!=null && path1Seg.equalsIgnoreCase(path2Seg)) {
matchingSegs++;
}
}
} else {
for(int i=0; i<path2Segs.size(); i++) {
String path1Seg = path1Segs.get(i);
String path2Seg = path2Segs.get(i);
if(path2Seg!=null && path2Seg.equalsIgnoreCase(path1Seg)) {
matchingSegs++;
}
}
}
return matchingSegs;
}
/**
* Removes .. in relative path by removing the .. and the segment preceding it.
* Used to compare a relative path to full path in validation
* @param originalStr the original string
* @return the string after collapsing ..
*/
private String collapseDotDots(String originalStr) {
String theString = originalStr;
while(theString.indexOf(XmlElement.SEPARATOR+DOT_DOT)!=-1) {
int dotdotIndex = theString.indexOf(XmlElement.SEPARATOR+DOT_DOT);
String leadingStr = theString.substring(0, dotdotIndex);
String trailingStr = theString.substring(dotdotIndex+3);
int lastDelim = leadingStr.lastIndexOf(XmlElement.SEPARATOR);
if(lastDelim!=-1) {
String newLeading = leadingStr.substring(0,lastDelim);
theString = newLeading+trailingStr;
} else {
theString = leadingStr+trailingStr;
}
}
return theString;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder text = new StringBuilder();
text.append("Column Info: "); //$NON-NLS-1$
text.append("name = ").append(getSymbolName()); //$NON-NLS-1$
text.append(", ordinal = ").append(getOrdinality()); //$NON-NLS-1$
text.append(", datatype = ").append(getDatatype()); //$NON-NLS-1$
text.append(", default = ").append(getDefaultValue()); //$NON-NLS-1$
text.append(", PATH = ").append(getRelativePath()); //$NON-NLS-1$
return text.toString();
}
}