SLING-9999 Remove cyclic dependency between scripting and servlets features

add ResourceType from bundle tracker
diff --git a/pom.xml b/pom.xml
index 36cd97d..ccafaaa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,6 +66,11 @@
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.annotation.versioning</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.framework</artifactId>
+            <scope>provided</scope>
+        </dependency>
 
         <dependency>
             <groupId>org.slf4j</groupId>
diff --git a/src/main/java/org/apache/sling/api/resource/type/ResourceType.java b/src/main/java/org/apache/sling/api/resource/type/ResourceType.java
new file mode 100644
index 0000000..79b2ae8
--- /dev/null
+++ b/src/main/java/org/apache/sling/api/resource/type/ResourceType.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.api.resource.type;
+
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.framework.Version;
+
+/**
+ * The {@code ResourceType} encapsulates details about a Sling resource type and provides methods for parsing resource type strings.
+ *
+ * <p>The following patterns are supported for parsing:</p>
+ * <ol>
+ * <li><tt>a/b/c</tt> - path-based</li>
+ * <li><tt>a/b/c/1.0.0</tt> - path-based, versioned</li>
+ * <li><tt>a.b.c</tt> - Java package name</li>
+ * <li><tt>a.b.c/1.0.0</tt> - Java package name, versioned</li>
+ * <li><tt>a</tt> - flat (sub-set of path-based)</li>
+ * </ol>
+ */
+public final class ResourceType {
+
+    private static final Pattern versionPattern = Pattern.compile("[\\d\\.]+(-.*)*$");
+
+    private final String type;
+    private final String version;
+    private final String resourceLabel;
+    private final String toString;
+
+    private ResourceType(@NotNull String type, @Nullable String version) {
+        this.type = type;
+        this.version = version;
+        if (type.lastIndexOf('/') != -1) {
+            resourceLabel = type.substring(type.lastIndexOf('/') + 1);
+        } else if (type.lastIndexOf('.') != -1) {
+            resourceLabel = type.substring(type.lastIndexOf('.') + 1);
+        } else {
+            resourceLabel = type;
+        }
+        toString = type + (version == null ? "" : "/" + version);
+    }
+
+    /**
+     * Returns a resource type's label. The label is important for script selection, since it will provide the name of the main script
+     * for this resource type. For more details check the Apache Sling
+     * <a href="https://sling.apache.org/documentation/the-sling-engine/url-to-script-resolution.html#scripts-for-get-requests">URL to
+     * Script Resolution</a> page
+     *
+     * @return the resource type label
+     */
+    @NotNull
+    public String getResourceLabel() {
+        return resourceLabel;
+    }
+
+    /**
+     * Returns the resource type string, without any version information.
+     *
+     * @return the resource type string
+     */
+    @NotNull
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * Returns the version, if available.
+     *
+     * @return the version, if available; {@code null} otherwise
+     */
+    @Nullable
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public String toString() {
+        return toString;
+    }
+
+    /**
+     * Given a {@code resourceTypeString}, this method will extract a {@link ResourceType} object.
+     * <p>The accepted patterns are:</p>
+     * <ol>
+     * <li><tt>a/b/c</tt> - path-based</li>
+     * <li><tt>a/b/c/1.0.0</tt> - path-based, versioned</li>
+     * <li><tt>a.b.c</tt> - Java package name</li>
+     * <li><tt>a.b.c/1.0.0</tt> - Java package name, versioned</li>
+     * <li><tt>a</tt> - flat (sub-set of path-based)</li>
+     * </ol>
+     *
+     * @param resourceTypeString the resource type string to parse
+     * @return a {@link ResourceType} object
+     * @throws IllegalArgumentException if the {@code resourceTypeString} cannot be parsed
+     */
+    @NotNull
+    public static ResourceType parseResourceType(@NotNull String resourceTypeString) {
+        String type = "";
+        String version = null;
+        if (Objects.nonNull(resourceTypeString) && !resourceTypeString.isEmpty()) {
+            int lastSlash = resourceTypeString.lastIndexOf('/');
+            if (lastSlash != -1 && !resourceTypeString.endsWith("/")) {
+                String versionString = resourceTypeString.substring(lastSlash + 1);
+                if (versionPattern.matcher(versionString).matches()) {
+                    try {
+                        version = Version.parseVersion(versionString).toString();
+                        type = resourceTypeString.substring(0, lastSlash);
+                    } catch (IllegalArgumentException e) {
+                        type = resourceTypeString;
+                    }
+                } else {
+                    type = resourceTypeString;
+                }
+            } else {
+                type = resourceTypeString;
+            }
+        }
+        if (type.isEmpty()) {
+            throw new IllegalArgumentException(String.format("Cannot extract a type for the resourceTypeString %s.", resourceTypeString));
+        }
+        return new ResourceType(type, version);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type, version, resourceLabel);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof ResourceType) {
+            ResourceType other = (ResourceType) obj;
+            return Objects.equals(type, other.type) && Objects.equals(version, other.version) && Objects.equals(resourceLabel,
+                    other.resourceLabel);
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/org/apache/sling/api/resource/type/package-info.java b/src/main/java/org/apache/sling/api/resource/type/package-info.java
new file mode 100644
index 0000000..06859b5
--- /dev/null
+++ b/src/main/java/org/apache/sling/api/resource/type/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+@Version("0.2.0")
+package org.apache.sling.api.resource.type;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/src/test/java/org/apache/sling/api/resource/type/ResourceTypeTest.java b/src/test/java/org/apache/sling/api/resource/type/ResourceTypeTest.java
new file mode 100644
index 0000000..8c3e0c0
--- /dev/null
+++ b/src/test/java/org/apache/sling/api/resource/type/ResourceTypeTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.sling.api.resource.type;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class ResourceTypeTest {
+
+    @Test
+    public void testSlashNoVersion() {
+        ResourceType t1 = ResourceType.parseResourceType("a/b/c");
+        assertEquals("a/b/c", t1.getType());
+        assertEquals("c", t1.getResourceLabel());
+        assertNull(t1.getVersion());
+    }
+
+    @Test
+    public void testSlashVersion() {
+        ResourceType t1 = ResourceType.parseResourceType("a/b/c/1.0.0");
+        assertEquals("a/b/c", t1.getType());
+        assertEquals("c", t1.getResourceLabel());
+        assertEquals("1.0.0", t1.getVersion());
+    }
+
+    @Test
+    public void testOneSegmentNoVersion() {
+        ResourceType t1 = ResourceType.parseResourceType("a");
+        assertEquals("a", t1.getType());
+        assertEquals("a", t1.getResourceLabel());
+        assertNull(t1.getVersion());
+    }
+
+    @Test
+    public void testOneSegmentVersion() {
+        ResourceType t1 = ResourceType.parseResourceType("a/1.2.3");
+        assertEquals("a", t1.getType());
+        assertEquals("a", t1.getResourceLabel());
+        assertEquals("1.2.3", t1.getVersion());
+    }
+
+    @Test
+    public void testDotNoVersion() {
+        ResourceType t1 = ResourceType.parseResourceType("a.b.c");
+        assertEquals("a.b.c", t1.getType());
+        assertEquals("c", t1.getResourceLabel());
+        assertNull(t1.getVersion());
+    }
+
+    @Test
+    public void testDotVersion() {
+        ResourceType t1 = ResourceType.parseResourceType("a.b.c/42.0.0");
+        assertEquals("a.b.c", t1.getType());
+        assertEquals("c", t1.getResourceLabel());
+        assertEquals("42.0.0", t1.getVersion());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testEmptyString() {
+        ResourceType t1 = ResourceType.parseResourceType(StringUtils.EMPTY);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNull() {
+        ResourceType t1 = ResourceType.parseResourceType(null);
+    }
+
+}