# HG changeset patch # User chegar # Date 1516130074 0 # Node ID 08b6eca8daaebfa7fff0c5c84bd61bc621a44923 # Parent 4f099d4d70b3498dae13fd73d185035f2d4e74ae http-client-tutorial: first cut diff -r 4f099d4d70b3 -r 08b6eca8daae .idea/.name --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.idea/.name Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,1 @@ +http-client-tutorial \ No newline at end of file diff -r 4f099d4d70b3 -r 08b6eca8daae .idea/compiler.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.idea/compiler.xml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff -r 4f099d4d70b3 -r 08b6eca8daae .idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_8_0.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_8_0.xml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff -r 4f099d4d70b3 -r 08b6eca8daae .idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_8_9.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_8_9.xml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff -r 4f099d4d70b3 -r 08b6eca8daae .idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_8_9.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_8_9.xml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff -r 4f099d4d70b3 -r 08b6eca8daae .idea/libraries/Maven__junit_junit_4_11.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.idea/libraries/Maven__junit_junit_4_11.xml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff -r 4f099d4d70b3 -r 08b6eca8daae .idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff -r 4f099d4d70b3 -r 08b6eca8daae .idea/misc.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.idea/misc.xml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff -r 4f099d4d70b3 -r 08b6eca8daae .idea/modules.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.idea/modules.xml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff -r 4f099d4d70b3 -r 08b6eca8daae .idea/vcs.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.idea/vcs.xml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff -r 4f099d4d70b3 -r 08b6eca8daae .idea/workspace.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.idea/workspace.xml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,664 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + localhost + 5050 + + + + + + $USER_HOME$/.subversion + + + + + 1516127026428 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No facets are configured + + + + + + + + testng + + + + + + + + 1.8 + + + + + + + + http-tutorial + + + + + + + + 9 + + + + + + + + Maven: org.hamcrest:hamcrest-core:1.3 + + + + + + + + \ No newline at end of file diff -r 4f099d4d70b3 -r 08b6eca8daae http-tutorial.iml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/http-tutorial.iml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff -r 4f099d4d70b3 -r 08b6eca8daae pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pom.xml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,56 @@ + + + 4.0.0 + + org.openjdk.http.tutorial + http-tutorial + 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 9 + 9 + --add-modules=jdk.incubator.httpclient + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20.1 + + --add-modules=jdk.incubator.httpclient + + + + + + + + + junit + junit + 4.11 + test + + + + + + + junit + junit + + + com.fasterxml.jackson.core + jackson-databind + 2.8.9 + + + + diff -r 4f099d4d70b3 -r 08b6eca8daae src/main/java/openjdk/http/tutorial/exercise1/Retrievals.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/java/openjdk/http/tutorial/exercise1/Retrievals.java Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package openjdk.http.tutorial.exercise1; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jdk.incubator.http.HttpClient; +import jdk.incubator.http.HttpRequest; +import jdk.incubator.http.HttpResponse; +import jdk.incubator.http.HttpResponse.BodyHandler; + +import static java.lang.System.out; +import static java.util.stream.Collectors.joining; +import static jdk.incubator.http.HttpClient.Version.*; +import static jdk.incubator.http.HttpResponse.BodyHandler.*; + +/** + * @author Chris Hegarty + */ +public class Retrievals { + + /** + * Exercise 1. + * + * Retrieve the response status code from a request to the given + * URI. The returned response code will be a int. + * + * Hint: use the discard BodyHandler since the response body is not + * interesting. + * + * Hint: static imports reduce boilerplate when using BodyHandlers + * and BodyProcessors, e.g. import static + * jdk.incubator.http.HttpResponse.BodyHandler.discard + */ + public static int retrieveTheStatusCode(URI uri) + throws IOException, InterruptedException + { + HttpClient client = HttpClient.newBuilder().version(HTTP_1_1).build(); + HttpRequest request = HttpRequest.newBuilder(uri).GET().build(); + HttpResponse response = client.send(request, discard(null)); + + return response.statusCode(); + } + + /** + * Exercise 1. + * + * Retrieve the response body from a given URI. Return the response + * body as a String. + * + * Hint: use the asString BodyHandler to convert the HTTP response + * body to a String. + * + * Hint: static imports reduce boilerplate when using BodyHandlers + * and BodyProcessors, e.g. import static + * jdk.incubator.http.HttpResponse.BodyHandler.asString + */ + public static String retrieveResourceAsString(URI uri) + throws IOException, InterruptedException + { + HttpClient client = HttpClient.newBuilder().version(HTTP_1_1).build(); + HttpRequest request = HttpRequest.newBuilder(uri).GET().build(); + HttpResponse response = client.send(request, asString()); + + return response.body(); + } + + /** + * Exercise 2. + * + * Retrieve the response body from a given URI, streaming teh body + * out to a file. Return the file's Path. + * + * Hint: use {@linkplain BodyHandler#asFile} to stream the HTTP + * response body to a file. + */ + public static Path retrieveResourceAsFile(URI uri) + throws IOException, InterruptedException + { + HttpClient client = HttpClient.newBuilder().build(); + HttpRequest request = HttpRequest.newBuilder() + .uri(uri) + .version(HTTP_1_1) + .GET() + .build(); + HttpResponse response = client.send(request, + asFile(Paths.get("retrieveResourceAsFile.txt"), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)); + + System.out.println("CHEGAR: sc=" + response.statusCode()); + System.out.println("CHEGAR: by=" + Files.readAllLines(response.body()).stream().collect(joining())); + + return response.body(); + } + + + /** + * Asserts that the response code is 200 ( OK ). + * + * Can be used in CompletableFuture pipelines when checking the + * response of an {@linkplain HttpClient#sendAsync} call. For + * example: + * client.sendAsync(request, bodyHandler) + * .thenApply(Retrievals::require200StatusCode) + * .thenApply(...) + * + */ + public static HttpResponse require200StatusCode(HttpResponse response) { + int sc = response.statusCode(); + if (sc != 200) { + IOException e = new IOException("Expected 200, got: " + sc); + throw new UncheckedIOException(e); + } + return response; + } + + /** + * Exercise 3. + * + * Retrieve the response body from a given URI, using the + * asynchronous send API, sendAsync. Return a CompletableFuture that + * completes with the response body as a String. + * + * Hint: The {@linkplain CompletableFuture#thenApply(Function)} + * method can be used to map the HttpResponse to a String. + */ + public static CompletableFuture retrieveResourceAsStringUsingAsyncAPI(URI uri) { + return HttpClient.newHttpClient() + .sendAsync(HttpRequest.newBuilder(uri).version(HTTP_1_1).build(), asString()) // TODO: why version needed? + .thenApply(Retrievals::require200StatusCode) + .thenApply(HttpResponse::body); + } + + + /** + * Wrapper around Jackson's ObjectMapper that provides an unchecked + * {@code readValue}, what can be used to help solve the next + * exercise, 4. + */ + public static class UncheckedObjectMapper extends ObjectMapper { + + /** Parses the given JSON string into a Map. */ + Map readValue(String content) { + try { + return this.readValue(content, new TypeReference<>(){}); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + } + + /** + * Exercise 4. + * + * Retrieve the response body from a given URI. The response body + * will be in the JSON format. The Jackson based UncheckedObjectMapper + * ( above ) can be used to parse the String response body into a + * Map. + * + * Hint: The asynchronous send API will allow construction of a + * pipeline of CompletableFutures. + * + * Hint: The {@linkplain CompletableFuture#thenApply(Function)} + * method can be used to map the HttpResponse to a String, and then + * again from a Sting to a Map ( via the object mapper ). + */ + public CompletableFuture> JSONBodyAsMap(URI uri) { + UncheckedObjectMapper objectMapper = new UncheckedObjectMapper(); + return HttpClient.newHttpClient() + .sendAsync(HttpRequest.newBuilder(uri).version(HTTP_1_1).build(), asString()) + .thenApply(HttpResponse::body) + .thenApply(objectMapper::readValue); + } + + + + /** + * Post the given {@code data}, and receive the same data in + * response. Return the response body data as a String. + */ + public static String postData(URI uri, String data) + throws IOException, InterruptedException + { + HttpClient client = HttpClient.newBuilder().build(); + HttpRequest request = HttpRequest.newBuilder() + .uri(uri) + .version(HTTP_1_1) + .POST(HttpRequest.BodyProcessor.fromString(data)) + .build(); + HttpResponse response = client.send(request, asString()); + + return response.body(); + } + + + // JSON processor +} diff -r 4f099d4d70b3 -r 08b6eca8daae src/main/main.iml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/main.iml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff -r 4f099d4d70b3 -r 08b6eca8daae src/test/java/openjdk/http/tutorial/Exercise1Test.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/test/java/openjdk/http/tutorial/Exercise1Test.java Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package openjdk.http.tutorial; + +/** + * @author Chris Hegarty + */ + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jdk.incubator.http.HttpClient; +import jdk.incubator.http.HttpRequest; +import jdk.incubator.http.HttpResponse; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import openjdk.http.tutorial.exercise1.Retrievals; +import openjdk.http.tutorial.exercise1.Retrievals.UncheckedObjectMapper; +import static java.lang.System.out; +import static java.nio.file.StandardOpenOption.*; +import static java.util.stream.Collectors.joining; +import static jdk.incubator.http.HttpClient.Version.*; +import static jdk.incubator.http.HttpResponse.BodyHandler.asFile; +import static jdk.incubator.http.HttpResponse.BodyHandler.asString; +import static jdk.incubator.http.HttpResponse.BodyHandler.discard; +import static org.junit.Assert.assertEquals; + +public class Exercise1Test { + + @Test + public void retrieveTheStatusCode() + throws IOException, InterruptedException + { + URI uri = URI.create("http://httpbin.org/get"); + + int expectedStatusCode = statusCode(uri); + int actualStatusCode = Retrievals.retrieveTheStatusCode(uri); + + assertEquals("Unexpected status code", + expectedStatusCode, + actualStatusCode); + } + + @Test + public void retrieveResourceAsString() + throws IOException, InterruptedException + { + URI uri = URI.create("http://httpbin.org/get"); + + String expectedResponseBody = bodyAsString(uri); + String actualResponseBody = Retrievals.retrieveResourceAsString(uri); + + assertEquals("Unexpected response body", + expectedResponseBody, + actualResponseBody); + } + + @Test + public void retrieveResourceAsFile() + throws IOException, InterruptedException + { + URI uri = URI.create("http://httpbin.org/get"); + + Path expectedResponseBodyFile = bodyAsFile(uri); + Path actualResponseBodyFile = Retrievals.retrieveResourceAsFile(uri); + byte[] b1 = Files.readAllBytes(expectedResponseBodyFile); + byte[] b2 = Files.readAllBytes(actualResponseBodyFile); + + System.out.println("CHEGAR b1 = " + new String(b1)); + System.out.println("CHEGAR b2 = " + new String(b2)); + + Assert.assertArrayEquals("Unexpected response body", b1, b2); + } + + @Test + public void retrieveResourceAsStringUsingAsyncAPI() + throws IOException, InterruptedException + { + URI uri = URI.create("http://httpbin.org/get"); + + String expectedResponseBody = bodyAsString(uri); + String actualResponseBody = + Retrievals.retrieveResourceAsStringUsingAsyncAPI(uri).join(); + + assertEquals("Unexpected response body", + expectedResponseBody, + actualResponseBody); + } + +// @Test +// public void JSONBodyAsMap() { +// throws IOException, InterruptedException +// URI uri = URI.create("http://httpbin.org/get"); +// +// UncheckedObjectMapper objectMapper = new UncheckedObjectMapper(); +// String expectedResponseBody = objectMapper(bodyAsString(uri)); +// String actualResponseBody = +// Retrievals.retrieveResourceAsStringUsingAsyncAPI(uri).join(); +// +// assertEquals("Unexpected response body", +// expectedResponseBody, +// actualResponseBody); +// } + + + + + + //@Test + public void postData() + throws IOException, InterruptedException + { + URI uri = URI.create("http://httpbin.org/post"); + String message = "Hello there!"; + + String actualResponseBody = Retrievals.postData(uri, message); + String expectedResponseBody = message; + + assertEquals("Unexpected response body", + expectedResponseBody, + actualResponseBody); + } + + /** Wrapper around Jackson's ObjectMapper that provides unchecked readValue. */ + static class UncheckedObjectMapper extends ObjectMapper{ + + Map readValue(String content) { + try { + return this.readValue(content, new TypeReference<>(){}); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + } + + private UncheckedObjectMapper objectMapper = new UncheckedObjectMapper(); + + + /** + * The Echo JSON service on echo.jsontest.com returns a customized + * JSON object that you can define through a REST-style URL. For + * example, calling http://echo.jsontest.com/key/value/one/two + * will return the following JSON: + * + * { + * “one”: “two”, + * “key”: “value” + * } + */ + @Test + public void bodyAsJSON() { + String[] pairs = new String[] { + "Name", "chegar", + "Country", "Ireland", + "Citizenship", "Irish" + }; + String path = Arrays.stream(pairs).collect(joining("/")); + URI uri = URI.create("http://echo.jsontest.com/" + path); + + HttpClient client = HttpClient.newBuilder().build(); + HttpRequest request = HttpRequest.newBuilder(uri).GET().build(); + client.sendAsync(request, asString()) + .thenCompose(response -> { // maps HttpResponse to String + assertEquals(response.statusCode(), 200); + return CompletableFuture.completedFuture(response.body()); }) + .thenAccept(body -> { // consumes the response body + out.println("received: " + body); + Map map = objectMapper.readValue(body); + + assertEquals(map.get("Name"), "chegar"); + assertEquals(map.get("Country"), "Ireland"); + assertEquals(map.get("Citizenship"), "Irish"); }) + .join(); + } + + + // ---- some trivial infrastructure to help output messages + + @Rule + public TestRule watcher = new TestWatcher() { + @Override + protected void starting(Description description) { + out.println("\nStarting test: " + description.getMethodName()); + } + @Override + protected void finished(Description description) { + out.println("Finished test: " + description.getMethodName()); + } + @Override + protected void failed(Throwable e, Description description) { + e.printStackTrace(); + } + }; + + + + // ---- demonstration code below + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + interface Peeker extends Function + { + void peek(T t); + + default T apply(T t) + { + peek(t); + return t; + } + } + + static void assertStatusCode200(HttpResponse response) { + assertEquals(200, response.statusCode()); + } + + private static int statusCode(URI uri) { + return HttpClient.newBuilder().version(HTTP_1_1).build() + .sendAsync(HttpRequest.newBuilder(uri).build(), discard(null)) + .thenApply((Peeker>)Exercise1Test::assertStatusCode200) + .thenApply(HttpResponse::statusCode) + .join(); + } + + private static String bodyAsString(URI uri) { + return HttpClient.newBuilder().version(HTTP_1_1).build() + .sendAsync(HttpRequest.newBuilder(uri).build(), asString()) + .thenApply((Peeker>)Exercise1Test::assertStatusCode200) + .thenApply(HttpResponse::body) + .join(); + } + +// private static String bodyAsString(URI uri) +// throws IOException, InterruptedException +// { +// HttpClient client = HttpClient.newBuilder().build(); +// HttpRequest request = HttpRequest.newBuilder(uri) +// .version(HttpClient.Version.HTTP_1_1) +// .GET() +// .build(); +// HttpResponse response = client.send(request, asString()); +// +// Assert.assertEquals(200, response.statusCode()); +// +// return response.body(); +// } + + private static Path bodyAsFile(URI uri) + throws IOException, InterruptedException + { + HttpClient client = HttpClient.newBuilder().build(); + HttpRequest request = HttpRequest.newBuilder(uri) + .version(HttpClient.Version.HTTP_1_1) + .GET() + .build(); + Path p = Paths.get("Exercise1Test_bodyAsFile.txt"); + HttpResponse response = client.send(request, + asFile(p, TRUNCATE_EXISTING, WRITE)); + + Assert.assertEquals(200, response.statusCode()); + + return response.body(); + } + + private static String postDataGetResponseBody(URI uri, String data) + throws IOException, InterruptedException + { + HttpClient client = HttpClient.newBuilder().build(); + HttpRequest request = HttpRequest.newBuilder() + .uri(uri) + .version(HttpClient.Version.HTTP_1_1) + .POST(HttpRequest.BodyProcessor.fromString(data)) + .build(); + HttpResponse response = client.send(request, asString()); + + ObjectMapper objectMapper = new ObjectMapper(); + Map map = objectMapper.readValue(response.body(), new TypeReference<>(){}); + + System.out.println("CHEGAR map: " + map); + + //JSONObject json = new JSONObject(myResponse); + + Assert.assertEquals(200, response.statusCode()); + + return response.body(); + } +} diff -r 4f099d4d70b3 -r 08b6eca8daae src/test/test.iml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/test/test.iml Tue Jan 16 19:14:34 2018 +0000 @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file