/*
* 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.internal.loader;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.ros.CommandLineVariables;
import org.ros.EnvironmentVariables;
import org.ros.address.InetAddressFactory;
import org.ros.exception.RosRuntimeException;
import org.ros.namespace.GraphName;
import org.ros.namespace.NameResolver;
import org.ros.node.NodeConfiguration;
import org.ros.node.NodeMain;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Create {@link NodeConfiguration} instances using a ROS command-line and
* environment specification.
*
* @author kwc@willowgarage.com (Ken Conley)
* @author damonkohler@google.com (Damon Kohler)
*/
public class CommandLineLoader {
private final List<String> argv;
private final List<String> nodeArguments;
private final List<String> remappingArguments;
private final Map<String, String> environment;
private final Map<String, String> specialRemappings;
private final Map<GraphName, GraphName> remappings;
private String nodeClassName;
/**
* Create new {@link CommandLineLoader} with specified command-line arguments.
* Environment variables will be pulled from default {@link System}
* environment variables.
*
* @param argv
* command-line arguments
*/
public CommandLineLoader(List<String> argv) {
this(argv, System.getenv());
}
/**
* Create new {@link CommandLineLoader} with specified command-line arguments
* and environment variables.
*
* @param argv
* command-line arguments
* @param environment
* environment variables
*/
public CommandLineLoader(List<String> argv, Map<String, String> environment) {
Preconditions.checkArgument(argv.size() > 0);
this.argv = argv;
this.environment = environment;
nodeArguments = Lists.newArrayList();
remappingArguments = Lists.newArrayList();
remappings = Maps.newHashMap();
specialRemappings = Maps.newHashMap();
parseArgv();
}
private void parseArgv() {
nodeClassName = argv.get(0);
for (String argument : argv.subList(1, argv.size())) {
if (argument.contains(":=")) {
remappingArguments.add(argument);
} else {
nodeArguments.add(argument);
}
}
}
public String getNodeClassName() {
return nodeClassName;
}
public List<String> getNodeArguments() {
return Collections.unmodifiableList(nodeArguments);
}
/**
* Create NodeConfiguration according to ROS command-line and environment
* specification.
*/
public NodeConfiguration build() {
parseRemappingArguments();
// TODO(damonkohler): Add support for starting up a private node.
NodeConfiguration nodeConfiguration = NodeConfiguration.newPublic(getHost());
nodeConfiguration.setParentResolver(buildParentResolver());
nodeConfiguration.setRosRoot(getRosRoot());
nodeConfiguration.setRosPackagePath(getRosPackagePath());
nodeConfiguration.setMasterUri(getMasterUri());
if (specialRemappings.containsKey(CommandLineVariables.NODE_NAME)) {
nodeConfiguration.setNodeName(specialRemappings.get(CommandLineVariables.NODE_NAME));
}
return nodeConfiguration;
}
private void parseRemappingArguments() {
for (String remapping : remappingArguments) {
Preconditions.checkState(remapping.contains(":="));
String[] remap = remapping.split(":=");
if (remap.length > 2) {
throw new IllegalArgumentException("Invalid remapping argument: " + remapping);
}
if (remapping.startsWith("__")) {
specialRemappings.put(remap[0], remap[1]);
} else {
remappings.put(GraphName.of(remap[0]), GraphName.of(remap[1]));
}
}
}
/**
* Precedence:
*
* <ol>
* <li>The __ns:= command line argument.</li>
* <li>The ROS_NAMESPACE environment variable.</li>
* </ol>
*/
private NameResolver buildParentResolver() {
GraphName namespace = GraphName.root();
if (specialRemappings.containsKey(CommandLineVariables.ROS_NAMESPACE)) {
namespace =
GraphName.of(specialRemappings.get(CommandLineVariables.ROS_NAMESPACE)).toGlobal();
} else if (environment.containsKey(EnvironmentVariables.ROS_NAMESPACE)) {
namespace = GraphName.of(environment.get(EnvironmentVariables.ROS_NAMESPACE)).toGlobal();
}
return new NameResolver(namespace, remappings);
}
/**
* Precedence (default: null):
*
* <ol>
* <li>The __ip:= command line argument.</li>
* <li>The ROS_IP environment variable.</li>
* <li>The ROS_HOSTNAME environment variable.</li>
* <li>The default host as specified in {@link NodeConfiguration}.</li>
* </ol>
*/
private String getHost() {
String host = InetAddressFactory.newLoopback().getHostAddress();
if (specialRemappings.containsKey(CommandLineVariables.ROS_IP)) {
host = specialRemappings.get(CommandLineVariables.ROS_IP);
} else if (environment.containsKey(EnvironmentVariables.ROS_IP)) {
host = environment.get(EnvironmentVariables.ROS_IP);
} else if (environment.containsKey(EnvironmentVariables.ROS_HOSTNAME)) {
host = environment.get(EnvironmentVariables.ROS_HOSTNAME);
}
return host;
}
/**
* Precedence:
*
* <ol>
* <li>The __master:= command line argument. This is not required but easy to
* support.</li>
* <li>The ROS_MASTER_URI environment variable.</li>
* <li>The default master URI as defined in {@link NodeConfiguration}.</li>
* </ol>
*/
private URI getMasterUri() {
URI uri = NodeConfiguration.DEFAULT_MASTER_URI;
try {
if (specialRemappings.containsKey(CommandLineVariables.ROS_MASTER_URI)) {
uri = new URI(specialRemappings.get(CommandLineVariables.ROS_MASTER_URI));
} else if (environment.containsKey(EnvironmentVariables.ROS_MASTER_URI)) {
uri = new URI(environment.get(EnvironmentVariables.ROS_MASTER_URI));
}
return uri;
} catch (URISyntaxException e) {
throw new RosRuntimeException("Invalid master URI: " + uri);
}
}
private File getRosRoot() {
if (environment.containsKey(EnvironmentVariables.ROS_ROOT)) {
return new File(environment.get(EnvironmentVariables.ROS_ROOT));
} else {
// For now, this is not required as we are not doing anything (e.g.
// ClassLoader) that requires it. In the future, this may become required.
return null;
}
}
private List<File> getRosPackagePath() {
if (environment.containsKey(EnvironmentVariables.ROS_PACKAGE_PATH)) {
String rosPackagePath = environment.get(EnvironmentVariables.ROS_PACKAGE_PATH);
List<File> paths = Lists.newArrayList();
for (String path : rosPackagePath.split(File.pathSeparator)) {
paths.add(new File(path));
}
return paths;
} else {
return Lists.newArrayList();
}
}
/**
* @param name
* the name of the class
* @return an instance of {@link NodeMain}
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
*/
public NodeMain loadClass(String name) throws ClassNotFoundException, InstantiationException,
IllegalAccessException {
Class<?> clazz = getClass().getClassLoader().loadClass(name);
return NodeMain.class.cast(clazz.newInstance());
}
}