Fixes Implement containsKey() and keySet().contains() consistently for the attributes maps.
MODIFY
asciidoctorj-core/src/main/java/org/asciidoctor/internal/RubyAttributesMapDecorator.java
asciidoctorj-core/src/main/java/org/asciidoctor/internal/RubyAttributesMapDecorator.java
@@ -2,7 +2,6 @@ package org.asciidoctor.internal;
import org.jruby.RubyHash;
import org.jruby.RubyString;
-import org.jruby.RubySymbol;
import org.jruby.javasupport.JavaEmbedUtils;
import org.jruby.runtime.builtin.IRubyObject;
@@ -34,7 +33,7 @@ public class RubyAttributesMapDecorator implements Map<String, Object> {
if (!(key instanceof String)) {
return false;
}
- return rubyHash.containsKey(key);
+ return rubyHash.containsKey(convertJavaToRubyKey((String) key));
}
@Override
@@ -47,14 +46,15 @@ public class RubyAttributesMapDecorator implements Map<String, Object> {
if (!(key instanceof String)) {
return false;
}
- Object value = rubyHash.get(key);
+ Object value = rubyHash.get(convertJavaToRubyKey((String) key));
return convertRubyValue(value);
}
@Override
public Object put(String key, Object value) {
- Object oldValue = get(key);
- rubyHash.put(key, convertJavaValue(value));
+ final Object convertedKey = convertJavaToRubyKey(key);
+ Object oldValue = rubyHash.get(convertedKey);
+ rubyHash.put(convertedKey, convertJavaValue(value));
return oldValue;
}
@@ -63,8 +63,9 @@ public class RubyAttributesMapDecorator implements Map<String, Object> {
if (!(key instanceof String)) {
return null;
}
- Object oldValue = get(key);
- rubyHash.remove(key);
+ Object convertedKey = convertJavaToRubyKey((String) key);
+ Object oldValue = rubyHash.get(convertedKey);
+ rubyHash.remove(convertedKey);
return convertRubyValue(oldValue);
}
@@ -137,6 +138,29 @@ public class RubyAttributesMapDecorator implements Map<String, Object> {
}
}
+ private Object convertJavaToRubyKey(String keyString) {
+ Object convertedKey;
+ if (isDigits(keyString)) {
+ convertedKey = Long.parseLong(keyString);
+ } else {
+ convertedKey = keyString;
+ }
+ return convertedKey;
+ }
+
+ private boolean isDigits(final String arg) {
+ if (arg.length() == 0) {
+ return false;
+ }
+ for (int i = 0; i < arg.length(); i++) {
+ final char c = arg.charAt(i);
+ if (c < '0' || '9' < c) {
+ return false;
+ }
+ }
+ return true;
+ }
+
@Override
public String toString() {
return createJavaMap().toString();

ADD
asciidoctorj-core/src/test/groovy/org/asciidoctor/internal/RubyAttributesMapDecoratorSpecification.groovy
+package org.asciidoctor.internal
+
+import org.asciidoctor.Asciidoctor
+import org.asciidoctor.ast.Block
+import org.asciidoctor.ast.Document
+import org.jboss.arquillian.spock.ArquillianSputnik
+import org.jboss.arquillian.test.api.ArquillianResource
+import org.junit.runner.RunWith
+import spock.lang.Specification
+
+@RunWith(ArquillianSputnik)
+class RubyAttributesMapDecoratorSpecification extends Specification {
+
+ public static final String ATTR_ONE = '1'
+
+ public static final String ATTR_NAME_ROLE = 'role'
+ public static final String ATTR_NAME_ID = 'id'
+
+ @ArquillianResource
+ private Asciidoctor asciidoctor
+
+ String attrValue = '''#idname.rolename'''
+
+ String documentWithPositionalAttribute = """
+[${attrValue}]
+Lorem ipsum dolor
+"""
+
+ def "should consistently show positional attributes as string keys"() {
+
+ when:
+ Document document = asciidoctor.load(documentWithPositionalAttribute, new HashMap<String, Object>())
+
+ Block block = (Block) document.getBlocks().get(0)
+ Map<String, Object> attributes = block.getAttributes()
+
+ then:
+ !attributes.containsKey(1L)
+ attributes.containsKey(ATTR_ONE)
+ attributes.keySet().contains(ATTR_ONE)
+ attributes.get(ATTR_ONE) == attrValue
+
+ }
+
+ def "should remove positional attributes by string keys"() {
+
+ given: 'a block with a positional attribute'
+ Document document = asciidoctor.load(documentWithPositionalAttribute, new HashMap<String, Object>())
+
+ Block block = (Block) document.getBlocks().get(0)
+
+ when: 'I remove the attribute with the name "1"'
+ Map<String, Object> attributes = block.getAttributes()
+ def oldValue = attributes.remove(ATTR_ONE)
+
+ then: 'The key with the positional attribute is gone'
+ !attributes.containsKey(ATTR_ONE)
+
+ and: 'remove returned the previous attribute value'
+ oldValue == attrValue
+
+ and: 'The attributes derived from the positional attribute are still there'
+ attributes.containsKey(ATTR_NAME_ROLE)
+ attributes.containsKey(ATTR_NAME_ID)
+ }
+
+ def "should return previous value on put"() {
+ given: 'a block with a positional attribute'
+ Document document = asciidoctor.load(documentWithPositionalAttribute, new HashMap<String, Object>())
+
+ Block block = (Block) document.getBlocks().get(0)
+
+ when: 'I put another value for the positional attribute 1'
+ def attributes = block.getAttributes()
+ def newValue = 42
+ def previousValue = attributes.put(ATTR_ONE, newValue)
+
+ then: 'put returned the previous value'
+ previousValue == attrValue
+
+ and: 'the block has the new attribute value'
+ attributes.get(ATTR_ONE) == newValue
+ }
+}

Fixes #604. Implement containsKey() and keySet().contains() consistently for the attributes maps.