I need help with a small mars lander video-game I'm making for my computer science class. We have to read a game config text file using scanner and use it as the rules for the different aspects of our game (Gravity, amount of fuel you have, etc.) She gave us different text files and they all have different difficulties and values, but they all have the same format, so I need to be able to simply call the different text file and have a new level ready to play. My question is:
How do I get the input from the file into separate variables so that I can manipulate them to create the game?
Here's the code for reading the text file, it also prints it out to the console
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class MarsLander {
public static void main(String [] args) {
try {
Scanner sc = new Scanner(new File("gameConfig.txt"));
while (sc.hasNext()){
String s = sc.next();
System.out.println(s);
}
sc.close();
}
catch (FileNotFoundException e) {
System.out.println("Failed to open file!");
}
}
}
Here is one of the text game config files:
1000 500
mars_sky.jpg
ship.png ship_bottom.png ship_left.png ship_right.png ship_landed.png ship_crashed.png
20 50
500.0 400.0
100
thrust.wav yay.wav explosion.wav
-0.1
2.0
0.5
500 50
In my solution, I was thinking about something slightly more generic. Let me first show you the piece of code I wrote. I will then explain its behaviour and particularities.
public class GameExample {
private static class Game {
private Long x, y;
private List<String> images = new ArrayList<>();
private Game(final Long x, final Long y, final List<String> images) {
this.x = x;
this.y = y;
this.images = images;
}
public Long getX() {
return x;
}
public Long getY() {
return y;
}
public List<String> getImages() {
return images;
}
public static class Builder {
// Parsing methods used by the builder to read the files and build the configuration
// TODO: add here builder methods for each line of the file
private final List<BiFunction<String, Game.Builder, Game.Builder>> parsingMethods = Arrays.asList(
(str, builder) -> builder.withPositions(str),
(str, builder) -> builder.withImages(str));
private Long x, y;
private List<String> images = new ArrayList<>();
private Builder withPositions(final String str) {
String[] positions = str.split(" ");
x = Long.valueOf(positions[0]);
y = Long.valueOf(positions[1]);
return this;
}
private Builder withImages(final String str) {
Stream.of(str.split(" ")).forEach(imgStr -> images.add(imgStr));
return this;
}
public Game build(final String filename) throws IOException {
Scanner sc = null;
try {
// Read the file line by line
List<String> lines = Files.lines(Paths.get(filename)).collect(Collectors.toList());
// Iterate over each line and call the configured method
IntStream.range(0, lines.size()).forEach(
index -> parsingMethods.get(index)
.apply(lines.get(index), this));
// Build an instance of the game
return new Game(x, y, images);
} catch (IOException e) {
e.printStackTrace();
throw e;
} finally {
if (sc != null) sc.close();
}
}
}
}
public static void main(String[] args) throws IOException {
final Game.Builder builder = new Game.Builder();
Game game = builder.build("file.txt");
System.out.println(game.getX() + ":" + game.getY());
System.out.println(game.getImages());
}
}
This piece of code would output:
10:15
test.jpg
with a given configuration file containing:
10 15
test.jpg
Let me explain what was done. We define a Game builder that has only one public method with the signature Game build(final String filename). It takes the filename and will build the game from the content of this file. The cornerstone of this approach is that the builder defines a list that determine which method of the builder is used for each line of the file:
private final List<BiFunction<String, Game.Builder, Game.Builder>> parsingMethods = Arrays.asList(
(str, builder) -> builder.withPositions(str),
(str, builder) -> builder.withImages(str));
This list says:
Use the method withPositions for the first line
Use the method withImages for the second line
Now, in the build method, it implements the logic that executes the methods on the lines:
// Iterate over each line and call the configured method
IntStream.range(0, lines.size()).forEach(
index -> parsingMethods.get(index)
.apply(lines.get(index), this));
We can therefore easily parse a new line of data by doing the following:
Add a new private method in the builder describing how to parse the line;
Add this method in the list called parsingMethods.
Related
I have a Java command-line program. I would like to create JUnit test case to be able to simulate System.in. Because when my program runs it will get into the while loop and waits for input from users. How do I simulate that in JUnit?
Thanks
It is technically possible to switch System.in, but in general, it would be more robust not to call it directly in your code, but add a layer of indirection so the input source is controlled from one point in your application. Exactly how you do that is an implementation detail - the suggestions of dependency injection are fine, but you don't necessarily need to introduce 3rd party frameworks; you could pass round an I/O context from the calling code, for example.
How to switch System.in:
String data = "Hello, World!\r\n";
InputStream stdin = System.in;
try {
System.setIn(new ByteArrayInputStream(data.getBytes()));
Scanner scanner = new Scanner(System.in);
System.out.println(scanner.nextLine());
} finally {
System.setIn(stdin);
}
Based on #McDowell's answer and another answer that shows how to test System.out, I would like to share my solution to give an input to a program and test its output.
As a reference, I use JUnit 4.12.
Let's say we have this program that simply replicates input to output:
import java.util.Scanner;
public class SimpleProgram {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print(scanner.next());
scanner.close();
}
}
To test it, we can use the following class:
import static org.junit.Assert.*;
import java.io.*;
import org.junit.*;
public class SimpleProgramTest {
private final InputStream systemIn = System.in;
private final PrintStream systemOut = System.out;
private ByteArrayInputStream testIn;
private ByteArrayOutputStream testOut;
#Before
public void setUpOutput() {
testOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(testOut));
}
private void provideInput(String data) {
testIn = new ByteArrayInputStream(data.getBytes());
System.setIn(testIn);
}
private String getOutput() {
return testOut.toString();
}
#After
public void restoreSystemInputOutput() {
System.setIn(systemIn);
System.setOut(systemOut);
}
#Test
public void testCase1() {
final String testString = "Hello!";
provideInput(testString);
SimpleProgram.main(new String[0]);
assertEquals(testString, getOutput());
}
}
I won't explain much, because I believe the code is readable and I cited my sources.
When JUnit runs testCase1(), it is going to call the helper methods in the order they appear:
setUpOutput(), because of the #Before annotation
provideInput(String data), called from testCase1()
getOutput(), called from testCase1()
restoreSystemInputOutput(), because of the #After annotation
I didn't test System.err because I didn't need it, but it should be easy to implement, similar to testing System.out.
There are a few ways to approach this. The most complete way is to pass in an InputStream while running the class under test which is a fake InputStream which passes simulated data to your class. You can look at a dependency injection framework (such as Google Guice) if you need to do this a lot in your code, but the simple way is:
public class MyClass {
private InputStream systemIn;
public MyClass() {
this(System.in);
}
public MyClass(InputStream in) {
systemIn = in;
}
}
Under test you would call the constructor that takes the input stream. You cloud even make that constructor package private and put the test in the same package, so that other code would not generally consider using it.
Try to refactor your code to use dependency injection. Instead of having your a method that uses System.in directly, have the method accept an InputStream as an argument. Then in your junit test, you'll be able to pass a test InputStream implementation in place of System.in.
You can write a clear test for the command line interface by using the TextFromStandardInputStream rule of the System Rules library.
public void MyTest {
#Rule
public final TextFromStandardInputStream systemInMock
= emptyStandardInputStream();
#Test
public void readTextFromStandardInputStream() {
systemInMock.provideLines("foo");
Scanner scanner = new Scanner(System.in);
assertEquals("foo", scanner.nextLine());
}
}
Full disclosure: I'm the author of that library.
You could create a custom InputStream and attach it to the System class
class FakeInputStream extends InputStream {
public int read() {
return -1;
}
}
And then use it with your Scanner
System.in = new FakeInputStream();
Before:
InputStream in = System.in;
...
Scanner scanner = new Scanner( in );
After:
InputStream in = new FakeInputStream();
...
Scanner scanner = new Scanner( in );
Although I think you should better to test how your class should work with the data read from the input stream and not really how it reads from there.
The problem with BufferedReader.readLine() is that it is a blocking method which waits for user input. It seems to me that you don't particularly want to simulate that (i.e. you want tests to be fast). But in a testing context it continually returns null at high speed during testing, which is irksome.
For a purist you can make the getInputLine below package-private, and mock it: easy-peezy.
String getInputLine() throws Exception {
return br.readLine();
}
... you'd have to make sure that you had a way of stopping (typically) a loop of user interaction with the app. You'd also have to cope with the fact that your "input lines" would always be the same until you somehow changed the doReturn of your mock: hardly typical of user input.
For a non-purist who wishes to make life easy for themselves (and produce readable tests) you could put all this stuff below in your app code:
private Deque<String> inputLinesDeque;
void setInputLines(List<String> inputLines) {
inputLinesDeque = new ArrayDeque<String>(inputLines);
}
private String getInputLine() throws Exception {
if (inputLinesDeque == null) {
// ... i.e. normal case, during app run: this is then a blocking method
return br.readLine();
}
String nextLine = null;
try {
nextLine = inputLinesDeque.pop();
} catch (NoSuchElementException e) {
// when the Deque runs dry the line returned is a "poison pill",
// signalling to the caller method that the input is finished
return "q";
}
return nextLine;
}
... in your test you might then go like this:
consoleHandler.setInputLines( Arrays.asList( new String[]{ "first input line", "second input line" }));
before triggering off the method in this "ConsoleHandler" class which needs input lines.
maybe like this (not tested):
InputStream save_in=System.in;final PipedOutputStream in = new PipedOutputStream(); System.setIn(new PipedInputStream(in));
in.write("text".getBytes("utf-8"));
System.setIn( save_in );
more parts:
//PrintStream save_out=System.out;final ByteArrayOutputStream out = new ByteArrayOutputStream();System.setOut(new PrintStream(out));
InputStream save_in=System.in;final PipedOutputStream in = new PipedOutputStream(); System.setIn(new PipedInputStream(in));
//start something that reads stdin probably in a new thread
// Thread thread=new Thread(new Runnable() {
// #Override
// public void run() {
// CoursesApiApp.main(new String[]{});
// }
// });
// thread.start();
//maybe wait or read the output
// for(int limit=0; limit<60 && not_ready ; limit++)
// {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
in.write("text".getBytes("utf-8"));
System.setIn( save_in );
//System.setOut(save_out);
#Stefan Birkner, Thanks!
Modify Pom.xml
Ref:
https://stackoverflow.com/a/66127606/8317677
https://github.com/stefanbirkner/system-lambda/blob/master/pom.xml
https://github.com/stefanbirkner/system-lambda/blob/master/src/test/java/com/github/stefanbirkner/systemlambda/WithTextFromSystemInTest.java
<properties>
<system-lambda.version>1.2.1</system-lambda.version>
</properties>
<dependencies>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-lambda</artifactId>
<version>${system-lambda.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Add function code
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class SimpleProgram003 {
public static void main(String[] args) {
try{
String c;
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
do{
c = in.readLine();
System.out.println(c);
String d = c;
}while(!c.equals("q"));
}catch(Exception e){
System.out.println("catch Exception");
}
}
}
Add test code
import static com.github.stefanbirkner.systemlambda.SystemLambda.*;
import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* Unit test for simple App. JUnit 4.x.
*/
public class SimpleProgram003Test {
private final InputStream systemIn = System.in;
private final PrintStream systemOut = System.out;
private ByteArrayInputStream testIn;
private ByteArrayOutputStream testOut;
#Before
public void setUpOutput() {
testOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(testOut));
}
private void setInput(String data) {
testIn = new ByteArrayInputStream(data.getBytes());
System.setIn(testIn);
}
private String getOutput() {
return testOut.toString();
}
#After
public void restoreSystemInputOutput() {
System.setIn(systemIn);
System.setOut(systemOut);
}
#Test
public void testCase1() {
final String testString = "Hello 1\nq\n";
setInput(testString);
SimpleProgram003.main(new String[0]);
// String a = getOutput();
assertEquals("Hello 1\r\nq\r\n", getOutput());
}
#Test // Multiply inputs
public void testCase2() throws Exception {
withTextFromSystemIn(
"Input1",
"Input2",
"q",
"Input3"
).execute(() -> {
SimpleProgram003.main(new String[0]);
// String a = getOutput();
assertEquals("Input1\r\nInput2\r\nq\r\n", getOutput());
});
}
}
I have a problem with the assignment. Basically the method printLinesWhichContain(String word) should print the lines that contain the given word, which works fine, and if the String is empty (""), print all lines in the file. The last part doesn't work. Any advice?
import java.io.File;
import java.util.Scanner;
public class Printer {
private File lol;
private Scanner reader;
public Printer(String fileName)throws Exception{
this.lol = new File(fileName);
this.reader = new Scanner(lol);
}
public void printLinesWhichContain(String word) {
if (word.isEmpty()) {
while (this.reader.hasNextLine()) {
String x = this.reader.nextLine();
System.out.println(x);
}
} else {
while (this.reader.hasNextLine()) {
String x = this.reader.nextLine();
if (x.contains(word)) {
System.out.println(x);
}
}
}
}
}
Main
public class Main {
public static void main(String[] args) throws Exception {
Printer printer = new Printer("src/textfile.txt");
printer.printLinesWhichContain("Väinämöinen");
System.out.println("-----");
printer.printLinesWhichContain("Frank Zappa");
System.out.println("-----");
printer.printLinesWhichContain("");
System.out.println("-----");
}
}
File text
Siinä vanha Väinämöinen
katseleikse käänteleikse
Niin tuli kevätkäkönen
näki koivun kasvavaksi
Miksipä on tuo jätetty
koivahainen kaatamatta
Sanoi vanha Väinämöinen
Output
Siinä vanha Väinämöinen
Sanoi vanha Väinämöinen
-----
-----
-----
In your application you created only one Scanner which can iterate over entire file only once. If you want to iterate over entire file each time you invoke printLinesWhichContain method, you need to reset your scanner, most likely by creating new one.
So one of options would be reinitializing already used scanner at the end of
public void printLinesWhichContain(String word) {
//...your original code
this.reader = new Scanner(lol);
}
method. But if you don't use scanner elsewhere outside of this method maybe it may be worth making it local variable instead of class field.
public void printLinesWhichContain(String word) {
Scanner reader = new Scanner(lol);
//...your original code
}
Other option could be reading file once and storing its content, using code like
List<String> allLines = Files.readAllLines(lol.toPath());
You can iterate over this list instead of file.
If you change the main to this:
public static void main(String[] args) throws Exception {
Printer printer = new Printer("src/textfile.txt");
printer.printLinesWhichContain("Väinämöinen");
System.out.println("-----");
printer.printLinesWhichContain("Frank Zappa");
System.out.println("-----");
printer = new Printer("src/textfile.txt");
printer.printLinesWhichContain("");
System.out.println("-----");
}
It will work. Reason being that, due to the way you've written your code, printer needs to be reinitialized so that it can start reading from the beginning again.
Considering the following function:
public void execute4() {
File filePath = new File(filePathData);
File[] files = filePath.listFiles((File filePathData) -> filePathData.getName().endsWith("CDR"));
List<CDR> cdrs = new ArrayList<CDR>();
Arrays.asList(files).parallelStream().forEach(file -> readCDRP(cdrs, file));
cdrs.sort(cdrsorter);
}
which reads a list of Files containing CDR and executes the readCDRP() which is this:
private void readCDRP(List<CDR> cdrs, File file) {
final CDR cdr = new CDR(file.getName());
try (BufferedReader bfr = new BufferedReader(new FileReader(file))) {
List<String> lines = bfr.lines().collect(Collectors.toList());
lines.parallelStream().forEach(e -> {
String[] data = e.split(",", -1);
CDREntry entry = new CDREntry(file.getName());
for (int i = 0; i < data.length; i++) {
entry.setField(i, data[i]);
}
cdr.addEntry(entry);
});
if (cdr != null) {
cdrs.add(cdr);
}
} catch (IOException e) {
e.printStackTrace();
}
}
What I observe is that occasionally and NOT all the time, I either get a ArrayIndexNotBound Exception at the readCDRP function over the line (which is awkward, as the list of cdr is an ArrayList() ):
cdr.addEntry(entry);
or
at the last line in execute4() where I apply the sorting.
I think the issue is that the first parallelStream from execute4 is not in a separate space in memory from the second parallelStream execution inside readCDRP() and also seems to share wrongly the data. Using "seem" word as I can't confirm and is just a hutch.
The questions are:
is my code buggy to the bone from JDK8 perspective?
Is there a workaround using the same flow, something like using CountDownLatch for example?
Is limitation of the ForkJoinPool ?
Thanks for any responce....
EDIT(1):
The addEntry is part of a class itself:
class CDR {
public final String fileName;
private final List<CDREntry> entries = new ArrayList<CDREntry>();
public CDR(String fileName) {
super();
this.fileName = fileName;
}
public List<CDREntry> getEntries() {
return entries;
}
public List<CDREntry> addEntry(CDREntry e) {
entries.add(e);
return entries;
}
public String getFileName() {
return this.fileName;
}
}
Your code is broken from a thread safety point of view. In readCDR you add elements to the cdrs list which is an ArrayList that does not support concurrent writes. That is why it breaks.
A better approach would be to have readCDR return a cdr object and do something like:
List<CDR> cdrs = Arrays.stream(files)
.parallel()
.map(this::readCDR)
.collect(Collectors.toList());
Also, using parallel streams for IO related operations is generally a bad idea, but that is another discussion.
When you starting programming in functional style you should prefer immutable objects which can be fully created via construction (or probably using builder pattern or some factory method). So your CDREntry class may look like this:
class CDREntry {
private final String[] fields;
private final String name;
public CDREntry(String name, String[] fields) {
this.name = name;
this.fields = fields;
}
// Add getters and whatever
}
And your CDR class may look like this:
class CDR {
private final String fileName;
private final List<CDREntry> entries;
public CDR(String fileName, List<CDREntry> entries) {
this.fileName = fileName;
this.entries = entries;
}
public List<CDREntry> getEntries() {
return entries;
}
public String getFileName() {
return this.fileName;
}
}
Having such classes things become easier. The rest of the code can be rewritten like this:
public void execute4() {
File filePath = new File(filePathData);
File[] files = filePath.listFiles((File data, String name) ->
data.getName().endsWith("CDR")); // fixed this line: it had compilation error
List<CDR> cdrs = Arrays.stream(files).parallel()
.map(this::readCDRP).sorted(cdrsorter)
.collect(Collectors.toList());
}
private CDR readCDRP(File file) {
try (BufferedReader bfr = new BufferedReader(new FileReader(file))) {
// I'm not sure that collecting lines into list
// before main processing was actually necessary
return bfr.lines().parallelStream()
.map(e -> new CDREntry(file.getName(), e.split(",", -1)))
.collect(Collectors.collectingAndThen(
Collectors.toList(), list -> new CDR(file.getName(), list)));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
In general remember that forEach is usually not the cleanest way to solve the tasks. It may be helpful when you integrate the streams into legacy code, but in general should be avoided.
you are using a parallel stream and a lambda that has side effects
(the lambda updates the ArrayList 'cdrs')
try to use a Collector or a Reduction-Operation.
My professor really threw us into this project with a blindfold on. We didn't go into depth on using and inserting files into Java. I'm getting a ton of errors, which are most likely due to my incorrect insertion of the file. I saved the text file in the same place the class file is saved on my computer, assuming that would be necessary. I've moved it around multiple places on my computer trying to get it to work. Here is the main program. I'm sorry if it's completely incorrect.
To explain what we're supposed to be doing further, here is the link to the prompt with the pseudocode. I haven't attempted to do all the actions listed because I haven't gotten the file to insert correctly yet.
http://jcsites.juniata.edu/faculty/rhodes/cs1/projects/program9Gen.html
Edit: This is the whole program in its glory. The class was created in a separate project as our introduction to Java classes. We were just told to use it again and insert the main program at the bottom just for ease of grading.
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class GenSeq
{
private String seq;
private String species;
private String startCodon;
private String stopCodon;
private String shape;
private String chromosomeLocation;
public GenSeq (String seq, String species, String startCodon, String stopCodon,
String shape, String chromosomeLocation){
this.seq = seq;
this.species = species;
this.startCodon = startCodon;
this.stopCodon = stopCodon;
this.shape = shape;
this.chromosomeLocation = chromosomeLocation;
}
//Allowing the program to later set constructors
//Creating all the appropriate getters and setters for the instance variables
public void setSpecies(String newSpecies){
species = newSpecies;
}
public String getSpecies(){
return species;
}
public void setStartCodon(String newStartCodon){
startCodon = newStartCodon;
}
public String getStartCodon(){
return startCodon;
}
public void setStopCodon(String newStopCodon){
stopCodon = newStopCodon;
}
public String getStopCodon(){
return stopCodon;
}
public void setShape(String newShape){
shape = newShape;
}
public String getShape(){
return shape;
}
public void setChromosomeLocation(String newChromosomeLocation){
chromosomeLocation = newChromosomeLocation;
}
public String getChromosomeLocation(){
return chromosomeLocation;
}
public String toString(){
return "Sequence length: " + seq.length() +
"\nSpecies: "+ species +
"\nStart Codon: "+ startCodon +
"\nStart Codon: "+ stopCodon+
"\nShape: "+ shape +
"\nChromosomal Location: " + chromosomeLocation;
//Creating a toString method to hold all the class data
}
}
public static void main (String args[ ])
{
GenSeq seqA = null;
//Setting constructor to default if not set
//Opening the file
Scanner inputStream = null;
String seq;
try
{
inputStream = new Scanner (new File ("W:\jcsites.junata.edu\students\morrian14\seq.txt"));
}
catch (FileNotFoundException e)
{
System.out.println ("Error opening the file ");
System.exit (0);
}
do{
inputStream = inputStream.trim();
if ('>' == inputStream.charAt(0)){
seq = inputStream.nextLine();
}
}
while (inputStream.hasNextLine());
inputStream.close();
}
The error is this same one repeated continuously
File: C:\LEXIPC\Users\Alexis\GenSeq.java [line: 83]
Error: class, interface, or enum expected
One obvious issue, the last line is clearly meant to have been written as inputStream.close(); and not input.Stream.close(); you will probably need a try .. catch ... around closing the stream too
What exactly is your question? A few notes though...
Get rid of the do{} while() and just do something like this:
while(inputStream.hasNextLine(){
if('>' == inputStream.charAt(0))
seq = inputStream.nextLine();
}
inputStream.close();
I am a bit confused as to why you are recycling seq to read from the file, as that is what you are using as your file's name. A better way to do this would be to use a File class for your file names. Consider: File seq = new File(.../filename.txt).
Also, if you find that you are using too many try/catch blocks, consider using an exception handling class to clean up your code.
This question already has answers here:
How to make IntelliJ prompt me for command line arguments
(3 answers)
Closed 8 years ago.
I have to develop a command line Java application in which the main() method accept 2 String parameters named respetivelly partitaIVA and nomePDF.
So, as starting point, I created this simple Main class:
public class Main {
public static void main(String[] args) {
System.out.println("Hello World !!!");
}
}
I think that I can perform this minimalistic application from the Windows console and that I can perform my application passion these parameters to it doing something like this in the Windows console (or in the Linux shell):
java Main 123456789 myDocument.pdf
and I think that I can retrieve it inside my application modifying the original code in this way:
public class Main {
public static void main(String[] args) {
System.out.println("Hello World !!!");
String partitaIVA = args[0];
String nomePDF = args[1];
}
}
So now I have 2 doubts about this topic:
1) I know that I can perform this application specifying my 2 parameters using the Windows command line or the Linux shell but can I do the same thing into my IDE console? Specifically in the Run tab of IntelliJ?
2) Can I specify in some way that the parameters that the user can specify are only 2?
1) There is something called run/debug configuration https://www.jetbrains.com/idea/help/creating-and-editing-run-debug-configurations.html (here are also sone details about the specific options you have: https://www.jetbrains.com/idea/help/creating-and-editing-run-debug-configurations.html#d1628194e152)
2) No, you can only print an error and guide the user
You should invest the time in learning a modern CLI argument parser:
I prefer JewelCli
<dependency>
<groupId>com.lexicalscope.jewelcli</groupId>
<artifactId>jewelcli</artifactId>
<version>0.8.9</version>
</dependency>
Here is an example that can be used as a base class:
public class Main
{
private static final Logger LOG;
static
{
LOG = LoggerFactory.getLogger(Main.class);
}
private static Args init(#Nonnull final String[] args)
{
final Cli<Args> cli = CliFactory.createCli(Args.class);
try
{
return cli.parseArguments(args);
}
catch (final ArgumentValidationException e)
{
for (final ValidationFailure vf : e.getValidationFailures())
{
LOG.error(vf.getMessage());
}
LOG.info(cli.getHelpMessage());
System.exit(2); // Bash standard for arg parsing errors
return null; // This is to make the compiler happy!
}
}
private static List<String> parseKey(#Nonnull final String key)
{
return new ArrayList<String>(Arrays.asList(key.toLowerCase().split("\\.")));
}
#SuppressWarnings("unchecked")
private static Map<String, Object> addNode(#Nonnull Map<String, Object> node, #Nonnull final List<String> keys, #Nonnull final String value)
{
if (keys.isEmpty())
{
return node;
}
else if (keys.size() == 1)
{
node.put(keys.remove(0), value.trim());
return node;
}
else if (node.containsKey(keys.get(0)))
{
return addNode((Map<String, Object>) node.get(keys.remove(0)), keys, value);
}
else
{
final Map<String, Object> map = new HashMap<String, Object>();
node.put(keys.remove(0), map);
return addNode(map, keys, value);
}
}
public static void main(final String[] args)
{
try
{
final Args a = init(args);
final Properties p = new Properties();
p.load(new FileInputStream(a.getInputFile()));
final HashMap<String, Object> root = new HashMap<String, Object>();
for (final String key : p.stringPropertyNames())
{
addNode(root, parseKey(key), p.getProperty(key));
}
switch (a.getFormat().toLowerCase().charAt(0))
{
case 'j': LOG.info(mapToJson(root)); break;
case 'b' : LOG.info(Strings.bytesToHex(mapToCbor(root))); break;
case 'x' : LOG.error("XML not implemented at this time!"); break;
default : LOG.error("Invalid format {}", a.getFormat());
}
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
interface Args
{
#Option(shortName = "i", longName = "input", description = "Properties file to read from.")
File getInputFile();
#Option(shortName = "o", longName = "output", description = "JSON file to output to.")
File getOutputFile();
#Option(shortName = "f", longName = "format", description = "Format of output Json|Binary|Xml")
String getFormat();
#Option(helpRequest = true, description = "Display Help", shortName = "h")
boolean getHelp();
}
}
In Intellij (Linux) you do:
Press Alt + Shift + F10 (the run shortcut)
Press right key
Go down to Edit
Then press Tab to go to "Program arguments".
This is where you pass the arugments in IntelliJ. After that just hit run.