package com.bazaarvoice.emodb.common.json;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParseException;
import com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import com.google.common.io.ByteSource;
import org.apache.http.MalformedChunkCodingException;
import org.apache.http.TruncatedChunkException;
import org.testng.annotations.Test;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

public class JsonStreamingArrayParserTest {

    @Test
    public void testParsing() {
        Iterator<Integer> iter = newParser(stream("[1,4,2,3]"), Integer.class);
        assertEquals((int) iter.next(), 1);
        assertEquals((int) iter.next(), 4);
        assertEquals((int) iter.next(), 2);
        assertEquals((int) iter.next(), 3);
        assertFalse(iter.hasNext());
    }

    @Test
    public void testMalformedJson() {
        // If the first character in the stream isn't valid (ie. '[') then assume we have a malformed response,
        // not that we encountered an early EOF.  It's an ambiguous situation, but the former is more likely.
        // Once we get past the first character it's likely that the rest is valid json (our server doesn't emit
        // invalid json!) so presume that parse exceptions are due to early EOF.
        try {
            newParser(stream(""), String.class);
            fail();
        } catch (RuntimeException e) {
            assertEquals(e.getClass(), RuntimeException.class);  // Should not be JsonStreamingEOFException
            assertTrue(e.getCause() instanceof JsonParseException);
        }

        try {
            newParser(stream("{\"key\":\"value\"}"), String.class);
            fail();
        } catch (RuntimeException e) {
            assertEquals(e.getClass(), RuntimeException.class);  // Should not be JsonStreamingEOFException
            assertTrue(e.getCause() instanceof JsonParseException);
        }
    }

    @Test
    public void testEOFException() {
        assertThrowsEOFException(stream("["), Integer.class);
        assertThrowsEOFException(stream("[5"), Integer.class);
        assertThrowsEOFException(stream("[5,"), Integer.class);
        assertThrowsEOFException(stream("[5,6"), Integer.class);
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testMalformedChunkException() throws Exception {
        ByteSource input = ByteSource.concat(
                ByteSource.wrap("[5,6".getBytes(Charsets.UTF_8)),
                exceptionStreamSupplier(new MalformedChunkCodingException("Bad chunk header")));
        assertThrowsEOFException(input.openStream(), Integer.class);
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testTruncatedChunkException() throws Exception {
        ByteSource input = ByteSource.concat(
                ByteSource.wrap("[5,6".getBytes(Charsets.UTF_8)),
                exceptionStreamSupplier(new TruncatedChunkException("Truncated chunk ( expected size: 3996; actual size: 1760)")));
        assertThrowsEOFException(input.openStream(), Integer.class);
    }

    @Test
    public void testJsonMapperException() throws Exception {
        assertThrowsProcessingException(stream("[\"seven\"]"), Integer.class);
    }

    @Test
    public void testJsonCreationException() throws Exception {
        assertThrowsProcessingException(stream("[{\"foo\":\"bar\"}]"), UncreatableObject.class);
    }

    public static class UncreatableObject {
        @JsonCreator
        public UncreatableObject(@JsonProperty("foo") String foo) {
            throw new IllegalArgumentException("always fails to create");
        }
    }

    private <T> void assertThrowsEOFException(InputStream in, Class<T> type) {
        assertThrowsException(in, type, JsonStreamingEOFException.class);
    }

    private <T> void assertThrowsProcessingException(InputStream in, Class<T> type) {
        assertThrowsException(in, type, JsonStreamProcessingException.class);
    }

    private <T, E extends Exception> void assertThrowsException(InputStream in, Class<T> type, Class<E> exceptionClass) {
        Iterator<T> iter = newParser(in, type);
        try {
            while (iter.hasNext()) {
                iter.next();
            }
            fail();
        } catch (Exception e) {
            if (!exceptionClass.isInstance(e)) {
                // Unexpected exception
                throw Throwables.propagate(e);
            }
        }
    }

    private <T> Iterator<T> newParser(InputStream in, Class<T> type) {
        return new JsonStreamingArrayParser<>(in, type);
    }

    private InputStream stream(String json) {
        return new ByteArrayInputStream(json.getBytes(Charsets.UTF_8));
    }

    private ByteSource exceptionStreamSupplier(final Throwable t) {
        return new ByteSource() {
            @Override
            public InputStream openStream() throws IOException {
                return exceptionStream(t);
            }
        };
    }

    private InputStream exceptionStream(final Throwable t) {
        return new InputStream() {
            @Override
            public int read() throws IOException {
                Throwables.propagateIfInstanceOf(t, IOException.class);
                throw Throwables.propagate(t);
            }
        };
    }
}

@@ -232,98 +232,18 @@
 ase.
-Charsets;%0Aimport com.google.common.base.Throwables;%0Aimport com.google.common.io.ByteSource
+Throwables
 ;%0Aim
@@ -488,20 +488,134 @@
 ava.
-util.Iterato
+io.SequenceInputStream;%0Aimport java.nio.charset.StandardCharsets;%0Aimport java.util.Iterator;%0Aimport java.util.function.Supplie
 r;%0A%0A
@@ -2604,43 +2604,8 @@
 est%0A
-    @SuppressWarnings(%22unchecked%22)%0A
     
@@ -2677,109 +2677,98 @@
     
-ByteSource input = ByteSource.concat(%0A                ByteSource.wrap(%22%5B5,6%22.getBytes(Charsets.UTF_8)
+Supplier%3CInputStream%3E input = () -%3E new SequenceInputStream(%0A                stream(%22%5B5,6%22
 ),%0A 
@@ -2793,32 +2793,24 @@
 eptionStream
-Supplier
 (new Malform
@@ -2852,16 +2852,25 @@
 eader%22))
+%0A        
 );%0A     
@@ -2899,34 +2899,27 @@
 ption(input.
-openStream
+get
 (), Integer.
@@ -2947,43 +2947,8 @@
 est%0A
-    @SuppressWarnings(%22unchecked%22)%0A
     
@@ -3020,109 +3020,98 @@
     
-ByteSource input = ByteSource.concat(%0A                ByteSource.wrap(%22%5B5,6%22.getBytes(Charsets.UTF_8)
+Supplier%3CInputStream%3E input = () -%3E new SequenceInputStream(%0A                stream(%22%5B5,6%22
 ),%0A 
@@ -3140,24 +3140,16 @@
 onStream
-Supplier
 (new Tru
@@ -3230,16 +3230,25 @@
 1760)%22))
+%0A        
 );%0A     
@@ -3281,26 +3281,19 @@
 n(input.
-openStream
+get
 (), Inte
@@ -4691,24 +4691,31 @@
     private 
+static 
 %3CT%3E Iterator
@@ -4829,32 +4829,39 @@
  %7D%0A%0A    private 
+static 
 InputStream stre
@@ -4932,16 +4932,24 @@
 etBytes(
+Standard
 Charsets
@@ -4981,271 +4981,14 @@
 ate 
-ByteSource exceptionStreamSupplier(final Throwable t) %7B%0A        return new ByteSource() %7B%0A            @Override%0A            public InputStream openStream() throws IOException %7B%0A                return exceptionStream(t);%0A            %7D%0A        %7D;%0A    %7D%0A%0A    private
+static
  Inp

rewrite JsonStreamingArrayParserTest to use java.util.function.Supplier (#314)