Dynamic String creation from property value - java

I have my application.properties:
test.md5.params=something1,something4
In my java class I am getting this particular value :
and need to create same strings as present in the property file, such as
public String calculate(RequestClass request)
{
List<String> params= //I am getting the values from application.prop
**(above part id done)**
My Question is below ::
now in my params list I have [something1,something4]
so I need to concatenate both the String values like below:
String finalString=request.getSomething1()+request.getSomething4();
return finalString;
}
My Question is how to do this dynamically and in my properties file I might receive "n" of something values.
Note : I need to make the code such that my class remains constant, if in future I am adding 10 more values in properties files, my final string should be returning like
String finalString=request.getSomething1()+request.getSomething4()+....all the values.;

Through reflection this is possible, below is one implementation.
public String calculate(RequestClass request) throws InvocationTargetException, IllegalAccessException {
List<String> params = Arrays.asList("something1", "something4");
// Do your logic to get the method Names from params, below is an simple example - paramsUpdated
List<String> paramsUpdated = Arrays.asList("getSomething1", "getSomething4");
// Reflection to get the methods of request class
Method[] methods = request.getClass().getMethods();
StringBuilder sb = new StringBuilder();
for (String param : paramsUpdated) {
for (Method method : methods) {
if (param.equals(method.getName())) {
sb.append(method.invoke(request));
}
}
}
return sb.toString();
}

Related

java.lang.ClassCastException: [Z cannot be cast to [Ljava.lang.String in reflective toString on model class

I have a model class in java and I overwrote the toString to provide me with a custom toString. The toString uses reflection to find the field names and values and it works when I run it locally via my ide. However when I run it via mvn agents I always seem to get the error:
java.lang.ClassCastException: [Z cannot be cast to [Ljava.lang.String
Here is the toString:
#SneakyThrows
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
Class<?> thisClass = Class.forName(this.getClass().getName());
Field[] aClassFields = thisClass.getDeclaredFields();
for (Field f : aClassFields) {
String fName = f.getName();
fName = fName.startsWith("_") ? fName.substring(1) : fName;
if (null != f.get(this)) {
if (f.get(this) instanceof String || f.get(this) instanceof List) {
sb.append(getVariableNameStr(fName, f.get(this).toString()));
} else {
StringBuilder stringArrayStr = new StringBuilder();
for (String s : (String[]) f.get(this)) {
stringArrayStr.append(fName).append(": ").append(s).append(", ");
}
sb.append(stringArrayStr);
}
}
}
return sb.toString().substring(0, sb.toString().length() - 2);
}
The line it fails on in the code is the following:
for (String s : (String[]) f.get(this)) {
Why does this pass locally and fail using mvn?
Can anybody tell me what is incorrect about this line?
Just to clear up - the model class has 3 types of field - String, List and String array. The errored line occurs on String array entries.
A
I would assume its caused by some other libraries or test suite touching your code. Or some difference in configuration between local run and maven might cause your objects to be wrapped in some other proxy classes, as proxy classes are often used by frameworks like spring.
You should filter out fields that are not part of object, by removing static and synthetic fields. As like I said in comment, [Z is a boolean[] field, so for sure you are getting some extra fields here.
Also reflections are already bad for performance, and you are repeating field.get call multiple times for no reason. And I don't understand at all why you are assuming field to be of String[] type without checking it.
public String toString() {
StringBuilder sb = new StringBuilder();
Class<?> thisClass = this.getClass();
Field[] aClassFields = thisClass.getDeclaredFields();
for (Field f : aClassFields) {
//skip static and synthetic fields:
if (f.isSynthetic() || Modifier.isStatic(f.getModifiers())) continue;
// get value only once:
Object value = f.get(this);
String fName = f.getName();
fName = fName.startsWith("_") ? fName.substring(1) : fName;
if (value != null) { // and use this value here
if (value instanceOf String[]) {
StringBuilder stringArrayStr = new StringBuilder();
for (String s : (String[]) value) { // and here
stringArrayStr.append(fName).append(": ").append(s).append(", ");
}
sb.append(stringArrayStr);
} else {
sb.append(getVariableNameStr(fName, value.toString()));
}
}
}
return sb.toString().substring(0, sb.toString().length() - 2);
}
I also reordered ifs to handle String[] case first, and use the simple generic toString for rest of possible objects, as this seems to be your case.
Also it would be much better solution to just generate normal toString method or use some libraries like ToStringBuilder from apache commons. As it does not look like you need to use reflections here at all.
[Z means boolean[] - not String[]. So the field is of type boolean[].

Can i put a file to an arraylist that does not have string type?

I have created a method that reads a file that contains numbers separated with ";",the problem is that the type of the arraylist is not string,so is there any other way that I can put the content of the file in the arraylist without changing its type to string? I also have the class which is the type of the arraylist and I have not filled it yet.
public void loadData(String fileName,ArrayList<Records> collection);
{
Scanner myFile = new Scanner (new File(fileName)).useDelimiter(";");
String token = "";
while(myFile.hasNext()) {
token = myFile.next();
collection.add(token);
}
myfile.close()
}
class Records{
}
The Records is not compatible with String unless you extend it (which is not possible in this case since String is final). If you define List<Records>, then only subtypes of Records can be added.
To add a new instance of Records to a collection, there must be first a way to resolve String and create a new instance of Records from it:
class Records {
public Records(String string) { /* your implementation */ }
}
Then you do:
String token = "";
while (myFile.hasNext()) {
token = myFile.next();
collection.add(new Records(token));
}

Invoke method by reflection with dynamic parameters

I'm invoking some method of Class's instance using the method.invoke(instance, args...) way but for each method inside the instance, as the invoke Javadoc rightly points out, each argument must be manually specified.
Thinking about Spring... how it could valorize parameters in controller's method behind the hood during HTTP calls? (but surely it does in a completely different way I think...)
I wonder if there's any way in Java to dynamically pass parameters in reflection (or not even reflection) without specifying each of them singularly.
EDIT
The instance class declaration is something like:
public class Something {
public void doSth(String par1, String par2, Integer par3) {
//....
}
public void doSthElse(String par1, Boolean par2) {
//....
}
public void doSthElseMore(Integer par1) {
//....
}
}
How I'm invoking each method:
...
for (Method method : instance.getDeclaredMethods()) {
Object[] array = //BL: build array of values to pass to the invoke method.
//1. doSth may be new Object[] {"abc", "def", 123}
//2. doSthElse iteration may be new Object[] {"abc", false}
//3. doSthElseMore iteration may be new Object[] {123}
return method.invoke(instance, array);
}
...
As shown above, each method inside Something class (instance) have a different number of parameters.
On each iteration, the array have a different number of values to pass to the invoke.
Actually as #Boris says all I had to do to complete my job was to convert each parameters to the correct type. In this way Java managed to invoke the correct method of the Something class with the correct parameters types.
My project is a Vert.x application using Vavr and jodd but the last return statement shows how I managed to solve.
public Object invokeMethod(Object service, Method method, RoutingContext routingContext) throws Exception {
MultiMap queryParams = routingContext.queryParams();
Map<String, String> pathParams = routingContext.pathParams();
Buffer body = routingContext.getBody();
// 1. type, 2. name, 3. value
List<Tuple3<Class<?>, String, Object>> list = List.empty();
for (Parameter par : method.getParameters()) {
ParamQuery paramQuery = par.getAnnotation(ParamQuery.class);
if (paramQuery != null) {
list = list.push(new Tuple3<Class<?>, String, Object>(par.getType(), paramQuery.value(),
queryParams.get(paramQuery.value())));
}
}
// TypeConverterManager used to "covnert" each object (String) from the HTTP call to the correct data type
return method.invoke(service, list.reverse()
.map(mapper -> TypeConverterManager.lookup(mapper._1()).convert(mapper._3())).toJavaArray());
}
However, this project can be found on GitHub
Since I notice you are using an Integer instead of a int (so no primitives parameters in your examples), you can send null to all your methods without any problems.
So you can create an array of the correct length and this will work in your case.
public static Object[] getParametersArray(Parameter[] param){
Object[] array = new Object[param.length];
// create default primitive values based on param[#].getType()
return array;
}
Then, all you have to do is to iterate the method:
Labo l = new Labo();
for(Method m : Labo.class.getDeclaredMethods()){
if((m.getModifiers() & Modifier.STATIC) > 0){
System.out.println("SKIP " + m.getName());
continue;
}
try {
m.invoke(l, getParametersArray(m.getParameters()));
} catch (Exception e) {
e.printStackTrace();
}
}
Notice the skipped static method, mostly because if you run this in the method containing the main method, you will have a recursive call.
This was tested with :
public void test(String s){
System.out.println("test String " + s);
}
public void test2(String s1, String s2){
System.out.println("test String " + s1 + " | String " + s2);
}
public void test(Integer s){
System.out.println("test Integer " + s);
}
SKIP main
test String null
test Integer null
SKIP getParametersArray
test String null | String null
Note : If you need to manage some primitive values, you will need to get the type of the parameter to provide a default value instead of null

Java Reflection - get method without specific parameters type

I am trying to get the method by java reflection, but i don't want to be specific about the parameters classes in getMethod().
public Object prepareFilter(String filter, String sort) {
Class filterClass = this.filterClass;
try {
Method createCriteriaMethod = filterClass.getMethod(CREATE_CRITERIA_METHOD);
Method orderByClauseMethod = filterClass.getMethod(ORDER_BY_CLAUSE_METHOD, String.class);
Class criteriaClass = createCriteriaMethod.getReturnType();
Object filterObject = filterClass.newInstance();
Object criteriaObject = createCriteriaMethod.invoke(filterObject);
for (ExtFilterRequest extFilter : ExtFilterRequest.decodeJson(filter)) {
StringBuilder sb = new StringBuilder()
.append(AND)
.append(WordUtils.capitalize(extFilter.getProperty()))
.append(extFilter.getCondition());
Method criteriaConditionMethod = criteriaClass.getMethod(sb.toString(), ????); // earlier extFilter.getTransformedValue().getClass()
criteriaConditionMethod.invoke(criteriaObject, extFilter.getTransformedValue());
}
String orderByClause = ExtSortRequest.getOrderByString(sort);
if (orderByClause != null)
orderByClauseMethod.invoke(filterObject, orderByClause);
return filterObject;
} catch (Exception e) {
// later
}
return null;
}
I have the methods generated by MyBatis and I want to call them by reflection with the decoded json that comes from extjs client. It looks like: operator, value and property. Following code is working with string values but i dont know what to put in the place of question marks when I call for example method which get a Date value (decoded value is always a string).
Is it possible to call getMethod with some hmm.. generic type and get the specific method? Or should I do it in another way?
Summarizing - if I have method like this:
public Criteria andSomeReferenceIsEqualTo(String value) {
addCriterion("some_ref =", value, "someRef");
return (Criteria) this;
}
and this
public Criteria andPrimDateEqualTo(Date value) {
addCriterionForJDBCDate("prim_date =", value, "primDate");
return (Criteria) this;
}
I want to call them in the same way by method I specify earlier - even if its boolean, list of values, string or integer.

JUnit Same testcase with different inputs [duplicate]

This question already has answers here:
Running the same JUnit test case multiple time with different data
(9 answers)
Closed 9 years ago.
I need to test a method, eg:
public ObjRet foo(string input)
{
...
return objRet;
}
For the above method, one test case involves input = {null, " ", "", etc} which would return the same default object and there are other test cases
Do I need to have separate test cases for each input (even though I need to validate for same default object)
or can i do something like this,
#Test
void testing()
{
String[] inputs = {null, "", " "};
List<ObjRet> received = new List<ObjRet>();
for(string input : inputs)
received.add(foo(input));
for(ObjRet obj : received)
Assert.assertEquals(obj, default);
}
I just want to make sure that if its a good practice to use for loops for assertions
The JUnitParams library is the perfect way to create the type of parameterized test you're describing. I include it by default with every project because this pattern is so common. See http://code.google.com/p/junitparams/ for the complete documentation (it's so easy it all fits on one page).
With JUnitParams, each String included in the String[] passed to the #Parameters annotation is parsed as a CSV and is split on commas, then leading and trailing whitespace is removed from each token. The number of tokens must also match the number of arguments to the test method. Since you need to include a null, an empty string and a string that consists only of whitespace, you'll need to use a method to provide your parameters as follows:
private static final Object DEFAULT_VALUE = new String("It works!");
private static final Object OTHER_VALUE = new String("It's broken!");
private Object foo(String input) {
Object output = DEFAULT_VALUE;
if(input != null && !"".equals(input.trim())) {
output = OTHER_VALUE;
}
return output;
}
#SuppressWarnings("unused")
private Object[] parameters() {
return $(
new Object[] { null },
new Object[] { "" },
new Object[] { " " }
// ,"Other value"
);
}
#Test
#Parameters(method = "parameters")
public void testing(String input) {
Object obj = foo(input);
assertEquals(obj, DEFAULT_VALUE);
}
The three tests defined in your original question will pass with this example code, but if you uncomment the fourth value ("Other value"), then fourth test will (properly) fail.

Categories