/********************************************************************************
*
* JOrocos Library
*
* Copyright (c) 2011
* All rights reserved.
*
* Luca Gherardi
* University of Bergamo
* Dept. of Information Technology and Mathematics
*
* -------------------------------------------------------------------------------
*
* File: AbstractOrocosComponent.java
* Created: Jul 27, 2011
*
* Author: <A HREF="mailto:luca.gherardi@unibg.it">Luca Gherardi</A>
*
* Supervised by: <A HREF="mailto:brugali@unibg.it">Davide Brugali</A>
*
* In cooperation with: <A HREF="mailto:herman.bruyninckx@mech.kuleuven.be">Herman Bruyninckx</A>
*
* -------------------------------------------------------------------------------
*
* This software is published under a dual-license: GNU Lesser General Public
* License LGPL 2.1 and BSD license. The dual-license implies that users of this
* code may choose which terms they prefer.
*
* -------------------------------------------------------------------------------
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of the University of Bergamo nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License LGPL as
* published by the Free Software Foundation, either version 2.1 of the
* License, or (at your option) any later version or the BSD license.
*
* This program 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 LGPL and the BSD license for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License LGPL and BSD license along with this program.
*
*******************************************************************************/
package it.unibg.robotics.jorocos.core;
import it.unibg.robotics.jorocos.core.AbstractOrocosConnection.LockPolicy;
import it.unibg.robotics.jorocos.core.OrocosDataPort.PortType;
import it.unibg.robotics.jorocos.exceptions.ConnectionToPortNotExistException;
import it.unibg.robotics.jorocos.exceptions.WrongPortTypeException;
import it.unibg.robotics.jorocos.exceptions.WrongUseOfConnectionException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Observer;
import org.apache.log4j.Logger;
import RTT.corba.CDataFlowInterface;
import RTT.corba.CNoSuchPortException;
import RTT.corba.CPortType;
import RTT.corba.CTaskContext;
import RTT.corba.CTaskState;
/**
* The Class AbstractOrocosComponent is a proxy to an Orocos task context and allows clients
* to introspect its data ports and its own service. The class offers the operations for creating
* connections to Orocos ports and writing and reading data on these ports.
*
* This class allows both the operations of reading and writing on the component data port.
* In order to be executed these operations require a connection between the java client and the Orocos port.
* Two types of connections are available: data and buffer.
* On a data connection the reader has access only to the last written value whereas on a buffer connection
* a predefined number of values can be stored.
*
* @author <A HREF="mailto:luca.gherardi@unibg.it">Luca Gherardi</A>
* @version 1.0
* @since August 2011
*/
public abstract class AbstractOrocosComponent{
/**
* The possible states of the component FSM.
* See the Orocos documentation for more information about the
* meaning of the states.
*/
public enum ComponentState{
INIT,
PRE_OPERATIONAL,
FATAL_ERROR,
EXCEPTION,
STOPPED,
RUNNING,
RUNTIME_ERROR
}
/** The input ports list. */
private ArrayList<OrocosDataPort> inputPorts;
/** The input ports names list. */
private ArrayList<String> inputPortsNames;
/** The output ports list. */
private ArrayList<OrocosDataPort> outputPorts;
/** The output ports names list. */
private ArrayList<String> outputPortsNames;
/**
* The connections map for the input ports.
* The key string is the port name,
* the value is another map which has an observer
* (the client which created the connection)
* as key and the connection as value.
*
* We store the connections in order to use them when we a certaint client want to write on
* a certain input port
* */
protected HashMap<String, HashMap<Object, AbstractOrocosConnection>> inputPortConnectionsMap;
/**
* The connections map for the output ports.
* The key string is the port name,
* the value is another map which has an observer
* (the client which created the connection)
* as key and the connection as value.
* */
protected HashMap<String, HashMap<Observer, AbstractOrocosConnection>> outputPortConnectionsMap;
// private ArrayList<OrocosService> services;
/** The logger. */
private Logger logger;
/** A pointer to the task context modeled by this class. */
private CTaskContext cTaskContext;
/** The service which encapsulate the services of this task context. */
protected AbstractOrocosService ownService;
/** The component name. */
private String name;
/** The component description. */
private String description;
/** A pointer to the data flow interface of the task context which is modeled by this class. */
private CDataFlowInterface cDataFlowInterface;
/**
* Instantiates a new abstract Orocos component.
*
* @param taskContext a pointer to the task context that has to be modeled
* @param introspect if true the constructor will introspect the component and store
* all the information about its services, ports and properties.
* This information can be retrieved by using the methods {@link #getOwnService()}
* (which provided information about services, operations and properties)
* and {@link #getPorts()}.
*/
public AbstractOrocosComponent(CTaskContext taskContext, boolean introspect){
this.cTaskContext = taskContext;
this.name = taskContext.getName();
this.description = taskContext.getDescription();
inputPorts = new ArrayList<OrocosDataPort>();
inputPortsNames = new ArrayList<String>();
outputPorts = new ArrayList<OrocosDataPort>();
outputPortsNames = new ArrayList<String>();
inputPortConnectionsMap = new HashMap<String, HashMap<Object,AbstractOrocosConnection>>();
outputPortConnectionsMap = new HashMap<String, HashMap<Observer,AbstractOrocosConnection>>();
cDataFlowInterface = taskContext.ports();
logger = Logger.getLogger(AbstractOrocosComponent.class);
if(introspect){
introspectOwnService(true);
introspectPorts();
}
}
/**
* Returns the component name.
*
* @return the component name
*/
public String getName(){
return name;
}
/**
* Returns the component description.
*
* @return the component description
*/
public String getDescritpion(){
return description;
}
/**
* Returns the current state of the component FSM.
*
* @return the current state of the component FSM
*/
public ComponentState getState(){
CTaskState state = cTaskContext.getTaskState();
if(state.equals(CTaskState.CInit))
return ComponentState.INIT;
else if(state.equals(CTaskState.CPreOperational))
return ComponentState.PRE_OPERATIONAL;
else if(state.equals(CTaskState.CFatalError))
return ComponentState.FATAL_ERROR;
else if(state.equals(CTaskState.CException))
return ComponentState.EXCEPTION;
else if(state.equals(CTaskState.CStopped))
return ComponentState.STOPPED;
else if(state.equals(CTaskState.CRunning))
return ComponentState.RUNNING;
else if(state.equals(CTaskState.CRunTimeError))
return ComponentState.RUNTIME_ERROR;
else
return null;
}
/**
* Calls the configure operation of the task context modeled by this class.
* For more information take a look at the documentation of the CTaskContext class.
*
* @return true if the operation completes without problems.
*/
public boolean configure(){
return cTaskContext.configure();
}
/**
* Calls the isConfigured operation of the task context modeled by this class.
* For more information take a look at the documentation of the CTaskContext class.
*
* @return true if the component is in the Stopped, Active or Running states.
*/
public boolean isConfigured(){
return cTaskContext.isConfigured();
}
/**
* Calls the start operation of the task context modeled by this class.
* For more information take a look at the documentation of the CTaskContext class.
*
* @return true if the operation completes without problems.
*/
public boolean start(){
return cTaskContext.start();
}
/**
* Calls the isRunning operation of the task context modeled by this class.
* For more information take a look at the documentation of the CTaskContext class.
*
* @return true if the component is in the Running or RunTimeError states.
*/
public boolean isRunning(){
return cTaskContext.isRunning();
}
/**
* Calls the stop operation of the task context modeled by this class.
* For more information take a look at the documentation of the CTaskContext class.
*
* @return true if the operation completes without problems.
*/
public boolean stop(){
return cTaskContext.stop();
}
/**
* Calls the resetException operation of the task context modeled by this class.
* For more information take a look at the documentation of the CTaskContext class.
*
* @return true if the operation completes without problems.
*/
public boolean resetException(){
return cTaskContext.resetException();
}
/**
* Calls the cleanup operation of the task context modeled by this class.
* For more information take a look at the documentation of the CTaskContext class.
*
* @return true if the operation completes without problems.
*/
public boolean cleanup(){
return cTaskContext.cleanup();
}
/**
* Calls the activate operation of the task context modeled by this class.
* For more information take a look at the documentation of the CTaskContext class.
*
* @return true if the operation completes without problems.
*/
public boolean activate(){
return cTaskContext.activate();
}
/**
* Calls the isActive operation of the task context modeled by this class.
* For more information take a look at the documentation of the CTaskContext class.
*
* @return true if the component is processing requests.
*/
public boolean isActive(){
return cTaskContext.isActive();
}
/**
* Introspect the ports of this component.
* It looks for the name and the types of all the ports and store them.
* They can be then retrieved by calling the methods {@link #getPorts()}, {@link #getInputPorts()} and
* {@link #getOutputPorts()}.
*/
public void introspectPorts() {
inputPorts = new ArrayList<OrocosDataPort>();
inputPortsNames = new ArrayList<String>();
outputPorts = new ArrayList<OrocosDataPort>();
outputPortsNames = new ArrayList<String>();
String[] portsNames = cDataFlowInterface.getPorts();
logger.info(" Introspection of the component ports: ");
for (int i = 0; i < portsNames.length; i++) {
OrocosDataPort port = introspectPort(portsNames[i]);
logger.info(" Port-" + (i+1)+ ": " + port.getName() + " (type: " + port.getPortType() + ", data type: " + port.getDataType() + ")");
}
}
/**
* Returns the port with the specified name.
* If the ports have not been yet introspected it executes the introspection
* by looking for the required port.
*
* @param portName the name of the port that has to be returned
* @return the required port
*/
public OrocosDataPort getPort(String portName) {
OrocosDataPort port;
if(inputPortsNames.contains(portName)){
for (Iterator<OrocosDataPort> iterator = inputPorts.iterator(); iterator.hasNext();) {
port = iterator.next();
if(port.getName().equals(portName)){
return port;
}
}
}
if(outputPortsNames.contains(portName)){
for (Iterator<OrocosDataPort> iterator = outputPorts.iterator(); iterator.hasNext();) {
port = iterator.next();
if(port.getName().equals(portName)){
return port;
}
}
}
return introspectPort(portName);
}
/**
* Returns all the component ports which have been previously instrospected.
* This method should be called after the method {@link #introspectPorts()}.
*
* @return the list of the component ports
*/
public ArrayList<OrocosDataPort> getPorts(){
ArrayList<OrocosDataPort> ports = new ArrayList<OrocosDataPort>();
ports.addAll(inputPorts);
ports.addAll(outputPorts);
return ports;
}
/**
* Returns all the names of the component ports which have been previously instrospected.
* This method should be called after the method {@link #introspectPorts()}.
*
* @return the list of the names of the component ports
*/
public ArrayList<String> getPortsNames(){
ArrayList<String> portsNames = new ArrayList<String>();
portsNames.addAll(inputPortsNames);
portsNames.addAll(outputPortsNames);
return portsNames;
}
/**
* Returns all the component input ports which have been previously instrospected.
* This method should be called after the method {@link #introspectPorts()}.
*
* @return the list of the component input ports
*/
public ArrayList<OrocosDataPort> getInputPorts(){
return inputPorts;
}
/**
* Returns all the names of the component input ports which have been previously instrospected.
* This method should be called after the method {@link #introspectPorts()}.
*
* @return the list of the names of the component input ports
*/
public ArrayList<String> getInputPortsNames(){
return inputPortsNames;
}
/**
* Returns all the component output ports which have been previously instrospected.
* This method should be called after the method {@link #introspectPorts()}.
*
* @return the list of the component output ports
*/
public ArrayList<OrocosDataPort> getOutputPorts(){
return outputPorts;
}
/**
* Returns all the names of the component output ports which have been previously instrospected.
* This method should be called after the method {@link #introspectPorts()}.
*
* @return the list of the names of the component output ports
*/
public ArrayList<String> getOutputPortsNames(){
return outputPortsNames;
}
/**
* Creates a data connection to the requested input port of this component
*
* @param portName the name of the port for which the connection has to be created
* @param lockPolicy the lock policy of the connection, see the class AbstactOrocosConnection for more info
* @param client the client that has to be connected to the port, usually should be "this"
* @return true if the connection has been successfully created
* @throws WrongPortTypeException
*/
abstract public boolean createDataConnectionToInputPort(String portName, LockPolicy lockPolicy, Object client) throws WrongPortTypeException;
/**
* Creates a buffer connection to the requested input port of this component
*
* @param portName the name of the port for which the connection has to be created
* @param lockPolicy the lock policy of the connection, see the class AbstactOrocosConnection for more info
* @param client the client that has to be connected to the port, usually should be "this"
* @param bufferSize the buffer dimension
* @return if the connection has been successfully created
* @throws WrongPortTypeException
*/
abstract public boolean createBufferConnectionToInputPort(String portName, LockPolicy lockPolicy, Object client, int bufferSize) throws WrongPortTypeException;
/**
* Creates a data connection to the requested output port of this component and subscribe to it
* in order to be notified when a new data will be available
*
* @param portName the name of the port for which the connection has to be created
* @param lockPolicy the lock policy of the connection, see the class AbstactOrocosConnection for more info
* @param observer the client that has to be connected to the port, usually should be "this".
* The client must be an instance of a class that implement the interface {@link java.util.Observer}.
* When a new data will be available on the output port the observer will be notified accordingly to the
* pattern observer/observable
* @param periodMs defines the frequency with which the availability
* of new data on the port will be checked (it is expressed as period in milliseconds).
* @return true if the connection has been successfully created
* @throws WrongPortTypeException
*/
abstract public boolean subscribeToDataOutputPort(String portName, LockPolicy lockPolicy, Observer observer, int periodMs) throws WrongPortTypeException;
/**
* Creates a buffer connection to the requested output port of this component and subscribe to it
* in order to be notified when a new data will be available
*
* @param portName the name of the port for which the connection has to be created
* @param lockPolicy the lock policy of the connection, see the class AbstactOrocosConnection for more info
* @param observer the client that has to be connected to the port, usually should be "this".
* The client must be an instance of a class that implement the interface {@link java.util.Observer}.
* When a new data will be available on the output port the observer will be notified accordingly to the
* pattern observer/observable
* @param periodMs defines the frequency with which the availability
* of new data on the port will be checked (it is expressed as period in milliseconds).
* @param bufferSize the buffer dimension
* @return true if the connection has been successfully created
* @throws WrongPortTypeException
*/
abstract public boolean subscribeToBufferOutputPort(String portName, LockPolicy lockPolicy, Observer observer, int periodMs, int bufferSize) throws WrongPortTypeException;
/**
* Unsubscribes the observer from the specified output port
*
* @param portName the name of the port for which the connection has to be removed
* @param observer the client that was connected for which the connection was created
* @return true if the connection has been successfully removed
*/
abstract public boolean unsubscribeFromOutputPort(String portName, Observer observer);
/**
* Writes a data on the required input port.
* A connection to the port has to be created before calling this method.
*
* @param portName the name of the port on which we want to write the data
* @param value the value we want to write. It has to be of the same type of the port
* @param client the client that want to write on the port, usually should be "this"
* @return true if the data has been written correctly
* @throws ConnectionToPortNotExistException
* @throws WrongPortTypeException
*/
public boolean writeOnPort(String portName, Object value, Object client) throws ConnectionToPortNotExistException, WrongPortTypeException{
if(! inputPortConnectionsMap.containsKey(portName) ||
! inputPortConnectionsMap.get(portName).containsKey(client)){
logger.error("The required Orocos connection to '" + portName + "' does not exist. You have to create it before writing ot reading");
throw new ConnectionToPortNotExistException(portName);
}
if(getPort(portName).getPortType() == PortType.OUTPUT_PORT){
logger.error("You are trying to write on an output port. It's not possible.");
throw new WrongPortTypeException(getPort(portName).getPortType());
}
try {
return inputPortConnectionsMap.get(portName).get(client).writeData(value);
} catch (WrongUseOfConnectionException e) {
throw new RuntimeException("This should not be possible",e);
}
}
/**
* Introspects the own service of the components and stores its
* services, operations and properties.
* The own service can be retrieved by using the method {@link #getOwnService()}
*
* @param introspect, if true the services encapsulated in the own service will be
* recursively introspected
*/
abstract public void introspectOwnService(boolean introspect);
/**
* Returns the own service of this component.
* You need to call this method if you want to call an operation of
* the task context.
* @return
*/
public AbstractOrocosService getOwnService(){
if(ownService == null){
introspectOwnService(false);
}
return ownService;
}
/**
* Returns a pointer the the data flow interface modeled by this component.
* @return a pointer the the data flow interface modeled by this component.
*/
public CDataFlowInterface getCDataFlowInterface(){
return cDataFlowInterface;
}
/**
* Returns a pointer the the data task context modeled by this component.
* @return a pointer the the task context modeled by this component.
*/
public CTaskContext getCTaskContext(){
return cTaskContext;
}
/**
* Introspect a specific component port.
*
* @param portName the name of the port that has to be introspected
* @return the component port with the specified name
*/
private OrocosDataPort introspectPort(String portName){
PortType type = null;
String dataType = "";
try {
if(cDataFlowInterface.getPortType(portName) == CPortType.CInput){
type = PortType.INPUT_PORT;
}else{
type = PortType.OUTPUT_PORT;
}
dataType = cDataFlowInterface.getDataType(portName);
} catch (CNoSuchPortException e) {
logger.error("The port you are asking does not exist.");
e.printStackTrace();
return null;
}
OrocosDataPort port = new OrocosDataPort(portName, type, dataType);
port.setComponent(this);
if(port.getPortType() == PortType.INPUT_PORT){
inputPorts.add(port);
inputPortsNames.add(portName);
}else{
outputPorts.add(port);
outputPortsNames.add(portName);
}
return port;
}
}