Repository: Wikidata/Wikidata-Toolkit
Pull request #427 (2019-08-06 14:58:59)

 Adds MediaInfoValueId and MediaInfoDocument


Patch:
diff --git a/wdtk-client/src/main/java/org/wikidata/wdtk/client/JsonSerializationAction.java b/wdtk-client/src/main/java/org/wikidata/wdtk/client/JsonSerializationAction.java
index e114b129..ddcd38da 100644
--- a/wdtk-client/src/main/java/org/wikidata/wdtk/client/JsonSerializationAction.java
+++ b/wdtk-client/src/main/java/org/wikidata/wdtk/client/JsonSerializationAction.java
@@ -26,6 +26,7 @@ import java.io.OutputStream;
 import org.wikidata.wdtk.datamodel.helpers.JsonSerializer;
 import org.wikidata.wdtk.datamodel.interfaces.ItemDocument;
 import org.wikidata.wdtk.datamodel.interfaces.LexemeDocument;
+import org.wikidata.wdtk.datamodel.interfaces.MediaInfoDocument;
 import org.wikidata.wdtk.datamodel.interfaces.PropertyDocument;
 
 /**
@@ -100,6 +101,11 @@ public class JsonSerializationAction extends DumpProcessingOutputAction {
 		this.serializer.processLexemeDocument(lexemeDocument);
 	}
 
+	@Override
+	public void processMediaInfoDocument(MediaInfoDocument mediaInfoDocument) {
+		this.serializer.processMediaInfoDocument(mediaInfoDocument);
+	}
+
 	@Override
 	public void close() {
 		this.serializer.close();
diff --git a/wdtk-client/src/main/java/org/wikidata/wdtk/client/RdfSerializationAction.java b/wdtk-client/src/main/java/org/wikidata/wdtk/client/RdfSerializationAction.java
index 4b9fc0e4..c54b46ed 100644
--- a/wdtk-client/src/main/java/org/wikidata/wdtk/client/RdfSerializationAction.java
+++ b/wdtk-client/src/main/java/org/wikidata/wdtk/client/RdfSerializationAction.java
@@ -34,6 +34,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.wikidata.wdtk.datamodel.interfaces.ItemDocument;
 import org.wikidata.wdtk.datamodel.interfaces.LexemeDocument;
+import org.wikidata.wdtk.datamodel.interfaces.MediaInfoDocument;
 import org.wikidata.wdtk.datamodel.interfaces.PropertyDocument;
 import org.wikidata.wdtk.rdf.PropertyRegister;
 import org.wikidata.wdtk.rdf.RdfSerializer;
@@ -196,6 +197,11 @@ public class RdfSerializationAction extends DumpProcessingOutputAction {
 		this.serializer.processLexemeDocument(lexemeDocument);
 	}
 
+	@Override
+	public void processMediaInfoDocument(MediaInfoDocument mediaInfoDocument) {
+		this.serializer.processMediaInfoDocument(mediaInfoDocument);
+	}
+
 	@Override
 	public void close() {
 		this.serializer.close();
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/Datamodel.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/Datamodel.java
index 28908749..d2e40372 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/Datamodel.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/Datamodel.java
@@ -39,7 +39,12 @@ public class Datamodel {
 	/**
 	 * The site IRI of Wikidata.
 	 */
-	static public String SITE_WIKIDATA = "http://www.wikidata.org/entity/";
+	static final public String SITE_WIKIDATA = "http://www.wikidata.org/entity/";
+
+	/**
+	 * The site IRI of Wikimedia Commons.
+	 */
+	static final public String SITE_WIKIMEDIA_COMMONS = "http://commons.wikimedia.org/entity/";
 
 	private final static DataObjectFactory factory = new DataObjectFactoryImpl();
 
@@ -183,6 +188,34 @@ public class Datamodel {
 		return factory.getSenseIdValue(id, SITE_WIKIDATA);
 	}
 
+	/**
+	 * Creates an {@link MediaInfoIdValue}.
+	 *
+	 * @param id
+	 *            a string of the form Mn... where n... is the string
+	 *            representation of a positive integer number
+	 * @param siteIri
+	 *            IRI to identify the site, usually the first part of the entity
+	 *            IRI of the site this belongs to, e.g.,
+	 *            "http://www.wikidata.org/entity/"
+	 * @return an {@link MediaInfoIdValue} corresponding to the input
+	 */
+	public static MediaInfoIdValue makeMediaInfoIdValue(String id, String siteIri) {
+		return factory.getMediaInfoIdValue(id, siteIri);
+	}
+
+	/**
+	 * Creates an {@link MediaInfoIdValue} for Wikimedia Commons.
+	 *
+	 * @param id
+	 *            a string of the form Mn... where n... is the string
+	 *            representation of a positive integer number
+	 * @return an {@link MediaInfoIdValue} corresponding to the input
+	 */
+	public static MediaInfoIdValue makeWikimediaCommonsMediaInfoIdValue(String id) {
+		return factory.getMediaInfoIdValue(id, SITE_WIKIMEDIA_COMMONS);
+	}
+
 	/**
 	 * Creates a {@link DatatypeIdValue}. The datatype IRI is usually one of the
 	 * constants defined in {@link DatatypeIdValue}, but this is not enforced,
@@ -810,4 +843,22 @@ public class Datamodel {
 		return factory.getSenseDocument(senseIdValue, glosses, statementGroups, 0);
 	}
 
+	/**
+	 * Creates an {@link MediaInfoDocument}.
+	 *
+	 * @param mediaInfoIdValue
+	 *            the id of the media that data is about
+	 * @param labels
+	 *            the list of labels of this media, with at most one label for
+	 *            each language code
+	 * @param statementGroups
+	 *            the list of statement groups of this media info; all of them must
+	 *            have the given mediaInfoIdValue as their subject
+	 * @return an {@link MediaInfoDocument} corresponding to the input
+	 */
+	public static MediaInfoDocument makeMediaInfoDocument(MediaInfoIdValue mediaInfoIdValue,
+												List<MonolingualTextValue> labels,
+												List<StatementGroup> statementGroups) {
+		return factory.getMediaInfoDocument(mediaInfoIdValue, labels, statementGroups, 0);
+	}
 }
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/DatamodelFilter.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/DatamodelFilter.java
index 47cf4b67..3d6757db 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/DatamodelFilter.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/DatamodelFilter.java
@@ -25,7 +25,7 @@ import org.wikidata.wdtk.datamodel.interfaces.*;
 import java.util.*;
 
 /**
- * This is a utility class that allows to filter {@link ItemDocument} and {@link PropertyDocument}
+ * This is a utility class that allows to filter {@link EntityDocument}
  * using the data declared in a {@link DocumentDataFilter}.
  */
 public class DatamodelFilter {
@@ -63,6 +63,15 @@ public class DatamodelFilter {
 		);
 	}
 
+	public MediaInfoDocument filter(MediaInfoDocument mediaInfo) {
+		return dataObjectFactory.getMediaInfoDocument(
+				mediaInfo.getEntityId(),
+				filterMonoLingualTextValues(mediaInfo.getLabels().values()),
+				filterStatementGroups(mediaInfo.getStatementGroups()),
+				mediaInfo.getRevisionId()
+		);
+	}
+
 	public LexemeDocument filter(LexemeDocument lexeme) {
 		return dataObjectFactory.getLexemeDocument(
 				lexeme.getEntityId(),
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/Equality.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/Equality.java
index cd823e9a..61687791 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/Equality.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/Equality.java
@@ -585,6 +585,31 @@ public class Equality {
 				&& (o1.getRevisionId() == other.getRevisionId());
 	}
 
+	/**
+	 * Returns true if the parameters are two {@link MediaInfoDocument} objects with
+	 * exactly the same data. It does not matter if they are different
+	 * implementations of the interface as long as their content is the same.
+	 *
+	 * @param o1
+	 *            the first object to compare
+	 * @param o2
+	 *            the second object to compare
+	 * @return true if both objects are equal
+	 */
+	public static boolean equalsMediaInfoDocument(MediaInfoDocument o1, Object o2) {
+		if (o2 == o1) {
+			return true;
+		}
+		if (!(o2 instanceof MediaInfoDocument)) {
+			return false;
+		}
+		MediaInfoDocument other = (MediaInfoDocument) o2;
+		return o1.getEntityId().equals(other.getEntityId())
+				&& o1.getLabels().equals(other.getLabels())
+				&& o1.getStatementGroups().equals(other.getStatementGroups())
+				&& (o1.getRevisionId() == other.getRevisionId());
+	}
+
 	private static boolean equalsTermedDocument(TermedDocument o1, TermedDocument other) {
 		return o1.getEntityId().equals(other.getEntityId())
 				&& o1.getAliases().equals(other.getAliases())
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/Hash.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/Hash.java
index 72ff5c71..2c619db7 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/Hash.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/Hash.java
@@ -369,6 +369,21 @@ public class Hash {
 		return result;
 	}
 
+	/**
+	 * Returns a hash code for the given object.
+	 *
+	 * @see java.lang.Object#hashCode()
+	 * @param o
+	 *            the object to create a hash for
+	 * @return the hash code of the object
+	 */
+	public static int hashCode(MediaInfoDocument o) {
+		int result;
+		result = o.getLabels().hashCode();
+		result = prime * result + o.getStatementGroups().hashCode();
+		return result;
+	}
+
 	/**
 	 * Returns a hash code for the given object.
 	 *
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/JsonSerializer.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/JsonSerializer.java
index ac672657..1749978b 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/JsonSerializer.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/JsonSerializer.java
@@ -22,15 +22,11 @@ package org.wikidata.wdtk.datamodel.helpers;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 
-import org.apache.commons.compress.utils.Charsets;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.wikidata.wdtk.datamodel.interfaces.EntityDocument;
-import org.wikidata.wdtk.datamodel.interfaces.EntityDocumentDumpProcessor;
-import org.wikidata.wdtk.datamodel.interfaces.ItemDocument;
-import org.wikidata.wdtk.datamodel.interfaces.PropertyDocument;
-import org.wikidata.wdtk.datamodel.interfaces.Statement;
+import org.wikidata.wdtk.datamodel.interfaces.*;
 
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -54,9 +50,9 @@ public class JsonSerializer implements EntityDocumentDumpProcessor {
 
 	private static final Logger logger = LoggerFactory.getLogger(JsonSerializer.class);
 
-	private static final byte[] JSON_START_LIST = "[\n".getBytes(Charsets.UTF_8);
-	private static final byte[] JSON_SEP = ",\n".getBytes(Charsets.UTF_8);
-	private static final byte[] JSON_END_LIST = "\n]".getBytes(Charsets.UTF_8);
+	private static final byte[] JSON_START_LIST = "[\n".getBytes(StandardCharsets.UTF_8);
+	private static final byte[] JSON_SEP = ",\n".getBytes(StandardCharsets.UTF_8);
+	private static final byte[] JSON_END_LIST = "\n]".getBytes(StandardCharsets.UTF_8);
 
 	/**
 	 * The stream that the resulting JSON is written to.
@@ -109,6 +105,16 @@ public class JsonSerializer implements EntityDocumentDumpProcessor {
 		serializeEntityDocument(propertyDocument);
 	}
 
+	@Override
+	public void processLexemeDocument(LexemeDocument lexemeDocument) {
+		serializeEntityDocument(lexemeDocument);
+	}
+
+	@Override
+	public void processMediaInfoDocument(MediaInfoDocument mediaInfoDocument) {
+		serializeEntityDocument(mediaInfoDocument);
+	}
+
 	@Override
 	public void close() {
 		try {
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/ToString.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/ToString.java
index efc4a934..9d3ae132 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/ToString.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/helpers/ToString.java
@@ -103,6 +103,17 @@ public class ToString {
 	public static String toString(SenseIdValue o) {
 		return o.getIri() + " (sense)";
 	}
+	/**
+	 * Returns a human-readable string representation of the given object.
+	 *
+	 * @see java.lang.Object#toString()
+	 * @param o
+	 *            the object to represent as string
+	 * @return a string representation of the object
+	 */
+	public static String toString(MediaInfoIdValue o) {
+		return o.getIri() + " (media-info)";
+	}
 
 	/**
 	 * Returns a human-readable string representation of the given object.
@@ -537,6 +548,25 @@ public class ToString {
 		return sb.toString();
 	}
 
+	/**
+	 * Returns a human-readable string representation of the given object.
+	 *
+	 * @see java.lang.Object#toString()
+	 * @param o
+	 *            the object to represent as string
+	 * @return a string representation of the object
+	 */
+	public static String toString(MediaInfoDocument o) {
+		StringBuilder sb = new StringBuilder();
+		boolean first;
+		sb.append("==MediaInfoDocument ").append(o.getEntityId().getIri());
+		sb.append(" (r").append(o.getRevisionId()).append(") ");
+		sb.append("==");
+		sb.append(toStringForLabeledDocument(o));
+		sb.append(toStringForStatementDocument(o));
+		return sb.toString();
+	}
+
 	protected static String toStringForStatementDocument(StatementDocument o) {
 		StringBuilder sb = new StringBuilder();
 
@@ -562,18 +592,7 @@ public class ToString {
 		StringBuilder sb = new StringBuilder();
 		boolean first;
 
-		sb.append("\n* Labels: ");
-		first = true;
-		SortedSet<String> labelKeys = new TreeSet<>(o.getLabels()
-				.keySet());
-		for (String key : labelKeys) {
-			if (first) {
-				first = false;
-			} else {
-				sb.append("; ");
-			}
-			sb.append(toString(o.getLabels().get(key)));
-		}
+		sb.append(toStringForLabeledDocument(o));
 
 		sb.append("\n* Descriptions: ");
 		first = true;
@@ -606,6 +625,33 @@ public class ToString {
 		return sb.toString();
 	}
 
+	/**
+	 * Returns a human-readable string representation of the given
+	 * {@link LabeledDocument}.
+	 *
+	 * @see java.lang.Object#toString()
+	 * @param o
+	 *            the object to represent as string
+	 * @return a string representation of the object
+	 */
+	private static String toStringForLabeledDocument(LabeledDocument o) {
+		StringBuilder sb = new StringBuilder();
+		boolean first;
+		sb.append("\n* Labels: ");
+		first = true;
+		SortedSet<String> labelKeys = new TreeSet<>(o.getLabels()
+				.keySet());
+		for (String key : labelKeys) {
+			if (first) {
+				first = false;
+			} else {
+				sb.append("; ");
+			}
+			sb.append(toString(o.getLabels().get(key)));
+		}
+		return sb.toString();
+	}
+
 	/**
 	 * Returns a human-readable string representation of a reference to a globe
 	 * on which coordinates may be present. Known globes (such as Earth) are
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/DataObjectFactoryImpl.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/DataObjectFactoryImpl.java
index 7792aafc..268bf1bc 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/DataObjectFactoryImpl.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/DataObjectFactoryImpl.java
@@ -63,6 +63,11 @@ public class DataObjectFactoryImpl implements DataObjectFactory {
 		return new SenseIdValueImpl(id, siteIri);
 	}
 
+	@Override
+	public MediaInfoIdValue getMediaInfoIdValue(String id, String siteIri) {
+		return new MediaInfoIdValueImpl(id, siteIri);
+	}
+
 	@Override
 	public DatatypeIdValue getDatatypeIdValue(String id) {
 		return new DatatypeIdImpl(id);
@@ -253,4 +258,15 @@ public class DataObjectFactoryImpl implements DataObjectFactory {
 										long revisionId) {
 		return new SenseDocumentImpl(senseIdValue, glosses, statementGroups, revisionId);
 	}
+
+
+	@Override
+	public MediaInfoDocument getMediaInfoDocument(MediaInfoIdValue mediaInfoIdValue,
+										List<MonolingualTextValue> labels,
+										List<StatementGroup> statementGroups,
+										long revisionId) {
+
+		return new MediaInfoDocumentImpl(
+				mediaInfoIdValue, labels, statementGroups, revisionId);
+	}
 }
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/EntityDocumentImpl.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/EntityDocumentImpl.java
index 95d3d5b6..9152f3ab 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/EntityDocumentImpl.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/EntityDocumentImpl.java
@@ -26,6 +26,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
 import org.apache.commons.lang3.Validate;
 import org.wikidata.wdtk.datamodel.interfaces.EntityDocument;
 import org.wikidata.wdtk.datamodel.interfaces.EntityIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.MediaInfoDocument;
 
 /**
  * Abstract Jackson implementation of {@link EntityDocument}. Like all Jackson
@@ -43,6 +44,7 @@ import org.wikidata.wdtk.datamodel.interfaces.EntityIdValue;
 		@Type(value = LexemeDocumentImpl.class, name = EntityDocumentImpl.JSON_TYPE_LEXEME),
 		@Type(value = FormDocumentImpl.class, name = EntityDocumentImpl.JSON_TYPE_FORM),
 		@Type(value = SenseDocumentImpl.class, name = EntityDocumentImpl.JSON_TYPE_SENSE),
+		@Type(value = MediaInfoDocumentImpl.class, name = EntityDocumentImpl.JSON_TYPE_MEDIA_INFO),
 		@Type(value = PropertyDocumentImpl.class, name = EntityDocumentImpl.JSON_TYPE_PROPERTY) })
 public abstract class EntityDocumentImpl implements EntityDocument {
 
@@ -66,6 +68,10 @@ public abstract class EntityDocumentImpl implements EntityDocument {
 	 * String used to refer to forms in JSON.
 	 */
 	static final String JSON_TYPE_SENSE = "sense";
+	/**
+	 * String used to refer to forms in JSON.
+	 */
+	static final String JSON_TYPE_MEDIA_INFO = "mediainfo";
 
 	/**
 	 * The id of the entity that the document refers to. This is not mapped to
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/EntityIdValueImpl.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/EntityIdValueImpl.java
index 05d8ccf5..af5ed980 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/EntityIdValueImpl.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/EntityIdValueImpl.java
@@ -62,6 +62,11 @@ public abstract class EntityIdValueImpl extends ValueImpl implements
 	 * lexemes senses.
 	 */
 	public final static String JSON_ENTITY_TYPE_SENSE = "sense";
+	/**
+	 * The string used in JSON to denote the type of entity id values that are
+	 * media info.
+	 */
+	public final static String JSON_ENTITY_TYPE_MEDIA_INFO = "mediainfo";
 
 	/**
 	 * The site IRI that this value refers to. This data not part of the JSON
@@ -126,6 +131,8 @@ public abstract class EntityIdValueImpl extends ValueImpl implements
 				return new FormIdValueImpl(id, siteIri);
 			case EntityIdValueImpl.JSON_ENTITY_TYPE_SENSE:
 				return new SenseIdValueImpl(id, siteIri);
+				case EntityIdValueImpl.JSON_ENTITY_TYPE_MEDIA_INFO:
+				return new MediaInfoIdValueImpl(id, siteIri);
 			default:
 				throw new IllegalArgumentException("Entity id \"" + id + "\" is not supported.");
 		}
@@ -152,6 +159,8 @@ public abstract class EntityIdValueImpl extends ValueImpl implements
 				} else {
 					return JSON_ENTITY_TYPE_LEXEME;
 				}
+			case 'M':
+				return JSON_ENTITY_TYPE_MEDIA_INFO;
 			case 'P':
 				return JSON_ENTITY_TYPE_PROPERTY;
 			case 'Q':
@@ -310,6 +319,8 @@ public abstract class EntityIdValueImpl extends ValueImpl implements
 					return  "L" + numericId;
 				case JSON_ENTITY_TYPE_PROPERTY:
 					return "P" + numericId;
+				case JSON_ENTITY_TYPE_MEDIA_INFO:
+					return "M" + numericId;
 				default:
 					throw new IllegalArgumentException("Entities of type \""
 							+ entityType + "\" are not supported in property values.");
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/LabeledStatementDocumentImpl.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/LabeledStatementDocumentImpl.java
new file mode 100644
index 00000000..bc029cb8
--- /dev/null
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/LabeledStatementDocumentImpl.java
@@ -0,0 +1,124 @@
+package org.wikidata.wdtk.datamodel.implementation;
+
+/*
+ * #%L
+ * Wikidata Toolkit Data Model
+ * %%
+ * Copyright (C) 2014 Wikidata Toolkit Developers
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import com.fasterxml.jackson.annotation.JacksonInject;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.wikidata.wdtk.datamodel.interfaces.*;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Abstract Jackson implementation of {@link LabeledDocument} and {@link StatementDocument}.
+ * You should not rely on it directly but build instances with the Datamodel helper and
+ * use {@link EntityDocumentImpl} for deserialization.
+ *
+ * @author Fredo Erxleben
+ * @author Antonin Delpeuch
+ * @author Thomas Pellissier Tanon
+ *
+ */
+abstract class LabeledStatementDocumentImpl extends StatementDocumentImpl implements LabeledStatementDocument {
+
+	protected final Map<String, MonolingualTextValue> labels;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param id
+	 * 		the identifier of the subject of this document
+	 * @param labels
+	 * 		the labels for this entity, at most one per language
+	 * @param claims
+	 * 		the statement groups contained in this document
+	 * @param revisionId
+	 * 		the id of the last revision of this document
+	 */
+	public LabeledStatementDocumentImpl(
+			EntityIdValue id,
+			List<MonolingualTextValue> labels,
+			List<StatementGroup> claims,
+			long revisionId) {
+		super(id, claims, revisionId);
+		this.labels = (labels == null) ? Collections.emptyMap() : constructTermMap(labels);
+	}
+
+	/**
+	 * Constructor used for JSON deserialization with Jackson.
+	 */
+	LabeledStatementDocumentImpl(
+			@JsonProperty("id") String jsonId,
+			@JsonProperty("labels") Map<String, MonolingualTextValue> labels,
+			@JsonProperty("claims") Map<String, List<StatementImpl.PreStatement>> claims,
+			@JsonProperty("lastrevid") long revisionId,
+			@JacksonInject("siteIri") String siteIri) {
+		super(jsonId, claims, revisionId, siteIri);
+		this.labels = (labels == null) ? Collections.emptyMap() : labels;
+	}
+
+	/**
+	 * Protected constructor provided to ease the creation
+	 * of copies. No check is made and each field is reused without
+	 * copying.
+	 *
+	 * @param labels
+	 * 		a map from language codes to monolingual values with
+	 * 	    the same language codes
+	 * @param claims
+	 * @param revisionId
+	 */
+	protected LabeledStatementDocumentImpl(
+			EntityIdValue subject,
+			Map<String, MonolingualTextValue> labels,
+			Map<String, List<Statement>> claims,
+			long revisionId) {
+		super(subject, claims, revisionId);
+		this.labels = labels;
+	}
+
+	@JsonProperty("labels")
+	@Override
+	public Map<String, MonolingualTextValue> getLabels() {
+		return Collections.unmodifiableMap(this.labels);
+	}
+
+	protected static Map<String, MonolingualTextValue> constructTermMap(List<MonolingualTextValue> terms) {
+		Map<String, MonolingualTextValue> map = new HashMap<>();
+		for(MonolingualTextValue term : terms) {
+			String language = term.getLanguageCode();
+			if(map.containsKey(language)) {
+				throw new IllegalArgumentException("Multiple terms provided for the same language.");
+			}
+			map.put(language, toTerm(term));
+		}
+		return map;
+	}
+
+	/**
+	 * We need to make sure the terms are of the right type, otherwise they will not be serialized correctly.
+	 */
+	private static MonolingualTextValue toTerm(MonolingualTextValue term) {
+		return term instanceof TermImpl ? term : new TermImpl(term.getLanguageCode(), term.getText());
+	}
+}
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImpl.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImpl.java
new file mode 100644
index 00000000..16498889
--- /dev/null
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImpl.java
@@ -0,0 +1,149 @@
+package org.wikidata.wdtk.datamodel.implementation;
+
+/*
+ * #%L
+ * Wikidata Toolkit Data Model
+ * %%
+ * Copyright (C) 2014 Wikidata Toolkit Developers
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.wikidata.wdtk.datamodel.helpers.Equality;
+import org.wikidata.wdtk.datamodel.helpers.Hash;
+import org.wikidata.wdtk.datamodel.helpers.ToString;
+import org.wikidata.wdtk.datamodel.interfaces.*;
+
+import java.util.*;
+
+
+/**
+ * Jackson implementation of {@link MediaInfoDocument}.
+ *
+ * @author Thomas Pellissier Tanon
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class MediaInfoDocumentImpl extends LabeledStatementDocumentImpl implements MediaInfoDocument {
+
+	/**
+	 * Constructor.
+	 *
+	 * @param id
+	 *            the id of the media that data is about
+	 * @param labels
+	 *            the list of captions of this media, with at most one label for
+	 *            each language code
+	 * @param statements
+	 *            the list of statement groups of this media info; all of them must
+	 *            have the given itemIdValue as their subject
+	 * @param revisionId
+	 *            the revision ID or 0 if not known; see
+	 *            {@link EntityDocument#getRevisionId()}
+	 */
+	public MediaInfoDocumentImpl(
+			MediaInfoIdValue id,
+			List<MonolingualTextValue> labels,
+			List<StatementGroup> statements,
+			long revisionId) {
+		super(id, labels, statements, revisionId);
+	}
+
+	/**
+	 * Constructor. Creates an object that can be populated during JSON
+	 * deserialization. Should only be used by Jackson for this very purpose.
+	 *
+	 * The claims parameter is here in case the Structured Data on Commons dev team
+	 * moves back from "statements" to "claims" or someone wrongly use the "claims" key.
+	 */
+	@JsonCreator
+	public MediaInfoDocumentImpl(
+			@JsonProperty("id") String jsonId,
+			@JsonProperty("labels") @JsonDeserialize(contentAs=TermImpl.class) Map<String, MonolingualTextValue> labels,
+			@JsonProperty("claims") Map<String, List<StatementImpl.PreStatement>> claims,
+			@JsonProperty("statements") Map<String, List<StatementImpl.PreStatement>> statements,
+			@JsonProperty("lastrevid") long revisionId,
+			@JacksonInject("siteIri") String siteIri) {
+		super(jsonId, labels, (statements == null) ? claims : statements, revisionId, siteIri);
+	}
+
+	/**
+	 * Protected constructor, meant to be used to create modified copies
+	 * of instances.
+	 */
+	protected MediaInfoDocumentImpl(
+			MediaInfoIdValue subject,
+			Map<String, MonolingualTextValue> labels,
+			Map<String, List<Statement>> claims,
+			long revisionId) {
+		super(subject, labels, claims, revisionId);
+	}
+
+	@JsonIgnore
+	@Override
+	public MediaInfoIdValue getEntityId() {
+		return new MediaInfoIdValueImpl(this.entityId, this.siteIri);
+	}
+
+	@JsonProperty("type")
+	String getType() {
+		return EntityDocumentImpl.JSON_TYPE_MEDIA_INFO;
+	}
+
+	@JsonProperty("statements")
+	@Override
+	public Map<String, List<Statement>> getJsonClaims() {
+		return super.getJsonClaims();
+	}
+
+	@Override
+	public int hashCode() {
+		return Hash.hashCode(this);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		return Equality.equalsMediaInfoDocument(this, obj);
+	}
+
+	@Override
+	public String toString() {
+		return ToString.toString(this);
+	}
+
+	@Override
+	public MediaInfoDocument withRevisionId(long newRevisionId) {
+		return new MediaInfoDocumentImpl(getEntityId(), labels, claims, newRevisionId);
+	}
+
+	@Override
+	public MediaInfoDocument withLabel(MonolingualTextValue newLabel) {
+		Map<String, MonolingualTextValue> newLabels = new HashMap<>(labels);
+		newLabels.put(newLabel.getLanguageCode(), newLabel);
+		return new MediaInfoDocumentImpl(getEntityId(), newLabels, claims, revisionId);
+	}
+
+	@Override
+	public MediaInfoDocument withStatement(Statement statement) {
+		Map<String, List<Statement>> newGroups = addStatementToGroups(statement, claims);
+		return new MediaInfoDocumentImpl(getEntityId(), labels, newGroups, revisionId);
+	}
+
+	@Override
+	public MediaInfoDocument withoutStatementIds(Set<String> statementIds) {
+		Map<String, List<Statement>> newGroups = removeStatements(statementIds, claims);
+		return new MediaInfoDocumentImpl(getEntityId(), labels, newGroups, revisionId);
+	}
+}
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoIdValueImpl.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoIdValueImpl.java
new file mode 100644
index 00000000..58bf2fdf
--- /dev/null
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoIdValueImpl.java
@@ -0,0 +1,97 @@
+package org.wikidata.wdtk.datamodel.implementation;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.wikidata.wdtk.datamodel.helpers.Equality;
+import org.wikidata.wdtk.datamodel.helpers.Hash;
+import org.wikidata.wdtk.datamodel.helpers.ToString;
+import org.wikidata.wdtk.datamodel.interfaces.EntityIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.MediaInfoIdValue;
+import org.wikidata.wdtk.datamodel.interfaces.ValueVisitor;
+
+/*
+ * #%L
+ * Wikidata Toolkit Data Model
+ * %%
+ * Copyright (C) 2014 Wikidata Toolkit Developers
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+/**
+ * Jackson implementation of {@link MediaInfoIdValue}.
+ *
+ * @author Thomas Pellissier Tanon
+ *
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonDeserialize()
+public class MediaInfoIdValueImpl extends EntityIdValueImpl implements MediaInfoIdValue {
+
+	/**
+	 * Constructor.
+	 *
+	 * @param id
+	 * 		the identifier of the entity, such as "M42"
+	 * @param siteIri
+	 *      the siteIRI that this value refers to
+	 */
+	MediaInfoIdValueImpl(
+			String id,
+			String siteIri) {
+		super(id, siteIri);
+		assertHasJsonEntityType(JSON_ENTITY_TYPE_MEDIA_INFO);
+	}
+	/**
+	 * Constructor used for deserialization with Jackson.
+	 *
+	 * @param value
+	 *     the inner JSON object deserialized as a {@link JacksonInnerEntityId}
+	 * @param siteIri
+	 *     the siteIRI that this value refers to.
+	 */
+	@JsonCreator
+	MediaInfoIdValueImpl(
+			@JsonProperty("value") JacksonInnerEntityId value,
+			@JacksonInject("siteIri") String siteIri) {
+		super(value, siteIri);
+		assertHasJsonEntityType(JSON_ENTITY_TYPE_MEDIA_INFO);
+	}
+
+	@JsonIgnore
+	@Override
+	public String getEntityType() {
+		return EntityIdValue.ET_MEDIA_INFO;
+	}
+
+	@Override
+	public <T> T accept(ValueVisitor<T> valueVisitor) {
+		return valueVisitor.visit(this);
+	}
+
+	@Override
+	public int hashCode() {
+		return Hash.hashCode(this);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		return Equality.equalsEntityIdValue(this, obj);
+	}
+
+	@Override
+	public String toString() {
+		return ToString.toString(this);
+	}
+}
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/TermedStatementDocumentImpl.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/TermedStatementDocumentImpl.java
index 4f57f0f3..0c17e711 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/TermedStatementDocumentImpl.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/TermedStatementDocumentImpl.java
@@ -46,14 +46,13 @@ import java.util.Map.Entry;
 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
 @JsonSubTypes({ //TODO: drop in future release
 		@Type(value = ItemDocumentImpl.class, name = EntityDocumentImpl.JSON_TYPE_ITEM),
-		@Type(value = PropertyDocumentImpl.class, name = EntityDocumentImpl.JSON_TYPE_PROPERTY) })
-public abstract class TermedStatementDocumentImpl extends StatementDocumentImpl implements TermedStatementDocument {
+		@Type(value = PropertyDocumentImpl.class, name = EntityDocumentImpl.JSON_TYPE_PROPERTY),
+		@Type(value = MediaInfoDocumentImpl.class, name = EntityDocumentImpl.JSON_TYPE_MEDIA_INFO) })
+public abstract class TermedStatementDocumentImpl extends LabeledStatementDocumentImpl implements TermedStatementDocument {
 
-	protected final Map<String, List<MonolingualTextValue>> aliases;
-	
-	protected final Map<String, MonolingualTextValue> labels;
 	protected final Map<String, MonolingualTextValue> descriptions;
-	
+	protected final Map<String, List<MonolingualTextValue>> aliases;
+
 	/**
 	 * Constructor.
 	 * 
@@ -78,12 +77,7 @@ public abstract class TermedStatementDocumentImpl extends StatementDocumentImpl
 			List<MonolingualTextValue> aliases,
 			List<StatementGroup> claims,
 			long revisionId) {
-		super(id, claims, revisionId);
-		if (labels != null) {
-			this.labels = constructTermMap(labels);
-		} else {
-			this.labels = Collections.emptyMap();
-		}
+		super(id, labels, claims, revisionId);
 		if (descriptions != null) {
 			this.descriptions = constructTermMap(descriptions);
 		} else {
@@ -107,12 +101,7 @@ public abstract class TermedStatementDocumentImpl extends StatementDocumentImpl
 			@JsonProperty("claims") Map<String, List<StatementImpl.PreStatement>> claims,
 			@JsonProperty("lastrevid") long revisionId,
 			@JacksonInject("siteIri") String siteIri) {
-		super(jsonId, claims, revisionId, siteIri);
-		if (labels != null) {
-			this.labels = labels;
-		} else {
-			this.labels = Collections.emptyMap();
-		}
+		super(jsonId, labels, claims, revisionId, siteIri);
 		if (descriptions != null) {
 			this.descriptions = descriptions;
 		} else {
@@ -149,8 +138,7 @@ public abstract class TermedStatementDocumentImpl extends StatementDocumentImpl
 			Map<String, List<MonolingualTextValue>> aliases,
 			Map<String, List<Statement>> claims,
 			long revisionId) {
-		super(subject, claims, revisionId);
-		this.labels = labels;
+		super(subject, labels, claims, revisionId);
 		this.descriptions = descriptions;
 		this.aliases = aliases;
 	}
@@ -178,29 +166,11 @@ public abstract class TermedStatementDocumentImpl extends StatementDocumentImpl
 		return Collections.unmodifiableMap(this.descriptions);
 	}
 
-	@JsonProperty("labels")
-	@Override
-	public Map<String, MonolingualTextValue> getLabels() {
-		return Collections.unmodifiableMap(this.labels);
-	}
-
 	@JsonIgnore
 	public String getSiteIri() {
 		return this.siteIri;
 	}
 	
-	protected static Map<String, MonolingualTextValue> constructTermMap(List<MonolingualTextValue> terms) {
-		Map<String, MonolingualTextValue> map = new HashMap<>();
-		for(MonolingualTextValue term : terms) {
-			String language = term.getLanguageCode();
-			if(map.containsKey(language)) {
-				throw new IllegalArgumentException("Multiple terms provided for the same language.");
-			}
-			map.put(language, toTerm(term));
-		}
-		return map;
-	}
-	
 	private static Map<String, List<MonolingualTextValue>> constructTermListMap(List<MonolingualTextValue> terms) {
 		Map<String, List<MonolingualTextValue>> map = new HashMap<>();
 		for(MonolingualTextValue term : terms) {
@@ -263,7 +233,4 @@ public abstract class TermedStatementDocumentImpl extends StatementDocumentImpl
 
 		}
 	}
-	
-	@Override
-	abstract public TermedStatementDocument withRevisionId(long newRevisionId);
 }
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/ValueImpl.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/ValueImpl.java
index c076a10c..62b4fa52 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/ValueImpl.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/implementation/ValueImpl.java
@@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
  * #L%
  */
 
+import org.wikidata.wdtk.datamodel.interfaces.MediaInfoIdValue;
 import org.wikidata.wdtk.datamodel.interfaces.Value;
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -184,6 +185,8 @@ public abstract class ValueImpl implements Value {
 					return FormIdValueImpl.class;
 				case EntityIdValueImpl.JSON_ENTITY_TYPE_SENSE:
 					return SenseIdValueImpl.class;
+				case EntityIdValueImpl.JSON_ENTITY_TYPE_MEDIA_INFO:
+					return MediaInfoIdValueImpl.class;
 				default:
 					throw new IllegalArgumentException("Entities of type \"" + entityType + "\" are not supported.");
 			}
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/DataObjectFactory.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/DataObjectFactory.java
index 42cb2e39..d71d68dc 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/DataObjectFactory.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/DataObjectFactory.java
@@ -108,6 +108,20 @@ public interface DataObjectFactory {
 	 */
 	SenseIdValue getSenseIdValue(String id, String siteIri);
 
+	/**
+	 * Creates a {@link MediaInfoIdValue}.
+	 *
+	 * @param id
+	 *            a string of the form Mn... where n... is the string
+	 *            representation of a positive integer number
+	 * @param siteIri
+	 *            IRI to identify the site, usually the first part of the entity
+	 *            IRI of the site this belongs to, e.g.,
+	 *            "http://www.wikidata.org/entity/"
+	 * @return a {@link MediaInfoIdValue} corresponding to the input
+	 */
+	MediaInfoIdValue getMediaInfoIdValue(String id, String siteIri);
+
 	/**
 	 * Creates a {@link DatatypeIdValue}. The datatype IRI is usually one of the
 	 * constants defined in {@link DatatypeIdValue}, but this is not enforced,
@@ -496,7 +510,7 @@ public interface DataObjectFactory {
 			long revisionId);
 
 	/**
-	 * Creates an {@link SenseDocument}.
+	 * Creates a {@link SenseDocument}.
 	 *
 	 * @param senseIdValue
 	 *            the id of the form that data is about
@@ -516,4 +530,25 @@ public interface DataObjectFactory {
 								 List<StatementGroup> statementGroups,
 								 long revisionId);
 
+	/**
+	 * Creates a {@link MediaInfoDocument}.
+	 *
+	 * @param mediaInfoIdValue
+	 *            the id of the form that data is about
+	 * @param labels
+	 *            the list of labels of this media info, with at most one label for
+	 *            each language code
+	 * @param statementGroups
+	 *            the list of statement groups of this lexeme; all of them must
+	 *            have the given mediaInfoIdValue as their subject
+	 * @param revisionId
+	 *            the revision ID or 0 if not known; see
+	 *            {@link EntityDocument#getRevisionId()}
+	 * @return a {@link MediaInfoDocument} corresponding to the input
+	 */
+	MediaInfoDocument getMediaInfoDocument(MediaInfoIdValue mediaInfoIdValue,
+										   List<MonolingualTextValue> labels,
+										   List<StatementGroup> statementGroups,
+										   long revisionId);
+
 }
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/DatatypeIdValue.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/DatatypeIdValue.java
index 18df468a..aa504343 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/DatatypeIdValue.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/DatatypeIdValue.java
@@ -49,6 +49,10 @@ public interface DatatypeIdValue {
 	 * IRI of the sense datatype in Wikibase.
 	 */
 	String DT_SENSE = "http://wikiba.se/ontology#WikibaseSense";
+	/**
+	 * IRI of the media info datatype in Wikibase.
+	 */
+	String DT_MEDIA_INFO = "http://wikiba.se/ontology#WikibaseMediaInfo";
 	/**
 	 * IRI of the string datatype in Wikibase.
 	 */
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/EntityDocumentProcessor.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/EntityDocumentProcessor.java
index 048d7d9d..81c1ec01 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/EntityDocumentProcessor.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/EntityDocumentProcessor.java
@@ -57,4 +57,12 @@ public interface EntityDocumentProcessor {
 	default void processLexemeDocument(LexemeDocument lexemeDocument) {
 	}
 
+	/**
+	 * Processes the given MediaInfoDocument.
+	 *
+	 * @param mediaInfoDocument
+	 *            the MediaInfoDocument
+	 */
+	default void processMediaInfoDocument(MediaInfoDocument mediaInfoDocument) {
+	}
 }
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/EntityIdValue.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/EntityIdValue.java
index 67332110..08eb37bf 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/EntityIdValue.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/EntityIdValue.java
@@ -64,6 +64,10 @@ public interface EntityIdValue extends IriIdentifiedValue {
 	 * IRI of the type of an entity that is a sense.
 	 */
 	String ET_SENSE = "http://www.wikidata.org/ontology#Sense";
+	/**
+	 * IRI of the type of an entity that is a media info.
+	 */
+	String ET_MEDIA_INFO = "http://www.wikidata.org/ontology#MediaInfo";
 	/**
 	 * IRI of the type of an unsupported entity, when no type could be
 	 * detected from the JSON representation. The IRI for ids associated
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/LabeledDocument.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/LabeledDocument.java
new file mode 100644
index 00000000..fad4efb7
--- /dev/null
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/LabeledDocument.java
@@ -0,0 +1,64 @@
+package org.wikidata.wdtk.datamodel.interfaces;
+
+/*
+ * #%L
+ * Wikidata Toolkit Data Model
+ * %%
+ * Copyright (C) 2014 Wikidata Toolkit Developers
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import java.util.Map;
+
+/**
+ * Interface for EntityDocuments that can be described by labels
+ * in several languages.
+ *
+ * @author Thomas Pellissier Tanon
+ */
+public interface LabeledDocument extends EntityDocument {
+	/**
+	 * Return a Map from Wikibase language codes to labels.
+	 *
+	 * @return the map of labels
+	 */
+	Map<String, MonolingualTextValue> getLabels();
+
+	/**
+	 * Returns the string label for the given language code, or null if there is
+	 * no label for this code. This is a convenience method for accessing the
+	 * data that can be obtained via {@link #getLabels()}.
+	 *
+	 * @param languageCode
+	 *            a string that represents language
+	 * @return the label string or null if it does not exists
+	 */
+	default String findLabel(String languageCode) {
+		MonolingualTextValue value = this.getLabels().get(languageCode);
+		return (value != null) ? value.getText() : null;
+	}
+
+	/**
+	 * Returns a copy of this document with an updated revision id.
+	 */
+	@Override
+	LabeledDocument withRevisionId(long newRevisionId);
+	
+	/**
+	 * Returns a new version of this document with a new label
+	 * (which overrides any existing label for this language).
+	 */
+	LabeledDocument withLabel(MonolingualTextValue newLabel);
+}
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/LabeledStatementDocument.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/LabeledStatementDocument.java
new file mode 100644
index 00000000..ecb7d9c2
--- /dev/null
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/LabeledStatementDocument.java
@@ -0,0 +1,47 @@
+package org.wikidata.wdtk.datamodel.interfaces;
+
+/*
+ * #%L
+ * Wikidata Toolkit Data Model
+ * %%
+ * Copyright (C) 2014 - 2018 Wikidata Toolkit Developers
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This interface just joins {@link LabeledDocument} and {@link StatementDocument}.
+ * 
+ * It is necessary to introduce this interface because the conflict between 
+ * the return types of the withRevisionId method in both interfaces needs to be resolved.
+ *
+ * @author Thomas Pellissier Tanon
+ */
+public interface LabeledStatementDocument extends LabeledDocument, StatementDocument {
+
+	@Override
+	LabeledStatementDocument withRevisionId(long newRevisionId);
+	
+	@Override
+	LabeledStatementDocument withLabel(MonolingualTextValue newLabel);
+	
+	@Override
+	LabeledStatementDocument withStatement(Statement statement);
+	
+	@Override
+	LabeledStatementDocument withoutStatementIds(Set<String> statementIds);
+}
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/MediaInfoDocument.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/MediaInfoDocument.java
new file mode 100644
index 00000000..65b525fb
--- /dev/null
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/MediaInfoDocument.java
@@ -0,0 +1,52 @@
+package org.wikidata.wdtk.datamodel.interfaces;
+
+/*
+ * #%L
+ * Wikidata Toolkit Data Model
+ * %%
+ * Copyright (C) 2014 Wikidata Toolkit Developers
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import java.util.Set;
+
+/**
+ * Interface for datasets that describe media entities.
+ *
+ * @author Thomas Pellissier Tanon
+ *
+ */
+public interface MediaInfoDocument extends LabeledStatementDocument {
+
+	/**
+	 * Returns the ID of the entity that the data refers to
+	 *
+	 * @return media info id
+	 */
+	@Override
+	MediaInfoIdValue getEntityId();
+
+	@Override
+	MediaInfoDocument withRevisionId(long newRevisionId);
+
+	@Override
+	MediaInfoDocument withLabel(MonolingualTextValue newLabel);
+
+	@Override
+	MediaInfoDocument withStatement(Statement statement);
+
+	@Override
+	MediaInfoDocument withoutStatementIds(Set<String> statementIds);
+}
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/MediaInfoIdValue.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/MediaInfoIdValue.java
new file mode 100644
index 00000000..d232139f
--- /dev/null
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/MediaInfoIdValue.java
@@ -0,0 +1,79 @@
+package org.wikidata.wdtk.datamodel.interfaces;
+
+import org.wikidata.wdtk.datamodel.helpers.Equality;
+import org.wikidata.wdtk.datamodel.helpers.Hash;
+
+/*
+ * #%L
+ * Wikidata Toolkit Data Model
+ * %%
+ * Copyright (C) 2014 Wikidata Toolkit Developers
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+/**
+ * The id of a Wikibase MediaInfo. Objects implementing this interface always return
+ * {@link EntityIdValue#ET_MEDIA_INFO} for {@link EntityIdValue#getEntityType()
+ * getEntityType}.
+ *
+ * @author Thomas Pellissier Tanon
+ *
+ */
+public interface MediaInfoIdValue extends EntityIdValue {
+
+	/**
+	 * Fixed {@link MediaInfoIdValue} that refers to a non-existing item. Can be used
+	 * as a placeholder object in situations where the entity id is irrelevant.
+	 */
+	MediaInfoIdValue NULL = new MediaInfoIdValue() {
+
+		@Override
+		public String getIri() {
+			return getSiteIri() + getId();
+		}
+
+		@Override
+		public <T> T accept(ValueVisitor<T> valueVisitor) {
+			return valueVisitor.visit(this);
+		}
+
+		@Override
+		public String getEntityType() {
+			return ET_MEDIA_INFO;
+		}
+
+		@Override
+		public String getId() {
+			return "M0";
+		}
+
+		@Override
+		public String getSiteIri() {
+			return EntityIdValue.SITE_LOCAL;
+		}
+		
+		@Override
+		public boolean equals(Object other) {
+			return Equality.equalsEntityIdValue(this, other);
+		}
+		
+		@Override
+		public int hashCode() {
+			return Hash.hashCode(this);
+		}
+
+	};
+
+}
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/TermedDocument.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/TermedDocument.java
index 1252067a..22578426 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/TermedDocument.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/TermedDocument.java
@@ -29,13 +29,7 @@ import java.util.Map;
  *
  * @author Markus Kroetzsch
  */
-public interface TermedDocument extends EntityDocument {
-	/**
-	 * Return a Map from Wikibase language codes to labels.
-	 *
-	 * @return the map of labels
-	 */
-	Map<String, MonolingualTextValue> getLabels();
+public interface TermedDocument extends LabeledDocument {
 
 	/**
 	 * Return a Map from Wikibase language codes to descriptions.
@@ -52,20 +46,6 @@ public interface TermedDocument extends EntityDocument {
 	 */
 	Map<String, List<MonolingualTextValue>> getAliases();
 
-	/**
-	 * Returns the string label for the given language code, or null if there is
-	 * no label for this code. This is a convenience method for accessing the
-	 * data that can be obtained via {@link #getLabels()}.
-	 *
-	 * @param languageCode
-	 *            a string that represents language
-	 * @return the label string or null if it does not exists
-	 */
-	default String findLabel(String languageCode) {
-		MonolingualTextValue value = this.getLabels().get(languageCode);
-		return (value != null) ? value.getText() : null;
-	}
-
 	/**
 	 * Returns the string description for the given language code, or null if
 	 * there is no description for this code. This is a convenience method for
@@ -90,6 +70,7 @@ public interface TermedDocument extends EntityDocument {
 	 * Returns a new version of this document with a new label
 	 * (which overrides any existing label for this language).
 	 */
+	@Override
 	TermedDocument withLabel(MonolingualTextValue newLabel);
 	
 	/**
diff --git a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/TermedStatementDocument.java b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/TermedStatementDocument.java
index 2114cb0f..ff19ed61 100644
--- a/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/TermedStatementDocument.java
+++ b/wdtk-datamodel/src/main/java/org/wikidata/wdtk/datamodel/interfaces/TermedStatementDocument.java
@@ -31,7 +31,7 @@ import java.util.Set;
  * @author antonin
  *
  */
-public interface TermedStatementDocument extends TermedDocument, StatementDocument {
+public interface TermedStatementDocument extends TermedDocument, LabeledStatementDocument {
 
 	@Override
 	TermedStatementDocument withRevisionId(long newRevisionId);
diff --git a/wdtk-dumpfiles/src/main/java/org/wikidata/wdtk/dumpfiles/JsonDumpFileProcessor.java b/wdtk-dumpfiles/src/main/java/org/wikidata/wdtk/dumpfiles/JsonDumpFileProcessor.java
index d0bd28a6..600d08a2 100644
--- a/wdtk-dumpfiles/src/main/java/org/wikidata/wdtk/dumpfiles/JsonDumpFileProcessor.java
+++ b/wdtk-dumpfiles/src/main/java/org/wikidata/wdtk/dumpfiles/JsonDumpFileProcessor.java
@@ -130,6 +130,9 @@ public class JsonDumpFileProcessor implements MwDumpFileProcessor {
 		} else if(document instanceof LexemeDocument) {
 			this.entityDocumentProcessor
 					.processLexemeDocument((LexemeDocument) document);
+		} else if(document instanceof MediaInfoDocument) {
+			this.entityDocumentProcessor
+					.processMediaInfoDocument((MediaInfoDocument) document);
 		}
 	}
 
diff --git a/wdtk-rdf/src/main/java/org/wikidata/wdtk/rdf/PropertyRegister.java b/wdtk-rdf/src/main/java/org/wikidata/wdtk/rdf/PropertyRegister.java
index b4dbef2c..72244515 100644
--- a/wdtk-rdf/src/main/java/org/wikidata/wdtk/rdf/PropertyRegister.java
+++ b/wdtk-rdf/src/main/java/org/wikidata/wdtk/rdf/PropertyRegister.java
@@ -219,6 +219,8 @@ public class PropertyRegister {
 				return DatatypeIdValue.DT_SENSE;
 			}
 			return DatatypeIdValue.DT_LEXEME;
+		case 'M':
+				return DatatypeIdValue.DT_MEDIA_INFO;
 		default:
 			logger.warn("Could not determine datatype of "+ propertyIdValue.getId() + ".");
 			logger.warn("Example value "+value.getId()+ " is not recognized as a valid entity id.");


Test patch:
diff --git a/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/helpers/DatamodelFilterTest.java b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/helpers/DatamodelFilterTest.java
index d687851d..5289b21a 100644
--- a/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/helpers/DatamodelFilterTest.java
+++ b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/helpers/DatamodelFilterTest.java
@@ -162,6 +162,37 @@ public class DatamodelFilterTest {
 		assertEquals(propertyDocumentFiltered, filter.filter(propertyDocument));
 	}
 
+	@Test
+	public void testLanguageFilterForMediaInfo() {
+		Set<String> languageFilter = new HashSet<>();
+		languageFilter.add("de");
+		languageFilter.add("he");
+		DocumentDataFilter documentDataFilter = new DocumentDataFilter();
+		documentDataFilter.setLanguageFilter(languageFilter);
+		DatamodelFilter filter = new DatamodelFilter(new DataObjectFactoryImpl(), documentDataFilter);
+
+		MediaInfoDocument mediaInfoDocument = Datamodel.makeMediaInfoDocument(
+				Datamodel.makeWikimediaCommonsMediaInfoIdValue("M42"),
+				Arrays.asList(
+						Datamodel.makeMonolingualTextValue("Label de", "de"),
+						Datamodel.makeMonolingualTextValue("Label en", "en"),
+						Datamodel.makeMonolingualTextValue("Label he", "he")
+				),
+				Collections.emptyList()
+		);
+
+		MediaInfoDocument mediaInfoDocumentFiltered = Datamodel.makeMediaInfoDocument(
+				Datamodel.makeWikimediaCommonsMediaInfoIdValue("M42"),
+				Arrays.asList(
+						Datamodel.makeMonolingualTextValue("Label de", "de"),
+						Datamodel.makeMonolingualTextValue("Label he", "he")
+				),
+				Collections.emptyList()
+		);
+
+		assertEquals(mediaInfoDocumentFiltered, filter.filter(mediaInfoDocument));
+	}
+
 	/**
 	 * Creates a statement group using the given property. The subject of the
 	 * statement group will be Wikidata's Q42.
@@ -382,6 +413,44 @@ public class DatamodelFilterTest {
 		assertEquals(lexemeDocumentFiltered, filter.filter(lexemeDocument));
 	}
 
+	@Test
+	public void testPropertyFilterForMediaInfo() {
+		MediaInfoIdValue s = Datamodel.makeWikimediaCommonsMediaInfoIdValue("M42");
+		PropertyIdValue p1 = Datamodel.makeWikidataPropertyIdValue("P1");
+		PropertyIdValue p2 = Datamodel.makeWikidataPropertyIdValue("P2");
+		PropertyIdValue p3 = Datamodel.makeWikidataPropertyIdValue("P3");
+		PropertyIdValue p4 = Datamodel.makeWikidataPropertyIdValue("P4");
+
+		Set<PropertyIdValue> propertyFilter = new HashSet<>();
+		propertyFilter.add(p1);
+		propertyFilter.add(p3);
+		DocumentDataFilter documentDataFilter = new DocumentDataFilter();
+		documentDataFilter.setPropertyFilter(propertyFilter);
+		DatamodelFilter filter = new DatamodelFilter(new DataObjectFactoryImpl(), documentDataFilter);
+
+		MediaInfoDocument mediaInfoDocument = Datamodel.makeMediaInfoDocument(
+				s,
+				Collections.emptyList(),
+				Arrays.asList(
+						makeTestStatementGroup(p1, s),
+						makeTestStatementGroup(p2, s),
+						makeTestStatementGroup(p3, s),
+						makeTestStatementGroup(p4, s)
+				)
+		);
+
+		MediaInfoDocument mediaInfoDocumentFiltered = Datamodel.makeMediaInfoDocument(
+				s,
+				Collections.emptyList(),
+				Arrays.asList(
+						makeTestStatementGroup(p1, s),
+						makeTestStatementGroup(p3, s)
+				)
+		);
+
+		assertEquals(mediaInfoDocumentFiltered, filter.filter(mediaInfoDocument));
+	}
+
 	@Test
 	public void testEmptySiteLinkFilterForItem() {
 		SiteLink s1 = Datamodel.makeSiteLink("Title 1", "site1", Collections.emptyList());
diff --git a/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/helpers/DatamodelTest.java b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/helpers/DatamodelTest.java
index a38e7e61..4bbe1925 100644
--- a/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/helpers/DatamodelTest.java
+++ b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/helpers/DatamodelTest.java
@@ -108,6 +108,21 @@ public class DatamodelTest {
 		assertEquals(o1, o2);
 	}
 
+	@Test
+	public final void testGetMediaInfoId() {
+		MediaInfoIdValue o1 = Datamodel.makeMediaInfoIdValue("M42", "foo");
+		MediaInfoIdValue o2 = factory.getMediaInfoIdValue("M42", "foo");
+		assertEquals(o1, o2);
+	}
+
+	@Test
+	public final void testGetWikimediaCommonsMediaInfoId() {
+		MediaInfoIdValue o1 = Datamodel.makeWikimediaCommonsMediaInfoIdValue("M42");
+		MediaInfoIdValue o2 = factory.getMediaInfoIdValue("M42",
+				"http://commons.wikimedia.org/entity/");
+		assertEquals(o1, o2);
+	}
+
 	@Test
 	public final void testGetDatatypeId() {
 		DatatypeIdValue o1 = Datamodel
@@ -423,4 +438,18 @@ public class DatamodelTest {
 				0);
 		assertEquals(o1, o2);
 	}
+
+	@Test
+	public final void testGetMediaInfoDocument() {
+		MediaInfoDocument o1 = Datamodel.makeMediaInfoDocument(
+				factory.getMediaInfoIdValue("M42", "foo"),
+				Collections.emptyList(),
+				Collections.emptyList());
+		MediaInfoDocument o2 = factory.getMediaInfoDocument(
+				factory.getMediaInfoIdValue("M42", "foo"),
+				Collections.emptyList(),
+				Collections.emptyList(),
+				0);
+		assertEquals(o1, o2);
+	}
 }
diff --git a/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/DataObjectFactoryImplTest.java b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/DataObjectFactoryImplTest.java
index 70223ed2..30f553a8 100644
--- a/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/DataObjectFactoryImplTest.java
+++ b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/DataObjectFactoryImplTest.java
@@ -69,6 +69,13 @@ public class DataObjectFactoryImplTest {
 		assertEquals(o1, o2);
 	}
 
+	@Test
+	public final void testGetMediaInfoId() {
+		MediaInfoIdValue o1 = new MediaInfoIdValueImpl("M22", "foo");
+		MediaInfoIdValue o2 = factory.getMediaInfoIdValue("M22", "foo");
+		assertEquals(o1, o2);
+	}
+
 	@Test
 	public final void testGetDatatypeId() {
 		DatatypeIdValue o1 = new DatatypeIdImpl(DatatypeIdValue.DT_TIME);
diff --git a/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/EntityIdValueImplTest.java b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/EntityIdValueImplTest.java
index 6083531a..01974685 100644
--- a/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/EntityIdValueImplTest.java
+++ b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/EntityIdValueImplTest.java
@@ -51,6 +51,11 @@ public class EntityIdValueImplTest {
 		assertEquals(new SenseIdValueImpl("L42-S1", "http://foo/"), EntityIdValueImpl.fromId("L42-S1", "http://foo/"));
 	}
 
+	@Test
+	public void testFromIdMediaInfo() {
+		assertEquals(new MediaInfoIdValueImpl("M42", "http://foo/"), EntityIdValueImpl.fromId("M42", "http://foo/"));
+	}
+
 	@Test(expected = IllegalArgumentException.class)
 	public void testFromIdFailure() {
 		EntityIdValueImpl.fromId("L42-P1", "http://foo/");
diff --git a/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImplTest.java b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImplTest.java
new file mode 100644
index 00000000..f6254171
--- /dev/null
+++ b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoDocumentImplTest.java
@@ -0,0 +1,241 @@
+package org.wikidata.wdtk.datamodel.implementation;
+
+/*
+ * #%L
+ * Wikidata Toolkit Data Model
+ * %%
+ * Copyright (C) 2014 Wikidata Toolkit Developers
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Test;
+import org.wikidata.wdtk.datamodel.helpers.DatamodelMapper;
+import org.wikidata.wdtk.datamodel.interfaces.*;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+public class MediaInfoDocumentImplTest {
+
+	private final ObjectMapper mapper = new DatamodelMapper("http://example.com/entity/");
+
+	private final MediaInfoIdValue mid = new MediaInfoIdValueImpl("M42", "http://example.com/entity/");
+	private final Statement s = new StatementImpl("MyId", StatementRank.NORMAL,
+			new SomeValueSnakImpl(new PropertyIdValueImpl("P42", "http://example.com/entity/")),
+			Collections.emptyList(), Collections.emptyList(), mid);
+	private final List<StatementGroup> statementGroups = Collections.singletonList(
+			new StatementGroupImpl(Collections.singletonList(s))
+	);
+	private final MonolingualTextValue label = new TermImpl("en", "label");
+	private final List<MonolingualTextValue> labelList = Collections.singletonList(label);
+
+	private final MediaInfoDocument mi1 = new MediaInfoDocumentImpl(mid, labelList, statementGroups,1234);
+	private final MediaInfoDocument mi2 = new MediaInfoDocumentImpl(mid, labelList, statementGroups,  1234);
+
+	private final String JSON_MEDIA_INFO_LABEL = "{\"type\":\"mediainfo\",\"id\":\"M42\",\"labels\":{\"en\":{\"language\":\"en\",\"value\":\"label\"}},\"statements\":{}}";
+	private final String JSON_MEDIA_INFO_DESCRIPTION = "{\"type\":\"mediainfo\",\"id\":\"M42\",\"labels\":{},\"descriptions\":{},\"statements\":{}}";
+	private final String JSON_MEDIA_INFO_STATEMENTS = "{\"type\":\"mediainfo\",\"id\":\"M42\",\"labels\":{},\"statements\":{\"P42\":[{\"rank\":\"normal\",\"id\":\"MyId\",\"mainsnak\":{\"property\":\"P42\",\"snaktype\":\"somevalue\"},\"type\":\"statement\"}]}}";
+	private final String JSON_MEDIA_INFO_CLAIMS = "{\"type\":\"mediainfo\",\"id\":\"M42\",\"labels\":{},\"claims\":{\"P42\":[{\"rank\":\"normal\",\"id\":\"MyId\",\"mainsnak\":{\"property\":\"P42\",\"snaktype\":\"somevalue\"},\"type\":\"statement\"}]}}";
+	private final String JSON_MEDIA_INFO_EMPTY_ARRAYS = "{\"type\":\"mediainfo\",\"id\":\"M42\",\"labels\":[],\"descriptions\":[],\"statements\":[],\"sitelinks\":[]}";
+
+	@Test
+	public void fieldsAreCorrect() {
+		assertEquals(mi1.getEntityId(), mid);
+		assertEquals(mi1.getLabels(), Collections.singletonMap(label.getLanguageCode(), label));
+		assertEquals(mi1.getStatementGroups(), statementGroups);
+	}
+
+	@Test
+	public void findLabels() {
+		assertEquals("label", mi1.findLabel("en"));
+		assertNull( mi1.findLabel("ja"));
+	}
+
+	@Test
+	public void equalityBasedOnContent() {
+		MediaInfoDocument irDiffLabel = new MediaInfoDocumentImpl(mid, Collections.emptyList(), statementGroups, 1234);
+		MediaInfoDocument irDiffStatementGroups = new MediaInfoDocumentImpl(mid,
+				labelList, 
+				Collections.emptyList(), 1234);
+		MediaInfoDocument irDiffRevisions = new MediaInfoDocumentImpl(mid,
+				labelList, 
+				statementGroups, 1235);
+
+		PropertyDocument pr = new PropertyDocumentImpl(
+				new PropertyIdValueImpl("P42", "foo"),
+				labelList, Collections.emptyList(),
+				Collections.emptyList(), Collections.emptyList(),
+				new DatatypeIdImpl(DatatypeIdValue.DT_STRING), 1234);
+
+		// we need to use empty lists of Statement groups to test inequality
+		// based on different item ids with all other data being equal
+		MediaInfoDocument irDiffMediaInfoIdValue = new MediaInfoDocumentImpl(
+				new MediaInfoIdValueImpl("M23", "http://example.org/"),
+				labelList, 
+				Collections.emptyList(), 1234);
+
+		assertEquals(mi1, mi1);
+		assertEquals(mi1, mi2);
+		assertNotEquals(mi1, irDiffLabel);
+		assertNotEquals(mi1, irDiffStatementGroups);
+		assertNotEquals(mi1, irDiffRevisions);
+		assertNotEquals(irDiffStatementGroups, irDiffMediaInfoIdValue);
+		assertNotEquals(mi1, pr);
+		assertNotEquals(mi1, null);
+		assertNotEquals(mi1, this);
+	}
+
+	@Test
+	public void hashBasedOnContent() {
+		assertEquals(mi1.hashCode(), mi2.hashCode());
+	}
+
+	@Test(expected = NullPointerException.class)
+	public void idNotNull() {
+		new MediaInfoDocumentImpl(null, Collections.emptyList(), statementGroups, 1234);
+	}
+
+	@Test
+	public void labelsCanBeNull() {
+		MediaInfoDocument doc = new MediaInfoDocumentImpl(mid, null, statementGroups, 1234);
+		assertTrue(doc.getLabels().isEmpty());
+	}
+
+	@Test
+	public void statementGroupsCanBeNull() {
+		MediaInfoDocument doc = new MediaInfoDocumentImpl(mid, Collections.emptyList(), null, 1234);
+		assertTrue(doc.getStatementGroups().isEmpty());
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void statementGroupsUseSameSubject() {
+		MediaInfoIdValue mid2 = new MediaInfoIdValueImpl("Q23", "http://example.org/");
+		Statement s2 = new StatementImpl("MyId", StatementRank.NORMAL,
+				new SomeValueSnakImpl(new PropertyIdValueImpl("P42", "http://wikibase.org/entity/")),
+				Collections.emptyList(),  Collections.emptyList(), mid2);
+		StatementGroup sg2 = new StatementGroupImpl(Collections.singletonList(s2));
+
+		List<StatementGroup> statementGroups2 = new ArrayList<>();
+		statementGroups2.add(statementGroups.get(0));
+		statementGroups2.add(sg2);
+
+		new MediaInfoDocumentImpl(mid, Collections.emptyList(), statementGroups2, 1234);
+	}
+
+	@Test
+	public void iterateOverAllStatements() {
+		Iterator<Statement> statements = mi1.getAllStatements();
+
+		assertTrue(statements.hasNext());
+		assertEquals(s, statements.next());
+		assertFalse(statements.hasNext());
+	}
+	
+	@Test
+	public void testWithRevisionId() {
+		assertEquals(1235L, mi1.withRevisionId(1235L).getRevisionId());
+		assertEquals(mi1, mi1.withRevisionId(1325L).withRevisionId(mi1.getRevisionId()));
+	}
+	
+	@Test
+	public void testWithLabelInNewLanguage() {
+		MonolingualTextValue newLabel = new MonolingualTextValueImpl(
+				"MediaInfo M42", "fr");
+		MediaInfoDocument withLabel = mi1.withLabel(newLabel);
+		assertEquals("MediaInfo M42", withLabel.findLabel("fr"));
+	}
+	
+	@Test
+	public void testAddStatement() {
+		Statement fresh = new StatementImpl("MyFreshId", StatementRank.NORMAL,
+				new SomeValueSnakImpl(new PropertyIdValueImpl("P29", "http://example.com/entity/")),
+				Collections.emptyList(), Collections.emptyList(), mid);
+		Claim claim = fresh.getClaim();
+		assertFalse(mi1.hasStatementValue(
+				claim.getMainSnak().getPropertyId(),
+				claim.getValue()));
+		MediaInfoDocument withStatement = mi1.withStatement(fresh);
+		assertTrue(withStatement.hasStatementValue(
+				claim.getMainSnak().getPropertyId(),
+				claim.getValue()));
+	}
+	
+	@Test
+	public void testDeleteStatements() {
+		Statement toRemove = statementGroups.get(0).getStatements().get(0);
+		MediaInfoDocument withoutStatement = mi1.withoutStatementIds(Collections.singleton(toRemove.getStatementId()));
+		assertNotEquals(withoutStatement, mi1);
+	}
+
+	@Test
+	public void testLabelsToJson() throws JsonProcessingException {
+		MediaInfoDocumentImpl document = new MediaInfoDocumentImpl(mid, labelList, Collections.emptyList(), 0);
+		JsonComparator.compareJsonStrings(JSON_MEDIA_INFO_LABEL, mapper.writeValueAsString(document));
+	}
+
+	@Test
+	public void testLabelToJava() throws IOException {
+		MediaInfoDocumentImpl document = new MediaInfoDocumentImpl(mid,
+				labelList, Collections.emptyList(), 0);
+		assertEquals(document, mapper.readValue(JSON_MEDIA_INFO_LABEL, EntityDocumentImpl.class));
+	}
+
+	@Test
+	public void testDescriptionsToJava() throws IOException {
+		MediaInfoDocumentImpl document = new MediaInfoDocumentImpl(mid,
+				Collections.emptyList(), Collections.emptyList(), 0);
+		assertEquals(document, mapper.readValue(JSON_MEDIA_INFO_DESCRIPTION, EntityDocumentImpl.class));
+	}
+
+	@Test
+	public void testStatementsToJson() throws JsonProcessingException {
+		MediaInfoDocumentImpl document = new MediaInfoDocumentImpl(mid, Collections.emptyList(), statementGroups,  0);
+		JsonComparator.compareJsonStrings(JSON_MEDIA_INFO_STATEMENTS, mapper.writeValueAsString(document));
+	}
+
+	@Test
+	public void testStatementsToJava() throws IOException {
+		MediaInfoDocumentImpl document = new MediaInfoDocumentImpl(mid, Collections.emptyList(), statementGroups, 0);
+		assertEquals(document, mapper.readValue(JSON_MEDIA_INFO_STATEMENTS, MediaInfoDocumentImpl.class));
+	}
+
+	@Test
+	public void testStatementsNamedClaimsToJava() throws IOException {
+		MediaInfoDocumentImpl document = new MediaInfoDocumentImpl(mid, Collections.emptyList(), statementGroups, 0);
+		assertEquals(document, mapper.readValue(JSON_MEDIA_INFO_CLAIMS, MediaInfoDocumentImpl.class));
+	}
+
+	/**
+	 * Checks support of wrong serialization of empty object as empty array
+	 */
+	@Test
+	public void testEmptyArraysForTerms() throws IOException {
+		MediaInfoDocumentImpl document = new MediaInfoDocumentImpl(mid, Collections.emptyList(), Collections.emptyList(), 0);
+
+		assertEquals(document, mapper.readerFor(MediaInfoDocumentImpl.class)
+				.with(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT)
+				.readValue(JSON_MEDIA_INFO_EMPTY_ARRAYS)
+		);
+	}
+
+}
diff --git a/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoIdValueImplTest.java b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoIdValueImplTest.java
new file mode 100644
index 00000000..bf318679
--- /dev/null
+++ b/wdtk-datamodel/src/test/java/org/wikidata/wdtk/datamodel/implementation/MediaInfoIdValueImplTest.java
@@ -0,0 +1,122 @@
+package org.wikidata.wdtk.datamodel.implementation;
+
+/*
+ * #%L
+ * Wikidata Toolkit Data Model
+ * %%
+ * Copyright (C) 2014 Wikidata Toolkit Developers
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Test;
+import org.wikidata.wdtk.datamodel.helpers.Datamodel;
+import org.wikidata.wdtk.datamodel.helpers.DatamodelMapper;
+import org.wikidata.wdtk.datamodel.interfaces.EntityIdValue;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+public class MediaInfoIdValueImplTest {
+
+	private final ObjectMapper mapper = new DatamodelMapper(Datamodel.SITE_WIKIMEDIA_COMMONS);
+
+	private final MediaInfoIdValueImpl mediaInfo1 = new MediaInfoIdValueImpl("M42", "http://commons.wikimedia.org/entity/");
+	private final MediaInfoIdValueImpl mediaInfo2 = new MediaInfoIdValueImpl("M42", "http://commons.wikimedia.org/entity/");
+	private final MediaInfoIdValueImpl mediaInfo3 = new MediaInfoIdValueImpl("M57", "http://commons.wikimedia.org/entity/");
+	private final MediaInfoIdValueImpl mediaInfo4 = new MediaInfoIdValueImpl("M42", "http://www.example.org/entity/");
+	private final String JSON_MEDIA_INFO_ID_VALUE = "{\"type\":\"wikibase-entityid\",\"value\":{\"entity-type\":\"mediainfo\",\"numeric-id\":42,\"id\":\"M42\"}}";
+	private final String JSON_MEDIA_INFO_ID_VALUE_WITHOUT_NUMERICAL_ID = "{\"type\":\"wikibase-entityid\",\"value\":{\"id\":\"M42\"}}";
+
+	@Test
+	public void entityTypeIsMediaInfo() {
+		assertEquals(mediaInfo1.getEntityType(), EntityIdValue.ET_MEDIA_INFO);
+	}
+
+	@Test
+	public void iriIsCorrect() {
+		assertEquals(mediaInfo1.getIri(), "http://commons.wikimedia.org/entity/M42");
+		assertEquals(mediaInfo4.getIri(), "http://www.example.org/entity/M42");
+	}
+
+	@Test
+	public void siteIriIsCorrect() {
+		assertEquals(mediaInfo1.getSiteIri(), "http://commons.wikimedia.org/entity/");
+	}
+
+	@Test
+	public void idIsCorrect() {
+		assertEquals(mediaInfo1.getId(), "M42");
+	}
+
+	@Test
+	public void equalityBasedOnContent() {
+		assertEquals(mediaInfo1, mediaInfo1);
+		assertEquals(mediaInfo1, mediaInfo2);
+		assertNotEquals(mediaInfo1, mediaInfo3);
+		assertNotEquals(mediaInfo1, mediaInfo4);
+		assertNotEquals(mediaInfo1, null);
+		assertNotEquals(mediaInfo1, this);
+	}
+
+	@Test
+	public void hashBasedOnContent() {
+		assertEquals(mediaInfo1.hashCode(), mediaInfo2.hashCode());
+	}
+
+	@Test(expected = RuntimeException.class)
+	public void idValidatedForFirstLetter() {
+		new MediaInfoIdValueImpl("Q12345", "http://commons.wikimedia.org/entity/");
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void idValidatedForNumber() {
+		new MediaInfoIdValueImpl("L34d23", "http://commons.wikimedia.org/entity/");
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void idValidatedForLength() {
+		new MediaInfoIdValueImpl("M", "http://commons.wikimedia.org/entity/");
+	}
+
+	@Test(expected = RuntimeException.class)
+	public void idNotNull() {
+		new MediaInfoIdValueImpl((String)null, "http://commons.wikimedia.org/entity/");
+	}
+
+	@Test(expected = NullPointerException.class)
+	public void baseIriNotNull() {
+		new MediaInfoIdValueImpl("M42", null);
+	}
+
+	@Test
+	public void testToJson() throws JsonProcessingException {
+		JsonComparator.compareJsonStrings(JSON_MEDIA_INFO_ID_VALUE, mapper.writeValueAsString(mediaInfo1));
+	}
+
+	@Test
+	public void testToJava() throws IOException {
+		assertEquals(mediaInfo1, mapper.readValue(JSON_MEDIA_INFO_ID_VALUE, ValueImpl.class));
+	}
+
+	@Test
+	public void testToJavaWithoutNumericalID() throws IOException {
+		assertEquals(mediaInfo1, mapper.readValue(JSON_MEDIA_INFO_ID_VALUE_WITHOUT_NUMERICAL_ID, ValueImpl.class));
+	}
+
+}