I can't help noticing that I'm using quite a lot of string comparisons while parsing a well defined XML file in Android (with a XmlPullParser).
As of now it typically looks something like this (somewhat simplified):
...
tag = parser.getName().toLowerCase();
if ("tag1".equals(tag)) {
// Do something with the state machine
}
else if ("tag2".equals(tag)) {
// Do something else with the state machine
}
...
else if ("tag23".equals(tag)) {
// Do something more with the state machine
}
What I would like to have instead is something like this (where StringMatcher would be the hypothetical happy-maker for me):
private static final StringMatcher tagMatcher = new StringMatcher(StringMatcher.NO_MATCH);
static {
tagMatcher.addString("tag1", 1);
tagMatcher.addString("tag2", 2);
....
tagMatcher.addString("tag23", 23);
}
...
tag = parser.getName().toLowerCase();
switch (tagMatcher.match(tag)) {
case 1:
// Do something with the state machine
break;
case 2:
// Do something else with the state machine
break;
...
case 23:
// Do something more with the state machine
break;
default:
Log.e("PARSER", "Unexpected tag: " + tag);
break;
}
As you see I would like a UriMatcher pattern applied to my XML file tags. Do any of you know of such a class I can use in Android? Any other fast filtering on strings would do as well (it would be neat, though, if the UriMatcher-pattern could be reused).
So far I've been looking at regular expressions but I'm not really sure I can fit it to my needs (I would like a switch - case style test) and, of course, the regular string comparison as shown in above example.
Cheers,
-- dbm
You can either use a HashMap since that does not need to iterate over the whole array to find the match value
private static final HashMap<String, Integer> tagMatcher =
new HashMap<String, Integer>();
static {
tagMatcher.put("tag1", 1);
tagMatcher.put("tag2", 2);
tagMatcher.put("tag23", 23);
}
private void parse (String node) {
Integer value = tagMatcher.get(node);
int match = value != null ? value.intValue() : 0;
switch (match) {
case 1:
// etc
break;
case 0: // no match
break;
}
}
or you can use a SparseIntArray using the same hash approach. Advantage here is that you don't need to box int into Integer which should result in a slight speed / memory advantage.
private static final SparseIntArray tagMatcher2 = new SparseIntArray();
private static void put(String key, int value) {
tagMatcher2.put(key.hashCode(), value);
}
private static int get(String key) {
return tagMatcher2.get(key.hashCode());
}
static {
put("tag1", 1);
put("tag2", 2);
put("tag23", 23);
}
private void parse2 (String node) {
switch (get(node)) {
case 1:
// etc
break;
case 0: // no match
break;
}
}
This is doing binary search instead of iterating over the whole thing like SparseArray#indexOfValue(t) does. Note that there is a chance of hash collisions in this approach.
I think using an approach like that is faster than a long chain of if (equals) else if (equals) for larger amounts of comparisons. The if .. else if approach needs to check String.equals() every time which boils down to comparing all characters of the strings while a hash based approach needs to calculate the hash value just once and can do a binary search over all known hash values then.
Use a SparseArray
static{
tagmatcher.append(0, "tag1");
tagmatcher.append(1, "tag2");
}
switch(tagmatcher.keyAt(tagmatcher.indexOfValue(tag))){
case 0:
break;
case 1:
break
}
But if you are going to add consecutive indexes you can always use an ArrayList
Related
When I'm comparing an EditText's value with a String var in java, it is going to else part.
case 2 is not executing, every time default case is executing
sem=(EditText)findViewById(R.id.csem);
sem1=sem.getText().toString();
switch (sem1) {
case "2":
c = 1;
i.putExtra("fees", c);
startActivity(i);
break;
default:
Toast.makeText(details.this, "Enter valid current sem", Toast.LENGTH_SHORT).show();
break;
Since it is a String, you should compare it using equals method
// Gets the value of the EditText
String value = ((EditText) findViewById(R.id.csem)).getText().toString();
if(value.equals("2")) {
// Do your things
}
switch statement on String objects is a new feature introduced in Java 1.7. Unfortunatelly Android requires version 1.6 or 1.5. This is why you have to forget for some time about such constructions.
You can avoid using if-statements-chain by storing the map of methods which will be executed for certain String: Map. You can always encapsulate Method it with some Handler object. Look here for more info: How to remove large if-else-if chain
and
why-cant-i-switch-on-a-string
I've been searching for hours and I can't seem to find my error, in fact it doesn't make any sense at all.
I have a android app which has a class that is responsible for parsing an XML document using the XmlPullParser. The result is a list of school days that holds some information about the day and also a list of lessons for each day. I am able to extract all those information correctly but for some reason I end up having the same list of lessons for every day in the end (the other 'header' information of that element remain correct though).
Complete method can be found here(too long to paste it here): http://pastebin.com/AwxqwxQb
I just post the outline here:
List<SubstitutionDay> parseReturnSubstitution() {
SubstitutionDay currentSubstitutionDay = new SubstitutionDay();
List<SubstitutionDay> results = new ArrayList<>();
Substitution currentSubstitution = new Substitution();
List<Substitution> currentSubstitutionList = new ArrayList<>();
int multipleClasses = 0, multiplePeriods = 0;
String text = "";
try {
// all the XmlPullParser set up
int eventType = xmlPullParser.getEventType();
while (eventType != END_DOCUMENT) {
String tag;
String[] tempStringArray;
tag = xmlPullParser.getName();
switch (eventType) {
case TEXT:
text = xmlPullParser.getText();
break;
case START_TAG:
switch (tag) {
case "kopf":
// reset the school day
currentSubstitutionDay = new SubstitutionDay();
break;
case "haupt":
// empty the list before new elements are added
currentSubstitutionList.clear();
break;
case "aktion":
// reset values for each new substitution
currentSubstitution = new Substitution();
multipleClasses = 0;
multiplePeriods = 0;
break;
}
break;
case END_TAG:
switch (tag) {
// iterate over xml elements that contain header information about the day
// iterate over the individual lessons
case "klasse":
break;
case "stunde":
break;
case "lehrer":
break;
case "raum":
break;
case "info":
break;
case "aktion":
break;
case "haupt":
// add the list of lessons to the day
// the number of lessons is correct here
currentSubstitutionDay.setSubstitutionList(currentSubstitutionList);
// add the whole day to the result set
// number of lessons is still correct
results.add(currentSubstitutionDay);
break;
}
break;
}
eventType = xmlPullParser.next();
}
} // catch statements follow here
// when I check for the size of results.get(i).getSubstitutionList().size() the size is the same for all elements
return results;
}
I'm really helpless right here as this shouldn't happening at all. Does anyone have an explanation for that? Any help is appreciated. If there's further information needed, just let me know!
Edit: The XML that is parsed can be found here: http://vplankl.gymnasium-beetzendorf.de/Vertretungsplan_Klassen.xml
The results I'm expecting are basically the number of rows from each table. However if one row is for let's say two periods (column name: Stunde) (4-5 for example) the result is increased by one because I store each Substitution/Lesson in one element of the list.
The lesson count for the last day is 5 - which is the number that is copied to all the other days.
The problem is that you only create one List<Substitution> object and keep clearing it out. Instead you should create a brand new list for each day.
Additionally, you should make a defensive copy when you add the list to a SubstitutionDay.
I am working on finishing up a project, and it's my first time using Proguard. I have a method to set background colors for the activity itself as well as some buttons. Before Proguard everything works fine. After Proguard the colors aren't set.
Before:
public void setBackgroundColor(String color, View background){
String id = "1";
try {
ColorId myObject = new ColorId();
Method method = ColorId.class.getMethod(color);
id = (String) method.invoke(myObject);
} catch (Exception e) {
e.printStackTrace();
id = "1";
}
int thisColor = Integer.valueOf(id);
switch(thisColor) {
case 0://black
background.setBackgroundColor(0xff000000);
break;
case 1://white
background.setBackgroundColor(0xffffffff);
break;
case 2://red
background.setBackgroundColor(0xffCC0000);
break;
...
default:
background.setBackgroundColor(0xff0099cc);
break;
}
}
After:
public void a(String paramString, View paramView){
try {
c localc = new c();
str = (String)c.class.getMethod(paramString, new Class[0]).invoke(localc, new Object[0]);
switch (Integer.valueOf(str).intValue()){
default:
paramView.setBackgroundColor(-16737844);
return;
}
}catch (Exception localException){
for (;;){
localException.printStackTrace();
String str = "1";
}
paramView.setBackgroundColor(-16777216);
return;
}
paramView.setBackgroundColor(-1);
return;
paramView.setBackgroundColor(-3407872);
return;
paramView.setBackgroundColor(-16737844);
return;
paramView.setBackgroundColor(-8355712);
return;
paramView.setBackgroundColor(-6697984);
return;
paramView.setBackgroundColor(-17613);
return;
paramView.setBackgroundColor(-5609780);
return;
paramView.setBackgroundColor(-35700);
}
Can anyone help explain what is happening here, and how I can make this method (and others in the future) work again after obfuscation? To me it looks like Proguard is rearranging things in regards to the switch.
Proguard shortens code by renaming classes and methods to have shorter names and by removing code that isn't referred to. Your code doesn't work because Proguard renamed or removed the ColorId methods black(), white(), and red(). To use reflection, you'd need to add Proguard keep directives to tell it to keep these methods and to keep their original names.
I don't have an explanation for why the "after" code's switch statement is messed up. Are you sure you decompiled it properly?
Why is the "before" code so convoluted? It uses reflection to look up a method by color name, then calls it to translate the color name to a String, parses the String to get an integer code, boxes the integer code into an Integer, unboxes it, uses a switch statement to pick a color value, then sets the background color, replicating the background.setBackgroundColor() call in each of the switch branches (breaking the DRY principle).
Reflection is an extreme tool to use in special cases like dynamically loaded code.
It'd be simpler, faster, and clearer to look up the color name in a HashMap:
static final int DEFAULT_COLOR = 0xff0099cc;
static final Map<String, Integer> colors = new HashMap<String, Integer>();
static {
colors.put("black", 0xff000000);
colors.put("white", 0xffffffff);
colors.put("red", 0xffCC0000);
}
public void setBackgroundColor(String color, View view) {
Integer colorInteger = colors.get(color);
int colorValue = colorInteger == null ? DEFAULT_COLOR : colorInteger.intValue();
view.setBackgroundColor(colorValue);
}
This HashMap is a good choice if the color has to be passed in as a string name. But if you can change the color argument, an enum would be more type safe, simpler, and faster:
public enum Color {
BLACK(0xff000000), WHITE(0xffffffff), RED(0xffCC0000), DEFAULT(0xff0099cc);
final int value;
Color(int value) { this.value = value; }
}
public void setBackgroundColor(Color color, View view) {
view.setBackgroundColor(color.value);
}
[It's better to define all your color values in an Android resource file (colors.xml). You can look them up by resource ID number.]
What is your proguard setup? Post your proguard-project.txt file (or proguard.cfg if you're using the old method). I'd suggest turning off obfuscation to see more clearly how the code is being changed. Use '-dontobfuscate'.
The 'after' code looks odd. Are you using the optimization common configuration file (proguard-android-optimize.txt)? If so, try to use without optimization to reduce how much your code is being modified.
I'm trying to learn how to use Mockito and i'm hung up on how to go about verifying a method on a certain object was called X amount of times.
I have the following test code
verify(record, times(1)).setValue(Mockito.any(String.class),Mockito.any(String.class));
and the following piece of production code i'm trying to test
The string[]'s i'm setting up to iterate through
protected String[] columnNames = {"_id", "created_at", "updated_at", "name"};
protected ColumnType[] columnTypes = {ColumnType.INTEGER, ColumnType.TIMESTAMP, ColumnType.TIMESTAMP, ColumnType.TEXT};
and the production code that's in a loop, iterating through the String[]
for (int i = 0; i < columnCount; i++) {
if (columnNames[i].equals("_id")) {
record.setId(cursor.getInt(0));
} else {
switch (columnTypes[i]) {
case BOOL:
record.setValue(columnNames[i], cursor.getInt(i));
break;
case TEXT:
record.setValue(columnNames[i], cursor.getString(i));
break;
case INTEGER:
record.setValue(columnNames[i], cursor.getInt(i));
break;
case TIMESTAMP:
record.setValue(columnNames[i], cursor.getLong(i));
break;
case LONG:
record.setValue(columnNames[i], cursor.getLong(i));
break;
case DOUBLE:
record.setValue(columnNames[i], cursor.getDouble(i));
break;
default:
record.setValue(columnNames[i], "");
break;
}
}
}
And this is the error i get
testDataSourceCanFindRecord(com.test.app.DataSourceTest) Time
elapsed: 0.081 sec <<< FAILURE!
org.mockito.exceptions.verification.TooManyActualInvocations:
customer.setValue(, ); Wanted 1 time:
-> at com.test.app.DataSourceTest.testDataSourceCanFindRecord(DataSourceTest.java:141)
But was 3 times. Undesired invocation:
-> at com.test.core.DataSource.cursorToRecord(DataSource.java:210)
I'm expecting record.setValue(String key, String value) to be called once because of the "name" field in the String[]. What's happening is Mockito is registering record.setValue(String key, Long value) as the same thing as record.setValue(String key, String value), which is incorrect. Line 210 is the setValue in the TIMESTAMP case. How can i correct this?
If you look at the source code for InvocationMatcher, it looks like the logic to check method equality is a little more generous than you might think, and could ignore method overloading in some circumstances.
I'm not 100% sure, but try replacing any(String.class) (which accepts any object of any type) with isA(String.class), which will filter out invocations where that parameter is not a String. (anyString only checks its type in Mockito 2.0 and beyond.) Counterintuitively, any(Foo.class) does not mean "anything as long as it's a Foo", it means "anything". This is due to change in Mockito 2.0.
Of course, you could also tighten up your verification so it checks that the key or value is equal to an expected value, but I'm not sure how possible that is in your case.
I'm still pretty new to this, but I'm trying to make a calculator for myself to use at work; similar to a resistor calculator.
I am hope to change a textview and imageview according to the last two digits entered into a edittext box. For example, if I were to type in "9,000,011", I would want to display a certain color of image and text that corresponds with "11" and the same color and text for say 1,000,011. Also different for 12, 13, and so on. this way No matter what number I type it only looks at the last two digits. Does anyone know the way to do this or maybe can just point me in the right direction?
here is how I'm
private void calculate() {
number = Double.parseDouble(inputnumber.getText().toString());
ImageView iv = (ImageView) this.findViewById(R.id.pairimage);
if (number == 6000001) {
iv.setImageResource(R.drawable.white);
txtnumber.setText("White");
} else if (number == 6000002) {
iv.setImageResource(R.drawable.red);
txtnumber.setText("Red");
}
//*and so on, all the way up to 99*
}
If you want just the last two, then I would use
String wholeNumber = inputnumber.getText().toString();
int n = Integer.valueOf(wholeNumber.subString(wholeNumber.length()-2, wholeNumber.length()-1);
and then a switch block:
switch(n) {
case 1:
iv.setImageResource(R.drawable.white);
txtnumber.setText("White");
break;
case 2:
iv.setImageResource(R.drawable.red);
txtnumber.setText("Red");
break;
}
etc.
Maybe convert the number to a String and then look at that e.g.
String theNumber = String.valueOf(number);
String lastTwoDigits =
theNumber.substring(theNumber.length() -2, theNumber.length());
Then you can have your if statements to read the lastTwoDigits. Or convert back to int (Integer.valueOf(lastTwoDigits); and use a switch statement. Or put the values 0-99 into a Map and have a Command object or something as the value which gets executed.
Obviously you'll need some validation here on the user input.
You have a couple of options. The easiest one is to use the modulus operator like:
number = number % 100;
switch (number) {
case 1:
// do stuff;
break;
case 2:
// do stuff;
break;
}
For this to work, though, "number" will have to be an integer. It's an integer in your examples, so that may work for you; otherwise you can try some math tricks like:
int number2 = (int)(number * 100) % 100;
Go with the sub-string approach. I've found that some number combinations don't multiply/divide well in java.