From f903d7e9a9379c79742ecc61a3901bdbe2a0a318 Mon Sep 17 00:00:00 2001 From: Joshua Jimenez Date: Tue, 8 Oct 2019 14:19:28 +0800 Subject: [PATCH] #496 Pipeline pattern (#967) * #496 Add pipeline module to parent pom :sparkles: * #496: Add main application class and test for pipeline * #496: Checkstyle format and add log messages on pipeline stages :art: * #496: Fill readme sections of pipeline :sparkles: * #496: Javadocs and checkstyle formatting :art: * #496: Follow PMD checks and add more explanation as block comment on App.java * #496: Apply requested PR changes by iluwatar :art: --- pipeline/README.md | 37 +++++++++++ pipeline/pom.xml | 47 +++++++++++++ .../main/java/com.iluwatar.pipeline/App.java | 66 +++++++++++++++++++ .../ConvertToCharArrayHandler.java | 45 +++++++++++++ .../java/com.iluwatar.pipeline/Handler.java | 32 +++++++++ .../java/com.iluwatar.pipeline/Pipeline.java | 46 +++++++++++++ .../RemoveAlphabetsHandler.java | 54 +++++++++++++++ .../RemoveDigitsHandler.java | 54 +++++++++++++++ .../java/com.iluwatar.pipeline/AppTest.java | 37 +++++++++++ .../com.iluwatar.pipeline/PipelineTest.java | 45 +++++++++++++ pom.xml | 1 + 11 files changed, 464 insertions(+) create mode 100644 pipeline/README.md create mode 100644 pipeline/pom.xml create mode 100644 pipeline/src/main/java/com.iluwatar.pipeline/App.java create mode 100644 pipeline/src/main/java/com.iluwatar.pipeline/ConvertToCharArrayHandler.java create mode 100644 pipeline/src/main/java/com.iluwatar.pipeline/Handler.java create mode 100644 pipeline/src/main/java/com.iluwatar.pipeline/Pipeline.java create mode 100644 pipeline/src/main/java/com.iluwatar.pipeline/RemoveAlphabetsHandler.java create mode 100644 pipeline/src/main/java/com.iluwatar.pipeline/RemoveDigitsHandler.java create mode 100644 pipeline/src/test/java/com.iluwatar.pipeline/AppTest.java create mode 100644 pipeline/src/test/java/com.iluwatar.pipeline/PipelineTest.java diff --git a/pipeline/README.md b/pipeline/README.md new file mode 100644 index 000000000..e990affd6 --- /dev/null +++ b/pipeline/README.md @@ -0,0 +1,37 @@ +--- +layout: pattern +title: Pipeline +folder: pipeline +permalink: /patterns/pipeline/ +categories: Behavioral +tags: + - Java + - Functional + - Difficulty-Intermediate +--- + +## Intent +Allows processing of data in a series of stages by giving in an initial input and passing the processed output to be used by the next stages. + +## Applicability +Use the Pipeline pattern when you want to + +* execute individual stages that yields a final value +* add readability to complex sequence of operations by providing a fluent builder as an interface +* improve testability of code since stages will most likely be doing a single thing, complying to the [Single Responsibility Principle (SRP)](https://java-design-patterns.com/principles/#single-responsibility-principle) + +## Typical Use Case + +* implement stages and execute them in an ordered manner + +## Real world examples + +* [java.util.Stream](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html) +* [Maven Build Lifecycle](http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html) +* [Functional Java](https://github.com/functionaljava/functionaljava) + +## Credits + +* [The Pipeline Pattern — for fun and profit](https://medium.com/@aaronweatherall/the-pipeline-pattern-for-fun-and-profit-9b5f43a98130) +* [The Pipeline design pattern (in Java)](https://medium.com/@deepakbapat/the-pipeline-design-pattern-in-java-831d9ce2fe21) +* [Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff963548(v=pandp.10)) \ No newline at end of file diff --git a/pipeline/pom.xml b/pipeline/pom.xml new file mode 100644 index 000000000..7ad60d562 --- /dev/null +++ b/pipeline/pom.xml @@ -0,0 +1,47 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.22.0-SNAPSHOT + + pipeline + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + diff --git a/pipeline/src/main/java/com.iluwatar.pipeline/App.java b/pipeline/src/main/java/com.iluwatar.pipeline/App.java new file mode 100644 index 000000000..7efa2ecca --- /dev/null +++ b/pipeline/src/main/java/com.iluwatar.pipeline/App.java @@ -0,0 +1,66 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + * + * 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.pipeline; + +/** + * The Pipeline pattern uses ordered stages to process a sequence of input values. + * Each implemented task is represented by a stage of the pipeline. You can think of + * pipelines as similar to assembly lines in a factory, where each item in the assembly + * line is constructed in stages. The partially assembled item is passed from one assembly + * stage to another. The outputs of the assembly line occur in the same order as that of the + * inputs. + * + * Classes used in this example are suffixed with "Handlers", and synonymously refers to the + * "stage". + */ +public class App { + /** + * Specify the initial input type for the first stage handler and the expected output type + * of the last stage handler as type parameters for Pipeline. Use the fluent builder by + * calling addHandler to add more stage handlers on the pipeline. + */ + public static void main(String[] args) { + /* + Suppose we wanted to pass through a String to a series of filtering stages and convert it + as a char array on the last stage. + + - Stage handler 1 (pipe): Removing the alphabets, accepts a String input and returns the + processed String output. This will be used by the next handler as its input. + + - Stage handler 2 (pipe): Removing the digits, accepts a String input and returns the + processed String output. This shall also be used by the last handler we have. + + - Stage handler 3 (pipe): Converting the String input to a char array handler. We would + be returning a different type in here since that is what's specified by the requirement. + This means that at any stages along the pipeline, the handler can return any type of data + as long as it fulfills the requirements for the next handler's input. + + Suppose we wanted to add another handler after ConvertToCharArrayHandler. That handler + then is expected to receive an input of char[] array since that is the type being returned + by the previous handler, ConvertToCharArrayHandler. + */ + new Pipeline<>(new RemoveAlphabetsHandler()) + .addHandler(new RemoveDigitsHandler()) + .addHandler(new ConvertToCharArrayHandler()); + } +} diff --git a/pipeline/src/main/java/com.iluwatar.pipeline/ConvertToCharArrayHandler.java b/pipeline/src/main/java/com.iluwatar.pipeline/ConvertToCharArrayHandler.java new file mode 100644 index 000000000..b69241914 --- /dev/null +++ b/pipeline/src/main/java/com.iluwatar.pipeline/ConvertToCharArrayHandler.java @@ -0,0 +1,45 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * 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.pipeline; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +/** + * Stage handler that converts an input String to its char[] array counterpart. + */ +class ConvertToCharArrayHandler implements Handler { + + private final Logger logger = LoggerFactory.getLogger(ConvertToCharArrayHandler.class); + + @Override + public char[] process(String input) { + char[] characters = input.toCharArray(); + logger.info(String.format("Current handler: %s, input is %s of type %s, output is %s, of type %s", + ConvertToCharArrayHandler.class, input, String.class, Arrays.toString(characters), Character[].class)); + + return characters; + } +} diff --git a/pipeline/src/main/java/com.iluwatar.pipeline/Handler.java b/pipeline/src/main/java/com.iluwatar.pipeline/Handler.java new file mode 100644 index 000000000..7d2bc0db7 --- /dev/null +++ b/pipeline/src/main/java/com.iluwatar.pipeline/Handler.java @@ -0,0 +1,32 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + * + * 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.pipeline; + +/** + * Forms a contract to all stage handlers to accept a certain type of input and return a processed output. + * @param the input type of the handler + * @param the processed output type of the handler + */ +interface Handler { + O process(I input); +} \ No newline at end of file diff --git a/pipeline/src/main/java/com.iluwatar.pipeline/Pipeline.java b/pipeline/src/main/java/com.iluwatar.pipeline/Pipeline.java new file mode 100644 index 000000000..8e231bbc4 --- /dev/null +++ b/pipeline/src/main/java/com.iluwatar.pipeline/Pipeline.java @@ -0,0 +1,46 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + * + * 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.pipeline; + +/** + * Main Pipeline class that initially sets the current handler. Processed output + * of the initial handler is then passed as the input to the next stage handlers. + * @param the type of the input for the first stage handler + * @param the final stage handler's output type + */ +class Pipeline { + + private final Handler currentHandler; + + Pipeline(Handler currentHandler) { + this.currentHandler = currentHandler; + } + + Pipeline addHandler(Handler newHandler) { + return new Pipeline<>(input -> newHandler.process(currentHandler.process(input))); + } + + O execute(I input) { + return currentHandler.process(input); + } +} \ No newline at end of file diff --git a/pipeline/src/main/java/com.iluwatar.pipeline/RemoveAlphabetsHandler.java b/pipeline/src/main/java/com.iluwatar.pipeline/RemoveAlphabetsHandler.java new file mode 100644 index 000000000..208505274 --- /dev/null +++ b/pipeline/src/main/java/com.iluwatar.pipeline/RemoveAlphabetsHandler.java @@ -0,0 +1,54 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + * + * 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.pipeline; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Stage handler that returns a new instance of String without the alphabet characters of the input string. + */ +class RemoveAlphabetsHandler implements Handler { + + private final Logger logger = LoggerFactory.getLogger(RemoveAlphabetsHandler.class); + + @Override + public String process(String input) { + StringBuilder inputWithoutAlphabets = new StringBuilder(); + + for (int index = 0; index < input.length(); index++) { + char currentCharacter = input.charAt(index); + if (Character.isAlphabetic(currentCharacter)) { + continue; + } + + inputWithoutAlphabets.append(currentCharacter); + } + + String inputWithoutAlphabetsStr = inputWithoutAlphabets.toString(); + logger.info(String.format("Current handler: %s, input is %s of type %s, output is %s, of type %s", + RemoveAlphabetsHandler.class, input, String.class, inputWithoutAlphabetsStr, String.class)); + + return inputWithoutAlphabetsStr; + } +} \ No newline at end of file diff --git a/pipeline/src/main/java/com.iluwatar.pipeline/RemoveDigitsHandler.java b/pipeline/src/main/java/com.iluwatar.pipeline/RemoveDigitsHandler.java new file mode 100644 index 000000000..e0c0aa6a8 --- /dev/null +++ b/pipeline/src/main/java/com.iluwatar.pipeline/RemoveDigitsHandler.java @@ -0,0 +1,54 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + * + * 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.pipeline; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Stage handler that returns a new instance of String without the digit characters of the input string. + */ +class RemoveDigitsHandler implements Handler { + + private final Logger logger = LoggerFactory.getLogger(RemoveDigitsHandler.class); + + @Override + public String process(String input) { + StringBuilder inputWithoutDigits = new StringBuilder(); + + for (int index = 0; index < input.length(); index++) { + char currentCharacter = input.charAt(index); + if (Character.isDigit(currentCharacter)) { + continue; + } + + inputWithoutDigits.append(currentCharacter); + } + + String inputWithoutDigitsStr = inputWithoutDigits.toString(); + logger.info(String.format("Current handler: %s, input is %s of type %s, output is %s, of type %s", + RemoveDigitsHandler.class, input, String.class, inputWithoutDigitsStr, String.class)); + + return inputWithoutDigitsStr; + } +} \ No newline at end of file diff --git a/pipeline/src/test/java/com.iluwatar.pipeline/AppTest.java b/pipeline/src/test/java/com.iluwatar.pipeline/AppTest.java new file mode 100644 index 000000000..79524575c --- /dev/null +++ b/pipeline/src/test/java/com.iluwatar.pipeline/AppTest.java @@ -0,0 +1,37 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + *

+ * 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.pipeline; + +import org.junit.jupiter.api.Test; + +/** + * Application Test + */ +public class AppTest { + + @Test + public void test() { + String[] args = {}; + App.main(args); + } +} diff --git a/pipeline/src/test/java/com.iluwatar.pipeline/PipelineTest.java b/pipeline/src/test/java/com.iluwatar.pipeline/PipelineTest.java new file mode 100644 index 000000000..1a2676e25 --- /dev/null +++ b/pipeline/src/test/java/com.iluwatar.pipeline/PipelineTest.java @@ -0,0 +1,45 @@ +/** + * The MIT License + * Copyright (c) 2014 Ilkka Seppälä + *

+ * 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.pipeline; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +/** + * Test for {@link Pipeline} + */ +public class PipelineTest { + + @Test + public void testAddHandlersToPipeline() { + Pipeline filters = new Pipeline<>(new RemoveAlphabetsHandler()) + .addHandler(new RemoveDigitsHandler()) + .addHandler(new ConvertToCharArrayHandler()); + + assertArrayEquals( + new char[] {'#', '!', '(', '&', '%', '#', '!'}, + filters.execute("#H!E(L&L0O%THE3R#34E!") + ); + } +} diff --git a/pom.xml b/pom.xml index 33219175e..39cfee91c 100644 --- a/pom.xml +++ b/pom.xml @@ -97,6 +97,7 @@ property intercepting-filter producer-consumer + pipeline poison-pill reader-writer-lock lazy-loading