/* * 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.kernel.impl.core; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.NotFoundException; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.ReturnableEvaluator; import org.neo4j.graphdb.StopEvaluator; import org.neo4j.graphdb.Traverser; import org.neo4j.graphdb.Traverser.Order; import org.neo4j.kernel.impl.nioneo.store.PropertyData; import org.neo4j.kernel.impl.nioneo.store.Record; import org.neo4j.kernel.impl.nioneo.store.RelationshipChainPosition; import org.neo4j.kernel.impl.transaction.LockType; import org.neo4j.kernel.impl.traversal.OldTraverserWrapper; import org.neo4j.kernel.impl.util.ArrayMap; import org.neo4j.kernel.impl.util.IntArray; class NodeImpl extends Primitive implements Node, Comparable<Node> { private ArrayMap<String,IntArray> relationshipMap = null; // private RelationshipGrabber relationshipGrabber = null; private RelationshipChainPosition relChainPosition = null; NodeImpl( int id, NodeManager nodeManager ) { super( id, nodeManager ); } // newNode will only be true for NodeManager.createNode NodeImpl( int id, boolean newNode, NodeManager nodeManager ) { super( id, newNode, nodeManager ); if ( newNode ) { relationshipMap = new ArrayMap<String,IntArray>(); relChainPosition = new RelationshipChainPosition( Record.NO_NEXT_RELATIONSHIP.intValue() ); } } @Override protected void changeProperty( int propertyId, Object value ) { nodeManager.nodeChangeProperty( this, propertyId, value ); } @Override protected int addProperty( PropertyIndex index, Object value ) { return nodeManager.nodeAddProperty( this, index, value ); } @Override protected void removeProperty( int propertyId ) { nodeManager.nodeRemoveProperty( this, propertyId ); } @Override protected ArrayMap<Integer,PropertyData> loadProperties( boolean light ) { return nodeManager.loadProperties( this, light ); } List<RelTypeElementIterator> getAllRelationships() { ensureFullRelationships(); List<RelTypeElementIterator> relTypeList = new LinkedList<RelTypeElementIterator>(); ArrayMap<String,IntArray> addMap = nodeManager.getCowRelationshipAddMap( this ); for ( String type : relationshipMap.keySet() ) { IntArray src = relationshipMap.get( type ); IntArray remove = nodeManager.getCowRelationshipRemoveMap( this, type ); IntArray add = null; if ( addMap != null ) { add = addMap.get( type ); } if ( src != null || add != null ) { relTypeList.add( RelTypeElement.create( type, this, src, add, remove ) ); } } if ( addMap != null ) { for ( String type : addMap.keySet() ) { if ( relationshipMap.get( type ) == null ) { IntArray remove = nodeManager.getCowRelationshipRemoveMap( this, type ); IntArray add = addMap.get( type ); relTypeList.add( RelTypeElement.create( type, this, null, add, remove ) ); } } } return relTypeList; } List<RelTypeElementIterator> getAllRelationshipsOfType( RelationshipType... types) { ensureFullRelationships(); List<RelTypeElementIterator> relTypeList = new LinkedList<RelTypeElementIterator>(); for ( RelationshipType type : types ) { IntArray src = relationshipMap.get( type.name() ); IntArray remove = nodeManager.getCowRelationshipRemoveMap( this, type.name() ); IntArray add = nodeManager.getCowRelationshipAddMap( this, type.name() ); if ( src != null || add != null ) { relTypeList.add( RelTypeElement.create( type.name(), this, src, add, remove ) ); } } return relTypeList; } public Iterable<Relationship> getRelationships() { return new IntArrayIterator( getAllRelationships(), this, Direction.BOTH, nodeManager, new RelationshipType[0] ); } public Iterable<Relationship> getRelationships( Direction dir ) { return new IntArrayIterator( getAllRelationships(), this, dir, nodeManager, new RelationshipType[0] ); } public Iterable<Relationship> getRelationships( RelationshipType type ) { RelationshipType types[] = new RelationshipType[] { type }; return new IntArrayIterator( getAllRelationshipsOfType( types ), this, Direction.BOTH, nodeManager, types ); } public Iterable<Relationship> getRelationships( RelationshipType... types ) { return new IntArrayIterator( getAllRelationshipsOfType(types ), this, Direction.BOTH, nodeManager, types ); } public Relationship getSingleRelationship( RelationshipType type, Direction dir ) { RelationshipType types[] = new RelationshipType[] { type }; Iterator<Relationship> rels = new IntArrayIterator( getAllRelationshipsOfType( types ), this, dir, nodeManager, types ); if ( !rels.hasNext() ) { return null; } Relationship rel = rels.next(); if ( rels.hasNext() ) { throw new NotFoundException( "More than one relationship[" + type + ", " + dir + "] found for " + this ); } return rel; } public Iterable<Relationship> getRelationships( RelationshipType type, Direction dir ) { RelationshipType types[] = new RelationshipType[] { type }; return new IntArrayIterator( getAllRelationshipsOfType( types ), this, dir, nodeManager, types ); } public void delete() { nodeManager.acquireLock( this, LockType.WRITE ); boolean success = false; try { ArrayMap<Integer,PropertyData> skipMap = nodeManager.getCowPropertyRemoveMap( this, true ); ArrayMap<Integer,PropertyData> removedProps = nodeManager.deleteNode( this ); if ( removedProps.size() > 0 ) { for ( int index : removedProps.keySet() ) { skipMap.put( index, removedProps.get( index ) ); } } success = true; } finally { nodeManager.releaseLock( this, LockType.WRITE ); if ( !success ) { setRollbackOnly(); } } } /** * If object <CODE>node</CODE> is a node, 0 is returned if <CODE>this</CODE> * node id equals <CODE>node's</CODE> node id, 1 if <CODE>this</CODE> * node id is greater and -1 else. * <p> * If <CODE>node</CODE> isn't a node a ClassCastException will be thrown. * * @param node * the node to compare this node with * @return 0 if equal id, 1 if this id is greater else -1 */ public int compareTo( Node n ) { long ourId = this.getId(), theirId = n.getId(); if ( ourId < theirId ) { return -1; } else if ( ourId > theirId ) { return 1; } else { return 0; } } /** * Returns true if object <CODE>o</CODE> is a node with the same id as * <CODE>this</CODE>. * * @param o * the object to compare * @return true if equal, else false */ @Override public boolean equals( Object o ) { // verify type and not null, should use Node inteface if ( !(o instanceof Node) ) { return false; } // The equals contract: // o reflexive: x.equals(x) // o symmetric: x.equals(y) == y.equals(x) // o transitive: ( x.equals(y) && y.equals(z) ) == true // then x.equals(z) == true // o consistent: the nodeId never changes return this.getId() == ((Node) o).getId(); } @Override public int hashCode() { return id; } /** * Returns this node's string representation. * * @return the string representation of this node */ @Override public String toString() { return "NodeImpl#" + this.getId(); } // caller is responsible for acquiring lock // this method is only called when a relationship is created or // a relationship delete is undone or when the full node is loaded void addRelationship( RelationshipType type, int relId ) { IntArray relationshipSet = nodeManager.getCowRelationshipAddMap( this, type.name(), true ); relationshipSet.add( relId ); } // caller is responsible for acquiring lock // this method is only called when a undo create relationship or // a relationship delete is invoked. void removeRelationship( RelationshipType type, int relId ) { IntArray relationshipSet = nodeManager.getCowRelationshipRemoveMap( this, type.name(), true ); relationshipSet.add( relId ); } private synchronized boolean ensureFullRelationships() { if ( relationshipMap == null ) { this.relChainPosition = nodeManager.getRelationshipChainPosition( this ); this.relationshipMap = new ArrayMap<String,IntArray>(); getMoreRelationships(); return true; } return false; } synchronized boolean getMoreRelationships() { if ( !relChainPosition.hasMore() ) { return false; } ArrayMap<String,IntArray> addMap = nodeManager.getMoreRelationships( this ); if ( addMap.size() == 0 ) { return false; } for ( String type : addMap.keySet() ) { IntArray addRels = addMap.get( type ); IntArray srcRels = relationshipMap.get( type ); if ( srcRels == null ) { relationshipMap.put( type, addRels ); } else { srcRels.addAll( addRels ); } } return true; } public Relationship createRelationshipTo( Node otherNode, RelationshipType type ) { return nodeManager.createRelationship( this, otherNode, type ); } /* Tentative expansion API public Expansion<Relationship> expandAll() { return Traversal.expanderForAllTypes().expand( this ); } public Expansion<Relationship> expand( RelationshipType type ) { return expand( type, Direction.BOTH ); } public Expansion<Relationship> expand( RelationshipType type, Direction direction ) { return Traversal.expanderForTypes( type, direction ).expand( this ); } public Expansion<Relationship> expand( Direction direction ) { return Traversal.expanderForAllTypes( direction ).expand( this ); } public Expansion<Relationship> expand( RelationshipExpander expander ) { return Traversal.expander( expander ).expand( this ); } */ public Traverser traverse( Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, RelationshipType relationshipType, Direction direction ) { return OldTraverserWrapper.traverse( new NodeProxy( id, nodeManager ), traversalOrder, stopEvaluator, returnableEvaluator, relationshipType, direction ); } public Traverser traverse( Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, RelationshipType firstRelationshipType, Direction firstDirection, RelationshipType secondRelationshipType, Direction secondDirection ) { return OldTraverserWrapper.traverse( new NodeProxy( id, nodeManager ), traversalOrder, stopEvaluator, returnableEvaluator, firstRelationshipType, firstDirection, secondRelationshipType, secondDirection ); } public Traverser traverse( Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, Object... relationshipTypesAndDirections ) { return OldTraverserWrapper.traverse( new NodeProxy( id, nodeManager ), traversalOrder, stopEvaluator, returnableEvaluator, relationshipTypesAndDirections ); } public boolean hasRelationship() { return getRelationships().iterator().hasNext(); } public boolean hasRelationship( RelationshipType... types ) { return getRelationships( types ).iterator().hasNext(); } public boolean hasRelationship( Direction dir ) { return getRelationships( dir ).iterator().hasNext(); } public boolean hasRelationship( RelationshipType type, Direction dir ) { return getRelationships( type, dir ).iterator().hasNext(); } protected void commitRelationshipMaps( ArrayMap<String,IntArray> cowRelationshipAddMap, ArrayMap<String,IntArray> cowRelationshipRemoveMap ) { if ( relationshipMap == null ) { // we will load full in some other tx return; } if ( cowRelationshipAddMap != null ) { for ( String type : cowRelationshipAddMap.keySet() ) { IntArray add = cowRelationshipAddMap.get( type ); IntArray remove = null; if ( cowRelationshipRemoveMap != null ) { remove = cowRelationshipRemoveMap.get( type ); } IntArray src = relationshipMap.get( type ); relationshipMap.put( type, IntArray.composeNew( src, add, remove ) ); } } if ( cowRelationshipRemoveMap != null ) { for ( String type : cowRelationshipRemoveMap.keySet() ) { if ( cowRelationshipAddMap != null && cowRelationshipAddMap.get( type ) != null ) { continue; } IntArray src = relationshipMap.get( type ); IntArray remove = cowRelationshipRemoveMap.get( type ); relationshipMap.put( type, IntArray.composeNew( src, null, remove ) ); } } } RelationshipChainPosition getRelChainPosition() { return relChainPosition; } }