Choose which methods to run, with user input - java

I have a list of methods within my class. And then want to have input string array, where the user can choose which methods they want to run. We are running expensive insurance calculations. And have over say eg 20 methods. Is there a way to conduct this without do an if check on each? maybe with reflection or interface?
#Override
public void ProductTest(ProductData productData, String[] methodNames) {
public void methodA(ProductData productData){...};
public void methodB(ProductData productData){...};
public void methodC(ProductData productData){...};
public void methodD(ProductData productData){...};
public void methodE(ProductData productData){...};
}
I am willing to change the Array into a different ObjectType if needed, to execute properly. Using SpringBoot, has it has a library of utility classes.

Use a Map<String, Consumer<ProductData>>, not separate method handles. Main reason - reflection is slow and dangerous when given user "input"
Use map.get(input).accept(product) to call it.
https://docs.oracle.com/javase/8/docs/api/index.html?java/util/function/Consumer.html
Example
Map<String, Consumer<ProductData>> map = new HashMap<>();
map.put("print_it", System.out::println);
map.put("print_id", data -> System.out.println(data.id));
map.put("id_to_hex", data -> {
int id = data.getId();
System.out.printf("0x%x%n", id);
});
ProductData data = new ProductData(16);
map.get("print_it").accept(data);
map.get("print_id").accept(data);
map.get("id_to_hex").accept(data);
Outputs
ProductData(id=16)
16
0x10
If you are planning on chaining consumers using andThen, you'd be better having an Optional<ProductData>, and using a Function<ProductData, ProductData> with Optional.map()

One way to do it is via reflection. You can iterate over methods in the class object and look for ones to run by name. Here's some example code--this would print out a list of names the user could type in:
myObject.getClass().getDeclaredMethods().each((method)->System.out.println(method.getName()))
And this is how you would call it once the user had made a selection:
productTest.getDeclaredMethods().each((method)->
if(method.getName().equals(userSelectedName))
method.invoke(productTest, productData)
)
The ONLY advantage to this approach is that you don't have to maintain a second structure (Switch, Map, etc...) and add to it every time you add a new method. A personality quirk makes me unwilling to do that (If adding something one place forces you to update a second, you're doing it wrong), but this doesn't bother everyone as much as it bothers me.
This isn't dangerous or anything, if you don't have a method in the class it can't call it, but if you are relying on users "Typing", I'd suggest listing out the options and allowing a numeric selection--or using reflection to build a map like OneCricketeer's.
I've used this pattern to write a testing language and fixture to test set-top TV boxes, it was super simple to parse a group of strings, map some to methods and other to parameters and have a very flexible, easily extensible testing language.
The method object also has a "getAnnotation()" which can be used to allow more flexible matching in the future.

You can use method invocation.
For example, you can have two methods, first one will loop through your methodNames array and call the second method:
public void callPassedMethods(ProductData productData, String[] methodNames) {
for (String m : methodNames) {
callMethod(productData, m)
}
}
And the second method will actually find a method in your class that matches the string passed and invoke it:
public void callMethod(ProductData productData, String methodName) {
try {
ClassName yourObj = new ClassName(); // Class where your methods are
Method method = yourObj.getClass().getDeclaredMethod(methodName, ProductData.class);
method.invoke(yourObj, productData);
} catch(NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// handle exceptions
}
}
Or, you can always use the good old switch statement:
for (String m : methodNames) {
switch (m) {
case "methodA":
methodA();
break;
case "methodB":
methodB();
break;
// ... continue with as many cases as you need
}
}

If you go with the reflection route, you don't really want to expose your method names to the end users. They might not be end user-friendly, and if they are, there is no reason for users to know this information and there might be methods, which are not supposed to be invoked by users. I would use custom annotations to build more flexible matching.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface UserChoice {
String userFriendlyOption();
int optionNumber();
}
optionNumber will be used for matching the method to invoke, userFriendlyOption is some user friendly text.
Annotate only the methods, supposed to be used by users.
#RequiredArgsConstructor
public class ProductData {
private final double data;
#UserChoice(userFriendlyOption = "see result for option a", optionNumber = 1)
public void methodA() {
System.out.println(data + 1);
}
#UserChoice(userFriendlyOption = "see result for option b", optionNumber = 2)
public void methodB() {
System.out.println(data + 2);
}
#UserChoice(userFriendlyOption = "see result for option c", optionNumber = 3)
public void methodC() {
System.out.println(data);
}
public void methodNotForUser() {
System.out.println("Should not be seen by users");
}
}
Like this methodNotForUser() can't be invoked by end users.
Simplified matcher might look like this.
#RequiredArgsConstructor
public class ProductTester {
private final ProductData data;
private Map<Integer, MethodData> map;
public void showOptions() {
if (this.map == null) {
this.map = new HashMap<>();
for (Method method : this.data.getClass().getMethods()) {
UserChoice userChoice = method.getAnnotation(UserChoice.class);
if (userChoice != null) {
String userRepresentation = userChoice.optionNumber() + " - " + userChoice.userFriendlyOption();
this.map.put(userChoice.optionNumber(), new MethodData(userRepresentation, method));
}
}
}
this.map.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(entry -> System.out.println(entry.getValue().getUserRepresentation()));
}
public void showOptionResult(int choice) {
MethodData methodData = this.map.get(choice);
if (methodData == null) {
System.out.println("Invalid choice");
return;
}
System.out.println("Result");
try {
methodData.getMethod().invoke(this.data);
} catch (IllegalAccessException | InvocationTargetException ignore) {
//should not happen
}
}
}
MethodData is simple pojo with the sole purpose to not recalculate user representation.
#RequiredArgsConstructor
#Getter
public class MethodData {
private final String userRepresentation;
private final Method method;
}
Short main to illustrate the idea and play around:
public class Temp {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Write initial value");
double data = scanner.nextDouble();
ProductData myData = new ProductData(data);
ProductTester tester = new ProductTester(myData);
tester.showOptions();
System.out.println("Write option number");
int userChoice = scanner.nextInt();
tester.showOptionResult(userChoice);
}
}

Related

Why does forEach method accept lambda that invokes method with multiple arguments when Consumer only takes one argument?

I am playing with forEach on a List<String>, and I'm confused about why the following line is acceptable:
policies.forEach(policy -> test.addToDatabase(policy, stats));
Since forEach requires a Consumer, and the Consumer accept method only takes one argument, I don't understand why the call to addtoDatabase is acceptable, as it takes two arguments. See below for full test code. Note that I am only playing around here to learn, so this code is not meant to be perfect or elegant.
public class ConsumerTest {
private Random random = new Random();
public static void main(String[] args) {
ConsumerTest test = new ConsumerTest();
List<String> policies = new ArrayList<>();
policies.add("11111");
policies.add("22222");
policies.add("33333");
policies.add("44444");
policies.add("55555");
Stats stats = test.new Stats();
policies.forEach(policy -> test.addToDatabase(policy, stats));
System.out.println("Success count: " + stats.getSuccessCount() + "\nFailure count: " + stats.getFailureCount());
}
private void addToDatabase(String policy, Stats stats) {
// simulate success/failure adding to DB with Random
if (random.nextBoolean()) {
stats.incrementSuccessCount();
System.out.println("Success for Policy " + policy);
} else {
stats.incrementFailureCount();
System.out.println("Failure for Policy " + policy);
}
}
class Stats {
private int successCount;
private int failureCount;
public void incrementSuccessCount() {
successCount++;
}
public void incrementFailureCount() {
failureCount++;
}
public int getSuccessCount() {
return successCount;
}
public int getFailureCount() {
return failureCount;
}
}
}
I am playing with forEach on a List of Strings, and I'm confused about why the following line is acceptable:
policies.forEach(policy -> test.addToDatabase(policy, stats));
It is. You confuse the parameter of the Iterable::forEach with the parameters of the statements inside the lambda expression. Since the only parameter inside the Iterable::forEach is Consumer<T> which is nothing than an implementation of the very same anonymous class:
Consumer<String> consumer = new Consumer<>() {
#Override
public void accept(final String policy) {
test.addToDatabase(policy, stats)
}
};
policies.forEach(consumer);
It is the same as:
Consumer<String> consumer = policy -> test.addToDatabase(policy, stats);
policies.forEach(consumer);
What is inside doesn't matter - the number of passed parameters into Iterable::forEach remains only one:
policies.forEach(policy -> {
log.info("adding to database");
test.addToDatabase(policy, stats);
log.info("added to database");
});
There is theoretically an unlimited number of statements and variables you can work with. The only condition that whatever you use inside the lambda expression must be effectively final.

Java Array of InnerClass throwing java.lang.NoSuchFieldError

I am trying to brushup java after a long time.
Any help is much appreciated.
For demonstration I have Animal Class that has an array of innerclass of Organs.
public class Animal
{
String nameOfAnimal;
Organs [] vitalOrgans = new Organs[3];
public Animal()
{
}
public String getNameOfAnimal() {
return nameOfAnimal;
}
public void setNameOfAnimal(String nameOfAnimal) {
this.nameOfAnimal = nameOfAnimal;
}
#Override
public String toString() {
return "Animal{" + "nameOfAnimal=" + nameOfAnimal + "}";
}
class Organs{
String nameOfOrgan;
public String getNameOfOrgan() {
return nameOfOrgan;
}
public void setNameOfOrgan(String nameOfOrgan) {
this.nameOfOrgan = nameOfOrgan;
}
#Override
public String toString() {
return "Organs{" + "nameOfOrgan=" + nameOfOrgan + '}';
}
}
}
Now in driver file when I make call there is no syntactical error but I get "Exception in thread "main" java.lang.NoSuchFieldError: vitalOrgans"
Animal mamal = new Animal();
mamal.setNameOfAnimal("Chimp");
mamal.vitalOrgans[0].setNameOfOrgan("Heart");
System.out.println(mamal.vitalOrgans[0].getNameOfOrgan());
What would be the way to make this (or similar idea) to work.
Thanks.
You would need to initialize the vitalOrgrans with new Organs(). Like:
public Animal() {
for (int i = 0; i < vitalOrgans.length; i++) {
vitalOrgans[i] = new Organs();
}
}
Because when you say :
Organs[] vitalOrgans = new Organs[3];
You are creating an array of 3 null Organs. Hence the null pointer exception, when accessing "vitalOrgans[i].".
Taking the relevant bit of code:
public class Animal
{
//...
Organs [] vitalOrgans = new Organs[3];
//...
}
Since your declaration of vitalOrgans was never given an access modifier (i.e. one of private, public, protected) it took on default access, which means only other classes in the same package can see it. Since your other block of code is not in the same package, it cannot see the field.
A minimally viable modification to just make it work would be to set the access to public:
public class Animal
{
//...
public Organs [] vitalOrgans = new Organs[3];
//...
}
While this works, it's not necessarily the best solution, as if you ever change how vitalOrgans is represented, or need to perform any validation, those edits would have to be done throughout the application. Thus, a better solution (and also, a major stylistic convention in Java for those exact reasons) is to make it (and all your fields, in fact) private and access via methods:
public class Animal {
private String nameOfAnimal;
private Organs[] vitalOrgans = new Organs[3];
//...
public Organs[] getVitalOrgans() {
return vitalOrgans;
}
//Alternative accessor that fetches only one organ.
public Organs getVitalOrgan(int index) {
if(index >= 0 && index < vitalOrgans.length)
return vitalOrgans[index];
else
return null;
}
public void setVitalOrgans(Organs[] vitalOrgans) {
this.vitalOrgans = vitalOrgans
}
//...
}
Your caller could then access Organs via either form of the get method (note, you probably want Organs to be public):
Animal.Organs futureMammalHeart = mamal.getVitalOrgan(0); //Animal.Organs due to Organs being an inner class.
if(futureMammalHeart != null) //Demonstration of null check. Safety first!
futureMammalHeart.setNameOfOrgan("Heart");
Animal.Organs[] mammalianVitalOrgans = mamal.getVitalOrgans();
if(mammalianVitalOrgans != null) //Just in case...
System.out.println(mamal.mammalianVitalOrgans[0].getNameOfOrgan());
Also, as Ari mentioned in his answer, don't forget to initialize the organs in your array, otherwise you will get a NullPointerException!

Use the command line to make new objects

In my program, the user needs to input what type of players the game will have. The players are "human", "good" (for a good AI), "bad" (for a bad AI) and "random" (for a random AI). Each of these players have their own class that extend one abstract class called PlayerType.
My struggle is mapping a String to the object so I can A) create a new object using the String as sort of a key and B) get the related String from an object of its subclass
Ultimately, I just want the implicit String to only appear once in the code so I can change it later if needed without refactoring.
I've tried using just a plain HashMap, but that seems clunky with searching the keys via the values. Also, I'm guessing that I'll have to use the getInstance() method of Class, which is a little less clunky, which is okay if it's the only way.
What I would do is create an enum which essentially functions as a factory for the given type.
public enum PlayerTypes {
GOOD {
#Override
protected PlayerType newPlayer() {
return new GoodPlayer();
}
},
BAD {
#Override
protected PlayerType newPlayer() {
return new BadPlayer();
}
},
RANDOM {
#Override
protected PlayerType newPlayer() {
return new RandomPlayer();
}
};
protected abstract PlayerType newPlayer();
public static PlayerType create(String input) {
for(PlayerTypes player : PlayerTypes.values()) {
if(player.name().equalsIgnoreCase(input)) {
return player.newPlayer();
}
}
throw new IllegalArgumentException("Invalid player type [" + input + "]");
}
)
Because then you can just call it like so:
String input = getInput();
PlayerTypes.create(input);
Of course, you'll get an IllegalArgumentException which you should probably handle by trying to get the input again.
EDIT: Apparently in this particular case, you can replace that loop with just merely
return PlayerTypes.valueOf(input).newPlayer();
And it'll do the same thing. I tend to match for additional constructor parameters in the enum, so I didn't think of using valueOf(), but it's definitely cleaner.
EDIT2: Only way to get that information back is to define an abstract method in your PlayerType class that returns the PlayerTypes enum for that given type.
public class PlayerType {
public abstract PlayerTypes getType();
}
public class GoodPlayer extends PlayerType {
#Override
public PlayerTypes getType() {
return PlayerTypes.GOOD;
}
}
I like the answer provided by Epic but I don't find maps to be clunky. So it's possible to keep a map and get the constructor call directly.
Map<String, Supplier<PlayerType> map = new HashMap<>();
map.put("human", Human::new);
Human h = map.get("human").get();
The two main options I can think of:
Using Class.newInstance(), as you mentioned (not sure if you had this exact way in mind):
// Set up your map
Map<String, Class> classes = new HashMap<String, Class>();
classes.put("int", Integer.class);
classes.put("string", String.class);
// Get your data
Object s = classes.get("string").newInstance();
You could use Class.getDeclaredConstructor.newInstance if you want to use a constructor with arguments (example).
Another option is using switch:
Object getObject(String identifier) {
switch (identifier) {
case "string": return new String();
case "int": return new Integer(4);
}
return null; // or throw an exception or return a default object
}
One potential solution:
public class ForFunFactory {
private ForFunFactory() {
}
public static AThing getTheAppropriateThing(final String thingIdentifier) {
switch (thingIdentifier) {
case ThingImplApple.id:
return new ThingImplApple();
case ThingImplBanana.id:
return new ThingImplBanana();
default:
throw new RuntimeException("AThing with identifier "
+ thingIdentifier + " not found.");
}
}
}
public interface AThing {
void doStuff();
}
class ThingImplApple implements AThing {
static final String id = "Apple";
#Override
public void doStuff() {
System.out.println("I'm an Apple.");
}
}
class ThingImplBanana implements AThing {
static final String id = "Banana";
#Override
public void doStuff() {
System.out.println("I'm a Banana.");
}
}

How print the array in another class?

Basically, i have a class where i have my arrays in, which is like this
public final class DepotDatabase {
private Driver[] arrayDrivers;
public DepotDatabase() {
arrayDrivers = new Driver[4];
arrayDrivers[0] = new Driver(1234, 1234, 0); // sample driver
arrayDrivers[1] = new Driver(4444, 4444, 0); // sample driver
arrayDrivers[2] = new Driver(1337, 1337, 1); // sample manager
arrayDrivers[3] = new Driver(1234, 1234, 0); // sample driver
}
and i want to print this array in another class, i did set up the array in another class
public Driver(int username, int password, int managerCheck) {
this.username = username;
this.password = password;
this.managerCheck = managerCheck;
}
but now i want to be able to print out all the drivers, but in another class which will be called ViewDrivers or something similar
You can create a method inside DepotDatabase to print the array, then create an object from and call print method.
public final class DepotDatabase {
private Driver[] arrayDrivers;
public void printArray() {
for (int i = 0; i < arrayDrivers.length; i++) {
Driver d = arrayDrivers[i];
System.out.println("Username : " + d.getUsername());
System.out.println("Password : " + d.getPassword());
System.out.println(" Manager Check: " + d.getManagerCheck());
}
}
the from the test class you can do:
public void execute() {
DepotDatabase ddb = new DepotDatabase();
ddb.printArray();
}
That's why you'll need to have getters and setters. You should have:
public Driver[] getDrivers() {
return arrayDrivers;
}
and in the other class, you simply call it (and print it or whatever).
Read this tutorial.
If you plan to print your array in another class you show create an accessor to it.
The common pattern for Java is to use "get plus name off attribute", getDrivers() you should also avoid the class name in such geter as it may changed due to application life.
public final class DepotDatabase {
//your code
public Driver[] getDrivers() {
return this.arrayDrivers;
}
}
Next question to answer is a returning the whole array is good idea. When you return it as above you loose control on it. And every one that call that method will be able to change the content of it.
To prevent this you should use so called Defensive copying
public Driver[] getDrivers() {
return Arrays.copyOf(arrayDrivers, arrayDrivers.length);
}
Then person will get an copy of it an will not harm your class.
The issue with this is that consumer of your class will have to call this method every time to get fresh list of cars.
To solve this issue you may want to user the [collection framework] where instead of array you cold define:
List<Driver> drivers new ArrayList<>();
and provide the drivers as [immutable] list
public Iterable<Driver> getDrivers() {
return java.util.Collections.unmodifiableList(drivers);
}
Iterable is an interface, that allow you to obtain an interator the the list consumer of class wold have possibility to traverse it. IF you wan to allow him to check that list contains some driver you can set the return type as Collection
class Storage {
private String items[] = new String[10];
public String[] getItems() {
return Arrays.copyOf(items, items.length);
}
}
class Store {
Storage storage = new Storage();
private void printStorage() {
String[] items = storage.getItems();
for (String item : items) {
}
}
}

Is it possible to set multiple messages using oval AbstractAnnotationCheck?

I am using the Oval validation framework to validate fields that HTML fields cannot hold malicious javascript code. For the malicious code detection, I am using an external framework that returns me a list of errors that I would like to use as error messages on the field. The problem I am running into is that I can only setMessage in the check implementation, while I would rather do something like setMessages(List). So while I am currently just joining the errors with a comma, I would rather pass them back up as a list.
Annotation
#Target({ ElementType.METHOD, ElementType.FIELD})
#Retention( RetentionPolicy.RUNTIME)
#Constraint(checkWith = HtmlFieldValidator.class)
public #interface HtmlField {
String message() default "HTML could not be validated";
}
Check
public class HtmlFieldValidator extends AbstractAnnotationCheck<HtmlDefaultValue> {
public boolean isSatisfied( Object o, Object o1, OValContext oValContext, Validator validator ) throws OValException {
if (o1 == null) {
return true;
} else {
CleanResults cleanResults = UIowaAntiSamy.cleanHtml((String) o1);
if (cleanResults.getErrorMessages().size() > 0) {
String errors = StringUtils.join(cleanResults.getErrorMessages(), ", ");
this.setMessage(errors);
return false;
} else {
return true;
}
}
}
}
Model class
class Foo {
#HtmlField
public String bar;
}
Controller code
Validator validator = new Validator(); // use the OVal validator
Foo foo = new Foo();
foo.bar = "<script>hack()</script>";
List<ConstraintViolation> violations = validator.validate(bo);
if (violations.size() > 0) {
// inform the user that I cannot accept the string because
// it contains invalid html, using error messages from OVal
}
If setMessage(String message) is a method created by a superclass, you can override it and once it receives the data, simply split the string into a list and call a second function in which you would actually place your code. On a side note, I would also recommend changing the separating string to something more unique as the error message itself could include a comma.
Your question doesn't really make much sense though. If you are "passing them back up" to a method implemented in a superclass, then this voids the entire point of your question as the superclass will be handling the data.
I am going to assume the setError methods is a simple setter that sets a String variable to store an error message that you plan to access after checking the data. Since you want to have the data in your preferred type, just create a new array of strings in your class and ignore the superclass. You can even use both if you so desire.
public class HtmlFieldValidator extends AbstractAnnotationCheck<HtmlDefaultValue> {
public String[] errorMessages = null;
public void setErrorMessages(String[] s) {
this.errorMessages = s;
}
public boolean isSatisfied( Object o, Object o1, OValContext oValContext, Validator validator ) throws OValException {
if (o1 == null) {
return true;
} else {
CleanResults cleanResults = UIowaAntiSamy.cleanHtml((String) o1);
if (cleanResults.getErrorMessages().size() > 0) {
//String errors = StringUtils.join(cleanResults.getErrorMessages(), ", ");
//this.setMessage(errors);
this.setErrorMessages(cleanResults.getErrorMessages());
return false;
} else {
return true;
}
}
}
}
Elsewhere:
HtmlFieldValidator<DefaultValue> hfv = new HtmlFieldValidator<DefaultValue>();
boolean satisfied = hfv.isSatisfied(params);
if (!satisfied) {
String[] errorMessages = hfv.errorMessages;
//instead of using their error message
satisfy(errorMessages);//or whatever you want to do
}
EDIT:
After you updated your code I see what you mean. While I think this is sort of overdoing it and it would be much easier to just convert the string into an array later, you might be able to do it by creating a new class that extends Validator its setMessage method. In the method, you would call super.setMethod as well as splitting and storing the string as an array in its class.
class ValidatorWithArray extends Validator {
public String[] errors;
public final static String SPLIT_REGEX = ";&spLit;";// Something unique so you wont accidentally have it in the error
public void setMessage(String error) {
super.setMessage(error);
this.errors = String.split(error, SPLIT_REGEX);
}
}
In HtmlFieldValidator:
public boolean isSatisfied( Object o, Object o1, OValContext oValContext, Validator validator ) throws OValException {
if (o1 == null) {
return true;
} else {
CleanResults cleanResults = UIowaAntiSamy.cleanHtml((String) o1);
if (cleanResults.getErrorMessages().size() > 0) {
String errors = StringUtils.join(cleanResults.getErrorMessages(), ValidatorWithArray.SPLIT_REGEX);
this.setMessage(errors);
return false;
} else {
return true;
}
}
}
And now just use ValidatorWithArray instead of Validator
The situation in which I want to achieve this was different from yours, however what I found was best in my case was to create an annotation for each error (rather than having one that would return multiple errors). I guess it depends on how many errors you are likely to be producing in my case it was only two or three.
This method makes also makes your code really easy to reuse as you can just add the annotations wherenever you need them and combine them at will.

Categories

Resources