Elseif never executing in onCommand - java

I am trying to make a plugin that adds "variables" into commands. You use /set (variablename) (value) to set a value and then you can use any command with var:(varname) (For example you could do /set foo bar and then do "/say var:foo" and it would say "bar" in chat) For some reason my
else if(Arrays.toString(args).contains("var:")) {
is either never executing or always returning false. Why is this, and how can I fix it?
Main plugin class:
public class main extends JavaPlugin implements Listener {
List<String> vars = new ArrayList<String>();
public void onEnable()
{
getLogger();
getServer().getPluginManager().registerEvents(this, this);
Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "Variables Enabled!");
}
public void onDisable()
{
Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "Variables Disabled!");
}
#Override
public boolean onCommand(CommandSender sender, Command command,
String label, String[] args) {
if(command.getName().equalsIgnoreCase("set")) {
vars.add(args[0] + ":" + args[1]);
sender.sendMessage(ChatColor.RED + "Variable " + args[0] + " added with the value " + args[1]);
}else if(Arrays.toString(args).contains("var:")) { //Line problem is on
int size = args.length;
for (int i=0; i<size; i++)
{
if(args[i].contains("var:")) {
String[] parts = args[i].split(":");
for (String temp : vars) {
String[] varname = temp.split(":");
if(varname[1].equals(parts[1])) {
args[i] = varname[2];
}
}
}
}
}
return super.onCommand(sender, command, label, args);
}
}
EDIT: The way I know it is a problem with my else if is that if I add
sender.sendMessage("test"); right under the elseif I never get the message "test" even when I have var: in my args.
EDIT 2: I've figured out one part of it. For some reason whenever I do something like /say or /broadcast the onCommand doesn't get fired...

It seems that your problem is not with the else if but with the innermost if. You compare the variable name of your command (foo) with the previously stored value (bar). Try instead:
if(varname[0].equals(parts[1])) {
args[i] = varname[1];
}
Of course, it would be even better to use more appropriate collection types. For example vars might be a Map<String, String>.

I solved my own problem! The problem is that onCommand only fires when taking commands from your plugin. I had to use a playercommandpreprocessevent for this.

The onCommand() method won't enable you to filter all the command requests being sent by a player, it only lets you implement the commands you listed in your plugin.yml. I would instead use the PlayerCommandPreprocessEvent which does allow you to "catch" all the commands a player tries to execute before they are handled by a specific plugin and change the command as well as the subsequent arguments.
I would also use a HashMap instead of a List that will map the names and values of the String variables to each other. Here's some example code I tested that should work:
//Somewhere else
private HashMap<String, String> variableMap = new HashMap<String, String>();
public boolean onCommand(CommandSender sender, Command command, String alias, String[] args) {
if (command.getName().equalsIgnoreCase("set")) {
if (args.length == 2) { //With this approach, the first argument is the name, the second the value
variableMap.put(args[0], args[1]);
sender.sendMessage(ChatColor.GREEN + "Variable " + args[0] + " now has value " + args[1] + ".");
}
}
return false;
}
#EventHandler
public void onCommand(PlayerCommandPreprocessEvent event) {
String[] args = event.getMessage().split(" "); //Get the message and split it to get the arguments
String newMessage = args[0]; //In this example I preserve the original command, so I'm not changing the first argument
for (int i = 1; i < args.length; i++) {
if (args[i].toLowerCase().startsWith("var:")) { //If it starts with "var:", case insensitive...
String[] parts = args[i].split(":");
if (parts.length == 2 && variableMap.containsKey(parts[1])) { //If it has two parts, and the variable map contains the name of that variable
newMessage += " " + variableMap.get(parts[1]); //Add the variable name and not the original argument
continue;
}
}
newMessage += " " + args[i]; //If the argument is not a variable or it is wrongly formatted, add the original argument to the combined string
}
event.setMessage(newMessage); //Set the new command message or contents
}

Related

Is it possible to check if method is declared in a group of classes?

I'm using ArchUnit and I want to check if all classes residing in a package declare one only public method called execute. I have this code:
JavaClasses importedClasses = new ClassFileImporter().importPackages("my.package");
methods().that().arePublic().should().haveName("execute").check(importedClasses);
It works but I would like this test to fail when execute method is absent in any class. With my code, test passes because all public methods (zero) actually have name "execute".
In order to test classes, you have to base your ArchRule on them (not on the methods that might not exist). You can use a custom condition to count public methods and test their names, e.g. like this:
ArchRule rule = classes()
.that().resideInAPackage("my.package")
.should(new ArchCondition<JavaClass>("have exactly one public method named 'execute'") {
#Override
public void check(JavaClass javaClass, ConditionEvents events) {
List<JavaMethod> publicMethods = javaClass.getMethods().stream()
.filter(javaMethod -> javaMethod.getModifiers().contains(PUBLIC))
.collect(toList());
boolean satisfied = false;
String message = javaClass.getName() + " contains " + publicMethods.size() + " public method";
if (publicMethods.size() == 1) {
JavaMethod method = publicMethods.get(0);
satisfied = method.getName().equals("execute");
message += " named '" + method.getName() + "' " + method.getSourceCodeLocation();
} else {
message += "s " + javaClass.getSourceCodeLocation();
}
events.add(new SimpleConditionEvent(javaClass, satisfied, message));
}
});

How to handle signs in java?

I have a command line with different parameters (strings and an int value).
The problem is that i have both spaces and = characters in this input string, which Java recognizes as separators.Now i wonder how to parse this into my program.
I look for a possibility to make it as simple as possible. The parameter values must also be passed to various subroutines. So I'm looking for a way to easily access the parameters to pass them to subroutines and at the same time control that each command line contains these parameters (and that they actually contain valid value).
Maybe someone could give me a hint how to do this the most "correct" way.
Something like this:
public class Demo {
public static void main(String[] args) {
Map<String, String> cliParams = new HashMap<>();
// Full set of required parameters
cliParams.put("t", null);
cliParams.put("vo", null);
cliParams.put("q", null);
for (String arg : args) {
String[] nameAndValue = arg.split("=", 2);
if (nameAndValue.length != 2)
throw new IllegalArgumentException("Invalid parameter syntax: " + arg);
String name = nameAndValue[0];
String value = nameAndValue[1];
if (cliParams.replace(name, value) != null)
throw new IllegalArgumentException("Parameter given more than once: " + arg);
}
cliParams.forEach((k, v) -> {
if (v == null)
throw new IllegalStateException("Required parameter missing: " + k);
});
int q = Integer.parseInt(cliParams.get("q"));
}
}

How can I pass arguments like these for example: runType=EOD threadCnt=10 cleanLogs=true in my commandline java program?

What is the best way to pass these arguments to a java commandline program. Currently, I have order dependent args that the user must remember. My program is called as below:
CommandLineRunner EOD 10 true
I want the user to be able to do this:
CommandLineRunner runType=EOD threadCnt=10 cleanLogs=true
Or change order of arguments.
CommandLineRunner threadCnt=10 runType=EOD cleanLogs=true
I would prefer not to use an external library for this but if there is a good one in java, please provide an example of how to use it to build my example. The = signs must be there since I need to make this program follow a standard for other python/ruby based cli applications we have in our organization.
Thank you in advance.
You can pass the parameters in the command line just after the main class:
java CommandLineRunner "runType=EOD" "threadCnt=10" "cleanLogs=true"
Then in the program:
import java.util.*;
public class CommandLineRunner {
public static void main(String args[]){
//Collect the input parameters...
HashMap<String, String> argsMap = new HashMap<String, String>();
for (int i = 0; i < args.length; i++){
StringTokenizer st = new StringTokenizer(args[i], "=");
while(st.hasMoreTokens()) {
String key = st.nextToken();
//Handle the case you don't define a key/value with '='
try{
String val = st.nextToken();
argsMap.put(key, val);
}catch(NoSuchElementException e){
argsMap.put(key, "");
}
}
}
//Then use them when needed...
for (Map.Entry<String,String> entry : argsMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println("arg: "+ key + "\tval:" + value);
if(key.equals("runType")){
//Add your business logic here
}
else if (key.equals("threadCnt")){
//Add your business logic here
}
else if (key.equals("cleanLogs")){
//Add your business logic here
}else
//Unknown argument
throw new RuntimeException("Invalid argument!");
}
}
}
So you will have your arguments in your map, despite of their input order.
Note
From Java7 you can use the more elegant switch construct to handle String (thus your arguments) too:
//Then use them when needed...
for (Map.Entry<String,String> entry : argsMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println("arg: "+ key + "\tval:" + value);
switch (key) {
case "runType":
//Add your business logic here
break;
case "threadCnt":
//Add your business logic here
break;
case "cleanLogs":
//Add your business logic here
break;
default:
//Unknown argument
throw new RuntimeException("Invalid argument!");
}
}
This will work as long as you don't use spaces in the values:
java CommandLineRunner threadCnt=10 runType=EOD cleanLogs=true
Then you'll have to parse each of the three arguments.

extending a class with an extra parameter

I got a simple question. I have a class, which I use for purpose of splitting a string in 2 years:
public class Period {
int firstYear;
int secondYear;
Period () {
}
Period(String periode) {
String [] periodeSplit = periode.split("-");
this.firstYear = Integer.parseInt(periodeSplit[0]);
this.secondYear = Integer.parseInt(periodeSplit[1]);
}
public String toString() {
return "Firstyear: " + this.firstYear + "\n" + "Secondyear: " + this.secondYear;
}
}
I now want to extend this class, not splitting the data into 2 different ints but into 3 different ints. So besides the 2 already exisiting integer vars I want one extra. Whats the easiest way of doing this?
Your help is appreciated!
Kind regards,
Kipt Scriddy
I think it would be better (and quite easy) the create more general class that will be able to deal with any number of years you pass to it:
public class Period {
int[] years;
Period() {
}
Period(String periode) {
String[] periodeSplit = periode.split("-");
years = new int[periodeSplit.length];
for (int i = 0; i < periodeSplit.length; i++) {
years[i] = Integer.parseInt(periodeSplit[i]);
}
}
public String toString() {
String result = "";
for (int i = 0; i < years.length; i++) {
result += "Year " + i + ":" + years[i] + "\n";
}
return result;
}
}
If the original class really have to be extended than it can be done like this:
class ExtendedPeriod extends Period {
int thirdPart;
ExtendedPeriod(String periode) {
String[] periodeSplit = periode.split("-");
this.firstYear = Integer.parseInt(periodeSplit[0]);
this.secondYear = Integer.parseInt(periodeSplit[1]);
this.thirdPart = Integer.parseInt(periodeSplit[1]);
}
public String toString() {
return "Day: " + this.firstYear + "\n" + "Month: " + this.secondYear
+ "\nYear: " + this.thirdPart;
}
}
I would recommand to change variable names 'firstYear' and 'secondYear' to something different, like 'firstPart', 'secondPart' because for extendedPeriod they aren't years anymore (I left them in my code so it would compile with yours but called the new int 'thirdPart'). I don't feel that this is the best use of inheritance but if that's what's needed. I also wanted to reuse toString from Period like this:
public String toString2() {
return super.toString() + "\nThird part: " + this.thirdPart;
}
but for it to have sense you would have to chagne toString method in Period not to call values 'years'.
When you extend the class, split it into two variables first, the one that's different from your current code, and then the one that your current code would handle.
Then simply call super(periode)
The child class will have access to the parent variables, since you made them default.
I wouldn't extend to just add a new year.
Why not make the entire thing generic enough, so that it supports whatever split you need.
public class Period {
String [] periodeSplit;
Period(String periode) {
periodeSplit = periode.split("-");
}
public String toString() {
//TODO : Iterate and print.
}
}

Searching object arraylist confusion

I wrote a method for searching an object in my arralist, however I need this search to take two parameters and compare them to the description of the object. What it is confusing me is that I do not know how to make my method take two parameter and those two parameters compare to one single description.
e.g Description of object: Blue Mustang
Now the search method has to take two parameters in this case one String "Blue" and one String "Mustang" and I do not have a clue how to do it. I found a way to just write the complete description as one parameter and works perfectly. I am leaving the method that I tried to change to look for two parameters and as well the method that takes one parameter and works fine. It is important to mention that the method also needs to look for cars that are not on loan so thats why there is also a comparison to the hire date since if the car is on loan will have one and if not it wouldn't.
Trying to change it to look for two parameters:
public Car searchCar(String description, String description2)
{
for (Car car : cars) {
if (car.getDescription().equals(description) && car.getDescription().equals(description2) && car.getHireDate().equals("")) {
System.out.println( ": " + car.getDescription() + " rate: £" + car.getDailyRate() + " Down payment: £"+ car.getDownPayment());
return car;
}
else if (car.getHireDate() != ("")) {
System.out.println("This car is rented!!");
}
else {
}
}
return null;
}
This is the one that takes one parameter and works fine:
public Car searchCar(String description)
{
for (Car car : cars) {
if (car.getDescription().equals(description) && car.getHireDate().equals("")) {
System.out.println( ": " + car.getDescription() + " rate: £" + car.getDailyRate() + " Down payment: £"+ car.getDownPayment());
return car;
}
else if (car.getHireDate() != ("")) {
System.out.println("This car is rented!!");
}
else {
}
}
return null;
}
description needs to be identical to description2
you could do :
String fulldescription=description+" "+description2;
and then
if (car.getDescription().equals(fulldescription)...
a different approach can be
if (car.getDescription().indexOf(description)>0 && car.getDescription().indexOf(description2)>0 )...
It's unlikely the description would be exactly equal to both parameters. Perhaps you're trying to match only part of the description?
Something like contains might be of interest.
Also note that this line:
else if (car.getHireDate() != ("")) {
will not work as you expect; you correctly compare strings in the previous statement (using equals) but fail to do so here.
First, you should define whether exact matches are necessary or if the parameters should be contained and there might be additional text in the description.
Exact matches
For exact matches, you can use the or-condition:
if ( (car.getDescription().equals(description) | car.getDescription().equals(description2) ) && car.getHireDate().equals("") ) {
...
}
This means that if the description exactly matches description or description2 you get a hit.
Alternatively you could concatenate the parameters if you know the order:
if ( car.getDescription().equals(description + " " + description2) && car.getHireDate().equals("") ) {
...
}
This would match "Blue Mustang" but not "Mustang Blue".
Optionally you could make the concatenated string a regular expression in order to allow for arbitrary whitespace:
if ( car.getDescription().matches("\\s*\\b" + description + "\\b\\s*\\b" + description2 + "\\b\\s*") && car.getHireDate().equals("") ) {
...
}
This would match " Blue Mustang " etc. but not "BlueMustang" due to the word boundary requirements ("\\b").
Note that there are many more possibilities so you should define how you want to match first.
Parameters contained
If you want to get all descriptions that contain both parameters, use contains(...) instead:
if ( car.getDescription().contains(description) && car.getDescription().contains(description2) && car.getHireDate().equals("") ) {
...
}
Note that this would match "Blue Ford Mustang", too ("Blue" and "Mustang" being the parameters).
In order to test for the occurence of substrings, you can use indexOf() (docs):
if (-1 != car.getDescription().indexOf(description) &&
-1 != && car.getDescription().indexOf(description2) &&
car.getHireDate().equals("")) {
you might want to make the comparison case-insensitive too.
I would suggest use contains and toLowerCase for String search. Also consider a variable argument search method like this:
public Car searchCar(String...descriptions) {
for (Car car : cars) {
if (car.getHireDate().equals("")) {
for (String d : descriptions) {
// assuming car.getDescription() != null
if (car.getDescription().toLowerCase().contains(d.toLowerCase())) {
System.out.println( ": " + car.getDescription() + " rate: £" + car.getDailyRate() + " Down payment: £"+ car.getDownPayment());
return car;
}
}
}
else
System.out.println("This car is rented!!");
}
return null;
}
Then you can call this method like:
Car c1 = searchCar("blue");
OR
Car c1 = searchCar("blue", "toyota");
OR
Car c1 = searchCar("blue", "red", "toyota", "mustang", "honda");
Consider encapsulating your search/match in a class which you can augment to include whatever search criteria you require.
Here is an example which satisfies your current requirements.
class CarSearch {
private boolean isIncludeHired;
private String[] descriptionWords;
private boolean isCaseInsensitive;
public void setIncludeHired(boolean b) {
isIncludeHired = b;
}
public void setCaseInsensitive(boolean b) {
this.isCaseInsensitive = b;
}
public void setDescriptionWords(String[] words) {
this.descriptionWords = words;
}
public boolean matches(Car car) {
boolean matches = true;
if (!isIncludeHired) {
if (car.getHireDate() != null) {
matches = false;
}
}
if (matches && descriptionWords != null) {
String description = car.getDescription();
if (isCaseInsensitive) {
description = description.toLowerCase();
}
boolean allMatch = true;
for (String word: descriptionWords) {
if (isCaseInsensitive) {
word = word.toLowerCase();
}
if (!description.contains(word)) {
allMatch = false;
break;
}
}
matches = allMatch;
}
return matches;
}
public List<Car> search(List<Car> cars) {
List<Car> matched = new ArrayList<Car>();
for (Car car: cars) {
if (matches(car)) {
matched.add(car);
}
}
return matched;
}
}
So now you could do something this:
CarSearch search = new CarSearch();
search.setCaseInsensitive(true);
search.setIncludeHired(false);
search.setDescriptionWords(new String[]{"blue", "mustang"});
List<Car> matches = search.search(cars);
You could also easily change your CarSearch class to allow other things, e.g. exact match, search by hire date, search by registration, etc.

Categories

Resources