Henning Snater has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/91179


Change subject: Upgraded ByPropertyIdArray
......................................................................

Upgraded ByPropertyIdArray

ByPropertyIdArray may act as an interface between a flat list of objects and 
its objects
grouped by property now. Objects can be added to and moved within the structure.

Change-Id: I771507cea8e38c117af9d27ed4af7b573bc1e897
---
M DataModel/ByPropertyIdArray.php
M tests/phpunit/ByPropertyIdArrayTest.php
2 files changed, 525 insertions(+), 1 deletion(-)


  git pull 
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/WikibaseDataModel 
refs/changes/79/91179/1

diff --git a/DataModel/ByPropertyIdArray.php b/DataModel/ByPropertyIdArray.php
index 2fb165b..90770af 100644
--- a/DataModel/ByPropertyIdArray.php
+++ b/DataModel/ByPropertyIdArray.php
@@ -24,6 +24,7 @@
  *
  * @licence GNU GPL v2+
  * @author Jeroen De Dauw < [email protected] >
+ * @author H. Snater < [email protected] >
  */
 class ByPropertyIdArray extends \ArrayObject {
 
@@ -96,4 +97,342 @@
                return $this->byId[$propertyId->getSerialization()];
        }
 
+       /**
+        * Returns the absolute index of an object or false if the object could 
not be found.
+        * @since 0.5
+        *
+        * @param object $object
+        * @return bool|int
+        *
+        * @throws RuntimeException
+        */
+       public function indexOf( $object ) {
+               if ( $this->byId === null ) {
+                       throw new RuntimeException( 'Index not build, call 
buildIndex first' );
+               }
+
+               $i = 0;
+               foreach( $this as $o ) {
+                       if( $o === $object ) {
+                               return $i;
+                       }
+                       $i++;
+               }
+               return false;
+       }
+
+       /**
+        * Returns the objects in a flat array (using the indexed form for 
generating the array).
+        * @since 0.5
+        *
+        * @return object[]
+        *
+        * @throws RuntimeException
+        */
+       public function toArray() {
+               if ( $this->byId === null ) {
+                       throw new RuntimeException( 'Index not build, call 
buildIndex first' );
+               }
+
+               $array = array();
+               foreach( $this->byId as $objects ) {
+                       foreach( $objects as $object ) {
+                               $array[] = $object;
+                       }
+               }
+               return $array;
+       }
+
+       /**
+        * Returns the absolute numeric indices of objects featuring the same 
property id.
+        * @since 0.5
+        *
+        * @param PropertyId $propertyId
+        * @return int[]
+        *
+        * @throws RuntimeException
+        */
+       protected function getNumericIndices( PropertyId $propertyId ) {
+               if ( $this->byId === null ) {
+                       throw new RuntimeException( 'Index not build, call 
buildIndex first' );
+               }
+
+               $propertyIndices = array();
+               $i = 0;
+
+               foreach( $this->byId as $objects ) {
+                       foreach( $objects as $object ) {
+                               if( $object->getPropertyId()->equals( 
$propertyId ) ) {
+                                       $propertyIndices[] = $i;
+                               }
+                               $i++;
+                       }
+               }
+
+               return $propertyIndices;
+       }
+
+       /**
+        * Moves an object within its "property group".
+        * @since 0.5
+        *
+        * @param object $object
+        * @param int $toIndex Absolute index within a "property group".
+        *
+        * @throws OutOfBoundsException
+        */
+       protected function moveInPropertyGroup( $object, $toIndex ) {
+               $currentIndex = $this->indexOf( $object );
+
+               if( $toIndex === $currentIndex ) {
+                       return;
+               }
+
+               $propertyId = $object->getPropertyId();
+               $propertyIdSerialization = 
$object->getPropertyId()->getSerialization();
+
+               $numericIndices = $this->getNumericIndices( $propertyId );
+
+               if(
+                       $toIndex > $numericIndices[count( $numericIndices ) - 
1] + 1
+                       || $toIndex < $numericIndices[0]
+               ) {
+                       throw new OutOfBoundsException( 'Object cannot be moved 
to ' . $toIndex );
+               }
+
+               $propertyGroup = array_combine( $numericIndices, 
$this->getByPropertyId( $propertyId ) );
+
+               if( $toIndex > $numericIndices[count( $numericIndices ) - 1] ) {
+                       // Move to the end.
+                       unset( $propertyGroup[$currentIndex] );
+                       $propertyGroup[] = $object;
+                       $this->byId[$propertyIdSerialization] = $propertyGroup;
+               } else {
+                       $insertBefore = $propertyGroup[$toIndex];
+                       unset( $propertyGroup[$currentIndex] );
+
+                       $this->byId[$propertyIdSerialization] = array();
+
+                       foreach( $propertyGroup as $o ) {
+                               if( $o === $insertBefore ) {
+                                       $this->byId[$propertyIdSerialization][] 
= $object;
+                               }
+                               $this->byId[$propertyIdSerialization][] = $o;
+                       }
+               }
+
+               $this->exchangeArray( $this->toArray() );
+       }
+
+       /**
+        * Moves a whole "property group".
+        * @since 0.5
+        *
+        * @param PropertyId $propertyId
+        * @param int $toIndex
+        */
+       protected function movePropertyGroup( PropertyId $propertyId, $toIndex 
) {
+               if( $this->getPropertyGroupIndex( $propertyId ) === $toIndex ) {
+                       return;
+               }
+
+               /**
+                * @var PropertyId
+                */
+               $insertBefore = null;
+
+               foreach( $this->getPropertyIds() as $pId ) {
+                       // Accepting other than the exact index by using <= 
letting the "property group" "latch"
+                       // in the next slot.
+                       if( $toIndex <= $this->getPropertyGroupIndex( $pId ) ) {
+                               $insertBefore = $pId;
+                               break;
+                       }
+               }
+
+               if( $propertyId->equals( $insertBefore ) ) {
+                       return;
+               }
+
+               $clone = $this->byId;
+               $serializedPropertyId = $propertyId->getSerialization();
+               $this->byId = array();
+
+               foreach( $clone as $serializedPId => $objects ) {
+                       $pId = new PropertyId( $serializedPId );
+                       if( $pId->equals( $propertyId ) ) {
+                               continue;
+                       } elseif( $pId->equals( $insertBefore ) ) {
+                               $this->byId[$serializedPropertyId] = 
$clone[$serializedPropertyId];
+                       }
+                       $this->byId[$serializedPId] = $objects;
+               }
+
+               if( is_null( $insertBefore ) ) {
+                       $this->byId[$serializedPropertyId] = 
$clone[$serializedPropertyId];
+               }
+
+               $this->exchangeArray( $this->toArray() );
+       }
+
+       /**
+        * Returns the index of a "property group" (the first object in the 
flat array that features
+        * the specified property). Returns false if property id could not be 
found.
+        * @since 0.5
+        *
+        * @param PropertyId $propertyId
+        * @return bool|int
+        *
+        * @throws RuntimeException
+        */
+       protected function getPropertyGroupIndex( PropertyId $propertyId ) {
+               if ( $this->byId === null ) {
+                       throw new RuntimeException( 'Index not build, call 
buildIndex first' );
+               }
+
+               $i = 0;
+
+               foreach( $this->byId as $serializedPropertyId => $objects ) {
+                       $pId = new PropertyId( $serializedPropertyId );
+                       if( $pId->equals( $propertyId ) ) {
+                               return $i;
+                       }
+                       $i += count( $objects );
+               }
+
+               return false;
+       }
+
+       /**
+        * Moves an existing object to a new index. Specifying an index outside 
the object's "property
+        * group" will move the object to the edge of the "property group" and 
shift the whole group
+        * to achieve the designated index for the object to move.
+        * @since 0.5
+        *
+        * @param object $object
+        * @param int $toIndex Absolute index where to move the object to.
+        *
+        * @throws RuntimeException|OutOfBoundsException
+        */
+       public function move( $object, $toIndex ) {
+               if ( $this->byId === null ) {
+                       throw new RuntimeException( 'Index not build, call 
buildIndex first' );
+               }
+
+               if( !in_array( $object, $this->toArray() ) ) {
+                       throw new OutOfBoundsException( 'Object not present in 
array' );
+               } elseif( $toIndex < 0 || $toIndex > count( $this ) ) {
+                       throw new OutOfBoundsException( 'Specified index is out 
of bounds' );
+               } elseif( $this->indexOf( $object ) === $toIndex ) {
+                       return;
+               }
+
+               // Determine whether to simply reindex the object within its 
"property group":
+               $propertyIndices = $this->getNumericIndices( 
$object->getPropertyId() );
+               $propertyIndices[] = $propertyIndices[count( $propertyIndices ) 
- 1] + 1;
+
+               if( in_array( $toIndex, $propertyIndices ) ) {
+                       $this->moveInPropertyGroup( $object, $toIndex );
+               } else {
+                       $edgeIndex = ( $toIndex < $propertyIndices[0] )
+                               ? $propertyIndices[0]
+                               : $propertyIndices[count( $propertyIndices ) - 
1];
+
+                       $this->moveInPropertyGroup( $object, $edgeIndex );
+                       $this->movePropertyGroup( $object->getPropertyId(), 
$toIndex );
+               }
+
+               $this->exchangeArray( $this->toArray() );
+       }
+
+       /**
+        * Adds an object at a specific index. If no index is specified, the 
object will be append to
+        * the end of its "property group" or - if no objects featuring the 
same property exist - to the
+        * absolute end of the array.
+        * Specifying an index outside a "property group" will place the new 
object at the specified
+        * index with the existing "property group" objects being shifted 
towards the new new object.
+        * @since 0.5
+        *
+        * @param object $object
+        * @param int $index Absolute index where to place the new object.
+        *
+        * @throws RuntimeException
+        */
+       public function add( $object, $index = null ) {
+               if ( $this->byId === null ) {
+                       throw new RuntimeException( 'Index not build, call 
buildIndex first' );
+               }
+
+               $propertyId = $object->getPropertyId();
+               $validIndices = $this->getNumericIndices( $propertyId );
+
+               if( count( $this ) === 0 ) {
+                       // Array is empty, just append object.
+                       $this->append( $object );
+
+               } elseif( count( $validIndices ) === 0 ) {
+                       // No objects featuring that property exist. The object 
may be inserted at a place
+                       // between existing "property groups".
+                       $this->append( $object );
+                       if( !is_null( $index ) ) {
+                               $this->buildIndex();
+                               $this->move( $object, $index );
+                       }
+
+               } else {
+                       // Objects featuring the same property as the object 
which is about to be added already
+                       // exist in the array.
+                       $this->addToPropertyGroup( $object, $index );
+               }
+
+               $this->buildIndex();
+       }
+
+       /*
+        * Adds an object to an existing property group at the specified 
absolute index.
+        * @since 0.5
+        *
+        * @param object $object
+        * @param int $index
+        *
+        * @throws OutOfBoundsException
+        */
+       protected function addToPropertyGroup( $object, $index = null ) {
+               $propertyId = $object->getPropertyId();
+               $validIndices = $this->getNumericIndices( $propertyId );
+
+               if( count( $validIndices ) === 0 ) {
+                       throw new OutOfBoundsException( 'No objects featuring 
the object\'s property exist' );
+               }
+
+               // Add index to allow placing object after the last object of 
the "property group":
+               $validIndices[] = $validIndices[count( $validIndices ) - 1] + 1;
+
+               if( is_null( $index ) ) {
+                       // If index is null, append object to "property group".
+                       $index = $validIndices[count( $validIndices ) - 1];
+               }
+
+               if( in_array( $index, $validIndices ) ) {
+                       // Add object at index within "property group".
+                       $this->byId[$propertyId->getSerialization()][] = 
$object;
+                       $this->exchangeArray( $this->toArray() );
+                       $this->move( $object, $index );
+
+               } else {
+                       // Index is out of the "property group"; The whole 
group needs to be moved.
+                       $this->movePropertyGroup( $propertyId, $index );
+
+                       // Move new object to the edge of the "property group" 
to receive its designated
+                       // index:
+                       if( $index < $validIndices[0] ) {
+                               array_unshift( 
$this->byId[$propertyId->getSerialization()], $object );
+                       } else {
+                               $this->byId[$propertyId->getSerialization()][] 
= $object;
+                       }
+               }
+
+               $this->exchangeArray( $this->toArray() );
+       }
+
 }
diff --git a/tests/phpunit/ByPropertyIdArrayTest.php 
b/tests/phpunit/ByPropertyIdArrayTest.php
index 54843b4..ca95321 100644
--- a/tests/phpunit/ByPropertyIdArrayTest.php
+++ b/tests/phpunit/ByPropertyIdArrayTest.php
@@ -5,7 +5,6 @@
 use DataValues\StringValue;
 use Wikibase\ByPropertyIdArray;
 use Wikibase\DataModel\Entity\PropertyId;
-use Wikibase\Property;
 use Wikibase\Snak;
 use Wikibase\Claim;
 use Wikibase\PropertyNoValueSnak;
@@ -28,6 +27,7 @@
  *
  * @licence GNU GPL v2+
  * @author Jeroen De Dauw < [email protected] >
+ * @author H. Snater < [email protected] >
  */
 class ByPropertyIdArrayTest extends \PHPUnit_Framework_TestCase {
 
@@ -65,6 +65,27 @@
                }
 
                return $argLists;
+       }
+
+       /**
+        * @return Claim[]
+        */
+       protected function claimsProvider() {
+               $snaks = array(
+                       new PropertyNoValueSnak( new PropertyId( 'P1' ) ),
+                       new PropertySomeValueSnak( new PropertyId( 'P1' ) ),
+                       new PropertyValueSnak( new PropertyId( 'P2' ), new 
StringValue( 'a' ) ),
+                       new PropertyValueSnak( new PropertyId( 'P2' ), new 
StringValue( 'b' ) ),
+                       new PropertyValueSnak( new PropertyId( 'P2' ), new 
StringValue( 'c' ) ),
+                       new PropertySomeValueSnak( new PropertyId( 'P3' ) ),
+               );
+
+               return array_map(
+                       function( Snak $snak ) {
+                               return new Claim( $snak );
+                       },
+                       $snaks
+               );
        }
 
        /**
@@ -145,4 +166,168 @@
                $indexedArray->getPropertyIds();
        }
 
+       /**
+        * @dataProvider listProvider
+        * @param array $objects
+        */
+       public function testIndexOf( array $objects ) {
+               $indexedArray = new ByPropertyIdArray( $objects );
+               $indexedArray->buildIndex();
+
+               $indicesSource = array();
+               $indicesDestination = array();
+
+               $i = 0;
+               foreach( $objects as $object ) {
+                       $indicesSource[$i++] = $object;
+                       $indicesDestination[$indexedArray->indexOf( $object )] 
= $object;
+               }
+
+               $this->assertEquals( $indicesSource, $indicesDestination );
+       }
+
+       /**
+        * @dataProvider listProvider
+        * @param array $objects
+        */
+       public function testToArray( array $objects ) {
+               $indexedArray = new ByPropertyIdArray( $objects );
+               $indexedArray->buildIndex();
+
+               $this->assertEquals( $objects, $indexedArray->toArray() );
+       }
+
+       public function moveProvider() {
+               $c = $this->claimsProvider();
+               $argLists = array();
+
+               $argLists[] = array( $c, $c[0], 0, $c );
+               $argLists[] = array( $c, $c[0], 1, $c );
+               $argLists[] = array( $c, $c[0], 2, array( $c[1], $c[0], $c[2], 
$c[3], $c[4], $c[5] ) );
+               $argLists[] = array( $c, $c[0], 3, array( $c[2], $c[3], $c[4], 
$c[1], $c[0], $c[5] ) );
+               $argLists[] = array( $c, $c[0], 4, array( $c[2], $c[3], $c[4], 
$c[1], $c[0], $c[5] ) );
+               $argLists[] = array( $c, $c[0], 5, array( $c[2], $c[3], $c[4], 
$c[1], $c[0], $c[5] ) );
+               $argLists[] = array( $c, $c[0], 6, array( $c[2], $c[3], $c[4], 
$c[5], $c[1], $c[0] ) );
+
+               $argLists[] = array( $c, $c[1], 0, array( $c[1], $c[0], $c[2], 
$c[3], $c[4], $c[5] ) );
+               $argLists[] = array( $c, $c[1], 5, array( $c[2], $c[3], $c[4], 
$c[0], $c[1], $c[5] ) );
+
+               $argLists[] = array( $c, $c[2], 0, array( $c[2], $c[3], $c[4], 
$c[0], $c[1], $c[5] ) );
+               $argLists[] = array( $c, $c[2], 4, array( $c[0], $c[1], $c[3], 
$c[2], $c[4], $c[5] ) );
+               $argLists[] = array( $c, $c[2], 5, array( $c[0], $c[1], $c[3], 
$c[4], $c[2], $c[5] ) );
+               $argLists[] = array( $c, $c[2], 6, array( $c[0], $c[1], $c[5], 
$c[3], $c[4], $c[2] ) );
+
+               $argLists[] = array( $c, $c[3], 0, array( $c[3], $c[2], $c[4], 
$c[0], $c[1], $c[5] ) );
+               $argLists[] = array( $c, $c[3], 1, array( $c[0], $c[1], $c[3], 
$c[2], $c[4], $c[5] ) );
+               $argLists[] = array( $c, $c[3], 2, array( $c[0], $c[1], $c[3], 
$c[2], $c[4], $c[5] ) );
+
+               $argLists[] = array( $c, $c[4], 0, array( $c[4], $c[2], $c[3], 
$c[0], $c[1], $c[5] ) );
+               $argLists[] = array( $c, $c[4], 2, array( $c[0], $c[1], $c[4], 
$c[2], $c[3], $c[5] ) );
+
+               $argLists[] = array( $c, $c[5], 0, array( $c[5], $c[0], $c[1], 
$c[2], $c[3], $c[4] ) );
+               $argLists[] = array( $c, $c[5], 1, array( $c[0], $c[1], $c[5], 
$c[2], $c[3], $c[4] ) );
+               $argLists[] = array( $c, $c[5], 2, array( $c[0], $c[1], $c[5], 
$c[2], $c[3], $c[4] ) );
+
+               return $argLists;
+       }
+
+       /**
+        * @dataProvider moveProvider
+        * @param array $objectsSource
+        * @param object $object
+        * @param int $toIndex
+        * @param array $objectsDestination
+        */
+       public function testMove(
+               array $objectsSource,
+               $object,
+               $toIndex,
+               array $objectsDestination
+       ) {
+               $indexedArray = new ByPropertyIdArray( $objectsSource );
+               $indexedArray->buildIndex();
+
+               $indexedArray->move( $object, $toIndex );
+
+               // Not using $indexedArray->toArray() here to test whether 
native array has been exchanged:
+               $reindexedArray = array();
+               foreach( $indexedArray as $o ) {
+                       $reindexedArray[] = $o;
+               }
+
+               $this->assertEquals( $objectsDestination, $reindexedArray );
+       }
+
+       public function 
testMoveThrowingOutOfBoundsExceptionIfObjectNotPresent() {
+               $claims = $this->claimsProvider();
+               $indexedArray = new ByPropertyIdArray( $claims );
+               $indexedArray->buildIndex();
+
+               $this->setExpectedException( 'OutOfBoundsException' );
+
+               $indexedArray->move( new Claim( new PropertyNoValueSnak( new 
PropertyId( 'P9999' ) ) ), 0 );
+       }
+
+       public function testMoveThrowingOutOfBoundsExceptionOnInvalidIndex() {
+               $claims = $this->claimsProvider();
+               $indexedArray = new ByPropertyIdArray( $claims );
+               $indexedArray->buildIndex();
+
+               $this->setExpectedException( 'OutOfBoundsException' );
+
+               $indexedArray->move( $claims[0], 9999 );
+       }
+
+       public function addProvider() {
+               $c = $this->claimsProvider();
+
+               $argLists = array();
+
+               $argLists[] = array( array(), $c[0], null, array( $c[0] ) );
+               $argLists[] = array( array(), $c[0], 1, array( $c[0] ) );
+               $argLists[] = array( array( $c[0] ), $c[2], 0, array( $c[2], 
$c[0] ) );
+               $argLists[] = array( array( $c[2], $c[1] ), $c[0], 0, array( 
$c[0], $c[1], $c[2] ) );
+               $argLists[] = array(
+                       array( $c[0], $c[1], $c[3] ),
+                       $c[5],
+                       1,
+                       array( $c[0], $c[1], $c[5], $c[3] )
+               );
+               $argLists[] = array(
+                       array( $c[0], $c[1], $c[5], $c[3] ),
+                       $c[2],
+                       2,
+                       array( $c[0], $c[1], $c[2], $c[3], $c[5] )
+               );
+               $argLists[] = array(
+                       array( $c[0], $c[1], $c[2], $c[3], $c[5] ),
+                       $c[4],
+                       null,
+                       array( $c[0], $c[1], $c[2], $c[3], $c[4], $c[5] )
+               );
+
+               return $argLists;
+       }
+
+       /**
+        * @dataProvider addProvider
+        * @param array $objectsSource
+        * @param object $object
+        * @param int $index
+        * @param array $objectsDestination
+        */
+       public function testAdd(
+               array $objectsSource,
+               $object,
+               $index,
+               array $objectsDestination
+       ) {
+               $indexedArray = new ByPropertyIdArray( $objectsSource );
+               $indexedArray->buildIndex();
+
+               $indexedArray->add( $object, $index );
+
+               $this->assertEquals( $objectsDestination, 
$indexedArray->toArray() );
+       }
+
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/91179
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I771507cea8e38c117af9d27ed4af7b573bc1e897
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/WikibaseDataModel
Gerrit-Branch: master
Gerrit-Owner: Henning Snater <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to