How to validate the length of Variable Arguments (Varargs) at compile time? - java

I want to write a function to do replacing in string template with varargs, for example:
public static String replace (String original, String... replacements) {
for (int i = 0; i < replacements.length; i += 2) {
original = original.replace(replacements[i], replacements[i + 1];
}
}
so I can call this function like:
String replaces = replace("NAME TIME", "NAME", "Abc", "TIME", "20:12");
Apparently, replacements.length should be a multiple of 2 and I want to validate this at compile time, I want to achieve something like:
#ValidateVarargs("length % 2 == 0", "replacements.length must be a multiple of 2")
public static String replace (String original, String... replacements) {
for (int i = 0; i < replacements.length; i += 2) {
original = original.replace(replacements[i], replacements[i + 1];
}
}
And when I call this function with odd number of replacement strings, It will give me some compilation errors like:
Compilation error: replacements.length must be a multiple of 2
At path/to/Xxx.java line yyy (where this function was called)
Is it possible? If possible, how to do it?
Or, if this can not be achieved by using Annotations, is there any IDE or IDE plugin or some other thing can do this validation before or during compiling?
Thanks.

I would suggest to redesign your api. Having method invocations with multiple string arguments in a row makes it difficult to tell which is the string being replaced and the replacement.
I would think of something like that:
StringReplacement.builder()
.replace("being replaced", "replacement")
.replace("yet another", "replacement")
.build()
.replace(stringToRunReplacementsOn);

Related

Iterate over String

So, I have this problem:
#!/usr/bin/env groovy
package com.fsfs.fdfs.fsf.utils
class GlobalVars {
// TEST-DEV
static String MY_URL1 = "https://myurl.com"
static String MY_URL2 = "https://:6443"
static String MYURLS_TEST = "${MY_URL1} ${MY_URL2}"
}
So I want to iterate over my URLS depending on the environment.
For example: in this ENV is TEST, but could be DEV, PROD and so on
for( Name in GlobalVars."MYURLS_${ENV}".split(/\s+/)) {
}
I'm not sure how to achieve this.
Basically, I want to iterate over a variable with a dynamic name.
The variable contains at least 2 strings
You could do something like this...
class GlobalVars {
// TEST-DEV
static String MY_URL1 = "https://myurl.com"
static String MY_URL2 = "https://:6443"
static String MYURLS_TEST = "${MY_URL1} ${MY_URL2}"
}
String ENV = 'TEST'
for( name in GlobalVars."MYURLS_${ENV}".split(/\s+/)) {
println name
}
You can look into CharacterIterator methods current() to get the current character and next() to move forward by one position. StringCharacterIterator provides the implementation of CharacterIterator.
or for a simpler task can
Create a while loop that would check each index of the string or a for loop like this
for (int i = 0; i < str.length(); i++) {
// Print current character
System.out.print(str.charAt(i) + " ");
}
Iterating Strings works out of the box in Groovy:
"bla".each{println it}
.each{} closure will go over all characters of the string.
Same can be achieved with a more classical for-loop:
for(c in "foo") println c
Either way should work.
I'm not sure what would be the benefit of making a space separated list of values just to parse it again. Seems easier with a map of lists.
class GlobalVars {
// TEST-DEV
static String MY_URL1 = 'https://myurl.com'
static String MY_URL2 = 'https://:6443'
static Map MY_URLS = [
'TEST': [
MY_URL1,
MY_URL2,
],
]
}
String ENV = 'TEST'
GlobalVars.MY_URLS[ENV].each {
println it
}
No regex, no dynamically generated property names. If you want to avoid typos in the environment names you can put them into an enum.

dumping or printing the name of a variable

This question mentions xpaths but it is really not specific to xpaths and really concerns any Java Strings.
I am using Java 8 and have about 40 Strings (public static final String in the class). An example is below:
private static final String employeeID_x = "//input[#id = 'id']";
private static final String employeeName = "//input[#id = 'EmployeeName']";
and there are some more complicated ones like
private static final String age_x = "//div[#id = 'Employee']/div[2]/span[1]";
etc. There are 40 of these. I want to verify all the xpaths so I made an array like
private static final String[] arr = {employeeID_x, employeeName_x, age_x, .....};
then to verify
for (String xp: arr) {
List<WebElement> eles = driver.findElement(By.xpath(xp));
if (eles.size() == 0) {
System.out.println(xp + " does not exist");
}
}
you get the idea. This works, but the error message is
"//div[#id = 'Employee']/div[2]/span[1] does not exist". I guess this is ok but I would rather have it say "age_x does not exist".
I don't know how to print the variable name. I have tried using Class and getFields() but that does not seem to work.
What I have to do is duplicate the array and put each element in quotes like this:
private static final String[] names= {"employeeID_x", "employeeName_x", "age_x", .....};
and then get the number of entries and use
for (int i = 0; i < arr.length; i++) {
String xp = arr[i];
String name = names[i];
List<WebElement> eles = driver.findElements(By.xpath(xp));
if (eles.size() == 0) {
System.out.println(name + " does not exist");
}
}
but as you can see this can get to be a pain. Is there anyway to get the name from xp? Maybe no, as I am afraid when it creates the array it substitutes the value of each string?
And as you can see, this is not specific to xpaths.
I don't know how to print the variable name.
With your current array, you can't (reasonably*) unless you can infer the variable name from the string. This line:
private static final String[] arr = {employeeID_x, employeeName_x, age_x, .....};
...copies the value of employeeID_x, etc., into the array. There is no ongoing link between that value and the variable it came from, just as in this code:
a = b;
...there is no ongoing link between a and b.
Your parallel arrays solution works but as you've said isn't ideal. Using a Map may be slightly better:
private static final Map<String, String> map = new HashMap<>();
static {
map.put("employeeID_x", employeeID_x);
map.put("employeeName_x", "employeeName_x);
// ...
}
Then loop through the map's entries, which give you both the name and value. This still has some repetition (e.g., in each put call you have to type the variable name twice), but it's much better from a maintenance perspective: Dramatically harder to get the two out of sync.
Another option is to use reflection: Your array would be of the names of the variables, and then you'd get the variable's value by using Class#getDeclaredField to get a Field instance for it, then get the value via Field#get. E.g., your array would be:
private static final String[] arr = new String[] { "employeeID_x", "employeeName" /*, ...*/ };
...then:
for (String name : names) {
Field f = YourClass.class.getDeclaredField(name);
String value = (String)f.get(null);
// ...test `value` here, report `name` if there's a problem
}
* "reasonably" - You could have the error code compare the string to every one of your fields and report the match, e.g.
if (theString.equals(employeeID_x)) {
theVariableName = "employeeID_x";
} else if (theString.equals(employeeName_x)) {
theVariableName = "employeeName_x";
} else if (...) {
// ...
...but...blech. :-) (And it assumes that two of these never have the same value.)

How to use a for loop to set String variable mixed with numbers to ""?

I have inherited a Java program which I need to change. In one part of the code, I see I have created over 1000 String variables such as:
String field01
String field02
...
String field1000
I want to make a for loop to set all of the mentioned variables to "", but I am having issues with building a correct format for the for loop.
How do I create field+(i) in the for loop to set field01 to "" and the rest?
A for loop... Well, you could make this an array, but there's not really any way to make this into a for loop without an array.
Here's an example with one:
String[] test = new String[1000];
for (int number; numer < 1000; number++){
test[number] = "";
}
You have to use Reflection for doing the same.
class Test {
String field1
String field2
...
String field1000
}
public class FieldTest {
public static void main(String args[]) {
Test t = new Test();
Class cls = t.getClass();
for(int i=0 ; i<=1000; i++){
Field f1 = cls.getField("field"+i);
f1.set(t, "");
}
}
}
You can't really do this in Java. An alternative is to make a String array where the index of the array is the number of the variable that you want. So field01 would be stored in your string array at index 1.
First, create an array. Second, use Arrays.fill:
String[] fields = new String[1000];
Arrays.fill(fields, "");
Later you can access or modify individual variables by indices like fields[index].
Creating 1000 variables with similar names is not how people program in Java.
I know it is not the exact answer, but may help you.or you will need to use reflection.
Map<String, String> container = new HashMap<String, String>();
for (int i = 0; i <1000; i++) {
container.put("field"+ i, "\"\" ");
}

How to process a Java List (sent from Java programs) in a Python function?

I have a Java List like shows here in the following Java code block:
List<Float> list = new ArrayList<Float>();
list.add(1.2345);
list.add(2.2345);
Then I have passed this list to a parameter of a Python function within a Python program like here shows:
def JListPrcessor(javalist):
print "pass the processor"
javalist = list(javalist)
print javalist
But some errors happened while the program was running, and the JListPrcessor just didn't print anything out.
I don't know if it's possible to do the argument passing of List type from Java to Python, or if there will be another way to do it? Any suggestions will be greatly appreciated, thank you very much!
Not sure what you are doing, but if you are having modules built in different languages, why not use a format in between that can be understood? You can change your list to JSON for example, using existing libraries, and then on your python side, parse that JSON back to a python dict. This should not require any effort from your side if you don't sent too complicated objects.
You cannot pass an ArrayList to a Python script, so you would have to turn your ArrayList into a string like so:
public static String listToString(List<Object> list)
{
String listConcat = "";
for(int i = 0; i < list.size(); i++)
{
String num = String.valueOf(list.get(i));
if((i + 1) != list.size())
{
listConcat += num + ",";
}
else
{
listConcat += num;
}
}
return listConcat;
}
public static void main(String[] args)
{
List<Object> list = new ArrayList<Object>();
list.add(1.2345);
list.add(2.2345);
System.out.println(listToString(list));
}
Next, you would have to call the Python script from your Java program with the ArrayList String as a command line argument, so you would call it like:
python foo.py "1.2345,2.2345"
Then in foo.py, you have to split the string at the comma and convert each string to a float:
import sys
def JListPrcessor(javalist):
print "pass the processor"
jlist = [float(num) for num in javalist.split(",")]
print jlist
try:
inputList = sys.argv[1]
except:
print "No input str given"
sys.exit(1)
JListPrcessor(inputList)
Which gives:
pass the processor
[1.2345, 2.2345]

Should the toString() method be added?

This is the piece of code.
List<BDDObject> childlist = savingObject.getChildren("TherapyAreaReference");
if (childlist.size() > 1) {
for (int i = 0; i < childlist.size() - 1; i++) {
String newMedcondRefChild = ((String) childlist
.get(i)
.getValue( IDDConstants.IDD_THERAPY_AREA_REF_VALUE))
.toLowerCase()
.trim()
.concat(((String) childlist
.get(i)
.getValue(IDDConstants.IDD_THERAPY_AREA_REF_TYPE_NAME))
.toLowerCase().trim());
}
}
IDDConstants has public static final strings defined in it. As StringBuffer is more effective, how can it be incorporated for the concat operations?
I'm guessing that the intention is to generate a list of 'reports', one for each BDDObject record found. Based on that idea, your code should look more like this:
public List<String> getReport(List<BDDObject> records) {
List<String> reports = new ArrayList<String>(record.size());
for (BDDObject record:records) {
String newMedcondRefChild = String.valueOf(record.getValue( IDDConstants.IDD_THERAPY_AREA_REF_VALUE))
.toLowerCase()
.trim() + String.valueOf(record.getValue(IDDConstants.IDD_THERAPY_AREA_REF_TYPE_NAME)))
.toLowerCase().trim());
reports.add(newMedcondRefChild);
}
return reports;
}
Regarding the question on whether toString() would be helpful, the only place where I see it fitting, would be on the BDDObject itself. It would look something like this:
class BDDObject {
...
#Override
public String toString() {
return String.valueOf(getValue(IDDConstants.IDD_THERAPY_AREA_REF_VALUE)).toLowerCase().trim() +
String.valueOf(getValue(IDDConstants.IDD_THERAPY_AREA_REF_TYPE_NAME)).toLowerCase().trim());
}
In which case, the function to create the report becomes trivial:
public List<String> getReport(List<BDDObject> records) {
List<String> reports = new ArrayList<String>(record.size());
for (BDDObject record:records) {
reports.add(record.toString());
}
return reports;
}
In case that what you want is a looooong string with all the values concatenated to it, you can use StringBuilder, like this:
public String getReport(List<BDDObject> records) {
StringBuilder sb = new StringBuilder();
for (BDDObject record:records) {
sb.append(String.valueOf(record.getValue( IDDConstants.IDD_THERAPY_AREA_REF_VALUE))
.toLowerCase()
.trim());
sb.append(String.valueOf(record.getValue(IDDConstants.IDD_THERAPY_AREA_REF_TYPE_NAME))
.toLowerCase().trim()));
}
return sb.toString();
}
This will return all the records appended after each other. I doubt its readability, but you I hope you get the idea. StringBuilder is helpful when you need to build a string iteratively (like in the previous example). StringBuilder should not be used to replace single String operations like : String a = b.get() + c.get(); given that the compiler implicitly creates a StringBuilder in these cases and therefore there's no actual performance improvement to be achieved.
In the code in your question, StringBuffer/StringBuilder will not give you any performance gains, because you concatenate only two strings. However, the question does not state what you are doing with the string in newMedconfRefChild. If your actual goal is to concatenate the strings of each loop iteration, then you should use a StringBuilder (use StringBuffer only when it is really necessary, prefer StringBuilder).

Categories

Resources