Repository: eclipse/microprofile-open-api (java)
Issue: Add methods to access a single extension to `Extensible`
Retrieving an extension via `Extensible` currently requires the use of `getExtensions` to acquire the entire extension map, which can then be used to pick the single entry. We should add `getExtension(String)` and `hasExtension(String)` methods to allow implementations to provide more efficient ways to access single entries.

Default methods can be provided to make more efficient implementations optional:

```java
default boolean hasExtension(String name) {
    Map<String, Object> map = getExtensions();
    if (map == null) {
        return false;
    }
    return map.containsKey(name);
}

default Object getExtension(String name) {
    Map<String, Object> map = getExtensions();
    if (map == null) {
        return null;
    }
    return map.get(name);
}
```

Pull request description: Add `Extensible.hasExtension` and `.getExtension`
Matching the helper methods for other model maps.

Fixes #666

Patch:
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/Extensible.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/Extensible.java
index 407204f..2b7c473 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/models/Extensible.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/Extensible.java
@@ -76,4 +76,34 @@ public interface Extensible<T extends Extensible<T>> {
      */
     void setExtensions(Map<String, Object> extensions);
 
+    /**
+     * Checks whether an extension with the given name is present in this Extensible's map of extensions.
+     *
+     * @param name
+     *            the key used to access the extension object. Always prefixed by "x-".
+     * @return {@code true} if an extension with the given name is present, otherwise {@code false}
+     */
+    default boolean hasExtension(String name) {
+        Map<String, Object> map = getExtensions();
+        if (map == null) {
+            return false;
+        }
+        return map.containsKey(name);
+    }
+
+    /**
+     * Returns the extension object with the given name from this Extensible's map of extensions.
+     *
+     * @param name
+     *            the key used to access the extension object. Always prefixed by "x-".
+     * @return the corresponding extension object, or {@code null} if no extension with the given name is present
+     */
+    default Object getExtension(String name) {
+        Map<String, Object> map = getExtensions();
+        if (map == null) {
+            return null;
+        }
+        return map.get(name);
+    }
+
 }
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/callbacks/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/callbacks/package-info.java
index cc8a571..fe05271 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/models/callbacks/package-info.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/callbacks/package-info.java
@@ -17,6 +17,6 @@
  * The behaviour of methods inherited from java.lang.Object are undefined by the MicroProfile OpenAPI specification.
  */
 
-@org.osgi.annotation.versioning.Version("2.0")
+@org.osgi.annotation.versioning.Version("2.1")
 @org.osgi.annotation.versioning.ProviderType
 package org.eclipse.microprofile.openapi.models.callbacks;
\ No newline at end of file
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/examples/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/examples/package-info.java
index 2956c67..d1d6898 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/models/examples/package-info.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/examples/package-info.java
@@ -31,6 +31,6 @@
  * </pre>
  */
 
-@org.osgi.annotation.versioning.Version("1.0")
+@org.osgi.annotation.versioning.Version("1.1")
 @org.osgi.annotation.versioning.ProviderType
 package org.eclipse.microprofile.openapi.models.examples;
\ No newline at end of file
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/headers/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/headers/package-info.java
index 3d3da47..4eb3a27 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/models/headers/package-info.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/headers/package-info.java
@@ -35,6 +35,6 @@
  * </pre>
  */
 
-@org.osgi.annotation.versioning.Version("1.0")
+@org.osgi.annotation.versioning.Version("1.1")
 @org.osgi.annotation.versioning.ProviderType
 package org.eclipse.microprofile.openapi.models.headers;
\ No newline at end of file
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/info/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/info/package-info.java
index 6ec0a09..1671c9d 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/models/info/package-info.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/info/package-info.java
@@ -40,6 +40,6 @@
  * </pre>
  */
 
-@org.osgi.annotation.versioning.Version("1.1")
+@org.osgi.annotation.versioning.Version("1.2")
 @org.osgi.annotation.versioning.ProviderType
 package org.eclipse.microprofile.openapi.models.info;
\ No newline at end of file
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/links/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/links/package-info.java
index 0b6e822..e28730a 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/models/links/package-info.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/links/package-info.java
@@ -29,6 +29,6 @@
  * </pre>
  */
 
-@org.osgi.annotation.versioning.Version("1.0")
+@org.osgi.annotation.versioning.Version("1.1")
 @org.osgi.annotation.versioning.ProviderType
 package org.eclipse.microprofile.openapi.models.links;
\ No newline at end of file
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/media/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/media/package-info.java
index 3e143df..a1fb925 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/models/media/package-info.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/media/package-info.java
@@ -30,6 +30,6 @@
  * </pre>
  */
 
-@org.osgi.annotation.versioning.Version("3.0")
+@org.osgi.annotation.versioning.Version("3.1")
 @org.osgi.annotation.versioning.ProviderType
 package org.eclipse.microprofile.openapi.models.media;
\ No newline at end of file
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/package-info.java
index f29b807..929acdc 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/models/package-info.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/package-info.java
@@ -40,6 +40,6 @@
  * </pre>
  */
 
-@org.osgi.annotation.versioning.Version("2.1")
+@org.osgi.annotation.versioning.Version("2.2")
 @org.osgi.annotation.versioning.ProviderType
 package org.eclipse.microprofile.openapi.models;
\ No newline at end of file
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/parameters/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/parameters/package-info.java
index bf390ff..fefc738 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/models/parameters/package-info.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/parameters/package-info.java
@@ -32,6 +32,6 @@
  * </pre>
  */
 
-@org.osgi.annotation.versioning.Version("1.0")
+@org.osgi.annotation.versioning.Version("1.1")
 @org.osgi.annotation.versioning.ProviderType
 package org.eclipse.microprofile.openapi.models.parameters;
\ No newline at end of file
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/responses/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/responses/package-info.java
index 41b9c37..8955422 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/models/responses/package-info.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/responses/package-info.java
@@ -34,6 +34,6 @@
  * </pre>
  */
 
-@org.osgi.annotation.versioning.Version("2.0")
+@org.osgi.annotation.versioning.Version("2.1")
 @org.osgi.annotation.versioning.ProviderType
 package org.eclipse.microprofile.openapi.models.responses;
\ No newline at end of file
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/security/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/security/package-info.java
index b9380d4..0eb28a4 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/models/security/package-info.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/security/package-info.java
@@ -28,6 +28,6 @@
  * </pre>
  */
 
-@org.osgi.annotation.versioning.Version("2.1")
+@org.osgi.annotation.versioning.Version("2.2")
 @org.osgi.annotation.versioning.ProviderType
 package org.eclipse.microprofile.openapi.models.security;
\ No newline at end of file
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/servers/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/servers/package-info.java
index 8a0f931..167a61a 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/models/servers/package-info.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/servers/package-info.java
@@ -35,6 +35,6 @@
  * </pre>
  */
 
-@org.osgi.annotation.versioning.Version("2.0")
+@org.osgi.annotation.versioning.Version("2.1")
 @org.osgi.annotation.versioning.ProviderType
 package org.eclipse.microprofile.openapi.models.servers;
\ No newline at end of file
diff --git a/api/src/main/java/org/eclipse/microprofile/openapi/models/tags/package-info.java b/api/src/main/java/org/eclipse/microprofile/openapi/models/tags/package-info.java
index 195d309..12a8a57 100644
--- a/api/src/main/java/org/eclipse/microprofile/openapi/models/tags/package-info.java
+++ b/api/src/main/java/org/eclipse/microprofile/openapi/models/tags/package-info.java
@@ -29,6 +29,6 @@
  * </pre>
  */
 
-@org.osgi.annotation.versioning.Version("1.0")
+@org.osgi.annotation.versioning.Version("1.1")
 @org.osgi.annotation.versioning.ProviderType
 package org.eclipse.microprofile.openapi.models.tags;
\ No newline at end of file


Test patch:
diff --git a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelConstructionTest.java b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelConstructionTest.java
index 9482aab..9481a99 100644
--- a/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelConstructionTest.java
+++ b/tck/src/main/java/org/eclipse/microprofile/openapi/tck/ModelConstructionTest.java
@@ -1781,16 +1781,28 @@ public class ModelConstructionTest extends Arquillian {
         assertEquals(map.size(), 2, "The extensions map is expected to contain two entries.");
         assertTrue(map.containsKey(extensionName1),
                 "The extensions map is expected to contain the key: " + extensionName1);
+        assertTrue(e.hasExtension(extensionName1),
+                "hasExtension is expected to return true for the key: " + extensionName1);
         assertTrue(map.containsKey(extensionName2),
                 "The extensions map is expected to contain the key: " + extensionName2);
+        assertTrue(e.hasExtension(extensionName2),
+                "hasExtension is expected to return true for the key: " + extensionName2);
         assertSame(map.get(extensionName1), obj1,
                 "The value associated with the key: " + extensionName1
                         + " is expected to be the same one that was added.");
+        assertSame(e.getExtension(extensionName1), obj1,
+                "getExtension should return the same instance added for the key: " + extensionName1);
         assertSame(map.get(extensionName2), obj2,
                 "The value associated with the key: " + extensionName2
                         + " is expected to be the same one that was added.");
+        assertSame(e.getExtension(extensionName2), obj2,
+                "getExtension should return the same instance added for the key: " + extensionName2);
         e.removeExtension(extensionName1);
         assertEquals(e.getExtensions().size(), 1, "The extensions map is expected to contain one entry.");
+        assertFalse(e.hasExtension(extensionName1),
+                "hasExtension is expected to return false for removed key: " + extensionName1);
+        assertNull(e.getExtension(extensionName1),
+                "getExtension is expected to return null for removed key: " + extensionName1);
         // Check that the extension map can be replaced with the setter and that it is returned by the getter.
         final Map<String, Object> newMap = new HashMap<>();
         e.setExtensions(newMap);