/**
 * Licensed to the zk9131 under one or more contributor license agreements.
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

package com.github.zk1931.pulsed;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Utility functions.
 */
public final class Utils {

  private static final Logger LOG = LoggerFactory.getLogger(Utils.class);

  private Utils() {}

  public static String toJson(Object obj) {
    GsonBuilder builder = new GsonBuilder();
    Gson gson = builder.setPrettyPrinting().create();
    return gson.toJson(obj);
  }

  public static void writeHeader(Node node, HttpServletResponse response) {
    writeHeader(node, response, null);
  }

  public static void writeHeader(Node node, HttpServletResponse response,
                                 AsyncContext context) {
    String type = "file";
    if (node instanceof DirNode) {
      type = "directory";
    }
    response.addHeader("version", Long.toString(node.version));
    response.addHeader("type", type);
    response.addHeader("path", node.fullPath);
    response.setStatus(HttpServletResponse.SC_OK);
    if (context != null) {
      context.complete();
    }
  }

  public static void writeData(Node node,
                               HttpServletResponse response)
      throws IOException {
    writeData(node, response, null);
  }

  public static void writeData(Node node,
                               HttpServletResponse response,
                               AsyncContext context) throws IOException {
    if (!(node instanceof FileNode)) {
      throw new RuntimeException("Node must be a file");
    }
    byte[] data = ((FileNode)node).data;
    response.getOutputStream().write(data);
    response.setContentLength(data.length);
    if (context != null) {
      context.complete();
    }
  }

  public static void writeChildren(Node node,
                                   HttpServletResponse response,
                                   boolean recursive) throws IOException {
    writeChildren(node, response, recursive, null);
  }

  public static void writeChildren(Node node,
                                   HttpServletResponse response,
                                   boolean recursive,
                                   AsyncContext context) throws IOException {
    if (!(node instanceof DirNode)) {
      throw new RuntimeException("Node must be a directory");
    }
    JsonWriter writer =
      new JsonWriter(new OutputStreamWriter(response.getOutputStream(),
                                            "UTF-8"));
    // 2-space indentation.
    writer.setIndent("  ");
    try {
      writeDir(node, writer, recursive);
    } finally {
      writer.close();
    }
    if (context != null) {
      context.complete();
    }
  }

  static void writeMetadata(Node node, JsonWriter writer) throws IOException {
    String type = "file";
    if (node instanceof DirNode) {
      type = "directory";
    }
    writer.beginObject();
    writer.name("version").value(node.version);
    writer.name("path").value(node.fullPath);
    writer.name("sessionID").value(node.sessionID);
    writer.name("type").value(type);
    writer.endObject();
  }

  static void writeDir(Node node, JsonWriter writer, boolean recursive)
      throws IOException {
    writer.beginObject();
    writer.name("version").value(node.version);
    writer.name("path").value(node.fullPath);
    writer.name("sessionID").value(node.sessionID);
    writer.name("type").value("dir");
    writer.name("children");
    writeChildren(node, writer, recursive);
    writer.endObject();
  }

  static void writeChildren(Node node, JsonWriter writer, boolean recursive)
      throws IOException {
    writer.beginArray();
    for (Node child : ((DirNode)node).children.values()) {
      if (recursive && child instanceof DirNode) {
        writeDir(child, writer, recursive);
      } else {
        writeMetadata(child, writer);
      }
    }
    writer.endArray();
  }

  public static void badRequest(HttpServletResponse response, String desc,
                                AsyncContext ctx) {
    try {
      response.setContentType("text/html");
      response.sendError(HttpServletResponse.SC_BAD_REQUEST, desc);
    } catch (IOException ex) {
      LOG.warn("IOExcepion {}", ex);
    } finally {
      if (ctx != null) {
        ctx.complete();
      }
    }
  }

  public static void badRequest(HttpServletResponse response, String desc) {
    badRequest(response, desc, null);
  }

  public static void notFound(HttpServletResponse response, String desc) {
    notFound(response, desc, null);
  }

  public static void notFound(HttpServletResponse response, String desc,
                              AsyncContext ctx) {
    try {
      response.setContentType("text/html");
      response.sendError(HttpServletResponse.SC_NOT_FOUND, desc);
    } catch (IOException ex) {
      LOG.warn("IOExcepion {}", ex);
    } finally {
      if (ctx != null) {
        ctx.complete();
      }
    }
  }

  public static void serviceUnavailable(HttpServletResponse response,
                                        AsyncContext ctx) {
    response.setContentType("text/html");
    response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
    response.setContentLength(0);
    if (ctx != null) {
      ctx.complete();
    }
  }

  public static void serviceUnavailable(HttpServletResponse response) {
    serviceUnavailable(response, null);
  }

  public static void replyOK(HttpServletResponse response, byte[] data) {
    replyOK(response, data, null);
  }

  public static void replyOK(HttpServletResponse response, byte[] data,
                             AsyncContext ctx) {
    try {
      response.setContentType("text/html");
      response.setStatus(HttpServletResponse.SC_OK);
      if (data != null) {
        response.getOutputStream().write(data);
        response.setContentLength(data.length);
      }
    } catch (IOException ex) {
      LOG.error("IOException ", ex);
    } finally {
      if (ctx != null) {
        ctx.complete();
      }
    }
  }

  public static Set<String> getQueryStrings(String query) {
    if (query == null) {
      return new HashSet<String>();
    }
    return new HashSet<String>(Arrays.asList(query.split("&")));
  }
}

@@ -945,43 +945,8 @@
 on;%0A
-import java.io.OutputStreamWriter;%0A
 impo
@@ -966,16 +966,16 @@
 Arrays;%0A
+
 import j
@@ -1975,24 +1975,98 @@
 .fullPath);%0A
+    response.addHeader(%22checksum%22, Long.toHexString(node.getChecksum()));%0A
     response
@@ -2708,52 +2708,8 @@
 a);%0A
-    response.setContentLength(data.length);%0A
     
@@ -3383,22 +3383,16 @@
 writer =
-%0A     
  new Jso
@@ -3403,110 +3403,27 @@
 ter(
-new OutputStreamWriter(response.getOutputStream(),%0A                                            %22UTF-8%22
+response.getWriter(
 ));%0A
@@ -4014,24 +4014,97 @@
 alue(type);%0A
+    writer.name(%22checksum%22).value(Long.toHexString(node.getChecksum()));%0A
     writer.e
@@ -4383,32 +4383,32 @@
 ode.sessionID);%0A
-
     writer.name(
@@ -4425,24 +4425,97 @@
 lue(%22dir%22);%0A
+    writer.name(%22checksum%22).value(Long.toHexString(node.getChecksum()));%0A
     writer.n

Add checksum in response header/json