/** * Copyright (c) 2002-2013 "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 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.kernel.impl.util; import java.util.Collection; import java.util.NoSuchElementException; import org.neo4j.graphdb.Direction; public class RelIdArray { private static final DirectionWrapper[] DIRECTIONS_FOR_OUTGOING = new DirectionWrapper[] { DirectionWrapper.OUTGOING, DirectionWrapper.BOTH }; private static final DirectionWrapper[] DIRECTIONS_FOR_INCOMING = new DirectionWrapper[] { DirectionWrapper.INCOMING, DirectionWrapper.BOTH }; private static final DirectionWrapper[] DIRECTIONS_FOR_BOTH = new DirectionWrapper[] { DirectionWrapper.OUTGOING, DirectionWrapper.INCOMING, DirectionWrapper.BOTH }; public static class EmptyRelIdArray extends RelIdArray { private static final DirectionWrapper[] EMPTY_DIRECTION_ARRAY = new DirectionWrapper[0]; private EmptyRelIdArray( String type ) { super( type ); } @Override public RelIdIterator iterator( final DirectionWrapper direction ) { return new RelIdIteratorImpl( this, EMPTY_DIRECTION_ARRAY ) { @Override public boolean hasNext() { return false; } @Override protected boolean nextBlock() { return false; } public void doAnotherRound() { } public RelIdIterator updateSource( RelIdArray newSource ) { return direction.iterator( newSource ); } }; } }; public static RelIdArray empty( String type ) { return new EmptyRelIdArray( type ); } public static RelIdArray EMPTY = new EmptyRelIdArray( "" ); private final String type; private IdBlock lastOutBlock; private IdBlock lastInBlock; public RelIdArray( String type ) { this.type = type; } public String getType() { return type; } protected RelIdArray( RelIdArray from ) { this( from.type ); this.lastOutBlock = from.lastOutBlock; this.lastInBlock = from.lastInBlock; } protected RelIdArray( String type, IdBlock out, IdBlock in ) { this( type ); this.lastOutBlock = out; this.lastInBlock = in; } /* * Adding an id with direction BOTH means that it's a loop */ public void add( long id, DirectionWrapper direction ) { IdBlock lastBlock = direction.getLastBlock( this ); long highBits = id&0xFFFFFFFF00000000L; if ( lastBlock == null || lastBlock.getHighBits() != highBits ) { IdBlock newLastBlock = null; if ( highBits == 0 && lastBlock == null ) { newLastBlock = new LowIdBlock(); } else { // TODO: instead of always creating a new id block when high bits change // traverse back and try find a fit newLastBlock = new HighIdBlock( highBits ); if ( lastBlock != null ) { lastBlock = lastBlock.upgradeIfNeeded(); newLastBlock.setPrev( lastBlock ); } } direction.setLastBlock( this, newLastBlock ); lastBlock = newLastBlock; } lastBlock.add( (int) id ); } public RelIdArray addAll( RelIdArray source ) { if ( source == null ) { return this; } if ( source.getLastLoopBlock() != null ) { return upgradeIfNeeded( source ).addAll( source ); } append( source, DirectionWrapper.OUTGOING ); append( source, DirectionWrapper.INCOMING ); append( source, DirectionWrapper.BOTH ); return this; } protected IdBlock getLastLoopBlock() { return null; } public RelIdArray shrink() { IdBlock shrunkOut = lastOutBlock != null ? lastOutBlock.shrink() : null; IdBlock shrunkIn = lastInBlock != null ? lastInBlock.shrink() : null; return shrunkOut == lastOutBlock && shrunkIn == lastInBlock ? this : new RelIdArray( type, shrunkOut, shrunkIn ); } protected void setLastLoopBlock( IdBlock block ) { throw new UnsupportedOperationException( "Should've upgraded to RelIdArrayWithLoops before this" ); } public RelIdArray upgradeIfNeeded( RelIdArray capabilitiesToMatch ) { return capabilitiesToMatch.getLastLoopBlock() != null ? new RelIdArrayWithLoops( this ) : this; } public RelIdArray downgradeIfPossible() { return this; } protected void append( RelIdArray source, DirectionWrapper direction ) { IdBlock toBlock = direction.getLastBlock( this ); IdBlock fromBlock = direction.getLastBlock( source ); if ( fromBlock != null ) { if ( toBlock == null ) { direction.setLastBlock( this, fromBlock.copy() ); } else if ( toBlock.getHighBits() == fromBlock.getHighBits() ) { toBlock.addAll( fromBlock ); if ( fromBlock.getPrev() != null ) { boolean isTheOnlyOne = toBlock.getPrev() == null; IdBlock last = last( toBlock ); last.setPrev( fromBlock.getPrev().copy() ); if ( isTheOnlyOne ) { direction.setLastBlock( this, last ); } } } else { boolean isTheOnlyOne = toBlock.getPrev() == null; IdBlock last = last( toBlock ); last.setPrev( fromBlock.copy() ); if ( isTheOnlyOne ) { direction.setLastBlock( this, last ); } } } } /** * Also upgrade along the way if necessary */ private static IdBlock last( IdBlock block ) { IdBlock previousInLoop = null; while ( true ) { block = block.upgradeIfNeeded(); if ( previousInLoop != null ) { previousInLoop.setPrev( block ); } IdBlock prev = block.getPrev(); if ( prev == null ) { return block; } previousInLoop = block; block = prev; } } public boolean isEmpty() { return lastOutBlock == null && lastInBlock == null && getLastLoopBlock() == null ; } public RelIdIterator iterator( DirectionWrapper direction ) { return direction.iterator( this ); } public RelIdArray newSimilarInstance() { return new RelIdArray( type ); } public static final IdBlock EMPTY_BLOCK = new LowIdBlock(); public static enum DirectionWrapper { OUTGOING( Direction.OUTGOING ) { @Override RelIdIterator iterator( RelIdArray ids ) { return new RelIdIteratorImpl( ids, DIRECTIONS_FOR_OUTGOING ); } @Override IdBlock getLastBlock( RelIdArray ids ) { return ids.lastOutBlock; } @Override void setLastBlock( RelIdArray ids, IdBlock block ) { ids.lastOutBlock = block; } }, INCOMING( Direction.INCOMING ) { @Override RelIdIterator iterator( RelIdArray ids ) { return new RelIdIteratorImpl( ids, DIRECTIONS_FOR_INCOMING ); } @Override IdBlock getLastBlock( RelIdArray ids ) { return ids.lastInBlock; } @Override void setLastBlock( RelIdArray ids, IdBlock block ) { ids.lastInBlock = block; } }, BOTH( Direction.BOTH ) { @Override RelIdIterator iterator( RelIdArray ids ) { return new RelIdIteratorImpl( ids, DIRECTIONS_FOR_BOTH ); } @Override IdBlock getLastBlock( RelIdArray ids ) { return ids.getLastLoopBlock(); } @Override void setLastBlock( RelIdArray ids, IdBlock block ) { ids.setLastLoopBlock( block ); } }; private final Direction direction; private DirectionWrapper( Direction direction ) { this.direction = direction; } abstract RelIdIterator iterator( RelIdArray ids ); /* * Only used during add */ abstract IdBlock getLastBlock( RelIdArray ids ); /* * Only used during add */ abstract void setLastBlock( RelIdArray ids, IdBlock block ); public Direction direction() { return this.direction; } } public static DirectionWrapper wrap( Direction direction ) { switch ( direction ) { case OUTGOING: return DirectionWrapper.OUTGOING; case INCOMING: return DirectionWrapper.INCOMING; case BOTH: return DirectionWrapper.BOTH; default: throw new IllegalArgumentException( "" + direction ); } } public static abstract class IdBlock { // First element is the actual length w/o the slack private int[] ids = new int[3]; /** * @return a copy of itself. The copy is also shrunk so that there's no * slack in the id array. */ IdBlock copy() { IdBlock copy = copyInstance(); int length = length(); copy.ids = new int[length+1]; System.arraycopy( ids, 0, copy.ids, 0, length+1 ); return copy; } /** * @return a shrunk version of itself. It returns itself if there is * no need to shrink it or a {@link #copy()} if there is slack in the array. */ IdBlock shrink() { return length() == ids.length-1 ? this : copy(); } /** * Upgrades to a {@link HighIdBlock} if this is a {@link LowIdBlock}. */ abstract IdBlock upgradeIfNeeded(); int length() { return ids[0]; } IdBlock getPrev() { return null; } abstract void setPrev( IdBlock prev ); protected abstract IdBlock copyInstance(); // Assume id has same high bits void add( int id ) { int length = ensureSpace( 1 ); ids[length+1] = id; ids[0] = length+1; } int ensureSpace( int delta ) { int length = length(); int newLength = length+delta; if ( newLength >= ids.length-1 ) { int calculatedLength = ids.length*3; if ( newLength > calculatedLength ) { calculatedLength = newLength*2; } int[] newIds = new int[calculatedLength]; System.arraycopy( ids, 0, newIds, 0, length+1 ); ids = newIds; } return length; } void addAll( IdBlock block ) { int otherBlockLength = block.length(); int length = ensureSpace( otherBlockLength+1 ); System.arraycopy( block.ids, 1, ids, length+1, otherBlockLength ); ids[0] = otherBlockLength+length; } long get( int index ) { assert index >= 0 && index < length(); return transform( ids[index+1] ); } abstract long transform( int id ); void set( long id, int index ) { // Assume same high bits ids[index+1] = (int) id; } abstract long getHighBits(); } private static class LowIdBlock extends IdBlock { @Override void setPrev( IdBlock prev ) { throw new UnsupportedOperationException(); } @Override IdBlock upgradeIfNeeded() { IdBlock highBlock = new HighIdBlock( 0 ); highBlock.ids = ((IdBlock)this).ids; return highBlock; } @Override long transform( int id ) { return (long)(id&0xFFFFFFFFL); } @Override protected IdBlock copyInstance() { return new LowIdBlock(); } @Override long getHighBits() { return 0; } } private static class HighIdBlock extends IdBlock { private final long highBits; private IdBlock prev; HighIdBlock( long highBits ) { this.highBits = highBits; } @Override IdBlock upgradeIfNeeded() { return this; } @Override IdBlock copy() { IdBlock copy = super.copy(); if ( prev != null ) { copy.setPrev( prev.copy() ); } return copy; } @Override IdBlock getPrev() { return prev; } @Override void setPrev( IdBlock prev ) { this.prev = prev; } @Override long transform( int id ) { return (((long)(id&0xFFFFFFFFL))|(highBits)); } @Override protected IdBlock copyInstance() { return new HighIdBlock( highBits ); } @Override long getHighBits() { return highBits; } } private static class IteratorState { private int blockIndex; private IdBlock block; private int relativePosition; private int absolutePosition; public IteratorState( IdBlock block, int relativePosition ) { this.block = block; this.relativePosition = relativePosition; } boolean nextBlock() { if ( block.getPrev() != null ) { block = block.getPrev(); relativePosition = 0; blockIndex++; return true; } return false; } boolean hasNext() { return relativePosition < block.length(); } /* * Only called if hasNext returns true */ long next() { absolutePosition++; return block.get( relativePosition++ ); } public void update( IdBlock lastBlock ) { for ( int i = 0; i < blockIndex; i++ ) { lastBlock = lastBlock.getPrev(); } this.block = lastBlock; } } public static class RelIdIteratorImpl implements RelIdIterator { private final DirectionWrapper[] directions; private int directionPosition = -1; private DirectionWrapper currentDirection; private IteratorState currentState; private final IteratorState[] states; private long nextElement; private boolean nextElementDetermined; private RelIdArray ids; RelIdIteratorImpl( RelIdArray ids, DirectionWrapper[] directions ) { this.ids = ids; this.directions = directions; this.states = new IteratorState[directions.length]; // Find the initial block which isn't null. There can be directions // which have a null block currently, but could potentially be set // after the next getMoreRelationships. IdBlock block = null; while ( block == null && directionPosition+1 < directions.length ) { currentDirection = directions[++directionPosition]; block = currentDirection.getLastBlock( ids ); } if ( block != null ) { currentState = new IteratorState( block, 0 ); states[directionPosition] = currentState; } } /* (non-Javadoc) * @see org.neo4j.kernel.impl.util.RelIdIterator#getType() */ @Override public String getType() { return ids.getType(); } /* (non-Javadoc) * @see org.neo4j.kernel.impl.util.RelIdIterator#getIds() */ @Override public RelIdArray getIds() { return ids; } /* (non-Javadoc) * @see org.neo4j.kernel.impl.util.RelIdIterator#updateSource(org.neo4j.kernel.impl.util.RelIdArray) */ @Override public RelIdIterator updateSource( RelIdArray newSource ) { if ( ids != newSource || newSource.couldBeNeedingUpdate() ) { ids = newSource; // Blocks may have gotten upgraded to support a linked list // of blocks, so reestablish those references. for ( int i = 0; i < states.length; i++ ) { if ( states[i] != null ) { states[i].update( directions[i].getLastBlock( ids ) ); } } } return this; } /* (non-Javadoc) * @see org.neo4j.kernel.impl.util.RelIdIterator#hasNext() */ @Override public boolean hasNext() { if ( nextElementDetermined ) { return nextElement != -1; } while ( true ) { if ( currentState != null && currentState.hasNext() ) { nextElement = currentState.next(); nextElementDetermined = true; return true; } else { if ( !nextBlock() ) { break; } } } // Keep this false since the next call could come after we've loaded // some more relationships nextElementDetermined = false; nextElement = -1; return false; } protected boolean nextBlock() { // Try next block in the chain if ( currentState != null && currentState.nextBlock() ) { return true; } // It's ok to return null here... which will result in hasNext // returning false. IntArrayIterator will try to get more relationships // and call hasNext again. return findNextBlock(); } /* (non-Javadoc) * @see org.neo4j.kernel.impl.util.RelIdIterator#doAnotherRound() */ @Override public void doAnotherRound() { directionPosition = -1; findNextBlock(); } protected boolean findNextBlock() { while ( directionPosition+1 < directions.length ) { currentDirection = directions[++directionPosition]; IteratorState nextState = states[directionPosition]; if ( nextState != null ) { currentState = nextState; return true; } IdBlock block = currentDirection.getLastBlock( ids ); if ( block != null ) { currentState = new IteratorState( block, 0 ); states[directionPosition] = currentState; return true; } } return false; } /* (non-Javadoc) * @see org.neo4j.kernel.impl.util.RelIdIterator#next() */ @Override public long next() { if ( !hasNext() ) { throw new NoSuchElementException(); } nextElementDetermined = false; return nextElement; } } public static RelIdArray from( RelIdArray src, RelIdArray add, Collection<Long> remove ) { if ( remove == null ) { if ( src == null ) { return add.downgradeIfPossible(); } if ( add != null ) { RelIdArray newArray = src.newSimilarInstance(); newArray.addAll( src ); newArray = newArray.addAll( add ); return newArray; } return src; } else { if ( src == null && add == null ) { return null; } RelIdArray newArray = null; if ( src != null ) { newArray = src.newSimilarInstance(); newArray.addAll( src ); evictExcluded( newArray, remove ); } else { newArray = add.newSimilarInstance(); } if ( add != null ) { newArray = newArray.upgradeIfNeeded( add ); for ( RelIdIteratorImpl fromIterator = (RelIdIteratorImpl) add.iterator( DirectionWrapper.BOTH ); fromIterator.hasNext();) { long value = fromIterator.next(); if ( !remove.contains( value ) ) { newArray.add( value, fromIterator.currentDirection ); } } } return newArray.shrink(); } } private static void evictExcluded( RelIdArray ids, Collection<Long> excluded ) { for ( RelIdIteratorImpl iterator = (RelIdIteratorImpl) DirectionWrapper.BOTH.iterator( ids ); iterator.hasNext(); ) { long value = iterator.next(); if ( excluded.contains( value ) ) { boolean swapSuccessful = false; IteratorState state = iterator.currentState; IdBlock block = state.block; for ( int j = block.length() - 1; j >= state.relativePosition; j--) { long backValue = block.get( j ); block.ids[0] = block.ids[0]-1; if ( !excluded.contains( backValue) ) { block.set( backValue, state.relativePosition-1 ); swapSuccessful = true; break; } } if ( !swapSuccessful ) // all elements from pos in remove { block.ids[0] = block.ids[0]-1; } } } } /** * Optimization in the lazy loading of relationships for a node. * {@link RelIdIterator#updateSource(RelIdArray)} is only called if * this returns true, i.e if a {@link RelIdArray} or {@link IdBlock} might have * gotten upgraded to handle f.ex loops or high id ranges so that the * {@link RelIdIterator} gets updated accordingly. */ public boolean couldBeNeedingUpdate() { return (lastOutBlock != null && lastOutBlock.getPrev() != null) || (lastInBlock != null && lastInBlock.getPrev() != null); } }