├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── LICENSE
├── README.md
├── diagrams
├── Class.puml
├── Class_dark.puml
├── Sequence.puml
├── Sequence_dark.puml
├── UseCase.puml
└── UseCase_dark.puml
├── pom.xml
└── src
├── main
├── java
│ └── org
│ │ └── example
│ │ ├── App.java
│ │ ├── AppImpl.java
│ │ ├── GuiceModule.java
│ │ ├── configuration
│ │ ├── Configuration.java
│ │ ├── ConfigurationImpl.java
│ │ └── Parameter.java
│ │ ├── display
│ │ ├── Terminal.java
│ │ └── TerminalImpl.java
│ │ ├── generator
│ │ ├── PasswordGenerator.java
│ │ ├── PasswordGeneratorImpl.java
│ │ ├── RandomCharacterGenerator.java
│ │ └── RandomCharacterGeneratorImpl.java
│ │ ├── processor
│ │ ├── ArgumentProcessor.java
│ │ └── ArgumentProcessorImpl.java
│ │ └── shuffler
│ │ ├── StringShuffler.java
│ │ └── StringShufflerImpl.java
└── resources
│ └── project.properties
└── test
├── java
└── org
│ └── example
│ ├── AppImplTest.java
│ ├── configuration
│ └── ConfigurationImplTest.java
│ ├── display
│ └── TerminalImplTest.java
│ ├── generator
│ ├── PasswordGeneratorImplTest.java
│ └── RandomCharacterGeneratorImplTest.java
│ ├── processor
│ └── ArgumentProcessorImplTest.java
│ └── shuffler
│ └── StringShufflerImplTest.java
└── resources
└── project.properties
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | # Sequence of patterns matched against refs/tags
4 | tags:
5 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
6 |
7 | name: Create Release
8 |
9 | jobs:
10 | build:
11 | name: Create Release
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout code
15 | uses: actions/checkout@master
16 | - name: Create Release
17 | id: create_release
18 | uses: actions/create-release@latest
19 | env:
20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 | with:
22 | tag_name: ${{ github.ref }}
23 | release_name: Release ${{ github.ref }}
24 | body: |
25 | **Release Notes:**
26 | - New Feature: Customizable Password Length in CLI Application. Users can now customize the password length between a minimum of 8 and a maximum of 128 characters, enhancing flexibility and security in password generation. To utilize this feature, simply use the `--password-length` option followed by your preferred number within the specified range. This enhancement aims to improve user experience by catering to various security requirements and preferences.
27 | draft: false
28 | prerelease: false
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ##############################
2 | ## Java
3 | ##############################
4 | .mtj.tmp/
5 | *.class
6 | *.jar
7 | *.war
8 | *.ear
9 | *.nar
10 | hs_err_pid*
11 |
12 | ##############################
13 | ## Maven
14 | ##############################
15 | target/
16 | pom.xml.tag
17 | pom.xml.releaseBackup
18 | pom.xml.versionsBackup
19 | pom.xml.next
20 | pom.xml.bak
21 | release.properties
22 | dependency-reduced-pom.xml
23 | buildNumber.properties
24 | .mvn/timing.properties
25 | .mvn/wrapper/maven-wrapper.jar
26 |
27 | ##############################
28 | ## Gradle
29 | ##############################
30 | bin/
31 | build/
32 | .gradle
33 | .gradletasknamecache
34 | gradle-app.setting
35 | !gradle-wrapper.jar
36 |
37 | ##############################
38 | ## IntelliJ
39 | ##############################
40 | out/
41 | .idea/
42 | .idea_modules/
43 | *.iml
44 | *.ipr
45 | *.iws
46 |
47 | ##############################
48 | ## Eclipse
49 | ##############################
50 | .settings/
51 | bin/
52 | tmp/
53 | .metadata
54 | .classpath
55 | .project
56 | *.tmp
57 | *.bak
58 | *.swp
59 | *~.nib
60 | local.properties
61 | .loadpath
62 | .factorypath
63 |
64 | ##############################
65 | ## NetBeans
66 | ##############################
67 | nbproject/private/
68 | build/
69 | nbbuild/
70 | dist/
71 | nbdist/
72 | nbactions.xml
73 | nb-configuration.xml
74 |
75 | ##############################
76 | ## Visual Studio Code
77 | ##############################
78 | .vscode/
79 | .code-workspace
80 |
81 | ##############################
82 | ## OS X
83 | ##############################
84 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Velimir Đurković
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # demo-java-cli
2 |
3 | ## Password Generator Application
4 |
5 | Welcome to the Password Generator Application! This tool is designed to help users effortlessly create secure passwords
6 | that are 16 characters long. It will create a unique combination of uppercase letters, lowercase letters, numbers, and
7 | special symbols to ensure strong security, making it suitable for protecting sensitive accounts and personal
8 | information. The generated password will comply with best practices for complexity and randomness, eliminating the need
9 | for users to create their own passwords while enhancing their online safety. Whether you're setting up a new account,
10 | enhancing your cybersecurity, or simply in need of a reliable password, this solution has got you covered. No additional
11 | options or complex settings are necessary; just generate and use your password with confidence! The code is open-source,
12 | inviting contributions and modifications from the community to enhance functionality while keeping safety at the
13 | forefront. Start protecting your digital life today with our Password Generator Application!
14 |
15 | ## Use Case Diagram
16 |
17 | A use case diagram for a password generator application illustrates the interactions between users and the system. The "
18 | Generate Password" use case allows users to create secure, random passwords.
19 |
20 |
21 |
22 |
23 |
24 |
25 | ## Sequence Diagram
26 |
27 | In a sequence diagram for a password generator application, the user initiates the process by launching the CLI
28 | application with the command to generate a password. The application triggers the password generation function. This
29 | function then systematically ensures that the generated password contains at least one lowercase letter, one uppercase
30 | letter, one number, and one special symbol, employing randomization to meet these criteria. Once the password is
31 | successfully generated, the application returns the secure password to the user, completing the interaction seamlessly
32 | without requiring any additional settings.
33 |
34 |
35 |
36 |
37 |
38 |
39 | ## Class Diagram
40 |
41 | The class diagram for a CLI password generator application outlines a system that automates the creation of secure
42 | passwords. At its core, the generator utilizes an algorithm that ensures each password includes at least one lowercase
43 | letter, one uppercase letter, one numeral, and one special character. The password generation process randomly selects
44 | characters from predefined sets corresponding to each required category. The system outputs a strong password without
45 | requiring any user input, enhancing convenience while ensuring security standards are met.
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/diagrams/Class.puml:
--------------------------------------------------------------------------------
1 | @startuml Class
2 | interface App {
3 | + start(args: String[])
4 | }
5 |
6 | class AppImpl {
7 | - argumentProcessor: ArgumentProcessor
8 | + {static} main(args: String[])
9 | }
10 |
11 | interface Configuration {
12 | + load(args: String[]): void
13 | + getValue(key: String): String
14 | + getValueAsInt(key: String): int
15 | + validate(): boolean
16 | }
17 |
18 | class ConfigurationImpl {
19 | - appSettings: HashMap
20 | - getParameterValueFromArguments(args: String[], key: String): String
21 | }
22 |
23 | interface ArgumentProcessor {
24 | + process(args: String[])
25 | }
26 |
27 | class ArgumentProcessorImpl {
28 | - configuration: Configuration
29 | - passwordGenerator: PasswordGenerator
30 | - terminal: Terminal
31 | }
32 |
33 | interface Terminal {
34 | + show(message: String)
35 | }
36 |
37 | class TerminalImpl {
38 | }
39 |
40 | interface PasswordGenerator {
41 | + generate(): String
42 | }
43 |
44 | class PasswordGeneratorImpl {
45 | - configuration: Configuration
46 | - randomCharacterGenerator: RandomCharacterGenerator
47 | - stringShuffler: StringShuffler
48 | }
49 |
50 | interface RandomCharacterGenerator {
51 | + generateUppercaseCharacter(): char
52 | + generateLowercaseCharacter(): char
53 | + generateDigitCharacter(): char
54 | + generateSpecialCharacter(): char
55 | + generateAllowedCharacter(): char
56 | }
57 |
58 | class RandomCharacterGeneratorImpl {
59 | - uppercaseCharacters: String
60 | - lowercaseCharacters: String
61 | - digitCharacters: String
62 | - specialCharacters: String
63 | - allowedCharacters: String
64 | - random: Random
65 | }
66 |
67 | interface StringShuffler {
68 | + shuffle(characters: String): String
69 | }
70 |
71 | class StringShufflerImpl {
72 | }
73 |
74 | Configuration <|-- ConfigurationImpl
75 | ArgumentProcessor <|-- ArgumentProcessorImpl
76 | Terminal <|-- TerminalImpl
77 | PasswordGenerator <|-- PasswordGeneratorImpl
78 | RandomCharacterGenerator <|-- RandomCharacterGeneratorImpl
79 | StringShuffler <|-- StringShufflerImpl
80 | App <|-- AppImpl
81 |
82 | AppImpl *--l ArgumentProcessor
83 |
84 | ArgumentProcessorImpl *--d PasswordGenerator
85 | ArgumentProcessorImpl *--r Terminal
86 |
87 | PasswordGeneratorImpl *--l RandomCharacterGenerator
88 | PasswordGeneratorImpl *--r StringShuffler
89 |
90 | PasswordGeneratorImpl *--d Configuration
91 | ArgumentProcessorImpl *--l Configuration
92 | @enduml
--------------------------------------------------------------------------------
/diagrams/Class_dark.puml:
--------------------------------------------------------------------------------
1 | @startuml Class
2 | !theme crt-green
3 | !include https://raw.githubusercontent.com/djvelimir/demo-java-cli/main/diagrams/Class.puml
4 | @enduml
--------------------------------------------------------------------------------
/diagrams/Sequence.puml:
--------------------------------------------------------------------------------
1 | @startuml Sequence
2 | participant AppImpl as app
3 | participant ArgumentProcessorImpl as argumentProcessor
4 | participant PasswordGeneratorImpl as passwordGenerator
5 | participant RandomCharacterGeneratorImpl as randomCharacterGenerator
6 | participant StringShufflerImpl as stringShuffler
7 | participant TerminalImpl as terminal
8 | participant ConfigurationImpl as configuration
9 |
10 | activate app
11 |
12 | app -> argumentProcessor: process(args)
13 | activate argumentProcessor
14 |
15 | argumentProcessor -> configuration: load(args)
16 | activate configuration
17 | argumentProcessor <-- configuration:
18 | deactivate configuration
19 |
20 | argumentProcessor -> configuration: validate()
21 | activate configuration
22 |
23 | configuration -> configuration: getValueAsInt("password-length")
24 | configuration -> configuration: validate password length
25 |
26 | argumentProcessor <-- configuration: return are arguments valid
27 | deactivate configuration
28 |
29 | alt Arguments are valid
30 | argumentProcessor -> passwordGenerator: generate()
31 | activate passwordGenerator
32 |
33 | passwordGenerator -> configuration: getValueAsInt("password-length")
34 | activate configuration
35 | passwordGenerator <-- configuration: return password length
36 | deactivate configuration
37 |
38 | passwordGenerator -> randomCharacterGenerator: generateUppercaseCharacter()
39 | activate randomCharacterGenerator
40 |
41 | randomCharacterGenerator --> passwordGenerator: return uppercase character
42 | deactivate randomCharacterGenerator
43 | passwordGenerator -> passwordGenerator: append character
44 |
45 | passwordGenerator -> randomCharacterGenerator: generateLowercaseCharacter()
46 | activate randomCharacterGenerator
47 |
48 | randomCharacterGenerator --> passwordGenerator: return lowercase character
49 | deactivate randomCharacterGenerator
50 | passwordGenerator -> passwordGenerator: append character
51 |
52 | passwordGenerator -> randomCharacterGenerator: generateDigitCharacter()
53 | activate randomCharacterGenerator
54 |
55 | randomCharacterGenerator --> passwordGenerator: return digit character
56 | deactivate randomCharacterGenerator
57 | passwordGenerator -> passwordGenerator: append character
58 |
59 | passwordGenerator -> randomCharacterGenerator: generateSpecialCharacter()
60 | activate randomCharacterGenerator
61 |
62 | randomCharacterGenerator --> passwordGenerator: return special character
63 | deactivate randomCharacterGenerator
64 | passwordGenerator -> passwordGenerator: append character
65 |
66 | loop password length - 4 times
67 | passwordGenerator -> randomCharacterGenerator: generateAllowedCharacter()
68 | activate randomCharacterGenerator
69 |
70 | randomCharacterGenerator --> passwordGenerator: return character
71 | deactivate randomCharacterGenerator
72 | passwordGenerator -> passwordGenerator: append character
73 | end
74 |
75 | passwordGenerator -> stringShuffler: shuffle(characters)
76 | activate stringShuffler
77 |
78 | stringShuffler --> passwordGenerator: return shuffled characters
79 | deactivate stringShuffler
80 |
81 | argumentProcessor <-- passwordGenerator: return generated password
82 | deactivate passwordGenerator
83 |
84 | argumentProcessor -> terminal: show(password)
85 | activate terminal
86 | argumentProcessor <-- terminal
87 | deactivate terminal
88 |
89 | else Arguments are not valid
90 | argumentProcessor -> terminal: show(usage)
91 | activate terminal
92 | argumentProcessor <-- terminal
93 | deactivate terminal
94 | end
95 |
96 | app <-- argumentProcessor
97 | deactivate argumentProcessor
98 |
99 | @enduml
--------------------------------------------------------------------------------
/diagrams/Sequence_dark.puml:
--------------------------------------------------------------------------------
1 | @startuml Sequence
2 | !theme crt-green
3 | !include https://raw.githubusercontent.com/djvelimir/demo-java-cli/main/diagrams/Sequence.puml
4 | @enduml
--------------------------------------------------------------------------------
/diagrams/UseCase.puml:
--------------------------------------------------------------------------------
1 | @startuml UseCase
2 | actor "User" as user
3 |
4 | rectangle demo-java-cli {
5 | usecase "Generate Password" as generatePassword
6 | }
7 |
8 | user -> generatePassword
9 | @enduml
--------------------------------------------------------------------------------
/diagrams/UseCase_dark.puml:
--------------------------------------------------------------------------------
1 | @startuml UseCase
2 | !theme crt-green
3 | !include https://raw.githubusercontent.com/djvelimir/demo-java-cli/main/diagrams/UseCase.puml
4 | @enduml
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 4.0.0
6 |
7 | org.example
8 | demo-java-cli
9 | 1.1.0
10 |
11 | demo-java-cli
12 | https://djvelimir.github.io
13 |
14 |
15 | UTF-8
16 | 21
17 | 21
18 |
19 |
20 |
21 |
22 | com.google.guava
23 | guava
24 | 33.4.0-jre
25 |
26 |
27 | com.google.inject
28 | guice
29 | 7.0.0
30 |
31 |
32 | org.junit.jupiter
33 | junit-jupiter-api
34 | 5.11.4
35 | test
36 |
37 |
38 | org.junit.jupiter
39 | junit-jupiter-engine
40 | 5.11.4
41 | test
42 |
43 |
44 | org.mockito
45 | mockito-junit-jupiter
46 | 5.14.2
47 | test
48 |
49 |
50 | org.mockito
51 | mockito-core
52 | 5.14.2
53 | test
54 |
55 |
56 | net.bytebuddy
57 | byte-buddy-agent
58 | 1.15.4
59 | test
60 |
61 |
62 |
63 |
64 |
65 |
66 | src/main/resources
67 | true
68 |
69 |
70 |
71 |
72 |
73 |
74 | maven-clean-plugin
75 | 3.3.2
76 |
77 |
78 |
79 | maven-resources-plugin
80 | 3.3.1
81 |
82 |
83 | maven-compiler-plugin
84 | 3.13.0
85 |
86 |
87 | maven-surefire-plugin
88 | 3.5.1
89 |
90 |
91 | maven-jar-plugin
92 | 3.4.1
93 |
94 |
95 | maven-install-plugin
96 | 3.1.2
97 |
98 |
99 | maven-deploy-plugin
100 | 3.1.2
101 |
102 |
103 |
104 | maven-site-plugin
105 | 3.12.1
106 |
107 |
108 | maven-project-info-reports-plugin
109 | 3.1.2
110 |
111 |
112 |
113 |
114 |
115 | org.apache.maven.plugins
116 | maven-surefire-plugin
117 |
118 |
119 | -javaagent:${settings.localRepository}/net/bytebuddy/byte-buddy-agent/1.15.4/byte-buddy-agent-1.15.4.jar
120 |
121 |
122 |
123 |
124 | org.apache.maven.plugins
125 | maven-jar-plugin
126 |
127 |
128 |
129 | true
130 | lib/
131 | org.example.AppImpl
132 |
133 |
134 |
135 |
136 |
137 | org.apache.maven.plugins
138 | maven-dependency-plugin
139 |
140 |
141 | copy-libs
142 | package
143 |
144 | copy-dependencies
145 |
146 |
147 | runtime
148 | false
149 | true
150 | ${project.build.directory}/lib
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/src/main/java/org/example/App.java:
--------------------------------------------------------------------------------
1 | package org.example;
2 |
3 | public interface App {
4 | void start(String[] args);
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/org/example/AppImpl.java:
--------------------------------------------------------------------------------
1 | package org.example;
2 |
3 | import com.google.inject.Guice;
4 | import com.google.inject.Inject;
5 | import com.google.inject.Injector;
6 | import org.example.processor.ArgumentProcessor;
7 |
8 | /**
9 | * Main application class
10 | */
11 | public class AppImpl implements App {
12 | private final ArgumentProcessor argumentProcessor;
13 |
14 | @Inject
15 | public AppImpl(ArgumentProcessor argumentProcessor) {
16 | this.argumentProcessor = argumentProcessor;
17 | }
18 |
19 | public static void main(String[] args) {
20 | Injector injector = Guice.createInjector(new GuiceModule());
21 |
22 | App app = injector.getInstance(App.class);
23 | app.start(args);
24 | }
25 |
26 | @Override
27 | public void start(String[] args) {
28 | argumentProcessor.process(args);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/org/example/GuiceModule.java:
--------------------------------------------------------------------------------
1 | package org.example;
2 |
3 | import com.google.inject.AbstractModule;
4 | import com.google.inject.Scopes;
5 | import org.example.configuration.Configuration;
6 | import org.example.configuration.ConfigurationImpl;
7 | import org.example.display.Terminal;
8 | import org.example.display.TerminalImpl;
9 | import org.example.generator.PasswordGenerator;
10 | import org.example.generator.PasswordGeneratorImpl;
11 | import org.example.generator.RandomCharacterGenerator;
12 | import org.example.generator.RandomCharacterGeneratorImpl;
13 | import org.example.processor.ArgumentProcessor;
14 | import org.example.processor.ArgumentProcessorImpl;
15 | import org.example.shuffler.StringShuffler;
16 | import org.example.shuffler.StringShufflerImpl;
17 |
18 | public class GuiceModule extends AbstractModule {
19 | @Override
20 | protected void configure() {
21 | bind(Configuration.class).to(ConfigurationImpl.class).in(Scopes.SINGLETON);
22 | bind(ArgumentProcessor.class).to(ArgumentProcessorImpl.class);
23 | bind(PasswordGenerator.class).to(PasswordGeneratorImpl.class);
24 | bind(RandomCharacterGenerator.class).to(RandomCharacterGeneratorImpl.class);
25 | bind(StringShuffler.class).to(StringShufflerImpl.class);
26 | bind(Terminal.class).to(TerminalImpl.class);
27 | bind(App.class).to(AppImpl.class);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/org/example/configuration/Configuration.java:
--------------------------------------------------------------------------------
1 | package org.example.configuration;
2 |
3 | public interface Configuration {
4 | void load(String[] args);
5 |
6 | String getValue(String key);
7 |
8 | int getValueAsInt(String key);
9 |
10 | boolean validate();
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/org/example/configuration/ConfigurationImpl.java:
--------------------------------------------------------------------------------
1 | package org.example.configuration;
2 |
3 | import java.util.Arrays;
4 | import java.util.HashMap;
5 |
6 | public final class ConfigurationImpl implements Configuration {
7 | private final HashMap appSettings = new HashMap<>();
8 |
9 | @Override
10 | public void load(String[] args) {
11 | // read params from args
12 | appSettings.computeIfAbsent(Parameter.PASSWORD_LENGTH_PARAMETER_NAME, x -> this.getParameterValueFromArguments(args, x));
13 |
14 | // add default values for missing parameters
15 | appSettings.putIfAbsent(Parameter.PASSWORD_LENGTH_PARAMETER_NAME, "16");
16 | }
17 |
18 | @Override
19 | public String getValue(String key) {
20 | return this.appSettings.get(key);
21 | }
22 |
23 | @Override
24 | public int getValueAsInt(String key) {
25 | return Integer.parseInt(this.getValue(key));
26 | }
27 |
28 | @Override
29 | public boolean validate() {
30 | if (appSettings.isEmpty()) {
31 | return true;
32 | }
33 |
34 | try {
35 | int passwordLength = getValueAsInt(Parameter.PASSWORD_LENGTH_PARAMETER_NAME);
36 | return passwordLength >= 8 && passwordLength <= 128;
37 | } catch (Exception ex) {
38 | return false;
39 | }
40 | }
41 |
42 | private String getParameterValueFromArguments(String[] args, String key) {
43 | int index = Arrays.asList(args).indexOf(String.format("--%s", key));
44 | if (index != -1 && index < args.length - 1) {
45 | return args[index + 1];
46 | }
47 |
48 | return null;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/org/example/configuration/Parameter.java:
--------------------------------------------------------------------------------
1 | package org.example.configuration;
2 |
3 | public final class Parameter {
4 | public static final String PASSWORD_LENGTH_PARAMETER_NAME = "password-length";
5 |
6 | private Parameter() {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/org/example/display/Terminal.java:
--------------------------------------------------------------------------------
1 | package org.example.display;
2 |
3 | public interface Terminal {
4 | void show(String message);
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/org/example/display/TerminalImpl.java:
--------------------------------------------------------------------------------
1 | package org.example.display;
2 |
3 | public class TerminalImpl implements Terminal {
4 | @Override
5 | public void show(String message) {
6 | System.out.println(message);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/org/example/generator/PasswordGenerator.java:
--------------------------------------------------------------------------------
1 | package org.example.generator;
2 |
3 | public interface PasswordGenerator {
4 | String generate();
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/org/example/generator/PasswordGeneratorImpl.java:
--------------------------------------------------------------------------------
1 | package org.example.generator;
2 |
3 | import com.google.inject.Inject;
4 | import org.example.configuration.Configuration;
5 | import org.example.configuration.Parameter;
6 | import org.example.shuffler.StringShuffler;
7 |
8 | public class PasswordGeneratorImpl implements PasswordGenerator {
9 | private final Configuration configuration;
10 | private final RandomCharacterGenerator randomCharacterGenerator;
11 | private final StringShuffler stringShuffler;
12 |
13 | @Inject
14 | public PasswordGeneratorImpl(RandomCharacterGenerator randomCharacterGenerator, StringShuffler stringShuffler, Configuration configuration) {
15 | this.configuration = configuration;
16 | this.randomCharacterGenerator = randomCharacterGenerator;
17 | this.stringShuffler = stringShuffler;
18 | }
19 |
20 | /**
21 | * Generate random password
22 | * Generated password length with a default value of 16, you can use a parameter for the desired length
23 | * Generated password contains at least one uppercase character
24 | * Generated password contains at least one lowercase character
25 | * Generated password contains at least one digit character
26 | * Generated password contains at least one special character
27 | *
28 | * @return generated password
29 | */
30 | @Override
31 | public String generate() {
32 | int passwordLength = configuration.getValueAsInt(Parameter.PASSWORD_LENGTH_PARAMETER_NAME);
33 | StringBuilder stringBuilder = new StringBuilder();
34 |
35 | stringBuilder.append(randomCharacterGenerator.generateUppercaseCharacter());
36 | stringBuilder.append(randomCharacterGenerator.generateLowercaseCharacter());
37 | stringBuilder.append(randomCharacterGenerator.generateDigitCharacter());
38 | stringBuilder.append(randomCharacterGenerator.generateSpecialCharacter());
39 |
40 | for (int i = 0; i < passwordLength - 4; i++) {
41 | stringBuilder.append(randomCharacterGenerator.generateAllowedCharacter());
42 | }
43 |
44 | return stringShuffler.shuffle(stringBuilder.toString());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/org/example/generator/RandomCharacterGenerator.java:
--------------------------------------------------------------------------------
1 | package org.example.generator;
2 |
3 | public interface RandomCharacterGenerator {
4 | char generateUppercaseCharacter();
5 |
6 | char generateLowercaseCharacter();
7 |
8 | char generateDigitCharacter();
9 |
10 | char generateSpecialCharacter();
11 |
12 | char generateAllowedCharacter();
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/org/example/generator/RandomCharacterGeneratorImpl.java:
--------------------------------------------------------------------------------
1 | package org.example.generator;
2 |
3 | import java.util.Random;
4 |
5 | public class RandomCharacterGeneratorImpl implements RandomCharacterGenerator {
6 | private final String uppercaseCharacters;
7 | private final String lowercaseCharacters;
8 | private final String digitCharacters;
9 | private final String specialCharacters;
10 | private final String allowedCharacters;
11 | private final Random random = new Random();
12 |
13 | public RandomCharacterGeneratorImpl() {
14 | uppercaseCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
15 | lowercaseCharacters = "abcdefghijklmnopqrstuvwxyz";
16 | digitCharacters = "0123456789";
17 | specialCharacters = "~`!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?";
18 | allowedCharacters = uppercaseCharacters
19 | .concat(lowercaseCharacters)
20 | .concat(digitCharacters)
21 | .concat(specialCharacters);
22 | }
23 |
24 | @Override
25 | public char generateUppercaseCharacter() {
26 | return uppercaseCharacters.charAt(random.nextInt(uppercaseCharacters.length()));
27 | }
28 |
29 | @Override
30 | public char generateLowercaseCharacter() {
31 | return lowercaseCharacters.charAt(random.nextInt(lowercaseCharacters.length()));
32 | }
33 |
34 | @Override
35 | public char generateDigitCharacter() {
36 | return digitCharacters.charAt(random.nextInt(digitCharacters.length()));
37 | }
38 |
39 | @Override
40 | public char generateSpecialCharacter() {
41 | return specialCharacters.charAt(random.nextInt(specialCharacters.length()));
42 | }
43 |
44 | @Override
45 | public char generateAllowedCharacter() {
46 | return allowedCharacters.charAt(random.nextInt(allowedCharacters.length()));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/org/example/processor/ArgumentProcessor.java:
--------------------------------------------------------------------------------
1 | package org.example.processor;
2 |
3 | public interface ArgumentProcessor {
4 | void process(String[] args);
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/org/example/processor/ArgumentProcessorImpl.java:
--------------------------------------------------------------------------------
1 | package org.example.processor;
2 |
3 | import com.google.inject.Inject;
4 | import org.example.configuration.Configuration;
5 | import org.example.display.Terminal;
6 | import org.example.generator.PasswordGenerator;
7 |
8 | import java.io.IOException;
9 | import java.util.Properties;
10 |
11 |
12 | public class ArgumentProcessorImpl implements ArgumentProcessor {
13 | private final Configuration configuration;
14 | private final PasswordGenerator passwordGenerator;
15 | private final Terminal terminal;
16 |
17 | @Inject
18 | public ArgumentProcessorImpl(Configuration configuration, PasswordGenerator passwordGenerator, Terminal terminal) {
19 | this.configuration = configuration;
20 | this.passwordGenerator = passwordGenerator;
21 | this.terminal = terminal;
22 | }
23 |
24 | @Override
25 | public void process(String[] args) {
26 | configuration.load(args);
27 |
28 | if (!configuration.validate()) {
29 | final Properties properties = new Properties();
30 | try {
31 | properties.load(this.getClass().getClassLoader().getResourceAsStream("project.properties"));
32 | } catch (IOException e) {
33 | throw new RuntimeException(e);
34 | }
35 |
36 | String usage = "Usage:" + System.lineSeparator() +
37 | "java -jar ./" + properties.getProperty("artifactId") + "-" + properties.getProperty("version") + ".jar [OPTIONS]" + System.lineSeparator() +
38 | System.lineSeparator() +
39 | "Options:" + System.lineSeparator() +
40 | "--password-length\tLength of the password, allowing values between 8 and 128 characters.";
41 | terminal.show(usage);
42 | return;
43 | }
44 |
45 | String password = passwordGenerator.generate();
46 | terminal.show(password);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/org/example/shuffler/StringShuffler.java:
--------------------------------------------------------------------------------
1 | package org.example.shuffler;
2 |
3 | public interface StringShuffler {
4 | String shuffle(String characters);
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/org/example/shuffler/StringShufflerImpl.java:
--------------------------------------------------------------------------------
1 | package org.example.shuffler;
2 |
3 | import java.util.Arrays;
4 | import java.util.Collections;
5 | import java.util.List;
6 |
7 | public class StringShufflerImpl implements StringShuffler {
8 | public String shuffle(String characters) {
9 | List list = Arrays.asList(characters.split(""));
10 | Collections.shuffle(list);
11 | return String.join("", list);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/resources/project.properties:
--------------------------------------------------------------------------------
1 | version=${project.version}
2 | artifactId=${project.artifactId}
--------------------------------------------------------------------------------
/src/test/java/org/example/AppImplTest.java:
--------------------------------------------------------------------------------
1 | package org.example;
2 |
3 | import org.example.processor.ArgumentProcessor;
4 | import org.junit.jupiter.api.Test;
5 | import org.junit.jupiter.api.extension.ExtendWith;
6 | import org.mockito.InjectMocks;
7 | import org.mockito.Mock;
8 | import org.mockito.junit.jupiter.MockitoExtension;
9 |
10 | import static org.mockito.Mockito.times;
11 | import static org.mockito.Mockito.verify;
12 |
13 | @ExtendWith(MockitoExtension.class)
14 | class AppImplTest {
15 | @Mock
16 | private ArgumentProcessor argumentProcessor;
17 |
18 | @InjectMocks
19 | private AppImpl app;
20 |
21 | @Test
22 | void shouldCallProcessMethod() {
23 | String[] args = new String[]{};
24 |
25 | app.start(args);
26 |
27 | verify(argumentProcessor, times(1)).process(args);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/java/org/example/configuration/ConfigurationImplTest.java:
--------------------------------------------------------------------------------
1 | package org.example.configuration;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.junit.jupiter.api.extension.ExtendWith;
5 | import org.mockito.InjectMocks;
6 | import org.mockito.junit.jupiter.MockitoExtension;
7 |
8 | import static org.junit.jupiter.api.Assertions.*;
9 |
10 | @ExtendWith(MockitoExtension.class)
11 | class ConfigurationImplTest {
12 | @InjectMocks
13 | private ConfigurationImpl configuration;
14 |
15 | @Test
16 | void testLoadWithValidInput() {
17 | String[] args = new String[]{String.format("--%s", Parameter.PASSWORD_LENGTH_PARAMETER_NAME), "12"};
18 | configuration.load(args);
19 | assertEquals("12", configuration.getValue(Parameter.PASSWORD_LENGTH_PARAMETER_NAME));
20 | }
21 |
22 | @Test
23 | void testLoadWithNoInputShouldUseDefaultValue() {
24 | String[] args = new String[]{};
25 | configuration.load(args);
26 | assertEquals("16", configuration.getValue(Parameter.PASSWORD_LENGTH_PARAMETER_NAME));
27 | }
28 |
29 | @Test
30 | void testGetValueAsInt() {
31 | String[] args = new String[]{String.format("--%s", Parameter.PASSWORD_LENGTH_PARAMETER_NAME), "20"};
32 | configuration.load(args);
33 | assertEquals(20, configuration.getValueAsInt(Parameter.PASSWORD_LENGTH_PARAMETER_NAME));
34 | }
35 |
36 | @Test
37 | void testGetValue() {
38 | String[] args = new String[]{String.format("--%s", Parameter.PASSWORD_LENGTH_PARAMETER_NAME), "20"};
39 | configuration.load(args);
40 | assertEquals("20", configuration.getValue(Parameter.PASSWORD_LENGTH_PARAMETER_NAME));
41 | }
42 |
43 | @Test
44 | void testGetValueShouldReturnNullForMissingKey() {
45 | assertNull(configuration.getValue("missingKey"));
46 | }
47 |
48 | @Test
49 | void testGetValueAsIntShouldThrowExceptionForInvalidNumber() {
50 | String[] args = {String.format("--%s", Parameter.PASSWORD_LENGTH_PARAMETER_NAME), "notANumber"};
51 | configuration.load(args);
52 | assertThrows(NumberFormatException.class, () -> configuration.getValueAsInt(Parameter.PASSWORD_LENGTH_PARAMETER_NAME));
53 | }
54 |
55 | @Test
56 | void testValidatePasswordLengthWithValidValue() {
57 | String[] args = new String[]{String.format("--%s", Parameter.PASSWORD_LENGTH_PARAMETER_NAME), "16"};
58 | configuration.load(args);
59 |
60 | boolean actual = configuration.validate();
61 |
62 | assertTrue(actual);
63 | }
64 |
65 | @Test
66 | void testValidatePasswordLengthNotNumber() {
67 | String[] args = new String[]{String.format("--%s", Parameter.PASSWORD_LENGTH_PARAMETER_NAME), "1c6"};
68 | configuration.load(args);
69 |
70 | boolean actual = configuration.validate();
71 |
72 | assertFalse(actual);
73 | }
74 |
75 | @Test
76 | void testValidatePasswordLengthBellowMin() {
77 | String[] args = new String[]{String.format("--%s", Parameter.PASSWORD_LENGTH_PARAMETER_NAME), "7"};
78 | configuration.load(args);
79 |
80 | boolean actual = configuration.validate();
81 |
82 | assertFalse(actual);
83 | }
84 |
85 | @Test
86 | void testValidatePasswordLengthUpperMax() {
87 | String[] args = new String[]{String.format("--%s", Parameter.PASSWORD_LENGTH_PARAMETER_NAME), "129"};
88 | configuration.load(args);
89 |
90 | boolean actual = configuration.validate();
91 |
92 | assertFalse(actual);
93 | }
94 |
95 | @Test
96 | void testValidateWithNoArgs() {
97 | String[] args = new String[]{};
98 | configuration.load(args);
99 |
100 | boolean actual = configuration.validate();
101 |
102 | assertTrue(actual);
103 | }
104 | }
--------------------------------------------------------------------------------
/src/test/java/org/example/display/TerminalImplTest.java:
--------------------------------------------------------------------------------
1 | package org.example.display;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.junit.jupiter.api.extension.ExtendWith;
5 | import org.mockito.InjectMocks;
6 | import org.mockito.junit.jupiter.MockitoExtension;
7 |
8 | import java.io.ByteArrayOutputStream;
9 | import java.io.PrintStream;
10 |
11 | import static org.junit.jupiter.api.Assertions.assertEquals;
12 |
13 | @ExtendWith(MockitoExtension.class)
14 | class TerminalImplTest {
15 | @InjectMocks
16 | private TerminalImpl terminal;
17 |
18 | @Test
19 | void checkShowMethod() {
20 | String message = "Test Message";
21 | String expected = message + System.lineSeparator();
22 |
23 | final PrintStream standardOut = System.out;
24 | final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();
25 |
26 | System.setOut(new PrintStream(outputStreamCaptor));
27 | terminal.show(message);
28 | System.setOut(standardOut);
29 |
30 | assertEquals(expected, outputStreamCaptor.toString());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/test/java/org/example/generator/PasswordGeneratorImplTest.java:
--------------------------------------------------------------------------------
1 | package org.example.generator;
2 |
3 | import org.example.configuration.Configuration;
4 | import org.example.configuration.Parameter;
5 | import org.example.shuffler.StringShuffler;
6 | import org.junit.jupiter.api.BeforeEach;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 | import org.mockito.InjectMocks;
10 | import org.mockito.Mock;
11 | import org.mockito.Mockito;
12 | import org.mockito.junit.jupiter.MockitoExtension;
13 |
14 | import static org.mockito.ArgumentMatchers.any;
15 | import static org.mockito.Mockito.times;
16 | import static org.mockito.Mockito.verify;
17 |
18 | @ExtendWith(MockitoExtension.class)
19 | class PasswordGeneratorImplTest {
20 | @Mock
21 | private RandomCharacterGenerator randomCharacterGenerator;
22 |
23 | @Mock
24 | private StringShuffler stringShuffler;
25 |
26 | @Mock
27 | private Configuration configuration;
28 |
29 | @InjectMocks
30 | private PasswordGeneratorImpl passwordGenerator;
31 |
32 | @BeforeEach
33 | void beforeEach() {
34 | Mockito.when(configuration.getValueAsInt(Parameter.PASSWORD_LENGTH_PARAMETER_NAME)).thenAnswer(invocation -> 16);
35 | }
36 |
37 | @Test
38 | void shouldCallGenerateUppercaseCharacter() {
39 | passwordGenerator.generate();
40 |
41 | verify(randomCharacterGenerator, times(1)).generateUppercaseCharacter();
42 | }
43 |
44 | @Test
45 | void shouldCallGenerateLowercaseCharacter() {
46 | passwordGenerator.generate();
47 |
48 | verify(randomCharacterGenerator, times(1)).generateLowercaseCharacter();
49 | }
50 |
51 | @Test
52 | void shouldCallGenerateDigitCharacter() {
53 | passwordGenerator.generate();
54 |
55 | verify(randomCharacterGenerator, times(1)).generateDigitCharacter();
56 | }
57 |
58 | @Test
59 | void shouldCallGenerateSpecialCharacter() {
60 | passwordGenerator.generate();
61 |
62 | verify(randomCharacterGenerator, times(1)).generateSpecialCharacter();
63 | }
64 |
65 | @Test
66 | void shouldCallGenerateAllowedCharacter12Times() {
67 | passwordGenerator.generate();
68 |
69 | verify(randomCharacterGenerator, times(12)).generateAllowedCharacter();
70 | }
71 |
72 | @Test
73 | void shouldCallShuffle() {
74 | passwordGenerator.generate();
75 |
76 | verify(stringShuffler, times(1)).shuffle(any());
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/test/java/org/example/generator/RandomCharacterGeneratorImplTest.java:
--------------------------------------------------------------------------------
1 | package org.example.generator;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.junit.jupiter.api.extension.ExtendWith;
5 | import org.mockito.InjectMocks;
6 | import org.mockito.junit.jupiter.MockitoExtension;
7 |
8 | import static org.junit.jupiter.api.Assertions.assertTrue;
9 |
10 | @ExtendWith(MockitoExtension.class)
11 | class RandomCharacterGeneratorImplTest {
12 | private final String uppercaseCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
13 | private final String lowercaseCharacters = "abcdefghijklmnopqrstuvwxyz";
14 | private final String digitCharacters = "0123456789";
15 | private final String specialCharacters = "~`!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?";
16 | private final String allowedCharacters = uppercaseCharacters
17 | .concat(lowercaseCharacters)
18 | .concat(digitCharacters)
19 | .concat(specialCharacters);
20 |
21 | @InjectMocks
22 | private RandomCharacterGeneratorImpl randomCharacterGenerator;
23 |
24 | @Test
25 | void checkGenerateUppercaseCharacterMethod() {
26 | char actual = randomCharacterGenerator.generateUppercaseCharacter();
27 |
28 | assertTrue(uppercaseCharacters.contains(String.valueOf(actual)));
29 | }
30 |
31 | @Test
32 | void checkGenerateLowercaseCharacterMethod() {
33 | char actual = randomCharacterGenerator.generateLowercaseCharacter();
34 |
35 | assertTrue(lowercaseCharacters.contains(String.valueOf(actual)));
36 | }
37 |
38 | @Test
39 | void checkGenerateDigitCharacterMethod() {
40 | char actual = randomCharacterGenerator.generateDigitCharacter();
41 |
42 | assertTrue(digitCharacters.contains(String.valueOf(actual)));
43 | }
44 |
45 | @Test
46 | void checkGenerateSpecialCharacterMethod() {
47 | char actual = randomCharacterGenerator.generateSpecialCharacter();
48 |
49 | assertTrue(specialCharacters.contains(String.valueOf(actual)));
50 | }
51 |
52 | @Test
53 | void checkGenerateAllowedCharacterMethod() {
54 | char actual = randomCharacterGenerator.generateAllowedCharacter();
55 |
56 | assertTrue(allowedCharacters.contains(String.valueOf(actual)));
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/test/java/org/example/processor/ArgumentProcessorImplTest.java:
--------------------------------------------------------------------------------
1 | package org.example.processor;
2 |
3 | import org.example.configuration.Configuration;
4 | import org.example.configuration.Parameter;
5 | import org.example.display.Terminal;
6 | import org.example.generator.PasswordGenerator;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 | import org.mockito.InjectMocks;
10 | import org.mockito.Mock;
11 | import org.mockito.junit.jupiter.MockitoExtension;
12 |
13 | import java.io.IOException;
14 | import java.util.Properties;
15 |
16 | import static org.mockito.Mockito.*;
17 |
18 | @ExtendWith(MockitoExtension.class)
19 | class ArgumentProcessorImplTest {
20 | @Mock
21 | Configuration configuration;
22 |
23 | @Mock
24 | private PasswordGenerator passwordGenerator;
25 |
26 | @Mock
27 | private Terminal terminal;
28 |
29 | @InjectMocks
30 | private ArgumentProcessorImpl argumentProcessor;
31 |
32 | @Test
33 | void checkProcessMethodForGeneratePassword() {
34 | String[] args = new String[]{String.format("--%s", Parameter.PASSWORD_LENGTH_PARAMETER_NAME), "16"};
35 | when(configuration.validate()).thenReturn(true);
36 |
37 | String sample = "A8!(,wV5YuI[Vr^>";
38 | when(passwordGenerator.generate()).thenReturn(sample);
39 |
40 | argumentProcessor.process(args);
41 |
42 | verify(configuration, times(1)).load(args);
43 | verify(passwordGenerator, times(1)).generate();
44 | verify(terminal, times(1)).show(sample);
45 | }
46 |
47 | @Test
48 | void checkProcessMethodForInvalidArguments() {
49 | final Properties properties = new Properties();
50 | try {
51 | properties.load(this.getClass().getClassLoader().getResourceAsStream("project.properties"));
52 | } catch (IOException e) {
53 | throw new RuntimeException(e);
54 | }
55 |
56 | String usage = "Usage:" + System.lineSeparator() +
57 | "java -jar ./" + properties.getProperty("artifactId") + "-" + properties.getProperty("version") + ".jar [OPTIONS]" + System.lineSeparator() +
58 | System.lineSeparator() +
59 | "Options:" + System.lineSeparator() +
60 | "--password-length\tLength of the password, allowing values between 8 and 128 characters.";
61 |
62 | String[] args = new String[]{String.format("--%s", Parameter.PASSWORD_LENGTH_PARAMETER_NAME), "1c6"};
63 | when(configuration.validate()).thenReturn(false);
64 |
65 | argumentProcessor.process(args);
66 |
67 | verify(configuration, times(1)).load(args);
68 | verify(terminal, times(1)).show(usage);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/test/java/org/example/shuffler/StringShufflerImplTest.java:
--------------------------------------------------------------------------------
1 | package org.example.shuffler;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.junit.jupiter.api.extension.ExtendWith;
5 | import org.mockito.InjectMocks;
6 | import org.mockito.junit.jupiter.MockitoExtension;
7 |
8 | import static org.junit.jupiter.api.Assertions.*;
9 |
10 | @ExtendWith(MockitoExtension.class)
11 | class StringShufflerImplTest {
12 | @InjectMocks
13 | private StringShufflerImpl stringShuffler;
14 |
15 | @Test
16 | void checkShuffleMethod() {
17 | String sample = "A8!(,wV5YuI[Vr^>";
18 | String actual = stringShuffler.shuffle(sample);
19 |
20 | assertNotEquals(sample, actual);
21 | assertEquals(sample.length(), actual.length());
22 |
23 | for (char item : sample.toCharArray()) {
24 | assertTrue(actual.contains(String.valueOf(item)));
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/resources/project.properties:
--------------------------------------------------------------------------------
1 | version=${project.version}
2 | artifactId=${project.artifactId}
--------------------------------------------------------------------------------