Repository: joelittlejohn/jsonschema2pojo (java)

Issue:
Properties with special character in the name (#, /, etc) cause IllegalArgumentException 'Path not present' when using 1.1.2
After updating to version [org.jsonschema2pojo:jsonschema2pojo-gradle-plugin](https://togithub.com/joelittlejohn/jsonschema2pojo) `1.1.2` of the plugin, we are no longer able to generate classes when the attributes contain `#`
This was possible on version `1.1.1`
For example `VERSV#`

```
{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "$ref": "#/definitions/BusinessData",
  "definitions": {
    "BusinessData": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "VERSV#": {
          "type": "string"
        },
        "HEADER": {
          "$ref": "#/definitions/Header"
        }
      },
      "title": "BusinessData"
    }
}
```

Pull request description:
Encode property names when building path to property
When creating a fragment with JSON Pointer path, we now encode property names using the rules from RFC 6901. This means that the property name can be treated as a single token, even if it contains special characters like # or /.

Closes #1402

Patch:
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/FragmentResolver.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/FragmentResolver.java
index 3efd7803..44649af6 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/FragmentResolver.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/FragmentResolver.java
@@ -48,10 +48,11 @@ public class FragmentResolver {
                 }
             }
 
-            if (tree.has(part)) {
-                return resolve(tree.get(part), path);
+            String decodedPart = JsonPointerUtils.decodeReferenceToken(part);
+            if (tree.has(decodedPart)) {
+                return resolve(tree.get(decodedPart), path);
             } else {
-                throw new IllegalArgumentException("Path not present: " + part);
+                throw new IllegalArgumentException("Path not present: " + decodedPart);
             }
         }
 
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/JsonPointerUtils.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/JsonPointerUtils.java
new file mode 100644
index 00000000..dc923f2b
--- /dev/null
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/JsonPointerUtils.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright © 2010-2020 Nokia
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jsonschema2pojo;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Collections;
+
+public class JsonPointerUtils {
+
+    private static Map<String,String> SUBSTITUTIONS = new LinkedHashMap<String, String>() {{
+        put("~", "~0");
+        put("/", "~1");
+        put("#", "~2");
+        put(".", "~3");
+    }};
+
+    public static String encodeReferenceToken(final String s) {
+        String encoded = s;
+        for (Map.Entry<String,String> sub : SUBSTITUTIONS.entrySet()) {
+            encoded = encoded.replace(sub.getKey(), sub.getValue());
+        }
+        return encoded;
+    }
+    
+    public static String decodeReferenceToken(final String s) {
+        String decoded = s;
+
+        List<String> reverseOrderedKeys = new ArrayList<String>(SUBSTITUTIONS.keySet());
+        Collections.reverse(reverseOrderedKeys);
+        for (String key : reverseOrderedKeys) {
+            decoded = decoded.replace(SUBSTITUTIONS.get(key), key);
+        }
+
+        return decoded;
+    }
+}
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertyRule.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertyRule.java
index 0b08b804..4ace4cc6 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertyRule.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertyRule.java
@@ -17,6 +17,7 @@
 package org.jsonschema2pojo.rules;
 
 import org.jsonschema2pojo.GenerationConfig;
+import org.jsonschema2pojo.JsonPointerUtils;
 import org.jsonschema2pojo.Schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
@@ -30,7 +31,6 @@ import com.sun.codemodel.JMod;
 import com.sun.codemodel.JType;
 import com.sun.codemodel.JVar;
 
-
 /**
  * Applies the schema rules that represent a property definition.
  *
@@ -70,9 +70,9 @@ public class PropertyRule implements Rule<JDefinedClass, JDefinedClass> {
 
         String pathToProperty;
         if (schema.getId() == null || schema.getId().getFragment() == null) {
-            pathToProperty = "#/properties/" + nodeName;
+            pathToProperty = "#/properties/" + JsonPointerUtils.encodeReferenceToken(nodeName);
         } else {
-            pathToProperty = "#" + schema.getId().getFragment() + "/properties/" + nodeName;
+            pathToProperty = "#" + schema.getId().getFragment() + "/properties/" + JsonPointerUtils.encodeReferenceToken(nodeName);
         }
 
         Schema propertySchema = ruleFactory.getSchemaStore().create(schema, pathToProperty, ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters());
diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/util/ReflectionHelper.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/util/ReflectionHelper.java
index d503008a..c43a2ed0 100644
--- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/util/ReflectionHelper.java
+++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/util/ReflectionHelper.java
@@ -65,7 +65,7 @@ public class ReflectionHelper {
     if (node.has("extends")) {
       String path;
       if (schema.getId().getFragment() == null) {
-        path = "#extends";
+        path = "#/extends";
       } else {
         path = "#" + schema.getId().getFragment() + "/extends";
       }


Test patch:
diff --git a/jsonschema2pojo-core/src/test/java/org/jsonschema2pojo/JsonPointerUtilsTest.java b/jsonschema2pojo-core/src/test/java/org/jsonschema2pojo/JsonPointerUtilsTest.java
new file mode 100644
index 00000000..c286cfc6
--- /dev/null
+++ b/jsonschema2pojo-core/src/test/java/org/jsonschema2pojo/JsonPointerUtilsTest.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright © 2010-2020 Nokia
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jsonschema2pojo;
+
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+
+public class JsonPointerUtilsTest {
+
+    @Test
+    public void testEncodeReferenceToken() {
+        assertThat(JsonPointerUtils.encodeReferenceToken("com/vsv#..."), is("com~1vsv~2~3~3~3"));
+        assertThat(JsonPointerUtils.encodeReferenceToken("~1~2~01~3"), is("~01~02~001~03"));
+    }
+
+    @Test
+    public void testDecodeReferenceToken() {
+        assertThat(JsonPointerUtils.decodeReferenceToken("com~1vsv~2~3~3~3"), is("com/vsv#..."));
+        assertThat(JsonPointerUtils.decodeReferenceToken("~01~02~001~03"), is("~1~2~01~3"));
+    }
+}
\ No newline at end of file
diff --git a/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/PropertiesIT.java b/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/PropertiesIT.java
index 14b34000..7ce086c3 100644
--- a/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/PropertiesIT.java
+++ b/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/PropertiesIT.java
@@ -34,7 +34,8 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 public class PropertiesIT {
-    @Rule public Jsonschema2PojoRule schemaRule = new Jsonschema2PojoRule();
+    @Rule
+    public Jsonschema2PojoRule schemaRule = new Jsonschema2PojoRule();
 
     private final ObjectMapper mapper = new ObjectMapper();
 
@@ -200,4 +201,15 @@ public class PropertiesIT {
         assertThat(jsonified.has("PROPERTY_ONE_TWO_THREE"), is(true));
         assertThat(jsonified.has("PROPERTY_ONE_TWO_THREE_four"), is(true));
     }
-}
+
+    @Test
+    public void propertyNamesWithSpecialCharacters() throws NoSuchMethodException, ClassNotFoundException {
+        ClassLoader resultsClassLoader = schemaRule.generateAndCompile("/schema/properties/propertiesWithSpecialCharacters.json", "com.example");
+        Class<?> generatedType = resultsClassLoader.loadClass("com.example.PropertiesWithSpecialCharacters");
+
+        assertNotNull(generatedType.getDeclaredMethod("getVersv"));
+        assertNotNull(generatedType.getDeclaredMethod("getFooBar"));
+        assertNotNull(generatedType.getDeclaredMethod("get$RfcNumber"));
+        assertNotNull(generatedType.getDeclaredMethod("getOrgHispDhisCommonFileTypeValueOptions"));
+    }
+}
\ No newline at end of file
diff --git a/jsonschema2pojo-integration-tests/src/test/resources/schema/properties/propertiesWithSpecialCharacters.json b/jsonschema2pojo-integration-tests/src/test/resources/schema/properties/propertiesWithSpecialCharacters.json
new file mode 100644
index 00000000..a93203ff
--- /dev/null
+++ b/jsonschema2pojo-integration-tests/src/test/resources/schema/properties/propertiesWithSpecialCharacters.json
@@ -0,0 +1,17 @@
+{
+  "type" : "object",
+  "properties" : {
+    "VERSV#" : {
+      "type" : "string"
+    },
+    "foo#bar" : {
+      "type" : "string"
+    },
+    "${RFC_NUMBER}" : {
+      "type" : "string"
+    },
+    "org.hisp.dhis.common.FileTypeValueOptions" : {
+      "type" : "string"
+    }
+  }
+}
\ No newline at end of file