diff --git a/pom.xml b/pom.xml
index 2738292..7a9ad7d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.flowingcode.vaadin.addons.demo
commons-demo
- 4.2.1-SNAPSHOT
+ 4.3.0-SNAPSHOT
Commons Demo
Common classes for add-ons demo
diff --git a/src/main/java/com/flowingcode/vaadin/addons/demo/DefaultSourceUrlResolver.java b/src/main/java/com/flowingcode/vaadin/addons/demo/DefaultSourceUrlResolver.java
new file mode 100644
index 0000000..cc1100a
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/demo/DefaultSourceUrlResolver.java
@@ -0,0 +1,49 @@
+package com.flowingcode.vaadin.addons.demo;
+
+import com.flowingcode.vaadin.addons.GithubBranch;
+import com.flowingcode.vaadin.addons.GithubLink;
+import java.util.Optional;
+
+/**
+ * Implementation of {@code SourceUrlResolver}.
+ *
+ * If no {@code value} or {@code clazz} is specified, and the demo view is annotated with
+ * {@link GithubLink}, then the source URL defaults to the location of the annotated class under
+ * {@code src/test/java} and the branch is determined from the value of {@link GithubBranch} in the
+ * demo view class (if the annotation is present) or the containing package of the demo view class.
+ * If the source URL is defaulted and no {@code GithubBranch} annotation is present either in the
+ * demo view class or its containing package, then the branch defaults to {@code master}.
+ *
+ * @author Javier Godoy / Flowing Code
+ */
+class DefaultSourceUrlResolver implements SourceUrlResolver {
+
+ @Override
+ public Optional resolveURL(TabbedDemo demo, Class> annotatedClass,
+ DemoSource annotation) {
+ String demoFile;
+ String url = annotation.value();
+ if (url.equals(DemoSource.GITHUB_SOURCE) || url.equals(DemoSource.DEFAULT_VALUE)) {
+ String className;
+ if (annotation.clazz() == DemoSource.class) {
+ className = annotatedClass.getName().replace('.', '/');
+ } else {
+ className = annotation.clazz().getName().replace('.', '/');
+ }
+ demoFile = "src/test/java/" + className + ".java";
+ } else if (url.startsWith("/src/test/")) {
+ demoFile = url.substring(1);
+ } else {
+ demoFile = null;
+ }
+
+ if (demoFile != null) {
+ String branch = TabbedDemo.lookupGithubBranch(demo.getClass());
+ return Optional.ofNullable(demo.getClass().getAnnotation(GithubLink.class))
+ .map(githubLink -> String.format("%s/blob/%s/%s", githubLink.value(), branch, demoFile));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/demo/DemoSource.java b/src/main/java/com/flowingcode/vaadin/addons/demo/DemoSource.java
index 32fa014..81410ab 100644
--- a/src/main/java/com/flowingcode/vaadin/addons/demo/DemoSource.java
+++ b/src/main/java/com/flowingcode/vaadin/addons/demo/DemoSource.java
@@ -36,13 +36,18 @@
* defaulted and no {@code GithubBranch} annotation is present either in the demo view class or its
* containing package, then the branch defaults to {@code master}.
*
+ * This behavior can be globally overridden by configuring a {@link SourceUrlResolver}.
+ *
* @author Javier Godoy / Flowing Code
+ * @see TabbedDemo#configureSourceUrlResolver(SourceUrlResolver)
*/
@Repeatable(DemoSources.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DemoSource {
+ /** @deprecated. Use {@link #DEFAULT_VALUE} */
+ @Deprecated
static final String GITHUB_SOURCE = "__GITHUB__";
static final String DEFAULT_VALUE = "__DEFAULT__";
@@ -52,7 +57,7 @@
*
* It is an error if both {@code value} and {@link #clazz()} are specified.
*/
- String value() default GITHUB_SOURCE;
+ String value() default DEFAULT_VALUE;
/**
* The class to display, if different from the annotated class.
diff --git a/src/main/java/com/flowingcode/vaadin/addons/demo/SourceUrlResolver.java b/src/main/java/com/flowingcode/vaadin/addons/demo/SourceUrlResolver.java
new file mode 100644
index 0000000..3e3644b
--- /dev/null
+++ b/src/main/java/com/flowingcode/vaadin/addons/demo/SourceUrlResolver.java
@@ -0,0 +1,23 @@
+package com.flowingcode.vaadin.addons.demo;
+
+import java.util.Optional;
+
+/**
+ * Interface for resolving the source URL of a demo class.
+ *
+ * @author Javier Godoy / Flowing Code
+ */
+public interface SourceUrlResolver {
+
+ /**
+ * Resolves the source URL for a given demo class and annotation.
+ *
+ * @param demo The {@link TabbedDemo} instance associated with the source.
+ * @param annotatedClass The class that is annotated with {@link DemoSource}.
+ * @param annotation The {@link DemoSource} annotation providing source metadata.
+ * @return An {@link Optional} containing the resolved URL if available, otherwise an empty
+ * {@link Optional}.
+ */
+ Optional resolveURL(TabbedDemo demo, Class> annotatedClass, DemoSource annotation);
+
+}
diff --git a/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java b/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java
index 5e775bb..df5cc59 100644
--- a/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java
+++ b/src/main/java/com/flowingcode/vaadin/addons/demo/TabbedDemo.java
@@ -20,7 +20,6 @@
package com.flowingcode.vaadin.addons.demo;
import com.flowingcode.vaadin.addons.GithubBranch;
-import com.flowingcode.vaadin.addons.GithubLink;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
@@ -45,6 +44,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
+import lombok.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -232,31 +232,33 @@ public void showRouterLayoutContent(HasElement content) {
applyTheme(getElement(), getThemeName());
}
- private Optional createSourceCodeTab(Class> annotatedClass, DemoSource annotation) {
- String demoFile;
- String url = annotation.value();
- if (url.equals(DemoSource.GITHUB_SOURCE)) {
- String className;
- if (annotation.clazz() == DemoSource.class) {
- className = annotatedClass.getName().replace('.', '/');
- } else {
- className = annotation.clazz().getName().replace('.', '/');
- }
- demoFile = "src/test/java/" + className + ".java";
- } else if (url.startsWith("/src/test/")) {
- demoFile = url.substring(1);
- } else {
- demoFile = null;
+ private static SourceUrlResolver resolver = null;
+
+ /**
+ * Configures the {@code SourceUrlResolver} for resolving source URLs. This method can only be
+ * called once; subsequent calls will result in an exception.
+ *
+ * @param resolver The {@code SourceUrlResolver} to be used. Must not be {@code null}.
+ * @throws IllegalStateException if a resolver has already been set.
+ * @throws NullPointerException if the provided {@code resolver} is {@code null}.
+ */
+ public static void configureSourceUrlResolver(@NonNull SourceUrlResolver resolver) {
+ if (TabbedDemo.resolver != null) {
+ throw new IllegalStateException();
}
+ TabbedDemo.resolver = resolver;
+ }
- if (demoFile != null) {
- String branch = lookupGithubBranch(this.getClass());
- url = Optional.ofNullable(this.getClass().getAnnotation(GithubLink.class))
- .map(githubLink -> String.format("%s/blob/%s/%s", githubLink.value(),
- branch, demoFile))
- .orElse(null);
+ private static SourceUrlResolver getResolver() {
+ if (resolver == null) {
+ resolver = new DefaultSourceUrlResolver();
}
-
+ return resolver;
+ }
+
+ private Optional createSourceCodeTab(Class> annotatedClass, DemoSource annotation) {
+ String url = getResolver().resolveURL(this, annotatedClass, annotation).orElse(null);
+
if (url==null) {
return Optional.empty();
}