package org.neo4j.graphdb; import java.lang.reflect.Array; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.neo4j.commons.iterator.FilteringIterable; /** * A convenience for specifying multiple {@link RelationshipType} / * {@link Direction} pairs. */ public class RelationshipExpander { public static RelationshipExpander ALL = new RelationshipExpander( new RelationshipType[0], new HashMap<String, Direction>() ); private final RelationshipType[] types; private final Map<String, Direction> directions; /** * Creates a new {@link RelationshipExpander} which is set to expand * relationships with {@code type} and {@code direction}. * * @param type the {@link RelationshipType} to expand. * @param dir the {@link Direction} to expand. * @return a new {@link RelationshipExpander}. */ public static RelationshipExpander forTypes( RelationshipType type, Direction dir ) { return new RelationshipExpander().add( type, dir ); } /** * Creates a new {@link RelationshipExpander} which is set to expand * relationships with two different types and directions. * * @param type1 a {@link RelationshipType} to expand. * @param dir1 a {@link Direction} to expand. * @param type2 another {@link RelationshipType} to expand. * @param dir2 another {@link Direction} to expand. * @return a new {@link RelationshipExpander}. */ public static RelationshipExpander forTypes( RelationshipType type1, Direction dir1, RelationshipType type2, Direction dir2 ) { return new RelationshipExpander().add( type1, dir1 ).add( type2, dir2 ); } /** * Creates a new {@link RelationshipExpander} which is set to expand * relationships with multiple types and directions. * * @param type1 a {@link RelationshipType} to expand. * @param dir1 a {@link Direction} to expand. * @param type2 another {@link RelationshipType} to expand. * @param dir2 another {@link Direction} to expand. * @param more additional pairs or type/direction to expand. * @return a new {@link RelationshipExpander}. */ public static RelationshipExpander forTypes( RelationshipType type1, Direction dir1, RelationshipType type2, Direction dir2, Object... more ) { RelationshipType[] types = extract( RelationshipType[].class, type1, type2, more, false ); Direction[] directions = extract( Direction[].class, dir1, dir2, more, true ); RelationshipExpander expander = new RelationshipExpander(); for ( int i = 0; i < types.length; i++ ) { expander = expander.add( types[i], directions[i] ); } return expander; } private static <T> T[] extract( Class<T[]> type, T obj1, T obj2, Object[] more, boolean odd ) { if ( more.length % 2 != 0 ) { throw new IllegalArgumentException(); } Object[] target = (Object[]) Array.newInstance( type, ( more.length / 2 ) + 2 ); try { target[0] = obj1; target[1] = obj2; for ( int i = 2; i < target.length; i++ ) { target[i] = more[( i - 2 ) * 2 + ( odd ? 1 : 0 )]; } } catch ( ArrayStoreException cast ) { throw new IllegalArgumentException( cast ); } return type.cast( target ); } protected RelationshipExpander( RelationshipType[] types, Map<String, Direction> directions) { this.types = types; this.directions = directions; } public RelationshipExpander() { this.types = new RelationshipType[0]; this.directions = new HashMap<String, Direction>(); } protected RelationshipType[] getTypes() { return this.types; } protected Direction getDirection( RelationshipType type ) { return this.directions.get( type ); } /** * Gets relationships from the {@code start} {@link Node}. The returned * {@link Relationship}s will match those criterias specified when * creating this expander. * * @return an {@link Iterable} of {@link Relationship}s matching the * criterias of this expander. */ public Iterable<Relationship> expand( Node start ) { if ( types.length == 0 ) { return start.getRelationships(); } if ( types.length == 1 ) { RelationshipType type = types[0]; Direction direction = directions.get( type.name() ); return start.getRelationships( type, direction ); } return getRelationshipsForMultipleTypes( start, types, directions ); } protected Iterable<Relationship> getRelationshipsForMultipleTypes( final Node start, RelationshipType[] types, final Map<String, Direction> directions ) { return new FilteringIterable<Relationship>( start.getRelationships( types ) ) { @Override protected boolean passes( Relationship item ) { switch ( directions.get( item.getType().name() ) ) { case INCOMING: return item.getEndNode().equals( start ); case OUTGOING: return item.getStartNode().equals( start ); default: return true; } } }; } @Override public int hashCode() { return Arrays.hashCode( types ); } @Override public boolean equals( Object obj ) { if (this == obj) { return true; } if ( obj instanceof RelationshipExpander ) { RelationshipExpander that = (RelationshipExpander) obj; return Arrays.equals( this.types, that.types ) && this.directions.equals( that.directions ); } return false; } /** * Adds a {@link RelationshipType} / {@link Direction} pair to the * criterias of {@link Relationship}s to be returned from * {@link #expand(Node)}. This instance will be left intact and a new * instance including the new addition will be returned. * * @param type the {@link RelationshipType}. * @param direction the {@link Direction} for that type. * @return the resulting expander. */ public RelationshipExpander add( RelationshipType type, Direction direction ) { Direction existingDirection = directions.get( type.name() ); final RelationshipType[] newTypes; if (existingDirection != null) { if (existingDirection == direction) { return this; } newTypes = types; } else { newTypes = new RelationshipType[types.length + 1]; System.arraycopy( types, 0, newTypes, 0, types.length ); newTypes[types.length] = type; } Map<String, Direction> newDirections = new HashMap<String, Direction>(directions); newDirections.put( type.name(), direction ); return (RelationshipExpander) newExpander(newTypes, newDirections); } protected RelationshipExpander newExpander( RelationshipType[] types, Map<String, Direction> directions ) { return new RelationshipExpander( types, directions ); } /** * Returns an expander which has all its {@link Direction}s reversed, * also see {@link Direction#reverse()} */ public RelationshipExpander reversed() { return newExpander( types, reverseDirections( directions ) ); } private static Map<String, Direction> reverseDirections( Map<String, Direction> directions ) { Map<String, Direction> result = new HashMap<String, Direction>(); for ( Map.Entry<String, Direction> entry : directions.entrySet() ) { result.put( entry.getKey(), entry.getValue().reverse() ); } return result; } }