diff --git a/pom.xml b/pom.xml index 52f24a500603..42ae2626cc72 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ 4.0.0 com.iluwatar java-design-patterns - 1.14.0-SNAPSHOT + 1.15.0-SNAPSHOT pom 2014 @@ -50,6 +50,7 @@ abstract-factory + tls builder factory-method prototype @@ -466,4 +467,4 @@ - \ No newline at end of file + diff --git a/tls/README.md b/tls/README.md new file mode 100644 index 000000000000..904d7ee054e7 --- /dev/null +++ b/tls/README.md @@ -0,0 +1,22 @@ +--- +layout: pattern +title: Thread Local Storage +folder: tls +permalink: /patterns/tls/ +pumlid: +categories: Concurrency +tags: + - Java + - Difficulty-Intermediate +--- + +## Intent +Securing variables global to a thread against being spoiled by other threads. That is needed if you use class variables or static variables in your Callable object or Runnable object that are not read-only. + +![alt text](./etc/tls.png "Thread Local Storage") + +## Applicability +Use the Thread Local Storage in any of the following situations + +* when you use class variables in your Callable / Runnalbe object that are not read-only and you use the same Callable instance in more than one thread running in parallel +* when you use static variables in your Callable / Runnable object that are not read-only and more than one instances of the Callable / Runnalbe may run in parallel threads. diff --git a/tls/etc/tls.png b/tls/etc/tls.png new file mode 100644 index 000000000000..442f39a0c730 Binary files /dev/null and b/tls/etc/tls.png differ diff --git a/tls/etc/tls.ucls b/tls/etc/tls.ucls new file mode 100644 index 000000000000..bd238346d0be --- /dev/null +++ b/tls/etc/tls.ucls @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tls/etc/tls.urm.puml b/tls/etc/tls.urm.puml new file mode 100644 index 000000000000..d2eedb371c1a --- /dev/null +++ b/tls/etc/tls.urm.puml @@ -0,0 +1,23 @@ +@startuml +package com.iluwatar.tls { + class App { + + App() + + main(args : String[]) {static} + - printAndCountDates(res : Result) : int {static} + - printAndCountExceptions(res : Result) : int {static} + } + class DateFormatCallable { + - dateValue : String + - df : ThreadLocal + + DateFormatCallable(inDateFormat : String, inDateValue : String) + + call() : Result + } + class Result { + - dateList : List + - exceptionList : List + + Result() + + getDateList() : List + + getExceptionList() : List + } +} +@enduml \ No newline at end of file diff --git a/tls/pom.xml b/tls/pom.xml new file mode 100644 index 000000000000..fa8bf6860c44 --- /dev/null +++ b/tls/pom.xml @@ -0,0 +1,43 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.15.0-SNAPSHOT + + tls + + + junit + junit + test + + + diff --git a/tls/src/main/java/com/iluwatar/tls/App.java b/tls/src/main/java/com/iluwatar/tls/App.java new file mode 100644 index 000000000000..634d36d26b90 --- /dev/null +++ b/tls/src/main/java/com/iluwatar/tls/App.java @@ -0,0 +1,146 @@ +/** + * The MIT License + * Copyright (c) 2016 Thomas Bauer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.tls; + +import java.util.Calendar; +import java.util.Date; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + * ThreadLocal pattern + *

+ * This App shows how to create an isolated space per each thread. In this + * example the usage of SimpleDateFormat is made to be thread-safe. This is an + * example of the ThreadLocal pattern. + *

+ * By applying the ThreadLocal pattern you can keep track of application + * instances or locale settings throughout the handling of a request. The + * ThreadLocal class works like a static variable, with the exception that it is + * only bound to the current thread! This allows us to use static variables in a + * thread-safe way. + *

+ * In Java, thread-local variables are implemented by the ThreadLocal class + * object. ThreadLocal holds a variable of type T, which is accessible via get/set + * methods. + *

+ * SimpleDateFormat is one of the basic Java classes and is not thread-safe. If + * you do not isolate the instance of SimpleDateFormat per each thread then + * problems arise. + *

+ * App converts the String date value 15/12/2015 to the Date format using the + * Java class SimpleDateFormat. It does this 20 times using 4 threads, each doing + * it 5 times. With the usage of as ThreadLocal in DateFormatCallable everything + * runs well. But if you comment out the ThreadLocal variant (marked with "//TLTL") + * and comment in the non ThreadLocal variant (marked with "//NTLNTL") you can + * see what will happen without the ThreadLocal. Most likely you will get incorrect + * date values and / or exceptions. + *

+ * This example clearly show what will happen when using non thread-safe classes + * in a thread. In real life this may happen one in of 1.000 or 10.000 conversions + * and those are really hard to find errors. + * + * @author Thomas Bauer, 2017 + */ +public class App { + /** + * Program entry point + * + * @param args + * command line args + */ + public static void main(String[] args) { + int counterDateValues = 0; + int counterExceptions = 0; + + // Create a callable + DateFormatCallable callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); + // start 4 threads, each using the same Callable instance + ExecutorService executor = Executors.newCachedThreadPool(); + + Future futureResult1 = executor.submit(callableDf); + Future futureResult2 = executor.submit(callableDf); + Future futureResult3 = executor.submit(callableDf); + Future futureResult4 = executor.submit(callableDf); + try { + Result[] result = new Result[4]; + result[0] = futureResult1.get(); + result[1] = futureResult2.get(); + result[2] = futureResult3.get(); + result[3] = futureResult4.get(); + + // Print results of thread executions (converted dates and raised exceptions) + // and count them + for (int i = 0; i < result.length; i++) { + counterDateValues = counterDateValues + printAndCountDates(result[i]); + counterExceptions = counterExceptions + printAndCountExceptions(result[i]); + } + + // a correct run should deliver 20 times 15.12.2015 + // and a correct run shouldn't deliver any exception + System.out.println("The List dateList contains " + counterDateValues + " date values"); + System.out.println("The List exceptionList contains " + counterExceptions + " exceptions"); + + } catch (Exception e) { + System.out.println("Abnormal end of program. Program throws exception: " + e); + } + executor.shutdown(); + } + + /** + * Print result (date values) of a thread execution and count dates + * + * @param res contains results of a thread execution + */ + private static int printAndCountDates(Result res) { + // a correct run should deliver 5 times 15.12.2015 per each thread + int counter = 0; + for (Date dt : res.getDateList()) { + counter++; + Calendar cal = Calendar.getInstance(); + cal.setTime(dt); + // Formatted output of the date value: DD.MM.YYYY + System.out.println( + cal.get(Calendar.DAY_OF_MONTH) + "." + cal.get(Calendar.MONTH) + "." + +cal.get(Calendar.YEAR)); + } + return counter; + } + + /** + * Print result (exceptions) of a thread execution and count exceptions + * + * @param res contains results of a thread execution + * @return number of dates + */ + private static int printAndCountExceptions(Result res) { + // a correct run shouldn't deliver any exception + int counter = 0; + for (String ex : res.getExceptionList()) { + counter++; + System.out.println(ex); + } + return counter; + } +} diff --git a/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java b/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java new file mode 100644 index 000000000000..a28e24d4e762 --- /dev/null +++ b/tls/src/main/java/com/iluwatar/tls/DateFormatCallable.java @@ -0,0 +1,98 @@ +/** + * The MIT License + * Copyright (c) 2016 Thomas Bauer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.tls; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.concurrent.Callable; + +/** + * DateFormatCallable converts string dates to a date format using + * SimpleDateFormat. The date format and the date value will be passed to the + * Callable by the constructor. The constructor creates a instance of + * SimpleDateFormat and stores it in a ThreadLocal class variable. For the + * complete description of the example see {@link App} + * + * You can comment out the code marked with //TLTL and comment in the + * code marked //NTLNTL. Then you can see what will happen if you do not + * use the ThreadLocal. For details see the description of {@link App} + * + * @author Thomas Bauer, 2017 + */ +public class DateFormatCallable implements Callable { + // class variables (members) + private ThreadLocal df; //TLTL + // private DateFormat df; //NTLNTL + + private String dateValue; // for dateValue Thread Local not needed + + + /** + * The date format and the date value are passed to the constructor + * + * @param inDateFormat + * string date format string, e.g. "dd/MM/yyyy" + * @param inDateValue + * string date value, e.g. "21/06/2016" + */ + public DateFormatCallable(String inDateFormat, String inDateValue) { + final String idf = inDateFormat; //TLTL + this.df = new ThreadLocal() { //TLTL + @Override //TLTL + protected DateFormat initialValue() { //TLTL + return new SimpleDateFormat(idf); //TLTL + } //TLTL + }; //TLTL + // this.df = new SimpleDateFormat(inDateFormat); //NTLNTL + this.dateValue = inDateValue; + } + + /** + * @see java.util.concurrent.Callable#call() + */ + @Override + public Result call() { + System.out.println(Thread.currentThread() + " started executing..."); + Result result = new Result(); + + // Convert date value to date 5 times + for (int i = 1; i <= 5; i++) { + try { + // this is the statement where it is important to have the + // instance of SimpleDateFormat locally + // Create the date value and store it in dateList + result.getDateList().add(this.df.get().parse(this.dateValue)); //TLTL +// result.getDateList().add(this.df.parse(this.dateValue)); //NTLNTL + } catch (Exception e) { + // write the Exception to a list and continue work + result.getExceptionList().add(e.getClass() + ": " + e.getMessage()); + } + + } + + System.out.println(Thread.currentThread() + " finished processing part of the thread"); + + return result; + } +} diff --git a/tls/src/main/java/com/iluwatar/tls/Result.java b/tls/src/main/java/com/iluwatar/tls/Result.java new file mode 100644 index 000000000000..951a02a2fe0a --- /dev/null +++ b/tls/src/main/java/com/iluwatar/tls/Result.java @@ -0,0 +1,40 @@ +/* + * Fiducia IT AG, All rights reserved. Use is subject to license terms. + */ + +package com.iluwatar.tls; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Result object that will be returned by the Callable {@link DateFormatCallable} + * used in {@link App} + * + * @author Thomas Bauer, 2017 + */ +public class Result { + // A list to collect the date values created in one thread + private List dateList = new ArrayList(); + + // A list to collect Exceptions thrown in one threads (should be none in + // this example) + private List exceptionList = new ArrayList(); + + /** + * + * @return List of date values collected within an thread execution + */ + public List getDateList() { + return dateList; + } + + /** + * + * @return List of exceptions thrown within an thread execution + */ + public List getExceptionList() { + return exceptionList; + } +} diff --git a/tls/src/test/java/com/iluwatar/tls/AppTest.java b/tls/src/test/java/com/iluwatar/tls/AppTest.java new file mode 100644 index 000000000000..073c0988a850 --- /dev/null +++ b/tls/src/test/java/com/iluwatar/tls/AppTest.java @@ -0,0 +1,40 @@ +/** + * The MIT License + * Copyright (c) 2016 Thomas Bauer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.tls; + +import org.junit.Test; + +/** + * Tests that thread local storage example runs without errors. + * + * @author Thomas Bauer, January 2017 + * + */ +public class AppTest { + @Test + public void test() throws Exception { + String[] args = {}; + App.main(args); + } +} diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java new file mode 100644 index 000000000000..b4f24be9bc7d --- /dev/null +++ b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTest.java @@ -0,0 +1,144 @@ +/** + * The MIT License + * Copyright (c) 2016 Thomas Bauer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.tls; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * + * Test of the Callable + * + * In this test {@link DateFormatCallable} is tested with only one thread (i.e. without concurrency situation) + *

+ * After a successful run 5 date values should be in the result object. All dates should have + * the same value (15.11.2015). To avoid problems with time zone not the date instances themselves + * are compared by the test. For the test the dates are converted into string format DD.MM.YYY + *

+ * Additionally the number of list entries are tested for both the list with the date values + * and the list with the exceptions + * + * @author Thomas Bauer, January 2017 + * + */ +public class DateFormatCallableTest { + + // Class variables used in setup() have to be static because setup() has to be static + /** + * Result object given back by DateFormatCallable + * -- Array with converted date values + * -- Array with thrown exceptions + */ + static Result result; + + /** + * The date values created by the run of of DateFormatRunnalbe. List will be filled in the setup() method + */ + static List createdDateValues = new ArrayList(); + + /** + * Expected number of date values in the date value list created by the run of DateFormatRunnalbe + */ + int expectedCounterDateValues = 5; + + /** + * Expected number of exceptions in the exception list created by the run of DateFormatRunnalbe. + */ + int expectedCounterExceptions = 0; + + /** + * Expected content of the list containing the date values created by the run of DateFormatRunnalbe + */ + List expectedDateValues = Arrays.asList("15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015"); + + /** + * Run Callable and prepare results for usage in the test methods + */ + @BeforeClass + public static void setup() { + // Create a callable + DateFormatCallable callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); + // start thread using the Callable instance + ExecutorService executor = Executors.newCachedThreadPool(); + Future futureResult = executor.submit(callableDf); + try { + result = futureResult.get(); + createdDateValues = convertDatesToString(result); + } catch (Exception e) { + fail("Setup failed: " + e); + } + executor.shutdown(); + } + + private static List convertDatesToString(Result res) { + // Format date value as DD.MM.YYYY + if (res == null || res.getDateList() == null || res.getDateList().size() == 0) { + return null; + } + List returnList = new ArrayList(); + + for (Date dt : res.getDateList()) { + Calendar cal = Calendar.getInstance(); + cal.setTime(dt); + returnList.add(cal.get(Calendar.DAY_OF_MONTH) + "." + cal.get(Calendar.MONTH) + "." + cal.get(Calendar.YEAR)); + } + return returnList; + } + + /** + * Test date values after the run of DateFormatRunnalbe. A correct run should deliver 5 times 15.12.2015 + */ + @Test + public void testDateValues() { + assertEquals(expectedDateValues, createdDateValues); + } + + /** + * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should deliver 5 date values + */ + @Test + public void testCounterDateValues() { + assertEquals(expectedCounterDateValues, result.getDateList().size()); + } + + /** + * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should deliver + * no exceptions + */ + @Test + public void testCounterExceptions() { + assertEquals(expectedCounterExceptions, result.getExceptionList().size()); + } +} diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java new file mode 100644 index 000000000000..e0a1507e91fb --- /dev/null +++ b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestIncorrectDateFormat.java @@ -0,0 +1,127 @@ +/** + * The MIT License + * Copyright (c) 2016 Thomas Bauer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.tls; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * + * Test of the Callable + * + * In this test {@link DateFormatCallable} is tested with only one thread (i.e. without concurrency situation) + *

+ * An incorrect formatted date is passed to the Callable + * After a successful run 0 date values and 5 exceptions should be in the result object. + * + * @author Thomas Bauer, January 2017 + * + */ +public class DateFormatCallableTestIncorrectDateFormat { + + // Class variables used in setup() have to be static because setup() has to be static + /** + * Result object given back by DateFormatCallable + * -- Array with converted date values + * -- Array with thrown exceptions + */ + static Result result; + + /** + * The date values created by the run of DateFormatRunnalbe. List will be filled in the setup() method + */ + static List createdExceptions = new ArrayList(); + + /** + * Expected number of date values in the date value list created by the run of DateFormatRunnalbe + */ + int expectedCounterDateValues = 0; + + /** + * Expected number of exceptions in the exception list created by the run of DateFormatRunnalbe. + */ + int expectedCounterExceptions = 5; + + /** + * Expected content of the list containing the exceptions created by the run of DateFormatRunnalbe + */ + List expectedExceptions = Arrays.asList("class java.text.ParseException: Unparseable date: \"15.12.2015\"", + "class java.text.ParseException: Unparseable date: \"15.12.2015\"", + "class java.text.ParseException: Unparseable date: \"15.12.2015\"", + "class java.text.ParseException: Unparseable date: \"15.12.2015\"", + "class java.text.ParseException: Unparseable date: \"15.12.2015\""); + + /** + * Run Callable and prepare results for usage in the test methods + */ + @BeforeClass + public static void setup() { + // Create a callable. Pass a string date value not matching the format string + DateFormatCallable callableDf = new DateFormatCallable("dd/MM/yyyy", "15.12.2015"); + // start thread using the Callable instance + ExecutorService executor = Executors.newCachedThreadPool(); + Future futureResult = executor.submit(callableDf); + try { + result = futureResult.get(); + } catch (Exception e) { + fail("Setup failed: " + e); + } + executor.shutdown(); + } + + /** + * Test Exceptions after the run of DateFormatRunnalbe. A correct run should deliver 5 times the + * same exception + */ + @Test + public void testExecptions() { + assertEquals(expectedExceptions, result.getExceptionList()); + } + + /** + * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should deliver no date values + */ + @Test + public void testCounterDateValues() { + assertEquals(expectedCounterDateValues, result.getDateList().size()); + } + + /** + * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should + * deliver 5 exceptions + */ + @Test + public void testCounterExceptions() { + assertEquals(expectedCounterExceptions, result.getExceptionList().size()); + } +} diff --git a/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java new file mode 100644 index 000000000000..635d6f25ad93 --- /dev/null +++ b/tls/src/test/java/com/iluwatar/tls/DateFormatCallableTestMultiThread.java @@ -0,0 +1,164 @@ +/** + * The MIT License + * Copyright (c) 2016 Thomas Bauer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.tls; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * + * Test of the Callable + * + * In this test {@link DateFormatCallable} is used by 4 threads in parallel + *

+ * After a successful run 5 date values should be in the result object of each thread. All dates + * should have the same value (15.11.2015). To avoid problems with time zone not the date instances + * themselves are compared by the test. For the test the dates are converted into string format DD.MM.YYY + *

+ * Additionally the number of list entries are tested for both the list with the date values + * and the list with the exceptions + * + * @author Thomas Bauer, January 2017 + * + */ +public class DateFormatCallableTestMultiThread { + + // Class variables used in setup() have to be static because setup() has to be static + /** + * Result object given back by DateFormatCallable, one for each thread + * -- Array with converted date values + * -- Array with thrown exceptions + */ + static Result[] result = new Result[4]; + + /** + * The date values created by the run of of DateFormatRunnalbe. List will be filled in the setup() method + */ + @SuppressWarnings("serial") + static class StringArrayList extends ArrayList { + /* nothing needed here */ + } + static List[] createdDateValues = new StringArrayList[4]; + + /** + * Expected number of date values in the date value list created by each thread + */ + int expectedCounterDateValues = 5; + + /** + * Expected number of exceptions in the exception list created by each thread + */ + int expectedCounterExceptions = 0; + + /** + * Expected content of the list containing the date values created by each thread + */ + List expectedDateValues = Arrays.asList("15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015", "15.11.2015"); + + /** + * Run Callable and prepare results for usage in the test methods + */ + @BeforeClass + public static void setup() { + // Create a callable + DateFormatCallable callableDf = new DateFormatCallable("dd/MM/yyyy", "15/12/2015"); + // start thread using the Callable instance + ExecutorService executor = Executors.newCachedThreadPool(); + Future futureResult1 = executor.submit(callableDf); + Future futureResult2 = executor.submit(callableDf); + Future futureResult3 = executor.submit(callableDf); + Future futureResult4 = executor.submit(callableDf); + try { + result[0] = futureResult1.get(); + result[1] = futureResult2.get(); + result[2] = futureResult3.get(); + result[3] = futureResult4.get(); + for (int i = 0; i < result.length; i++) { + createdDateValues[i] = convertDatesToString(result[i]); + } + } catch (Exception e) { + fail("Setup failed: " + e); + } + executor.shutdown(); + } + + private static List convertDatesToString(Result res) { + // Format date value as DD.MM.YYYY + if (res == null || res.getDateList() == null || res.getDateList().size() == 0) { + return null; + } + List returnList = new StringArrayList(); + + for (Date dt : res.getDateList()) { + Calendar cal = Calendar.getInstance(); + cal.setTime(dt); + returnList.add(cal.get(Calendar.DAY_OF_MONTH) + "." + cal.get(Calendar.MONTH) + "." + cal.get(Calendar.YEAR)); + } + return returnList; + } + + /** + * Test date values after the run of DateFormatRunnalbe. A correct run should deliver 5 times 15.12.2015 + * by each thread + */ + @Test + public void testDateValues() { + for (int i = 0; i < createdDateValues.length; i++) { + assertEquals(expectedDateValues, createdDateValues[i]); + } + } + + /** + * Test number of dates in the list after the run of DateFormatRunnalbe. A correct run should + * deliver 5 date values by each thread + */ + @Test + public void testCounterDateValues() { + for (int i = 0; i < result.length; i++) { + assertEquals(expectedCounterDateValues, result[i].getDateList().size()); + } + } + + /** + * Test number of Exceptions in the list after the run of DateFormatRunnalbe. A correct run should + * deliver no exceptions + */ + @Test + public void testCounterExceptions() { + for (int i = 0; i < result.length; i++) { + assertEquals(expectedCounterExceptions, result[i].getExceptionList().size()); + } + } +}