├── .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 | Use Case Diagram 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 | Sequence Diagram 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 | Class Diagram 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} --------------------------------------------------------------------------------