/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.ros.namespace;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import org.ros.exception.RosRuntimeException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
/**
* ROS graph resource name.
*
* @see <a href="http://www.ros.org/wiki/Names">Names documentation</a>
*
* @author damonkohler@google.com (Damon Kohler)
*/
public class GraphName {
@VisibleForTesting
static final String ANONYMOUS_PREFIX = "anonymous_";
private static final String ROOT = "/";
private static final String SEPARATOR = "/";
/**
* RegExp string for a valid graph name.
*/
public static final String VALID_GRAPH_NAME_REGEXP = "^([\\~\\/A-Za-z][\\w_\\/]*)?$";
// TODO(damonkohler): Why make empty names valid?
/**
* Graph names must match this pattern to be valid.
* <p>
* Note that empty graph names are considered valid.
*/
public static final Pattern VALID_GRAPH_NAME_PATTERN = Pattern
.compile(VALID_GRAPH_NAME_REGEXP);
private static AtomicInteger anonymousCounter = new AtomicInteger();
private final String name;
// TODO(damonkohler): This is not safe across multiple hosts/processes.
// Instead, try to use the same algorithm as in cpp and Python.
/**
* Creates an anonymous {@link GraphName}.
*
* @return a new {@link GraphName} suitable for creating an anonymous node
*/
public static GraphName newAnonymous() {
return new GraphName(ANONYMOUS_PREFIX + anonymousCounter.incrementAndGet());
}
/**
* @return a {@link GraphName} representing the root namespace
*/
public static GraphName root() {
return new GraphName(ROOT);
}
/**
* @return an empty {@link GraphName}
*/
public static GraphName empty() {
return new GraphName("");
}
/**
* Returns a new {@link GraphName} of the specified {@link #name}.
*
* @param name
* the name of this resource
* @return a new {@link GraphName} for {@link #name}
*/
public static GraphName of(String name) {
return new GraphName(canonicalize(name));
}
private GraphName(String name) {
Preconditions.checkNotNull(name);
this.name = name;
}
/**
* Validate and convert the graph name into its canonical representation.
* Canonical representations have no trailing slashes and can be global,
* private, or relative.
*
* @param name
* @return the canonical name for this {@link GraphName}
*/
private static String canonicalize(String name) {
if (!VALID_GRAPH_NAME_PATTERN.matcher(name).matches()) {
throw new RosRuntimeException(String.format("Graph name '%s' must match pattern '%s'", name, VALID_GRAPH_NAME_REGEXP));
}
// Trim trailing slashes for canonical representation.
while (!name.equals(GraphName.ROOT) && name.endsWith(SEPARATOR)) {
name = name.substring(0, name.length() - 1);
}
if (name.startsWith("~/")) {
name = "~" + name.substring(2);
}
return name;
}
/**
* Is this a /global/name?
*
* <ul>
* <li>
* If node node1 in the global / namespace accesses the resource /bar, that
* will resolve to the name /bar.</li>
* <li>
* If node node2 in the /wg/ namespace accesses the resource /foo, that will
* resolve to the name /foo.</li>
* <li>
* If node node3 in the /wg/ namespace accesses the resource /foo/bar, that
* will resolve to the name /foo/bar.</li>
* </ul>
*
* @return {@code true} if this name is a global name, {@code false} otherwise
*/
public boolean isGlobal() {
return !isEmpty() && name.charAt(0) == '/';
}
/**
* @return {@code true} if this {@link GraphName} represents the root
* namespace, {@code false} otherwise
*/
public boolean isRoot() {
return name.equals(GraphName.ROOT);
}
/**
* @return {@code true} if this {@link GraphName} is empty, {@code false}
* otherwise
*/
public boolean isEmpty() {
return name.isEmpty();
}
/**
* Is this a ~private/name?
*
* <ul>
* <li>
* If node node1 in the global / namespace accesses the resource ~bar, that
* will resolve to the name /node1/bar.
* <li>
* If node node2 in the /wg/ namespace accesses the resource ~foo, that will
* resolve to the name /wg/node2/foo.
* <li>If node node3 in the /wg/ namespace accesses the resource ~foo/bar,
* that will resolve to the name /wg/node3/foo/bar.
* </ul>
*
* @return {@code true} if the name is a private name, {@code false} otherwise
*/
public boolean isPrivate() {
return !isEmpty() && name.charAt(0) == '~';
}
/**
* Is this a relative/name?
*
* <ul>
* <li>If node node1 in the global / namespace accesses the resource ~bar,
* that will resolve to the name /node1/bar.
* <li>If node node2 in the /wg/ namespace accesses the resource ~foo, that
* will resolve to the name /wg/node2/foo.
* <li>If node node3 in the /wg/ namespace accesses the resource ~foo/bar,
* that will resolve to the name /wg/node3/foo/bar.
* </ul>
*
* @return true if the name is a relative name.
*/
public boolean isRelative() {
return !isPrivate() && !isGlobal();
}
/**
* @return the parent of this {@link GraphName} in its canonical
* representation or an empty {@link GraphName} if there is no parent
*/
public GraphName getParent() {
if (name.length() == 0) {
return GraphName.empty();
}
if (name.equals(GraphName.ROOT)) {
return GraphName.root();
}
int slashIdx = name.lastIndexOf('/');
if (slashIdx > 1) {
return new GraphName(name.substring(0, slashIdx));
}
if (isGlobal()) {
return GraphName.root();
}
return GraphName.empty();
}
/**
* @return a {@link GraphName} without the leading parent namespace
*/
public GraphName getBasename() {
int slashIdx = name.lastIndexOf('/');
if (slashIdx > -1) {
if (slashIdx + 1 < name.length()) {
return new GraphName(name.substring(slashIdx + 1));
}
return GraphName.empty();
}
return this;
}
/**
* Convert name to a relative name representation. This does not take any
* namespace into account; it simply strips any preceding characters for
* global or private name representation.
*
* @return a relative {@link GraphName}
*/
public GraphName toRelative() {
if (isPrivate() || isGlobal()) {
return new GraphName(name.substring(1));
}
return this;
}
/**
* Convert name to a global name representation. This does not take any
* namespace into account; it simply adds in the global prefix "/" if missing.
*
* @return a global {@link GraphName}
*/
public GraphName toGlobal() {
if (isGlobal()) {
return this;
}
if (isPrivate()) {
return new GraphName(GraphName.ROOT + name.substring(1));
}
return new GraphName(GraphName.ROOT + name);
}
/**
* Join this {@link GraphName} with another.
*
* @param other
* the {@link GraphName} to join with, if other is global, this will
* return other
* @return a {@link GraphName} representing the concatenation of this
* {@link GraphName} and {@code other}
*/
public GraphName join(GraphName other) {
if (other.isGlobal() || isEmpty()) {
return other;
}
if (isRoot()) {
return other.toGlobal();
}
if (other.isEmpty()) {
return this;
}
return new GraphName(toString() + SEPARATOR + other.toString());
}
/**
* @see #join(GraphName)
*/
public GraphName join(String other) {
return join(GraphName.of(other));
}
@Override
public String toString() {
return name;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
GraphName other = (GraphName) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}