Passed
Push — remove-hashable-object-storage ( d8c663 )
by Bene
05:55 queued 02:35
created

ReferenceList::indexOf()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 12
rs 9.4285
cc 3
eloc 7
nc 3
nop 1
1
<?php
2
3
namespace Wikibase\DataModel;
4
5
use Comparable;
6
use Hashable;
7
use InvalidArgumentException;
8
use SplObjectStorage;
9
use Traversable;
10
use Wikibase\DataModel\Internal\MapValueHasher;
11
use Wikibase\DataModel\Snak\Snak;
12
13
/**
14
 * List of Reference objects.
15
 *
16
 * Note that this implementation is based on SplObjectStorage and
17
 * is not enforcing the type of objects set via it's native methods.
18
 * Therefore one can add non-Reference-implementing objects when
19
 * not sticking to the methods of the References interface.
20
 *
21
 * @since 0.1
22
 * Does not implement References anymore since 2.0
23
 *
24
 * @licence GNU GPL v2+
25
 * @author Jeroen De Dauw < [email protected] >
26
 * @author H. Snater < [email protected] >
27
 * @author Thiemo Mättig
28
 * @author Bene* < [email protected] >
29
 */
30
class ReferenceList extends SplObjectStorage implements Comparable {
31
32
	/**
33
	 * @param Reference[]|Traversable $references
34
	 *
35
	 * @throws InvalidArgumentException
36
	 */
37
	public function __construct( $references = array() ) {
38
		if ( !is_array( $references ) && !( $references instanceof Traversable ) ) {
39
			throw new InvalidArgumentException( '$references must be an array or an instance of Traversable' );
40
		}
41
42
		foreach ( $references as $reference ) {
43
			if ( !( $reference instanceof Reference ) ) {
44
				throw new InvalidArgumentException( 'Every element in $references must be an instance of Reference' );
45
			}
46
47
			$this->addReference( $reference );
48
		}
49
	}
50
51
	/**
52
	 * Adds the provided reference to the list.
53
	 *
54
	 * @since 0.1
55
	 *
56
	 * @param Reference $reference
57
	 * @param int|null $index
58
	 *
59
	 * @throws InvalidArgumentException
60
	 */
61
	public function addReference( Reference $reference, $index = null ) {
62
		if ( !is_int( $index ) && $index !== null ) {
63
			throw new InvalidArgumentException( '$index must be an integer or null' );
64
		}
65
66
		if ( $index === null || $index >= count( $this ) ) {
67
			// Append object to the end of the reference list.
68
			$this->attach( $reference );
69
		} else {
70
			$this->insertReferenceAtIndex( $reference, $index );
71
		}
72
	}
73
74
	/**
75
	 * @see SplObjectStorage::attach
76
	 *
77
	 * @param Reference $reference
78
	 * @param mixed $data Unused in the ReferenceList class.
79
	 */
80
	public function attach( $reference, $data = null ) {
81
		if ( !$reference->isEmpty() ) {
82
			parent::attach( $reference, $data );
83
		}
84
	}
85
86
	/**
87
	 * @since 1.1
88
	 *
89
	 * @param Snak[]|Snak $snaks
90
	 * @param Snak [$snak2,...]
91
	 *
92
	 * @throws InvalidArgumentException
93
	 */
94
	public function addNewReference( $snaks = array() /*...*/ ) {
95
		if ( $snaks instanceof Snak ) {
96
			$snaks = func_get_args();
97
		}
98
99
		$this->addReference( new Reference( $snaks ) );
100
	}
101
102
	/**
103
	 * @param Reference $reference
104
	 * @param int $index
105
	 */
106
	private function insertReferenceAtIndex( Reference $reference, $index ) {
107
		$referencesToShift = array();
108
		$i = 0;
109
110
		// Determine the references that need to be shifted and detach them:
111
		foreach ( $this as $object ) {
112
			if ( $i++ >= $index ) {
113
				$referencesToShift[] = $object;
114
			}
115
		}
116
117
		foreach ( $referencesToShift as $object ) {
118
			$this->detach( $object );
119
		}
120
121
		// Attach the new reference and reattach the previously detached references:
122
		$this->attach( $reference );
123
124
		foreach ( $referencesToShift as $object ) {
125
			$this->attach( $object );
126
		}
127
	}
128
129
	/**
130
	 * Returns if the list contains a reference with the same hash as the provided reference.
131
	 *
132
	 * @since 0.1
133
	 *
134
	 * @param Reference $reference
135
	 *
136
	 * @return boolean
137
	 */
138
	public function hasReference( Reference $reference ) {
139
		return $this->contains( $reference )
140
			|| $this->hasReferenceHash( $reference->getHash() );
141
	}
142
143
	/**
144
	 * Returns the index of a reference or false if the reference could not be found.
145
	 *
146
	 * @since 0.5
147
	 *
148
	 * @param Reference $reference
149
	 *
150
	 * @return int|boolean
151
	 */
152
	public function indexOf( Reference $reference ) {
153
		$index = 0;
154
155
		foreach ( $this as $object ) {
156
			if ( $object === $reference ) {
157
				return $index;
158
			}
159
			$index++;
160
		}
161
162
		return false;
163
	}
164
165
	/**
166
	 * Removes the reference with the same hash as the provided reference if such a reference exists in the list.
167
	 *
168
	 * @since 0.1
169
	 *
170
	 * @param Reference $reference
171
	 */
172
	public function removeReference( Reference $reference ) {
173
		$this->removeReferenceHash( $reference->getHash() );
174
	}
175
176
	/**
177
	 * Returns if the list contains a reference with the provided hash.
178
	 *
179
	 * @since 0.3
180
	 *
181
	 * @param string $referenceHash
182
	 *
183
	 * @return boolean
184
	 */
185
	public function hasReferenceHash( $referenceHash ) {
186
		return $this->getReference( $referenceHash ) !== null;
187
	}
188
189
	/**
190
	 * Removes the reference with the provided hash if it exists in the list.
191
	 *
192
	 * @since 0.3
193
	 *
194
	 * @param string $referenceHash	`
195
	 */
196
	public function removeReferenceHash( $referenceHash ) {
197
		$reference = $this->getReference( $referenceHash );
198
199
		if ( $reference !== null ) {
200
			$this->detach( $reference );
201
		}
202
	}
203
204
	/**
205
	 * Returns the reference with the provided hash, or null if there is no such reference in the list.
206
	 *
207
	 * @since 0.3
208
	 *
209
	 * @param string $referenceHash
210
	 *
211
	 * @return Reference|null
212
	 */
213
	public function getReference( $referenceHash ) {
214
		/**
215
		 * @var Hashable $hashable
216
		 */
217
		foreach ( $this as $hashable ) {
218
			if ( $hashable->getHash() === $referenceHash ) {
219
				return $hashable;
220
			}
221
		}
222
223
		return null;
224
	}
225
226
	/**
227
	 * @see Serializable::serialize
228
	 *
229
	 * @since 2.1
230
	 *
231
	 * @return string
232
	 */
233
	public function serialize() {
234
		return serialize( iterator_to_array( $this ) );
235
	}
236
237
	/**
238
	 * @see Serializable::unserialize
239
	 *
240
	 * @since 2.1
241
	 *
242
	 * @param string $data
243
	 */
244
	public function unserialize( $data ) {
245
		$this->__construct( unserialize( $data ) );
246
	}
247
248
	/**
249
	 * @since 4.4
250
	 *
251
	 * @return bool
252
	 */
253
	public function isEmpty() {
254
		return $this->count() === 0;
255
	}
256
257
	/**
258
	 * Removes duplicates bases on hash value.
259
	 *
260
	 * @since 0.2
261
	 */
262
	public function removeDuplicates() {
263
		$knownHashes = array();
264
265
		/**
266
		 * @var Hashable $hashable
267
		 */
268
		foreach ( iterator_to_array( $this ) as $hashable ) {
269
			$hash = $hashable->getHash();
270
271
			if ( in_array( $hash, $knownHashes ) ) {
272
				$this->detach( $hashable );
273
			}
274
			else {
275
				$knownHashes[] = $hash;
276
			}
277
		}
278
	}
279
280
	/**
281
	 * The hash is purely valuer based. Order of the elements in the array is not held into account.
282
	 *
283
	 * @since 0.3
284
	 *
285
	 * @return string
286
	 */
287
	public function getValueHash() {
288
		$hasher = new MapValueHasher();
289
		return $hasher->hash( $this );
290
	}
291
292
	/**
293
	 * @see Comparable::equals
294
	 *
295
	 * The comparison is done purely value based, ignoring the order of the elements in the array.
296
	 *
297
	 * @since 0.3
298
	 *
299
	 * @param mixed $target
300
	 *
301
	 * @return bool
302
	 */
303
	public function equals( $target ) {
304
		if ( $this === $target ) {
305
			return true;
306
		}
307
308
		return $target instanceof self
309
		       && $this->getValueHash() === $target->getValueHash();
310
	}
311
312
}
313