summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/SemanticMediaWiki/src/DataModel/SubSemanticData.php
blob: 89fa42a31f26e449199fe5ef5f9e7e9b9a898563 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
<?php

namespace SMW\DataModel;

use SMW\DIProperty;
use SMW\DIWikiPage;
use SMW\Exception\SubSemanticDataException;
use SMW\SemanticData;

/**
 * @private
 *
 * Internal handling of the SubSemanticData container and its subsequent
 * add and remove operations.
 *
 * @license GNU GPL v2+
 * @since 2.5
 *
 * @author Markus Krötzsch
 * @author Jeroen De Dauw
 * @author mwjames
 */
class SubSemanticData {

	/**
	 * States whether repeated values should be avoided. Not needing
	 * duplicate elimination (e.g. when loading from store) can save some
	 * time, especially in subclasses like SMWSqlStubSemanticData, where
	 * the first access to a data item is more costy.
	 *
	 * @note This setting is merely for optimization. The SMW data model
	 * never cares about the multiplicity of identical data assignments.
	 *
	 * @var boolean
	 */
	private $noDuplicates;

	/**
	 * DIWikiPage object that is the subject of this container.
	 * Subjects can never be null (and this is ensured in all methods setting
	 * them in this class).
	 *
	 * @var DIWikiPage
	 */
	private $subject;

	/**
	 * Semantic data associated to subobjects of the subject of this
	 * SMWSemanticData.
	 * These key-value pairs of subObjectName (string) =>SMWSemanticData.
	 *
	 * @since 2.5
	 * @var SemanticData[]
	 */
	private $subSemanticData = [];

	/**
	 * Internal flag that indicates if this semantic data will accept
	 * subdata. Semantic data objects that are subdata already do not allow
	 * (second level) subdata to be added. This ensures that all data is
	 * collected on the top level, and in particular that there is only one
	 * way to represent the same data with subdata. This is also useful for
	 * diff computation.
	 *
	 * @var boolean
	 */
	private $subDataAllowed = true;

	/**
	 * Maximum depth for an recursive sub data assignment
	 *
	 * @var integer
	 */
	private $subContainerMaxDepth = 3;

	/**
	 * @since 2.5
	 *
	 * @param DIWikiPage $subject
	 * @param boolean $noDuplicates stating if duplicate data should be avoided
	 */
	public function __construct( DIWikiPage $subject, $noDuplicates = true ) {
		$this->clear();
		$this->subject = $subject;
		$this->noDuplicates = $noDuplicates;
	}

	/**
	 * This object is added to the parser output of MediaWiki, but it is
	 * not useful to have all its data as part of the parser cache since
	 * the data is already stored in more accessible format in SMW. Hence
	 * this implementation of __sleep() makes sure only the subject is
	 * serialised, yielding a minimal stub data container after
	 * unserialisation. This is a little safer than serialising nothing:
	 * if, for any reason, SMW should ever access an unserialised parser
	 * output, then the Semdata container will at least look as if properly
	 * initialised (though empty).
	 *
	 * @return array
	 */
	public function __sleep() {
		return [ 'subject', 'subSemanticData' ];
	}

	/**
	 * Return subject to which the stored semantic annotations refer to.
	 *
	 * @return DIWikiPage subject
	 */
	public function getSubject() {
		return $this->subject;
	}

	/**
	 * This is used as contingency where the serialized SementicData still
	 * has an array object reference.
	 *
	 * @since 2.5
	 *
	 * @return ContainerSemanticData[]
	 */
	public function copyDataFrom( array $subSemanticData ) {
		$this->subSemanticData = $subSemanticData;
	}

	/**
	 * Return the array of subSemanticData objects in form of
	 * subobjectName => ContainerSemanticData
	 *
	 * @since 2.5
	 *
	 * @return ContainerSemanticData[]
	 */
	public function getSubSemanticData() {
		return $this->subSemanticData;
	}

	/**
	 * @since 2.5
	 */
	public function clear() {
		$this->subSemanticData = [];
	}

	/**
	 * @since 2.5
	 *
	 * @param string $subobjectName|null
	 *
	 * @return boolean
	 */
	public function hasSubSemanticData( $subobjectName = null ) {

		if ( $this->subSemanticData === [] || $subobjectName === '' ) {
			return false;
		}

		return $subobjectName !== null ? isset( $this->subSemanticData[$subobjectName] ) : true;
	}

	/**
	 * Find a particular subobject container using its name as identifier
	 *
	 * @since 2.5
	 *
	 * @param string $subobjectName
	 *
	 * @return ContainerSemanticData|null
	 */
	public function findSubSemanticData( $subobjectName ) {

		if ( $this->hasSubSemanticData( $subobjectName ) && isset( $this->subSemanticData[$subobjectName] ) ) {
			return $this->subSemanticData[$subobjectName];
		}

		return null;
	}

	/**
	 * Add data about subobjects
	 *
	 * Will only work if the data that is added is about a subobject of
	 * this SMWSemanticData's subject. Otherwise an exception is thrown.
	 * The SMWSemanticData object that is given will belong to this object
	 * after the operation; it should not be modified further by the caller.
	 *
	 * @since 2.5
	 *
	 * @param SemanticData $semanticData
	 *
	 * @throws SubSemanticDataException if not adding data about a subobject of this data
	 */
	public function addSubSemanticData( SemanticData $semanticData ) {

		if ( $semanticData->subContainerDepthCounter > $this->subContainerMaxDepth ) {
			throw new SubSemanticDataException( "Cannot add further subdata with the depth of {$semanticData->subContainerDepthCounter}. You are trying to add data beyond the max depth of {$this->subContainerMaxDepth} to an SemanticData object." );
		}

		$subobjectName = $semanticData->getSubject()->getSubobjectName();

		if ( $subobjectName == '' ) {
			throw new SubSemanticDataException( "Cannot add data that is not about a subobject." );
		}

		if ( $semanticData->getSubject()->getDBkey() !== $this->getSubject()->getDBkey() ) {
			throw new SubSemanticDataException( "Data for a subobject of {$semanticData->getSubject()->getDBkey()} cannot be added to {$this->getSubject()->getDBkey()}." );
		}

		$this->appendSubSemanticData( $semanticData, $subobjectName );
	}

	/**
	* Remove data about a subobject
	*
	* If the removed data is not about a subobject of this object,
	* it will silently be ignored (nothing to remove). Likewise,
	* removing data that is not present does not change anything.
	*
	* @since 2.5
	*
	* @param SemanticData $semanticData
	*/
	public function removeSubSemanticData( SemanticData $semanticData ) {

		if ( $semanticData->getSubject()->getDBkey() !== $this->getSubject()->getDBkey() ) {
			return;
		}

		$subobjectName = $semanticData->getSubject()->getSubobjectName();

		if ( $this->hasSubSemanticData( $subobjectName ) ) {
			$this->subSemanticData[$subobjectName]->removeDataFrom( $semanticData );

			if ( $this->subSemanticData[$subobjectName]->isEmpty() ) {
				unset( $this->subSemanticData[$subobjectName] );
			}
		}
	}

	/**
	 * Remove property and all values associated with this property.
	 *
	 * @since 2.5
	 *
	 * @param $property DIProperty
	 */
	public function removeProperty( DIProperty $property ) {

		 // Inverse properties cannot be used for an annotation
		if ( $property->isInverse() ) {
			return;
		}

		foreach ( $this->subSemanticData as $containerSemanticData ) {
			$containerSemanticData->removeProperty( $property );
		}
	}

	private function appendSubSemanticData( $semanticData, $subobjectName ) {

		if ( $this->hasSubSemanticData( $subobjectName ) ) {
			$this->subSemanticData[$subobjectName]->importDataFrom( $semanticData );

			foreach ( $semanticData->getSubSemanticData() as $containerSemanticData ) {
				$this->addSubSemanticData( $containerSemanticData );
			}

			return;
		}

		$semanticData->subContainerDepthCounter++;

		foreach ( $semanticData->getSubSemanticData() as $containerSemanticData ) {

			// Skip container that are known to be registered (avoids recursive statement extension)
			if ( $this->hasSubSemanticData( $containerSemanticData->getSubject()->getSubobjectName() ) ) {
				continue;
			}

			$this->addSubSemanticData( $containerSemanticData );
		}

		$semanticData->clearSubSemanticData();
		$this->subSemanticData[$subobjectName] = $semanticData;
	}

}