Wednesday, October 29, 2014

org.w3c.dom.DOMException: Motörhead at org.apache.harmony.xml.dom.NodeImpl.setNameNS(NodeImpl.java:241)

As an example, let's take a look at the simple XML file (test.xml):
<?xml version="1.0" encoding="UTF-8" ?>
<test>
 <Motörhead>Ace of Spades</Motörhead>
</test>
If you are confused by the tag name that contains special character, rest assured that is perfectly valid, accents and diacritics are acceptable, see here.

Let's take the following Java snippet and run it with 1.6 or 1.7 compiler:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
        
DocumentBuilder db = factory.newDocumentBuilder();
File file = new File(INPUT_FOLDER + "test.xml");
Document document = db.parse(file);
Everything runs fine, as it should. However, if we run the same code on Android (any version), we'll get the following:

org.w3c.dom.DOMException: Motörhead
at org.apache.harmony.xml.dom.NodeImpl.setNameNS(NodeImpl.java:241)
at org.apache.harmony.xml.dom.ElementImpl.(ElementImpl.java:51)
...
at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:183)


Ah, that little green robot ... always full of surprises. Let us not be lazy and see what is really happening in Android's NodeImpl.setNameNS() method:

(240)  if (!DocumentImpl.isXMLIdentifier(qualifiedName)) {
(241)      throw new DOMException(DOMException.INVALID_CHARACTER_ERR, 
               qualifiedName);
(242)  }
Ok, let's take a look at DocumentImpl.isXMLIdentifier()
(92)   private static boolean isXMLIdentifierStart(char c) {
(93)       return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') 
               || (c == '_');
(94)   }
(95)   
(96)   private static boolean isXMLIdentifierPart(char c) {
(97)       return isXMLIdentifierStart(c) || (c >= '0' && c <= '9') 
               || (c == '-') || (c == '.');
(98)   }
(99)   
(100)  static boolean isXMLIdentifier(String s) {
(101)      if (s.length() == 0) {
(102)          return false;
(103)      }
(104)   
(105)      if (!isXMLIdentifierStart(s.charAt(0))) {
(106)          return false;
(107)      }
(108)   
(109)      for (int i = 1; i < s.length(); i++) {
(110)          if (!isXMLIdentifierPart(s.charAt(i))) {
(111)              return false;
(112)          }
(113)      }
(114)   
(115)      return true;
(116)  }
Faulty implementation. Which is not surprise since Android's org.apache.harmony.xml has a lot of issues open. I submitted this one here.

Now I am sure that you, just like me, can not wait for Android engineers to fix the problem, besides the faulty library is already present on millions of Android devices right now and your app has to run on those devices too. So workaround would be not to use Android's library at all, but to replace it with something reliable. I have solved my problems using Xerces for Android that I have found here.

Case closed!

Sunday, October 14, 2012

Conversion to Dalvik format failed with error 1 (Dx local variable type mismatch)

Recently I got infamous "Conversion to Dalvik format failed with error 1" message while I was trying to import my own (obfuscated and optimized) JAR to one of my projects. The full message is quoted bellow:

EXCEPTION FROM SIMULATION:
[2012-10-09 18:50:04 - AnotherTest] Dx local variable type mismatch: attempt to set or access a value of type java.lang.Object using a local variable of type int. This is symptomatic of .class transformation tools that ignore local variable information.

[2012-10-09 18:50:04 - AnotherTest] Dx ...at bytecode offset 00000019
locals[0000]: Lbiz/binarysolutions/signature/CaptureBase;
locals[0001]: I
locals[0002]: Landroid/view/KeyEvent;
stack[0002]: Lbiz/binarysolutions/signature/CaptureBase;
stack[0001]: Lbiz/binarysolutions/signature/CaptureBase;
stack[top0]: Lbiz/binarysolutions/signature/CaptureBase;
...while working on block 0016
...while working on method onKeyDown:(ILandroid/view/KeyEvent;)Z
...while processing onKeyDown (ILandroid/view/KeyEvent;)Z
...while processing biz/binarysolutions/signature/CaptureBase.class

[2012-10-09 18:50:04 - AnotherTest] Dx 1 error; aborting
[2012-10-09 18:50:04 - AnotherTest] Conversion to Dalvik format failed with error 1
[2012-10-09 18:50:04 - AnotherTest] Dx 

Obviously, it was ProGuard's fault since importing the original JAR file went without any problems. Although the message (quoted part marked in bold) was cryptic at first, the clue that it happened in my onKeyDown method (also marked in bold) was enough to start digging.

So the original onKeyDown looked as following:
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
  if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
    if (signatureView.isModified()) {
 Toast.makeText(this, getToastMessageID(), Toast.LENGTH_LONG)
          .show();
 return true;
    }
  }
return super.onKeyDown(keyCode, event);
}

I took the problematic JAR file processed by ProGuard, decompiled the CaptureBase.class file and took a look at onKeyDown again:
public boolean onKeyDown(int keyCode, KeyEvent event)
  {
    if ((keyCode == 4) && (event.getRepeatCount() == 0) && 
      (this.a.isModified())) {
      keyCode = this; 
      Toast.makeText(this, getResources().getIdentifier(
        "biz_binarysolutions_signature_ToastMessage", "string", 
        keyCode.getPackageName()), 1).show();
      return true;
    }
    return super.onKeyDown(keyCode, event);
  }
Well, now it all makes sense - for some 'optimization' reason, ProGuard assigned this (android.app.Activity) to keyCode (local variable of type int) and used it in getPackageName() call. Dalvik does not like it.

The fix would be the following: you need to add !code/allocation/variable string to your -optimizations command in ProGuard config file. Mine looks like the following:

-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*,!code/allocation/variable

And that should be it!