/* * Copyright 2015 GoDataDriven B.V. * * 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. */ package io.divolte.server.mincode; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.format.DataFormatDetector; import com.fasterxml.jackson.core.io.IOContext; import com.fasterxml.jackson.core.io.InputDecorator; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.After; import org.junit.Before; import org.junit.Test; import javax.annotation.ParametersAreNonnullByDefault; import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; import static org.junit.Assert.*; @ParametersAreNonnullByDefault public class MincodeFactoryTest { private static final String MINCODE = "sAn arbitrary string!"; private static final String EXPECTED_STRING = "An arbitrary string"; private ObjectMapper mincodeMapper; private Optional<Path> cleanupFile = Optional.empty(); @Before public void setUp() throws Exception { mincodeMapper = new ObjectMapper(new MincodeFactory()); } @After public void tearDown() throws Exception { mincodeMapper = null; cleanupFile.ifPresent(path -> { try { Files.deleteIfExists(path); } catch (final IOException e) { throw new UncheckedIOException("Error cleaning up temporary file", e); } cleanupFile = Optional.empty(); }); } private Path createTemporyFile(final String content) throws IOException { final Path tempFile = Files.createTempFile("mincodetest", "mc"); cleanupFile = Optional.of(tempFile); Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8)); return tempFile; } @Test public void testReadingFromString() throws Exception { assertEquals(EXPECTED_STRING, mincodeMapper.readValue(MINCODE, String.class)); } @Test public void testReadingFromByteArray() throws Exception { final byte[] bytes = MINCODE.getBytes(StandardCharsets.UTF_8); assertEquals(EXPECTED_STRING, mincodeMapper.readValue(bytes, String.class)); mincodeMapper.getFactory().setInputDecorator(INVERTING_DECORATOR); assertEquals(EXPECTED_STRING, mincodeMapper.readValue(invert(bytes), String.class)); } @Test public void testReadingFromPartOfByteArray() throws Exception { // Create an array with our bytes to decode in the middle somewhere. final byte[] needle = MINCODE.getBytes(StandardCharsets.UTF_8); final byte[] haystack = new byte[needle.length + 780]; System.arraycopy(needle, 0, haystack, 45, needle.length); // The actual test. assertEquals(EXPECTED_STRING, mincodeMapper.readValue(haystack, 45, needle.length, String.class)); } @Test public void testReadingFromReader() throws Exception { assertEquals(EXPECTED_STRING, mincodeMapper.readValue(new StringReader(MINCODE), String.class)); } @Test public void testReadingFromFile() throws Exception { final Path file = createTemporyFile(MINCODE); assertEquals(EXPECTED_STRING, mincodeMapper.readValue(file.toFile(), String.class)); } @Test public void testReadingFromUrl() throws Exception { final Path file = createTemporyFile(MINCODE); assertEquals(EXPECTED_STRING, mincodeMapper.readValue(file.toUri().toURL(), String.class)); } @Test public void testReadingFromInputStream() throws Exception { final Path file = createTemporyFile(MINCODE); try (final InputStream is = Files.newInputStream(file)) { assertEquals(EXPECTED_STRING, mincodeMapper.readValue(is, String.class)); } } @Test public void testReadingFromCharArray() throws Exception { final char[] chars = MINCODE.toCharArray(); final JsonFactory factory = mincodeMapper.getFactory(); assertEquals(EXPECTED_STRING, mincodeMapper.readValue(factory.createParser(chars), String.class)); factory.setInputDecorator(INVERTING_DECORATOR); assertEquals(EXPECTED_STRING, mincodeMapper.readValue(factory.createParser(invert(chars)), String.class)); } @Test public void testReadingFromPartOfCharArray() throws Exception { // Create an array with our characters to decode in the middle somewhere. final char[] needle = MINCODE.toCharArray(); final char[] haystack = new char[needle.length + 780]; System.arraycopy(needle, 0, haystack, 45, needle.length); final JsonParser parser = mincodeMapper.getFactory().createParser(haystack, 45, needle.length); assertEquals(EXPECTED_STRING, mincodeMapper.readValue(parser, String.class)); } @Test public void testDataDetector() throws Exception { final JsonFactory factory = mincodeMapper.getFactory(); final DataFormatDetector detector = new DataFormatDetector(factory); assertEquals(factory.getFormatName(), detector.findFormat(MINCODE.getBytes(StandardCharsets.UTF_8)).getMatchedFormatName()); // Zero-length mincode is invalid. assertFalse(detector.findFormat(new byte[0]).hasMatch()); // 'z' is not a valid record type. assertFalse(detector.findFormat("zoop".getBytes(StandardCharsets.UTF_8)).hasMatch()); // '.' is a valid record type, but not as the first record. assertFalse(detector.findFormat(".".getBytes(StandardCharsets.UTF_8)).hasMatch()); } private static byte invert(final byte b) { return (byte)(b ^ 0xff); } private static byte[] invert(final byte[] bytes) { for (int i = 0; i < bytes.length; ++i) { bytes[i] = invert(bytes[i]); } return bytes; } private static char[] invert(final char[] chars, final int offset, final int length) { for (int i = offset; i < offset + length; ++i) { chars[i] ^= 0xffff; } return chars; } private static char[] invert(final char[] chars) { return invert(chars, 0, chars.length); } private static final InputDecorator INVERTING_DECORATOR = new InputDecorator() { @Override public InputStream decorate(final IOContext ctxt, final InputStream in) throws IOException { return new InputStream() { @Override public int read() throws IOException { final int b = in.read(); return b != -1 ? invert((byte)b) : -1; } @Override public void close() throws IOException { in.close(); super.close(); } }; } @Override public InputStream decorate(final IOContext ctxt, final byte[] src, final int offset, final int length) throws IOException { return new ByteArrayInputStream(invert(src.clone())); } @Override public Reader decorate(final IOContext ctxt, final Reader r) throws IOException { return new Reader() { @Override public int read(final char[] cbuf, final int off, final int len) throws IOException { final int count = r.read(cbuf, off, len); invert(cbuf, off, len); return count; } @Override public void close() throws IOException { r.close(); } }; } }; }