*/
package org.opendaylight.yangtools.yang.data.util;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.base.CharMatcher;
-import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import java.util.LinkedList;
-import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.List;
import org.opendaylight.yangtools.concepts.Builder;
import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+
/**
- *
* Iterator which lazily parses {@link PathArgument} from string representation.
*
+ * <p>
* Note that invocation of {@link #hasNext()} or {@link #next()} may result in
- * throwing of {@link IllegalArgumentException} if underlying string represenation
+ * throwing of {@link IllegalArgumentException} if underlying string representation
* is not correctly serialized or does not represent instance identifier valid
* for associated schema context.
- *
- * In order to obtain {@link Iterable} or {@link java.util.Collection} please use
- * {@link com.google.common.collect.ImmutableList#copyOf(java.util.Iterator)}
- * with this Iterator, which will trigger computation of all path arguments.
- *
*/
-class XpathStringParsingPathArgumentBuilder implements Builder<Iterable<PathArgument>> {
+final class XpathStringParsingPathArgumentBuilder implements Builder<List<PathArgument>> {
/**
- * Matcher matching WSP YANG ABNF token
- *
+ * Matcher matching WSP YANG ABNF token.
*/
private static final CharMatcher WSP = CharMatcher.anyOf(" \t");
/**
* Matcher matching IDENTIFIER first char token.
- *
*/
- private static final CharMatcher IDENTIFIER_FIRST_CHAR =
- CharMatcher.inRange('a', 'z')
- .or(CharMatcher.inRange('A', 'Z'))
- .or(CharMatcher.is('_')).precomputed();
+ private static final CharMatcher IDENTIFIER_FIRST_CHAR = CharMatcher.inRange('a', 'z')
+ .or(CharMatcher.inRange('A', 'Z')).or(CharMatcher.is('_')).precomputed();
+
/**
- *
- * Matcher matching IDENTIFIER token
- *
+ * Matcher matching IDENTIFIER token.
*/
- private static final CharMatcher IDENTIFIER =
- IDENTIFIER_FIRST_CHAR
- .or(CharMatcher.inRange('0', '9'))
+ private static final CharMatcher IDENTIFIER = IDENTIFIER_FIRST_CHAR.or(CharMatcher.inRange('0', '9'))
.or(CharMatcher.anyOf(".-")).precomputed();
- private static final CharMatcher SQUOTE = CharMatcher.is('\'');
- private static final CharMatcher DQUOTE = CharMatcher.is('"');
+ private static final CharMatcher QUOTE = CharMatcher.anyOf("'\"");
private static final char SLASH = '/';
private static final char COLON = ':';
private static final char PRECONDITION_START = '[';
private static final char PRECONDITION_END = ']';
+ private final List<PathArgument> product = new ArrayList<>();
private final AbstractStringInstanceIdentifierCodec codec;
private final String data;
- private final LinkedList<PathArgument> product = new LinkedList<>();
-
private DataSchemaContextNode<?> current;
+ private QNameModule lastModule;
private int offset;
- XpathStringParsingPathArgumentBuilder(AbstractStringInstanceIdentifierCodec codec, String data) {
- this.codec = Preconditions.checkNotNull(codec);
- this.data = Preconditions.checkNotNull(data);
+ XpathStringParsingPathArgumentBuilder(final AbstractStringInstanceIdentifierCodec codec, final String data) {
+ this.codec = requireNonNull(codec);
+ this.data = requireNonNull(data);
this.current = codec.getDataContextTree().getRoot();
this.offset = 0;
}
-
@Override
- public Iterable<PathArgument> build() {
+ public List<PathArgument> build() {
while (!allCharactersConsumed()) {
product.add(computeNextArgument());
}
}
private PathArgument computeNextArgument() {
- checkValid(SLASH == currentChar(),"Identifier must start with '/'.");
+ checkValid(SLASH == currentChar(), "Identifier must start with '/'.");
skipCurrentChar();
-
- QName name = nextQName();
- if(allCharactersConsumed() || SLASH == currentChar()) {
+ checkValid(!allCharactersConsumed(), "Identifier cannot end with '/'.");
+ final QName name = nextQName();
+ // Memoize module
+ lastModule = name.getModule();
+ if (allCharactersConsumed() || SLASH == currentChar()) {
return computeIdentifier(name);
- } else {
- checkValid(PRECONDITION_START == currentChar(), "Last element must be identifier, predicate or '/'");
- return computeIdentifierWithPredicate(name);
}
- }
+ checkValid(PRECONDITION_START == currentChar(), "Last element must be identifier, predicate or '/'");
+ return computeIdentifierWithPredicate(name);
+ }
- private DataSchemaContextNode<?> nextContextNode(QName name) {
+ private DataSchemaContextNode<?> nextContextNode(final QName name) {
current = current.getChild(name);
checkValid(current != null, "%s is not correct schema node identifier.",name);
- while(current.isMixin()) {
+ while (current.isMixin()) {
product.add(current.getIdentifier());
current = current.getChild(name);
}
return current;
}
-
/**
- *
* Creates path argument with predicates and sets offset
* to end of path argument.
*
* @param name QName of node, for which predicates are computed.
* @return PathArgument representing node selection with predictes
*/
- private PathArgument computeIdentifierWithPredicate(QName name) {
+ private PathArgument computeIdentifierWithPredicate(final QName name) {
DataSchemaContextNode<?> currentNode = nextContextNode(name);
checkValid(currentNode.isKeyedEntry(), "Entry %s does not allow specifying predicates.", name);
ImmutableMap.Builder<QName,Object> keyValues = ImmutableMap.builder();
- while(!allCharactersConsumed() && PRECONDITION_START == currentChar()) {
+ while (!allCharactersConsumed() && PRECONDITION_START == currentChar()) {
skipCurrentChar();
skipWhitespaces();
final QName key;
- if(DOT == currentChar()) {
+ if (DOT == currentChar()) {
key = null;
skipCurrentChar();
} else {
skipWhitespaces();
checkCurrentAndSkip(EQUALS, "Precondition must contain '='");
skipWhitespaces();
- final Object value = deserializeValue(key,nextQuotedValue());
+ final String keyValue = nextQuotedValue();
skipWhitespaces();
checkCurrentAndSkip(PRECONDITION_END, "Precondition must ends with ']'");
// Break-out from method for leaf-list case
- if(key == null && currentNode.isLeaf()) {
+ if (key == null && currentNode.isLeaf()) {
checkValid(offset == data.length(), "Leaf argument must be last argument of instance identifier.");
- return new YangInstanceIdentifier.NodeWithValue(name, value);
+ return new NodeWithValue<>(name, keyValue);
}
+ final DataSchemaContextNode<?> keyNode = currentNode.getChild(key);
+ checkValid(keyNode != null, "%s is not correct schema node identifier.", key);
+ final Object value = codec.deserializeKeyValue(keyNode.getDataSchemaNode(), keyValue);
keyValues.put(key, value);
}
- return new YangInstanceIdentifier.NodeIdentifierWithPredicates(name, keyValues.build());
+ return NodeIdentifierWithPredicates.of(name, keyValues.build());
}
- private PathArgument computeIdentifier(QName name) {
+ private PathArgument computeIdentifier(final QName name) {
DataSchemaContextNode<?> currentNode = nextContextNode(name);
checkValid(!currentNode.isKeyedEntry(), "Entry %s requires key or value predicate to be present", name);
return currentNode.getIdentifier();
}
-
/**
- *
* Returns following QName and sets offset to end of QName.
*
* @return following QName.
*/
private QName nextQName() {
- // Consume prefix or identifie
+ // Consume prefix or identifier
final String maybePrefix = nextIdentifier();
- final String prefix,localName;
- if(COLON == currentChar()) {
- // previous token is prefix;
- prefix = maybePrefix;
+ if (!allCharactersConsumed() && COLON == currentChar()) {
+ // previous token is prefix
skipCurrentChar();
- localName = nextIdentifier();
- } else {
- prefix = "";
- localName = maybePrefix;
+ return codec.createQName(maybePrefix, nextIdentifier());
}
- return createQName(prefix, localName);
+
+ return codec.createQName(lastModule, maybePrefix);
}
/**
- * Returns true if all characters from input string
- * were consumed.
+ * Returns true if all characters from input string were consumed.
*
- * @return true if all characters from input string
- * were consumed.
+ * @return true if all characters from input string were consumed.
*/
private boolean allCharactersConsumed() {
return offset == data.length();
}
-
- private QName createQName(String prefix, String localName) {
- return codec.createQName(prefix, localName);
- }
-
/**
- *
* Skips current char if it equals expected otherwise fails parsing.
*
* @param expected Expected character
* @param errorMsg Error message if {@link #currentChar()} does not match expected.
*/
- private void checkCurrentAndSkip(char expected, String errorMsg) {
+ private void checkCurrentAndSkip(final char expected, final String errorMsg) {
checkValid(expected == currentChar(), errorMsg);
offset++;
}
-
/**
+ * Fails parsing if a condition is not met.
*
- * Deserializes value for supplied key
- *
- * @param key Name of referenced key, If null, referenced leaf is previous encountered item.
- * @param value Value to be checked and deserialized
- * @return Object representing value in yang-data-api format.
- */
- private Object deserializeValue(@Nullable QName key, String value) {
- // FIXME: Use codec to deserialize value to correct Java type
- return value;
- }
-
- /**
- *
- * Fails parsing if condition is not met.
- *
+ * <p>
* In case of error provides pointer to failed instance identifier,
- * offset on which failure occured with explanation.
+ * offset on which failure occurred with explanation.
*
* @param condition Fails parsing if {@code condition} is false
* @param errorMsg Error message which will be provided to user.
- * @param attributes
*/
- private void checkValid(boolean condition, String errorMsg, Object... attributes) {
- Preconditions.checkArgument(condition, "Could not parse Instance Identifier '%s'. Offset: %s : Reason: %s",
- data,
- offset,
- String.format(errorMsg, attributes));
+ private void checkValid(final boolean condition, final String errorMsg, final Object... attributes) {
+ if (!condition) {
+ throw new IllegalArgumentException(String.format(
+ "Could not parse Instance Identifier '%s'. Offset: %s : Reason: %s", data, offset,
+ String.format(errorMsg, attributes)));
+ }
}
/**
- *
- * Returns following value of quoted literal (without qoutes)
- * and sets offset after literal.
+ * Returns following value of quoted literal (without quotes) and sets offset after literal.
*
* @return String literal
*/
private String nextQuotedValue() {
- char quoteChar = currentChar();
- checkValidQuotation(quoteChar);
+ final char quoteChar = currentChar();
+ checkValid(QUOTE.matches(quoteChar), "Value must be qoute escaped with ''' or '\"'.");
skipCurrentChar();
- int valueStart = offset;
- int endQoute = data.indexOf(quoteChar, offset);
- String value = data.substring(valueStart, endQoute);
+ final int valueStart = offset;
+ final int endQoute = data.indexOf(quoteChar, offset);
+ final String value = data.substring(valueStart, endQoute);
offset = endQoute;
skipCurrentChar();
return value;
}
/**
- * Increases processing offset by 1
+ * Increases processing offset by 1.
*/
private void skipCurrentChar() {
offset++;
}
/**
- * Skip whitespace characters, sets offset to first following
- * non-whitespace character.
+ * Skip whitespace characters, sets offset to first following non-whitespace character.
*/
private void skipWhitespaces() {
nextSequenceEnd(WSP);
* @return string which matches IDENTIFIER YANG ABNF token
*/
private String nextIdentifier() {
- int start = offset;
- checkValid(IDENTIFIER_FIRST_CHAR.matches(currentChar()), "Identifier must start with character from set 'a-zA-Z_'");
+ checkValid(IDENTIFIER_FIRST_CHAR.matches(currentChar()),
+ "Identifier must start with character from set 'a-zA-Z_'");
+ final int start = offset;
nextSequenceEnd(IDENTIFIER);
return data.substring(start, offset);
}
- private void nextSequenceEnd(CharMatcher matcher) {
- while(!allCharactersConsumed() && matcher.matches(data.charAt(offset))) {
+ private void nextSequenceEnd(final CharMatcher matcher) {
+ while (!allCharactersConsumed() && matcher.matches(data.charAt(offset))) {
offset++;
}
}
-
- private void checkValidQuotation(char quoteChar) {
- checkValid(
- SQUOTE.matches(quoteChar) || DQUOTE.matches(quoteChar),
- "Value must be qoute escaped with ''' or '\"'.");
-
- }
-
}