diff --git a/src/ByPropertyIdMap.php b/src/ByPropertyIdMap.php new file mode 100644 index 00000000..8eecbfd1 --- /dev/null +++ b/src/ByPropertyIdMap.php @@ -0,0 +1,154 @@ + + */ +class ByPropertyIdMap { + + /** + * Map of serialized property ids pointing to + * a list of elements with that property id + * + * @var array[] + */ + private $byPropertyId = array(); + + /** + * @var PropertyId[] + */ + private $propertyIds = array(); + + /** + * @param PropertyIdProvider[]|Traversable $propertyIdProviders + * + * @throws InvalidArgumentException + */ + public function __construct( $propertyIdProviders ) { + $byPropertyIdGrouper = new ByPropertyIdGrouper( $propertyIdProviders ); + + $this->propertyIds = $byPropertyIdGrouper->getPropertyIds(); + foreach ( $this->propertyIds as $propertyId ) { + $this->byPropertyId[$propertyId->getSerialization()] = $byPropertyIdGrouper->getByPropertyId( $propertyId ); + } + } + + /** + * @param PropertyId $propertyId + * @param int|null $index + * + * @throws InvalidArgumentException + * @throws OutOfBoundsException + */ + public function moveGroupToIndex( PropertyId $propertyId, $index ) { + $this->assertIsIndex( $index ); + + $oldIndex = array_search( $propertyId, $this->propertyIds ); + + if ( $oldIndex === false ) { + throw new OutOfBoundsException( 'There is no group for property id ' . $propertyId->getSerialization() ); + } + + if ( $index === null ) { + $index = count( $this->propertyIds ); + } + + array_splice( $this->propertyIds, $oldIndex, 1 ); + array_splice( $this->propertyIds, $index, 0, array( $propertyId ) ); + + $this->propertyIds = array_values( $this->propertyIds ); + } + + /** + * @param PropertyIdProvider $propertyIdProvider + * @param int|null $index + * + * @throws InvalidArgumentException + * @throws OutOfBoundsException + */ + public function moveElementToIndex( PropertyIdProvider $propertyIdProvider, $index ) { + $this->assertIsIndex( $index ); + + $idSerialization = $propertyIdProvider->getPropertyId()->getSerialization(); + + if ( !isset( $this->byPropertyId[$idSerialization] ) ) { + throw new OutOfBoundsException( 'There is no group for property id ' . $idSerialization ); + } + + $oldIndex = array_search( $propertyIdProvider, $this->byPropertyId[$idSerialization] ); + + if ( $oldIndex === false ) { + throw new OutOfBoundsException( 'The property id provider does not exist in this map' ); + } + + if ( $index === null ) { + $index = count( $this->byPropertyId[$idSerialization] ); + } + + array_splice( $this->byPropertyId[$idSerialization], $oldIndex, 1 ); + array_splice( $this->byPropertyId[$idSerialization], $index, 0, array( $propertyIdProvider ) ); + + $this->byPropertyId[$idSerialization] = array_values( $this->byPropertyId[$idSerialization] ); + } + + /** + * @param PropertyIdProvider $propertyIdProvider + * @param int|null $index + * + * @throws InvalidArgumentException + */ + public function addElementAtIndex( PropertyIdProvider $propertyIdProvider, $index ) { + $this->assertIsIndex( $index ); + + $idSerialization = $propertyIdProvider->getPropertyId()->getSerialization(); + + if ( !isset( $this->byPropertyId[$idSerialization] ) ) { + $this->byPropertyId[$idSerialization] = array(); + $this->propertyIds[] = $propertyIdProvider->getPropertyId(); + } + + if ( $index === null ) { + $index = count( $this->byPropertyId[$idSerialization] ); + } + + array_splice( $this->byPropertyId[$idSerialization], $index, 0, array( $propertyIdProvider ) ); + + $this->byPropertyId[$idSerialization] = array_values( $this->byPropertyId[$idSerialization] ); + } + + private function assertIsIndex( $index ) { + if ( ( !is_int( $index ) || $index < 0 ) && $index !== null ) { + throw new InvalidArgumentException( '$index must be a non-negative integer or null' ); + } + } + + /** + * @return PropertyIdProvider[] + */ + public function getFlatArray() { + $propertyIdProviders = array(); + + foreach ( $this->propertyIds as $propertyId ) { + foreach ( $this->byPropertyId[$propertyId->getSerialization()] as $propertyIdProvider ) { + $propertyIdProviders[] = $propertyIdProvider; + } + } + + return $propertyIdProviders; + } + +} diff --git a/tests/unit/ByPropertyIdMapTest.php b/tests/unit/ByPropertyIdMapTest.php new file mode 100644 index 00000000..1cfc9fb2 --- /dev/null +++ b/tests/unit/ByPropertyIdMapTest.php @@ -0,0 +1,309 @@ + + */ +class ByPropertyIdMapTest extends \PHPUnit_Framework_TestCase { + + private function getPropertyIdProvider( $propertyId, $value ) { + return new PropertyValueSnak( new PropertyId( $propertyId ), new StringValue( $value ) ); + } + + private function assertTypesAreEqual( array $types, ByPropertyIdMap $byPropertyIdMap ) { + $flatArray = $byPropertyIdMap->getFlatArray(); + + array_walk( $flatArray, function( PropertyValueSnak &$snak ) { + $snak = $snak->getDataValue()->getValue(); + } ); + + $this->assertEquals( $types, $flatArray ); + } + + public function testConstructWithGroupedList_getFlatArrayReturnsSameList() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ), + $this->getPropertyIdProvider( 'P789', 'boo' ) + ) ); + + $this->assertTypesAreEqual( array( 'foo', 'bar', 'baz', 'boo' ), $byPropertyIdMap ); + } + + public function testConstructWithUngroupedList_getFlatArrayReturnsGroupedList() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ), + $this->getPropertyIdProvider( 'P321', 'boo' ) + ) ); + + $this->assertTypesAreEqual( array( 'foo', 'boo', 'bar', 'baz' ), $byPropertyIdMap ); + } + + public function testGivenIntegerIndex_moveGroupToIndexMovesGroup() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ), + $this->getPropertyIdProvider( 'P789', 'boo' ) + ) ); + + $byPropertyIdMap->moveGroupToIndex( new PropertyId( 'P123' ), 2 ); + + $this->assertTypesAreEqual( array( 'foo', 'boo', 'bar', 'baz' ), $byPropertyIdMap ); + } + + public function testGivenZeroIndex_moveGroupToIndexMovesGroupToBeginning() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ), + $this->getPropertyIdProvider( 'P789', 'boo' ) + ) ); + + $byPropertyIdMap->moveGroupToIndex( new PropertyId( 'P789' ), 0 ); + + $this->assertTypesAreEqual( array( 'boo', 'foo', 'bar', 'baz' ), $byPropertyIdMap ); + } + + public function testGivenHugeIndex_moveGroupToIndexMovesGroupToEnd() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ), + $this->getPropertyIdProvider( 'P789', 'boo' ) + ) ); + + $byPropertyIdMap->moveGroupToIndex( new PropertyId( 'P321' ), 999999 ); + + $this->assertTypesAreEqual( array( 'bar', 'baz', 'boo', 'foo' ), $byPropertyIdMap ); + } + + public function testGivenNullIndex_moveGroupToIndexMovesGroupToEnd() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ), + $this->getPropertyIdProvider( 'P789', 'boo' ) + ) ); + + $byPropertyIdMap->moveGroupToIndex( new PropertyId( 'P321' ), null ); + + $this->assertTypesAreEqual( array( 'bar', 'baz', 'boo', 'foo' ), $byPropertyIdMap ); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGivenNonIntegerIndex_moveGroupToIndexThrowsException() { + $byPropertyIdMap = new ByPropertyIdMap( array() ); + + $byPropertyIdMap->moveGroupToIndex( new PropertyId( 'P42' ), false ); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGivenNegativeIndex_moveGroupToIndexThrowsException() { + $byPropertyIdMap = new ByPropertyIdMap( array() ); + + $byPropertyIdMap->moveGroupToIndex( new PropertyId( 'P42' ), -1 ); + } + + /** + * @expectedException OutOfBoundsException + */ + public function testGivenNotExistingPropertyId_moveGroupToIndexThrowsException() { + $byPropertyIdMap = new ByPropertyIdMap( array() ); + + $byPropertyIdMap->moveGroupToIndex( new PropertyId( 'P42' ), 0 ); + } + + public function testGivenIntegerIndex_moveElementToIndexMovesPropertyIdProvider() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ), + $this->getPropertyIdProvider( 'P123', 'boo' ) + ) ); + + $propertyIdProvider = $this->getPropertyIdProvider( 'P123', 'bar' ); + $byPropertyIdMap->moveElementToIndex( $propertyIdProvider, 1 ); + + $this->assertTypesAreEqual( array( 'foo', 'baz', 'bar', 'boo' ), $byPropertyIdMap ); + } + + public function testGivenZeroIndex_moveElementToIndexMovesPropertyIdProviderToBeginning() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ), + $this->getPropertyIdProvider( 'P123', 'boo' ) + ) ); + + $propertyIdProvider = $this->getPropertyIdProvider( 'P123', 'boo' ); + $byPropertyIdMap->moveElementToIndex( $propertyIdProvider, 0 ); + + $this->assertTypesAreEqual( array( 'foo', 'boo', 'bar', 'baz' ), $byPropertyIdMap ); + } + + public function testGivenHugeIndex_moveElementToIndexMovesPropertyIdProviderToEnd() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ), + $this->getPropertyIdProvider( 'P123', 'boo' ) + ) ); + + $propertyIdProvider = $this->getPropertyIdProvider( 'P123', 'baz' ); + $byPropertyIdMap->moveElementToIndex( $propertyIdProvider, 999999 ); + + $this->assertTypesAreEqual( array( 'foo', 'bar', 'boo', 'baz' ), $byPropertyIdMap ); + } + + public function testGivenNullIndex_moveElementToIndexMovesPropertyIdProviderToEnd() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ), + $this->getPropertyIdProvider( 'P123', 'boo' ) + ) ); + + $propertyIdProvider = $this->getPropertyIdProvider( 'P123', 'baz' ); + $byPropertyIdMap->moveElementToIndex( $propertyIdProvider, null ); + + $this->assertTypesAreEqual( array( 'foo', 'bar', 'boo', 'baz' ), $byPropertyIdMap ); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGivenNonIntegerIndex_moveElementToIndexThrowsException() { + $byPropertyIdMap = new ByPropertyIdMap( array() ); + + $byPropertyIdMap->moveElementToIndex( $this->getPropertyIdProvider( 'P42', 'foo' ), false ); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGivenNegativeIndex_moveElementToIndexThrowsException() { + $byPropertyIdMap = new ByPropertyIdMap( array() ); + + $byPropertyIdMap->moveElementToIndex( $this->getPropertyIdProvider( 'P42', 'foo' ), -1 ); + } + + /** + * @expectedException OutOfBoundsException + */ + public function testGivenNotExistingPropertyId_moveElementToIndexThrowsException() { + $byPropertyIdMap = new ByPropertyIdMap( array() ); + + $byPropertyIdMap->moveElementToIndex( $this->getPropertyIdProvider( 'P42', 'foo' ), 0 ); + } + + /** + * @expectedException OutOfBoundsException + */ + public function testGivenNotExistingPropertyIdProvider_moveElementToIndexThrowsException() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P42', 'foo' ) + ) ); + + $byPropertyIdMap->moveElementToIndex( $this->getPropertyIdProvider( 'P42', 'bar' ), 0 ); + } + + public function testGivenIntegerIndex_addElementAtIndexAddsPropertyIdProvider() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ) + ) ); + + $propertyIdProvider = $this->getPropertyIdProvider( 'P123', 'boo' ); + $byPropertyIdMap->addElementAtIndex( $propertyIdProvider, 1 ); + + $this->assertTypesAreEqual( array( 'foo', 'bar', 'boo', 'baz' ), $byPropertyIdMap ); + } + + public function testGivenZeroIndex_addElementAtIndexAddsPropertyIdProviderAtBeginning() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ) + ) ); + + $propertyIdProvider = $this->getPropertyIdProvider( 'P321', 'boo' ); + $byPropertyIdMap->addElementAtIndex( $propertyIdProvider, 0 ); + + $this->assertTypesAreEqual( array( 'boo', 'foo', 'bar', 'baz' ), $byPropertyIdMap ); + } + + public function testGivenHugeIndex_addElementAtIndexAddsPropertyIdProviderAtEnd() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ) + ) ); + + $propertyIdProvider = $this->getPropertyIdProvider( 'P321', 'boo' ); + $byPropertyIdMap->addElementAtIndex( $propertyIdProvider, 999999 ); + + $this->assertTypesAreEqual( array( 'foo', 'boo', 'bar', 'baz' ), $byPropertyIdMap ); + } + + public function testGivenNullIndex_addElementAtIndexAddsPropertyIdProviderAtEnd() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ) + ) ); + + $propertyIdProvider = $this->getPropertyIdProvider( 'P321', 'boo' ); + $byPropertyIdMap->addElementAtIndex( $propertyIdProvider, null ); + + $this->assertTypesAreEqual( array( 'foo', 'boo', 'bar', 'baz' ), $byPropertyIdMap ); + } + + public function testGivenNotExistingPropertyId_addElementAtIndexCreatesNewGroupAtEnd() { + $byPropertyIdMap = new ByPropertyIdMap( array( + $this->getPropertyIdProvider( 'P321', 'foo' ), + $this->getPropertyIdProvider( 'P123', 'bar' ), + $this->getPropertyIdProvider( 'P123', 'baz' ) + ) ); + + $propertyIdProvider = $this->getPropertyIdProvider( 'P456', 'boo' ); + $byPropertyIdMap->addElementAtIndex( $propertyIdProvider, 1 ); + + $this->assertTypesAreEqual( array( 'foo', 'bar', 'baz', 'boo' ), $byPropertyIdMap ); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGivenNonIntegerIndex_addElementAtIndexThrowsException() { + $byPropertyIdMap = new ByPropertyIdMap( array() ); + + $byPropertyIdMap->addElementAtIndex( $this->getPropertyIdProvider( 'P42', 'foo' ), false ); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGivenNegativeIndex_addElementAtIndexThrowsException() { + $byPropertyIdMap = new ByPropertyIdMap( array() ); + + $byPropertyIdMap->addElementAtIndex( $this->getPropertyIdProvider( 'P42', 'foo' ), -1 ); + } + +}