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);
+ }
+
+}