Added KrillStats class

Change-Id: Ib574021ce1358ff7b6ed3dba81447f2d50911b5e
diff --git a/Changes b/Changes
index 4f91121..672c91e 100644
--- a/Changes
+++ b/Changes
@@ -1,4 +1,4 @@
-0.55.6 2016-06-23
+0.55.6 2016-06-25
         - [bugfix] distance with key "t" uses default foundry (diewald)
 	- [cleanup] Renamed fromJson() to fromKoral() (diewald)
 	- [cleanup] Removed deprecated methods in Krill:
@@ -11,6 +11,7 @@
 	- [feature] Added getDoc() method to KrillIndex (diewald)
         - [bugfix] Fixed UID handling (diewald)
 	- [feature] Added document method to Web-API (diewald)
+	- [feature] Added experimental KrillStats class (diewald)
 
 0.55.5 2016-05-02
 	- [performance] Changed to a dynamic window for sorting in FocusSpans (margaretha)
diff --git a/misc/errorcodes.md b/misc/errorcodes.md
index 27da151..b01ac08 100644
--- a/misc/errorcodes.md
+++ b/misc/errorcodes.md
@@ -10,6 +10,7 @@
 610: "Missing request parameters"
 620: "Unable to generate JSON"
 621: "Unable to parse JSON"
+630: "Document not found"
 651: "Unable to extend context"
 680: "Server is up and running!"
 681: "Document was added successfully", document id
diff --git a/src/main/java/de/ids_mannheim/korap/KrillIndex.java b/src/main/java/de/ids_mannheim/korap/KrillIndex.java
index 43b7928..97576cf 100644
--- a/src/main/java/de/ids_mannheim/korap/KrillIndex.java
+++ b/src/main/java/de/ids_mannheim/korap/KrillIndex.java
@@ -836,7 +836,7 @@
             log.warn(e.getLocalizedMessage());
         };
 
-        text.addError(830, "Filter was empty");
+        text.addError(630, "Document not found");
 
         return text;
     };
diff --git a/src/main/java/de/ids_mannheim/korap/KrillStats.java b/src/main/java/de/ids_mannheim/korap/KrillStats.java
new file mode 100644
index 0000000..898058b
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/KrillStats.java
@@ -0,0 +1,78 @@
+package de.ids_mannheim.korap;
+
+import java.util.*;
+import java.io.IOException;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import de.ids_mannheim.korap.response.Notifications;
+
+import java.nio.ByteBuffer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Create a Statistics object.
+ * 
+ * This is early work and highliy experimental!
+ *
+ * <blockquote><pre>
+ * KrillStats ks = new KrillStats(json);
+ * </pre></blockquote>
+ *
+ * Should serialize to something like
+ *
+ * "stats" : {
+ *   "@type" : "koral:stats",
+ *   "collection" : [
+ *     {
+ *       "@type" : "stats:collection",
+ *       "foundry" : "base",
+ *       "layer" : "s",
+ *       "key" : "s",
+ *       "value" : 450
+ *     },
+ *     {
+ *       "@type" : "stats:collection",
+ *       "key" : "texts",
+ *       "value" : 2
+ *     }
+ *   ]
+ * }
+ *
+ * 
+ * @author diewald
+ */
+/*
+ * TODO: THIS IS CURRENTLY HIGHLY EXPERIMENTAL
+ */
+public final class KrillStats extends Notifications {
+
+    // Logger
+    private final static Logger log = LoggerFactory
+            .getLogger(KrillStats.class);
+
+    // This advices the java compiler to ignore all loggings
+    public static final boolean DEBUG = false;
+
+
+    /**
+     * Construct a new KrillStats.
+     * 
+     */
+    public KrillStats () {};
+
+    @Override
+    public JsonNode toJsonNode () {
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectNode json = mapper.createObjectNode();
+
+        json.put("@type", "koral:stats");
+
+        return (JsonNode) json;
+    }    
+};
diff --git a/src/main/java/de/ids_mannheim/korap/index/AbstractDocument.java b/src/main/java/de/ids_mannheim/korap/index/AbstractDocument.java
index 4589bb4..f90945f 100644
--- a/src/main/java/de/ids_mannheim/korap/index/AbstractDocument.java
+++ b/src/main/java/de/ids_mannheim/korap/index/AbstractDocument.java
@@ -33,7 +33,7 @@
  * 
  * @author diewald
  */
-@JsonInclude(Include.NON_NULL)
+@JsonInclude(Include.NON_EMPTY)
 @JsonIgnoreProperties(ignoreUnknown = true)
 public abstract class AbstractDocument extends Response {
     ObjectMapper mapper = new ObjectMapper();
@@ -428,8 +428,9 @@
      * @throws NumberFormatException
      */
     public void setUID (String UID) throws NumberFormatException {
-        if (UID != null)
+        if (UID != null) {
             this.UID = Integer.parseInt(UID);
+        };
     };
 
 
@@ -1216,6 +1217,10 @@
     public JsonNode toJsonNode () {
         ObjectNode json = (ObjectNode) super.toJsonNode();
         json.putAll((ObjectNode) mapper.valueToTree(this));
+
+        if (this.getUID() == 0)
+            json.remove("UID");
+
         return json;
     };
 };
diff --git a/src/main/java/de/ids_mannheim/korap/index/FieldDocument.java b/src/main/java/de/ids_mannheim/korap/index/FieldDocument.java
index baed325..73a7ecf 100644
--- a/src/main/java/de/ids_mannheim/korap/index/FieldDocument.java
+++ b/src/main/java/de/ids_mannheim/korap/index/FieldDocument.java
@@ -35,7 +35,6 @@
  * @author diewald
  */
 @JsonIgnoreProperties(ignoreUnknown = true)
-// @JsonDeserialize(using = FieldDocumentDeserializer.class)
 public class FieldDocument extends AbstractDocument {
     ObjectMapper mapper = new ObjectMapper();
 
@@ -309,8 +308,10 @@
     @Override
     @JsonIgnore
     public void setUID (int ID) {
-        super.setUID(ID);
-        this.addString("UID", new Integer(ID).toString());
+        if (ID != 0) {
+            super.setUID(ID);
+            this.addString("UID", new Integer(ID).toString());
+        }
     };
 
     @Override
diff --git a/src/main/java/de/ids_mannheim/korap/response/Response.java b/src/main/java/de/ids_mannheim/korap/response/Response.java
index 4ac95f9..ccb3056 100644
--- a/src/main/java/de/ids_mannheim/korap/response/Response.java
+++ b/src/main/java/de/ids_mannheim/korap/response/Response.java
@@ -12,6 +12,7 @@
 import de.ids_mannheim.korap.KrillCollection;
 import de.ids_mannheim.korap.KrillMeta;
 import de.ids_mannheim.korap.KrillQuery;
+import de.ids_mannheim.korap.KrillStats;
 import de.ids_mannheim.korap.response.Notifications;
 
 /**
@@ -41,6 +42,7 @@
     private KrillMeta meta;
     private KrillCollection collection;
     private KrillQuery query;
+    private KrillStats stats;
 
     private String version, name, node, listener;
 
@@ -444,6 +446,37 @@
     };
 
 
+    /**
+     * Get the associated statistics object.
+     * In case no statistics information was defined yet,
+     * a new {@link KrillStats} object will be created.
+     * 
+     * @return The attached {@link KrillStats} object.
+     */
+    @JsonIgnore
+    public KrillStats getStats () {
+        if (this.stats == null)
+            this.stats = new KrillStats();
+        return this.stats;
+    };
+
+
+    /**
+     * Set a new {@link KrillStats} object.
+     * 
+     * @param stats
+     *            A {@link KrillStats} object.
+     * @return The {@link Response} object for chaining
+     */
+    @JsonIgnore
+    public Response setStats (KrillStats stats) {
+        this.stats = stats;
+
+        // Move messages from the stats
+        return (Response) this.moveNotificationsFrom(stats);
+    };
+
+
     public void addJsonNode (String key, ObjectNode value) {
         if (this.jsonFields == null)
             this.jsonFields = new HashMap<String, ObjectNode>(4);
diff --git a/src/main/java/de/ids_mannheim/korap/response/Result.java b/src/main/java/de/ids_mannheim/korap/response/Result.java
index 01102a1..83d08f4 100644
--- a/src/main/java/de/ids_mannheim/korap/response/Result.java
+++ b/src/main/java/de/ids_mannheim/korap/response/Result.java
@@ -270,6 +270,7 @@
      */
     public JsonNode toJsonNode () {
         ObjectNode json = (ObjectNode) mapper.valueToTree(super.toJsonNode());
+
         this._addMeta(json);
 
         // Add matches
diff --git a/src/main/java/de/ids_mannheim/korap/server/Resource.java b/src/main/java/de/ids_mannheim/korap/server/Resource.java
index a439480..08f5399 100644
--- a/src/main/java/de/ids_mannheim/korap/server/Resource.java
+++ b/src/main/java/de/ids_mannheim/korap/server/Resource.java
@@ -43,6 +43,7 @@
 import java.sql.SQLException;
 import com.mchange.v2.c3p0.ComboPooledDataSource;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 
 /**
@@ -192,6 +193,40 @@
     };
 
 
+    // Return corpus info
+    @GET
+    @Path("/corpus")
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getCorpus (@Context UriInfo uri) {
+        ObjectMapper mapper = new ObjectMapper();
+
+        // TODO: Accept fields!!!!
+
+        final Response kresp = _initResponse();
+        if (kresp.hasErrors())
+            return kresp.toJsonString();
+
+        // TODO: Statistics should be node fields - not annotations!
+        // TODO: This is just temporary
+        KrillIndex ki = Node.getIndex();
+
+        ObjectNode obj = mapper.createObjectNode();
+        obj.put("tokens", ki.numberOf("tokens"));
+        obj.put("base/texts", ki.numberOf("base/texts"));
+        obj.put("base/sentences", ki.numberOf("base/sentences"));
+        obj.put("base/paragraphs", ki.numberOf("base/paragraphs"));
+
+        // <legacy>
+        obj.put("sentences", ki.numberOf("sentences"));
+        obj.put("paragraphs", ki.numberOf("paragraphs"));
+        // </legacy>
+
+        kresp.addJsonNode("stats", obj);
+        return kresp.toJsonString();
+    };
+
+    // PUT: Return corpus info for virtual corpus
+
 
     /**
      * Find matches in the lucene index based on UIDs and return one
diff --git a/src/test/java/de/ids_mannheim/korap/server/TestResource.java b/src/test/java/de/ids_mannheim/korap/server/TestResource.java
index d390de5..11f27d7 100644
--- a/src/test/java/de/ids_mannheim/korap/server/TestResource.java
+++ b/src/test/java/de/ids_mannheim/korap/server/TestResource.java
@@ -183,17 +183,64 @@
         assertEquals("WPD", res.at("/corpusID").asText());
         assertEquals(5, res.at("/UID").asInt());
         assertEquals("WPD_AAA.00005", res.at("/ID").asText());
+
+        // Get document by UID
+        resp = target.path("/index/17").request().get(String.class);
+        res = mapper.readTree(resp);
+
+        assertEquals(630, res.at("/errors/0/0").asInt());
+        assertTrue(res.at("/UID").isMissingNode());
+
+        // Get corpus statistics
+        resp = target.path("/corpus").request().get(String.class);
+        res = mapper.readTree(resp);
+
+        assertEquals(281, res.at("/stats/sentences").asInt());
+        assertEquals(174, res.at("/stats/paragraphs").asInt());
+        assertEquals(2661, res.at("/stats/tokens").asInt());
+
+        assertEquals(7, res.at("/stats/base~1texts").asInt());
     };
 
 
     /*
     @Test
     public void testRemoving () throws IOException {
-                resp = target.path("/index/" + i).request("application/json")
-                        .put(jsonE, String.class);
+        String resp;
+        JsonNode res;
+
+        String json = StringfromFile(getClass().getResource("/wiki/02439.json")
+                .getFile());
+        Entity jsonE = Entity.json(json);
+
+        try {
+            // Put new documents to the index
+            resp = target.path("/index/02439").request("application/json")
+                    .put(jsonE, String.class);
+
+            res = mapper.readTree(resp);
+
+            // Check mirroring
+            assertEquals(2439, res.at("/text/UID").asInt());
+            assertEquals("milena", res.at("/meta/node").asText());
+            assertEquals(681, res.at("/messages/0/0").asInt());
+        }
+        catch (Exception e) {
+            fail("Server response failed " + e.getMessage() + " (Known issue)");
+        };
+
+        // Commit!
+        resp = target.path("/index").request("application/json")
+                .post(Entity.text(""), String.class);
+        res = mapper.readTree(resp);
+        assertEquals("milena", res.at("/meta/node").asText());
+
+        // Staged data committed
+        assertEquals(683, res.at("/messages/0/0").asInt());
     };
     */
 
+
     @Test
     public void testCollection () throws IOException {