/*
* Copyright (c) 2002-2009 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.shell.neo.apps;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import org.neo4j.api.core.Node;
import org.neo4j.api.core.Relationship;
import org.neo4j.shell.AppCommandParser;
import org.neo4j.shell.OptionValueType;
import org.neo4j.shell.Output;
import org.neo4j.shell.Session;
import org.neo4j.shell.ShellException;
/**
* Mimics the POSIX application with the same name, i.e. traverses to a node.
*/
public class Cd extends NeoApp
{
/**
* The {@link Session} key to use to store the current node and working
* directory (i.e. the path which the client got to it).
*/
public static final String WORKING_DIR_KEY = "WORKING_DIR";
/**
* Constructs a new cd application.
*/
public Cd()
{
this.addValueType( "a", new OptionContext( OptionValueType.NONE,
"Absolute id, new primitive doesn't need to be connected to " +
"the\ncurrent one" ) );
this.addValueType( "r", new OptionContext( OptionValueType.NONE,
"Makes the supplied id represent a relationship instead of " +
"a node" ) );
}
@Override
public String getDescription()
{
return "Changes the current node or relationship, i.e. traverses " +
"one step to another\nnode or relationship. Usage: cd <id>";
}
@Override
protected String exec( AppCommandParser parser, Session session,
Output out ) throws ShellException, RemoteException
{
List<TypedId> paths = readPaths( session );
NodeOrRelationship current = getCurrent( session );
NodeOrRelationship newThing = null;
if ( parser.arguments().isEmpty() )
{
newThing = NodeOrRelationship.wrap(
getNeoServer().getNeo().getReferenceNode() );
paths.clear();
}
else
{
String arg = parser.arguments().get( 0 );
TypedId newId = current.getTypedId();
if ( arg.equals( ".." ) )
{
if ( paths.size() > 0 )
{
newId = paths.remove( paths.size() - 1 );
}
}
else if ( arg.equals( "." ) )
{
}
else if ( arg.equals( "start" ) || arg.equals( "end" ) )
{
newId = getStartOrEnd( current, arg );
paths.add( current.getTypedId() );
}
else
{
long suppliedId = Long.parseLong( arg );
if ( parser.options().containsKey( "r" ) )
{
newId = new TypedId( NodeOrRelationship.TYPE_RELATIONSHIP,
suppliedId );
}
else
{
newId = new TypedId( NodeOrRelationship.TYPE_NODE,
suppliedId );
}
if ( newId.equals( current.getTypedId() ) )
{
throw new ShellException( "Can't cd to where you stand" );
}
boolean absolute = parser.options().containsKey( "a" );
if ( !absolute && !this.isConnected( current, newId ) )
{
throw new ShellException(
getDisplayName( getNeoServer(), session, newId ) +
" isn't connected to the current primitive," +
" use -a to force it to go there anyway" );
}
paths.add( current.getTypedId() );
}
newThing = this.getThingById( newId );
}
setCurrent( session, newThing );
session.set( WORKING_DIR_KEY, this.makePath( paths ) );
return null;
}
private TypedId getStartOrEnd( NodeOrRelationship current, String arg )
throws ShellException
{
if ( !current.isRelationship() )
{
throw new ShellException( "Only allowed on relationships" );
}
Node newNode = null;
if ( arg.equals( "start" ) )
{
newNode = current.asRelationship().getStartNode();
}
else if ( arg.equals( "end" ) )
{
newNode = current.asRelationship().getEndNode();
}
else
{
throw new ShellException( "Unknown alias '" + arg + "'" );
}
return NodeOrRelationship.wrap( newNode ).getTypedId();
}
private boolean isConnected( NodeOrRelationship current, TypedId newId )
throws ShellException
{
if ( current.isNode() )
{
Node currentNode = current.asNode();
for ( Relationship rel : currentNode.getRelationships() )
{
if ( newId.isNode() )
{
if ( rel.getOtherNode( currentNode ).getId() ==
newId.getId() )
{
return true;
}
}
else
{
if ( rel.getId() == newId.getId() )
{
return true;
}
}
}
}
else
{
if ( newId.isRelationship() )
{
return false;
}
Relationship relationship = current.asRelationship();
if ( relationship.getStartNode().getId() == newId.getId() ||
relationship.getEndNode().getId() == newId.getId() )
{
return true;
}
}
return false;
}
/**
* Reads the session variable specified in {@link #WORKING_DIR_KEY} and
* returns it as a list of {@link TypedId}s.
* @param session the session to read from.
* @return the working directory as a list.
* @throws RemoteException if an RMI error occurs.
*/
public static List<TypedId> readPaths( Session session )
throws RemoteException
{
List<TypedId> list = new ArrayList<TypedId>();
String path = (String) session.get( WORKING_DIR_KEY );
if ( path != null && path.trim().length() > 0 )
{
for ( String typedId : path.split( "," ) )
{
list.add( new TypedId( typedId ) );
}
}
return list;
}
private String makePath( List<TypedId> paths )
{
StringBuffer buffer = new StringBuffer();
for ( TypedId typedId : paths )
{
if ( buffer.length() > 0 )
{
buffer.append( "," );
}
buffer.append( typedId.toString() );
}
return buffer.length() > 0 ? buffer.toString() : null;
}
}