/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.controller;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
/**
* An element of a path specification for matching operations with addresses.
* @author Brian Stansberry
* @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
*/
public class PathElement {
public static final String WILDCARD_VALUE = "*";
private final String key;
private final String value;
private final boolean multiTarget;
private final int hashCode;
/**
* Construct a new instance with a wildcard value.
* @param key the path key to match
* @return the new path element
*/
public static PathElement pathElement(final String key) {
return new PathElement(key);
}
/**
* Construct a new instance.
* @param key the path key to match
* @param value the path value or wildcard to match
* @return the new path element
*/
public static PathElement pathElement(final String key, final String value) {
return new PathElement(key, value);
}
/**
* Construct a new instance with a wildcard value.
* @param key the path key to match
*/
PathElement(final String key) {
this(key, WILDCARD_VALUE);
}
/**
* Construct a new instance.
* @param key the path key to match
* @param value the path value or wildcard to match
*/
PathElement(final String key, final String value) {
if (!isValidKey(key)) {
final String element = key + "=" + value;
throw new OperationClientIllegalArgumentException(ControllerLogger.ROOT_LOGGER.invalidPathElementKey(element, key));
}
if (value == null || value.isEmpty()) {
final String element = key + "=" + value;
throw new OperationClientIllegalArgumentException(ControllerLogger.ROOT_LOGGER.invalidPathElementValue(element, value, ' '));
}
boolean multiTarget = false;
if(key.equals(WILDCARD_VALUE)) {
this.key = WILDCARD_VALUE;
multiTarget = true;
} else {
this.key = key;
}
if (value.equals(WILDCARD_VALUE)) {
this.value = WILDCARD_VALUE;
multiTarget = true;
} else if (value.charAt(0) == '[' && value.charAt(value.length() - 1) == ']') {
this.value = value.substring(1, value.length() - 1);
multiTarget |= value.indexOf(',') != -1;
} else {
this.value = value;
}
this.multiTarget = multiTarget;
hashCode = key.hashCode() * 19 + value.hashCode();
}
/**
* A valid key contains alphanumerics and underscores, cannot start with a
* number, and cannot start or end with {@code -}.
*/
private static boolean isValidKey(final String s) {
// Equivalent to this regex \*|[_a-zA-Z](?:[-_a-zA-Z0-9]*[_a-zA-Z0-9]) but faster
if (s == null) {
return false;
}
if (s.equals(WILDCARD_VALUE)) {
return true;
}
int lastIndex = s.length() - 1;
if (lastIndex == -1) {
return false;
}
if (!isValidKeyStartCharacter(s.charAt(0))) {
return false;
}
for (int i = 1; i < lastIndex; i++) {
if (!isValidKeyCharacter(s.charAt(i))) {
return false;
}
}
if (lastIndex > 0 && !isValidKeyEndCharacter(s.charAt(lastIndex))) {
return false;
}
return true;
}
private static boolean isValidKeyStartCharacter(final char c) {
return c == '_'
|| c >= 'a' && c <= 'z'
|| c >= 'A' && c <= 'Z';
}
private static boolean isValidKeyEndCharacter(final char c) {
return c == '_'
|| c >= '0' && c <= '9'
|| c >= 'a' && c <= 'z'
|| c >= 'A' && c <= 'Z';
}
private static boolean isValidKeyCharacter(char c) {
return c == '_' || c == '-'
|| c >= '0' && c <= '9'
|| c >= 'a' && c <= 'z'
|| c >= 'A' && c <= 'Z';
}
/**
* Get the path key.
* @return the path key
*/
public String getKey() {
return key;
}
/**
* Get the path value.
* @return the path value
*/
public String getValue() {
return value;
}
/**
* Determine whether the given property matches this element.
* @param property the property to check
* @return {@code true} if the property matches
*/
public boolean matches(Property property) {
return property.getName().equals(key) && (value == WILDCARD_VALUE || property.getValue().asString().equals(value));
}
/**
* Determine whether the value is the wildcard value.
* @return {@code true} if the value is the wildcard value
*/
public boolean isWildcard() {
return WILDCARD_VALUE == value; //this is ok as we are expecting exact same object.
}
public boolean isMultiTarget() {
return multiTarget;
}
public String[] getSegments() {
return value.split(",");
}
public String[] getKeyValuePair(){
return new String[]{key,value};
}
@Override
public int hashCode() {
return hashCode;
}
/**
* Determine whether this object is equal to another.
* @param other the other object
* @return {@code true} if they are equal, {@code false} otherwise
*/
public boolean equals(Object other) {
return other instanceof PathElement && equals((PathElement) other);
}
/**
* Determine whether this object is equal to another.
* @param other the other object
* @return {@code true} if they are equal, {@code false} otherwise
*/
public boolean equals(PathElement other) {
return this == other || other != null && other.key.equals(key) && other.value.equals(value);
}
@Override
public String toString() {
return "\"" + key + "\" => \"" + value + "\"";
}
/**
* AS7-2905. An IAE that implements OperationClientException. Allows PathElement to continue to throw IAE
* in case client code expects that failure type, but lets operation handling code detect that the
* IAE is a client error.
*/
private static class OperationClientIllegalArgumentException extends IllegalArgumentException implements OperationClientException {
private static final long serialVersionUID = -9073168544821068948L;
private OperationClientIllegalArgumentException(final String msg) {
super(msg);
assert msg != null : "msg is null";
}
@Override
public ModelNode getFailureDescription() {
return new ModelNode(getLocalizedMessage());
}
}
}