blob: 0415dbb21d55cb8018a83192145319c7cf61818e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 Oracle. 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:
* Oracle - initial API and implementation
*******************************************************************************/
package org.eclipse.jpt.jaxb.eclipselink.core.internal.context.xpath.java;
import java.util.List;
import java.util.Vector;
import org.eclipse.jpt.common.core.internal.utility.SimpleTextRange;
import org.eclipse.jpt.common.core.utility.TextRange;
import org.eclipse.jpt.common.utility.Filter;
import org.eclipse.jpt.common.utility.internal.ArrayTools;
import org.eclipse.jpt.common.utility.internal.CollectionTools;
import org.eclipse.jpt.common.utility.internal.StringTools;
import org.eclipse.jpt.common.utility.internal.iterables.CompositeIterable;
import org.eclipse.jpt.common.utility.internal.iterables.EmptyIterable;
import org.eclipse.jpt.common.utility.internal.iterables.FilteringIterable;
import org.eclipse.jpt.common.utility.internal.iterables.SingleElementIterable;
import org.eclipse.jpt.common.utility.internal.iterables.TransformationIterable;
import org.eclipse.jpt.jaxb.core.JaxbNode;
import org.eclipse.jpt.jaxb.core.context.JaxbPackage;
import org.eclipse.jpt.jaxb.core.context.JaxbPackageInfo;
import org.eclipse.jpt.jaxb.core.context.XmlNs;
import org.eclipse.jpt.jaxb.core.context.XmlSchema;
import org.eclipse.jpt.jaxb.core.xsd.XsdAttributeUse;
import org.eclipse.jpt.jaxb.core.xsd.XsdElementDeclaration;
import org.eclipse.jpt.jaxb.core.xsd.XsdTypeDefinition;
import org.eclipse.jpt.jaxb.eclipselink.core.internal.validation.ELJaxbValidationMessageBuilder;
import org.eclipse.jpt.jaxb.eclipselink.core.internal.validation.ELJaxbValidationMessages;
import org.eclipse.wst.validation.internal.provisional.core.IMessage;
public class XPath {
public static String DELIM = "/";
public static String ATT_PREFIX = "@";
public static String TEXT = "text()";
public static String SELF = ".";
public static String COLON = ":";
public static char OPEN_BRACKET = '[';
public static char CLOSE_BRACKET = ']';
public static String attributeXPath(String prefix, String localName) {
if (prefix == null) {
return StringTools.concatenate(ATT_PREFIX, localName);
}
return StringTools.concatenate(ATT_PREFIX, prefix, COLON, localName);
}
public static String elementXPath(String prefix, String localName) {
if (prefix == null) {
return localName;
}
return StringTools.concatenate(prefix, COLON, localName);
}
List<Step> steps;
XPath(String xpathString) {
this.steps = new Vector<Step>();
parse(xpathString);
}
protected int getIndex(Step step) {
return this.steps.indexOf(step);
}
protected Step getFirstStep() {
if (this.steps.size() > 0) {
return this.steps.get(0);
}
return null;
}
protected Step getNextStep(Step step) {
int nextIndex = getIndex(step) + 1;
if (this.steps.size() > nextIndex) {
return this.steps.get(nextIndex);
}
return null;
}
protected void parse(String xpath) {
for (String segment : xpath.split(DELIM, -1)) {
this.steps.add(createStep(segment));
}
}
protected Step createStep(String stepValue) {
if (TEXT.equals(stepValue)) {
return new TextStep();
}
else if (SELF.equals(stepValue)) {
return new SelfStep();
}
else if (stepValue.startsWith(ATT_PREFIX)) {
return tryCreateAttributeStep(stepValue);
}
else {
return tryCreateElementStep(stepValue);
}
}
protected Step tryCreateAttributeStep(String stepValue) {
// must start with "@" - already tested that
try {
String attributeStepValue = stepValue.substring(1);
String[] stepParts = parseStepParts(attributeStepValue);
return new AttributeStep(stepParts[0], stepParts[1], ArrayTools.subArray(stepParts, 2, stepParts.length));
}
catch (IllegalArgumentException ife) {
return new InvalidStep(stepValue);
}
}
protected Step tryCreateElementStep(String stepValue) {
try {
String[] stepParts = parseStepParts(stepValue);
return new ElementStep(stepParts[0], stepParts[1], ArrayTools.subArray(stepParts, 2, stepParts.length));
}
catch (IllegalArgumentException ife) {
return new InvalidStep(stepValue);
}
}
protected String[] parseStepParts(String stepValue) {
String[] predicate = new String[0];
int predicateStart = stepValue.lastIndexOf(OPEN_BRACKET);
while (predicateStart >= 0) {
int predicateEnd = stepValue.lastIndexOf(CLOSE_BRACKET);
if (predicateEnd <= predicateStart || predicateEnd != stepValue.length() - 1) {
throw new IllegalArgumentException();
}
predicate = ArrayTools.add(predicate, 0, stepValue.substring(predicateStart +1, predicateEnd));
stepValue = stepValue.substring(0, predicateStart);
predicateStart = stepValue.lastIndexOf(OPEN_BRACKET);
}
String nsPrefix = null;
int colon = stepValue.indexOf(COLON);
if (colon > 0) {
if (stepValue.indexOf(COLON, colon + 1) != -1) {
throw new IllegalArgumentException();
}
nsPrefix = stepValue.substring(0, colon);
stepValue = stepValue.substring(colon +1);
}
if (StringTools.stringIsEmpty(stepValue)
|| stepValue.indexOf(COLON) >= 0
|| stepValue.indexOf(OPEN_BRACKET) >= 0
|| stepValue.indexOf(CLOSE_BRACKET) >= 0
|| stepValue.indexOf(' ') >= 0) {
throw new IllegalArgumentException();
}
String[] result = ArrayTools.add(new String[0], nsPrefix);
result = ArrayTools.add(result, stepValue);
result = ArrayTools.addAll(result, predicate);
return result;
}
// ***** content assist *****
public Iterable<String> getCompletionProposals(
Context context, XsdTypeDefinition xsdType, int pos, Filter<String> filter) {
return getFirstStep().getCompletionProposals(context, xsdType, StringTools.EMPTY_STRING, pos, filter);
}
// ***** validation *****
public void validate(Context context, XsdTypeDefinition xsdType, List<IMessage> messages) {
getFirstStep().validate(context, xsdType, messages);
}
protected TextRange buildTextRange(Context context, Step step) {
TextRange entireTextRange = context.getTextRange();
int start = 0;
for (Step each : this.steps) {
int length = each.getValue().length() + 2; // include leading/trailing " or /
if (each == step) {
return new SimpleTextRange(entireTextRange.getOffset() + start, length, entireTextRange.getLineNumber());
}
else {
start += length - 1; // only include leading " or /
}
}
throw new IllegalArgumentException("Step must be in list of this XPath's steps.");
}
protected class AttributeStep
extends NamedComponentStep {
protected AttributeStep(String nsPrefix, String localName, String[] predicates) {
super(nsPrefix, localName, predicates);
}
@Override
protected String getValue() {
return ATT_PREFIX + super.getValue();
}
@Override
protected XsdTypeDefinition resolveNextType(Context context, XsdTypeDefinition previousType) {
String namespace = resolveNamespace(context);
if (namespace == null) {
return null;
}
XsdAttributeUse xsdAttribute = previousType.getAttribute(namespace, this.localName);
return (xsdAttribute == null) ? null : xsdAttribute.getAttributeDeclaration().getType();
}
@Override
protected void validate(
Context context, XsdTypeDefinition previousType, List<IMessage> messages) {
if (getNextStep() != null) {
messages.add(
ELJaxbValidationMessageBuilder.buildMessage(
IMessage.HIGH_SEVERITY,
ELJaxbValidationMessages.XPATH__ATTRIBUTE_SEGMENT_MUST_BE_LAST_SEGMENT,
context.getContextObject(),
getTextRange(context)));
return;
}
super.validate(context, previousType, messages);
}
@Override
protected XsdTypeDefinition validateLocalName(
Context context, XsdTypeDefinition previousType, String namespace, List<IMessage> messages) {
XsdAttributeUse xsdAttribute= previousType.getAttribute(namespace, this.localName);
if (xsdAttribute == null) {
messages.add(
ELJaxbValidationMessageBuilder.buildMessage(
IMessage.HIGH_SEVERITY,
ELJaxbValidationMessages.XPATH__UNRESOLVED_ATTRIBUTE,
new String[] { namespace, this.localName },
context.getContextObject(),
getTextRange(context)));
return null;
}
else {
return xsdAttribute.getAttributeDeclaration().getType();
}
}
// @Override
// protected TextRange buildNamespacePrefixTextRange(CompilationUnit astRoot) {
// // assume nsPrefix != null
// TextRange stepTextRange = ELJavaXmlPath.this.buildTextRange(this, astRoot);
// return new SimpleTextRange(stepTextRange.getOffset() + 1, this.nsPrefix.length(), stepTextRange.getLineNumber());
// }
}
protected class ElementStep
extends NamedComponentStep {
protected ElementStep(String nsPrefix, String localName, String[] predicates) {
super(nsPrefix, localName, predicates);
}
@Override
protected XsdTypeDefinition
resolveNextType(Context context, XsdTypeDefinition previousType) {
String namespace = resolveNamespace(context);
if (namespace == null) {
return null;
}
XsdElementDeclaration xsdElement = previousType.getElement(namespace, this.localName);
return (xsdElement == null) ? null : xsdElement.getType();
}
@Override
protected XsdTypeDefinition validateLocalName(
Context context, XsdTypeDefinition previousType, String namespace, List<IMessage> messages) {
XsdElementDeclaration xsdElement = previousType.getElement(namespace, this.localName);
if (xsdElement == null) {
messages.add(
ELJaxbValidationMessageBuilder.buildMessage(
IMessage.HIGH_SEVERITY,
ELJaxbValidationMessages.XPATH__UNRESOLVED_ELEMENT,
new String[] { namespace, this.localName },
context.getContextObject(),
getTextRange(context)));
return null;
}
else {
return xsdElement.getType();
}
}
// @Override
// protected TextRange buildNamespacePrefixTextRange(CompilationUnit astRoot) {
// // assume nsPrefix != null
// TextRange stepTextRange = ELJavaXmlPath.this.buildTextRange(this, astRoot);
// return new SimpleTextRange(stepTextRange.getOffset(), this.nsPrefix.length(), stepTextRange.getLineNumber());
// }
}
protected class InvalidStep
extends Step {
protected String value;
protected InvalidStep(String value) {
super();
this.value = value;
}
@Override
protected String getValue() {
return this.value;
}
@Override
protected XsdTypeDefinition
resolveNextType(Context context, XsdTypeDefinition previousType) {
return null;
}
@Override
protected void validate(
Context context, XsdTypeDefinition previousType, List<IMessage> messages) {
messages. add(ELJaxbValidationMessageBuilder.buildMessage(
IMessage.HIGH_SEVERITY,
ELJaxbValidationMessages.XPATH__INVALID_FORM_ILLEGAL_SEGMENT,
new String[] { getValue() },
context.getContextObject(),
getTextRange(context)));
return;
}
}
protected abstract class NamedComponentStep
extends Step {
protected String nsPrefix;
protected String localName;
protected List<String> predicates;
protected NamedComponentStep(String nsPrefix, String localName, String[] predicates) {
super();
this.nsPrefix = nsPrefix;
this.localName = localName;
this.predicates = new Vector<String>();
CollectionTools.addAll(this.predicates, predicates);
}
@Override
protected String getValue() {
StringBuffer sb = new StringBuffer();
if (this.nsPrefix != null) {
sb.append(nsPrefix);
sb.append(COLON);
}
sb.append(this.localName);
for (String predicate : this.predicates) {
sb.append(OPEN_BRACKET);
sb.append(predicate);
sb.append(CLOSE_BRACKET);
}
return sb.toString();
}
protected String resolveNamespace(Context context) {
JaxbPackage pkg = context.getJaxbPackage();
if (this.nsPrefix == null) {
return "";
}
else {
JaxbPackageInfo pkgInfo = pkg.getPackageInfo();
return (pkgInfo == null) ? null : pkgInfo.getNamespaceForPrefix(this.nsPrefix);
}
}
@Override
protected void validate(
Context context, XsdTypeDefinition previousType, List<IMessage> messages) {
String namespace = resolveNamespace(context);
if (namespace == null) {
messages.add(
ELJaxbValidationMessageBuilder.buildMessage(
IMessage.HIGH_SEVERITY,
ELJaxbValidationMessages.XPATH__INVALID_NS_PREFIX,
new String[] { this.nsPrefix },
context.getContextObject(),
getTextRange(context)));
}
XsdTypeDefinition nextType = (previousType == null) ? null : validateLocalName(context, previousType, namespace, messages);
if (nextType != null) {
Step nextStep = getNextStep();
if (nextStep != null) {
nextStep.validate(context, nextType, messages);
}
}
}
protected abstract XsdTypeDefinition validateLocalName(
Context context, XsdTypeDefinition previousType, String namespace, List<IMessage> messages);
// protected abstract TextRange buildNamespacePrefixTextRange(CompilationUnit astRoot);
}
protected class SelfStep
extends Step {
protected SelfStep() {
super();
}
@Override
protected String getValue() {
return SELF;
}
@Override
protected XsdTypeDefinition
resolveNextType(Context context, XsdTypeDefinition previousType) {
return previousType;
}
@Override
protected void validate(
Context context, XsdTypeDefinition previousType, List<IMessage> messages) {
if (getIndex() != 0) {
messages.add(
ELJaxbValidationMessageBuilder.buildMessage(
IMessage.HIGH_SEVERITY,
ELJaxbValidationMessages.XPATH__SELF_SEGMENT_MUST_BE_FIRST_SEGMENT,
context.getContextObject(),
getTextRange(context)));
return;
}
if (getNextStep() != null) {
getNextStep().validate(context, previousType, messages);
}
}
}
protected abstract class Step {
protected int getIndex() {
return XPath.this.getIndex(this);
}
protected Step getNextStep() {
return XPath.this.getNextStep(this);
}
protected abstract String getValue();
protected abstract XsdTypeDefinition resolveNextType(Context context, XsdTypeDefinition previousType);
protected Iterable<String> getCompletionProposals(
Context context, XsdTypeDefinition previousType,
final String prefix, int pos, Filter<String> filter) {
if (getTextRange(context).includes(pos) || getNextStep() == null) {
return StringTools.convertToJavaStringLiterals(
new FilteringIterable<String>(
new TransformationIterable<String, String>(
new CompositeIterable<String>(
getTextProposals(context, previousType),
getAttributeProposals(context, previousType),
getElementProposals(context, previousType))) {
@Override
protected String transform(String o) {
return StringTools.concatenate(prefix, o);
}
},
filter));
}
Step nextStep = getNextStep();
XsdTypeDefinition nextType = resolveNextType(context, previousType);
if (nextStep != null && nextType != null) {
return nextStep.getCompletionProposals(context, nextType, prefix + getValue() + DELIM, pos, filter);
}
return new SingleElementIterable(TEXT);
}
protected Iterable<String> getTextProposals(Context context, final XsdTypeDefinition xsdType) {
return (xsdType.hasTextContent()) ? new SingleElementIterable(TEXT) : EmptyIterable.instance();
}
protected Iterable<String> getAttributeProposals(Context context, final XsdTypeDefinition xsdType) {
return new CompositeIterable<String>(
new CompositeIterable<String>(
new TransformationIterable<XmlNs, Iterable<String>>(getXmlNsInfos(context)) {
@Override
protected Iterable<String> transform(final XmlNs xmlns) {
return new TransformationIterable<String, String>(xsdType.getAttributeNames(xmlns.getNamespaceURI())) {
@Override
protected String transform(String o) {
return XPath.attributeXPath(xmlns.getPrefix(), o);
}
};
}
}),
new TransformationIterable<String, String>(xsdType.getAttributeNames("")) {
@Override
protected String transform(String o) {
return XPath.attributeXPath(null, o);
}
});
}
protected Iterable<String> getElementProposals(Context context, final XsdTypeDefinition xsdType) {
return new CompositeIterable<String>(
new CompositeIterable<String>(
new TransformationIterable<XmlNs, Iterable<String>>(getXmlNsInfos(context)) {
@Override
protected Iterable<String> transform(final XmlNs xmlns) {
return new TransformationIterable<String, String>(xsdType.getElementNames(xmlns.getNamespaceURI(), false)) {
@Override
protected String transform(String o) {
return XPath.elementXPath(xmlns.getPrefix(), o);
}
};
}
}),
xsdType.getElementNames("", false));
}
protected Iterable<XmlNs> getXmlNsInfos(Context context) {
JaxbPackageInfo pkgInfo = context.getJaxbPackage().getPackageInfo();
XmlSchema xmlSchema = (pkgInfo == null) ? null : pkgInfo.getXmlSchema();
return (xmlSchema == null) ? EmptyIterable.<XmlNs>instance() : xmlSchema.getXmlNsPrefixes();
}
protected void validate(Context context, XsdTypeDefinition previousType, List<IMessage> messages) {
// no op
}
protected TextRange getTextRange(Context context) {
return XPath.this.buildTextRange(context, this);
}
}
protected class TextStep
extends Step {
protected TextStep() {
super();
}
@Override
protected String getValue() {
return TEXT;
}
@Override
protected XsdTypeDefinition
resolveNextType(Context context, XsdTypeDefinition previousType) {
return previousType;
}
@Override
protected void validate(
Context context, XsdTypeDefinition previousType, List<IMessage> messages) {
if (getNextStep() != null) {
messages.add(
ELJaxbValidationMessageBuilder.buildMessage(
IMessage.HIGH_SEVERITY,
ELJaxbValidationMessages.XPATH__TEXT_SEGMENT_MUST_BE_LAST_SEGMENT,
context.getContextObject(),
getTextRange(context)));
}
}
}
public interface Context {
JaxbNode getContextObject();
JaxbPackage getJaxbPackage();
TextRange getTextRange();
}
}