blob: 050b119d55657dada0547fd3b2acf522ffa9e549 [file] [log] [blame]
/*
* Copyright (c) 1998, 2019 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 v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// 11/05/2014-2.6 Tomas Kraus
// - 449818: Initial API and implementation.
package org.eclipse.persistence.internal.mappings.converters;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* INTERNAL:
* Attribute name tokenizer.
* @author Tomas Kraus
*/
public class AttributeNameTokenizer implements Iterable<String> {
/** INTERNAL: Attribute name dot notation separator. */
public static final char SEPARATOR = '.';
/** Regular expression capturing group name for attribute name prefix. */
private static final String PREFIX = "prefix";
/** Regular expression matching {@code "value."} substring. */
private final static Pattern VALUE_DOT_PATTERN = Pattern.compile(AttributeNamePrefix.VALUE.getName() + "\\"
+ AttributeNameTokenizer.SEPARATOR);
/** Regular expression matching {@code "key."} substring. */
private final static Pattern KEY_DOT_PATTERN = Pattern.compile(AttributeNamePrefix.KEY.getName() + "\\"
+ AttributeNameTokenizer.SEPARATOR);
/**
* INTERNAL:
* Return an attribute name without {@code value.} dot notation prefix.
* @param attributeName Attribute name containing {@code value.} prefix.
* @return Attribute name without {@code value.} prefix or {@code null} when no prefix was found.
*/
public static String getNameAfterVersion(final String attributeName) {
final Matcher matcher = VALUE_DOT_PATTERN.matcher(attributeName);
if (matcher.find()) {
return attributeName.substring(matcher.end());
}
return null;
}
/**
* INTERNAL:
* Return an attribute name without {@code key.} dot notation prefix.
* @param attributeName Attribute name containing {@code key.} prefix.
* @return Attribute name without {@code key.} prefix or {@code null} when no prefix was found.
*/
public static String getNameAfterKey(final String attributeName) {
final Matcher matcher = KEY_DOT_PATTERN.matcher(attributeName);
if (matcher.find()) {
return attributeName.substring(matcher.end());
}
return null;
}
/** Attribute name to be parsed. */
private final String attributeName;
/**
* INTERNAL:
* Creates an instance of attribute name tokenizer.
*/
public AttributeNameTokenizer(final String attributeName) {
this.attributeName = attributeName;
}
/**
* INTERNAL:
* Returns an {@code Iterator<String>} over attribute name tokens.
* @return An {@code Iterator<String>} over attribute name tokens.
*/
@Override
public Iterator<String> iterator() {
return new TokensIterator(attributeName);
}
// This duplicates Iterator<String> interface but may be useful to avoid
// class casting.
/**
* INTERNAL:
* Returns an {@code TokensIterator} over attribute name tokens.
* @return An {@code TokensIterator} over attribute name tokens.
*/
public TokensIterator tokensIterator() {
return new TokensIterator(attributeName);
}
/**
* INTERNAL:
* Attribute name tokenizer parser implemented as an {@code Iterator<String>} over individual attribute name tokens.
*
*/
public static final class TokensIterator
implements Iterator<String> {
/** Regular expression used to parse attribute name and extract prefix and tokens from it. There are two basic
* capturing groups:<ul>
* <li><i>prefix</i> to extract attribute name prefix ({@code "key"} or {@code "value"})</li>
* <li><i>name token</i> to extract individual attribute name tokens</li></ul>
*/
private final static Pattern PREFIX_PATTERN = Pattern.compile("(?:(?<" + PREFIX + ">"
+ AttributeNamePrefix.KEY.getName() + "|" + AttributeNamePrefix.VALUE.getName()+")\\.){0,1}"
+ "([^\\.]+)");
/** Regular expression used to parse attribute name and extract tokens from it. There is just one capturing
* group:<ul>
* <li><i>name token</i> to extract individual attribute name tokens</li></ul>
*/
private final static Pattern SIMPLE_PATTERN = Pattern.compile("([^\\.]+)");
/** Regular expression used to parse attribute name and extract tokens from it. There is just one capturing
* group:<ul>
* <li><i>name token</i> to extract individual attribute name tokens</li></ul>
*/
private final static Pattern NEXT_PATTERN = Pattern.compile("\\.([^\\.]+)");
/** Regular expression matching engine. */
private final Matcher matcher;
/** Prefix found in attribute name. */
private final AttributeNamePrefix prefix;
/** Next token to be returned. */
private String token;
/**
* INTERNAL:
* Creates an instance of attribute name tokenizer iterator. Simple parser without prefix parsing will be used
* so ({@code "key."} and {@code "value."}) will be returned as regular attribute name tokens.
* @param attributeName Attribute name to be parsed.
*/
public TokensIterator(final String attributeName) {
this(attributeName, false);
}
/**
* INTERNAL:
* Creates an instance of attribute name tokenizer iterator.
* @param attributeName Attribute name to be parsed.
* @param isPrefix Do search for attribute name prefixes ({@code "key."} and {@code "value."})?
*/
public TokensIterator(final String attributeName, boolean isPrefix) {
// Use regular expression without prefix. Prefix will be null.
if (!isPrefix) {
matcher = SIMPLE_PATTERN.matcher(attributeName);
// Provided attribute name is matching regular expression.
if (matcher.lookingAt()) {
token = matcher.groupCount() > 0 ? matcher.group(1) : null;
} else {
token = null;
}
prefix = null;
// Use regular expression with prefix. Prefix will be known.
} else {
matcher = PREFIX_PATTERN.matcher(attributeName);
if (matcher.lookingAt()) {
final String prefixString = matcher.group(PREFIX);
prefix = AttributeNamePrefix.toValue(prefixString != null ? prefixString : "");
token = matcher.groupCount() > 1 ? matcher.group(2) : null;
} else {
prefix = AttributeNamePrefix.NULL;
token = null;
}
}
matcher.usePattern(NEXT_PATTERN);
matcher.region(matcher.end(), matcher.regionEnd());
}
/**
* INTERNAL:
* Get attribute name prefix.
* @return Attribute name prefix.
*/
public AttributeNamePrefix getPrefix() {
return prefix;
}
/**
* INTERNAL:
* Returns {@code true} if the iteration has more elements. In other words, returns {@code true}
* if {@link #next()} would return an element rather than throwing an exception.
* @return Value of {@code true} if the iteration has more elements or {@code false} otherwise.
*/
@Override
public boolean hasNext() {
return token != null;
}
/**
* INTERNAL:
* Return the next attribute name token from attribute name.
* @return The next attribute name token.
* @throws NoSuchElementException when attribute name has no more tokens.
*/
@Override
public String next() {
final String tokenToReturn = token;
if (matcher.lookingAt()) {
token = matcher.groupCount() > 0 ? matcher.group(1) : null;
matcher.region(matcher.end(), matcher.regionEnd());
} else {
token = null;
}
return tokenToReturn;
}
/**
* INTERNAL:
* Removal of attribute name tokens makes no sense.
* @throws UnsupportedOperationException is always thrown on invocation.
*/
@Override
public void remove() {
throw new UnsupportedOperationException("Removal of attribute name tokens makes no sense");
}
}
}