From 99ac68479423f499de006a137e798ac2d02f026b Mon Sep 17 00:00:00 2001
From: James Rosewell
Date: Sat, 14 Nov 2026 00:11:02 +0000
Subject: [PATCH 1/8] Test that every example ends its output with a Find out
more section
Adds FindOutMore to the Device Detection and IP Intelligence examples'
shared modules, with its own tests, and checks that each on-premise console
example run with no settings ends with the section and that every console
example with a main method writes it.
---
.../console/ExamplesWithoutSettingsTest.java | 16 ++
.../console/FindOutMoreCoverageTest.java | 74 ++++++++
.../examples/shared/FindOutMore.java | 161 ++++++++++++++++++
.../examples/shared/FindOutMoreTest.java | 120 +++++++++++++
.../console/ExamplesWithoutSettingsTest.java | 16 ++
.../console/FindOutMoreCoverageTest.java | 74 ++++++++
.../examples/shared/FindOutMore.java | 161 ++++++++++++++++++
.../examples/shared/FindOutMoreTest.java | 120 +++++++++++++
8 files changed, 742 insertions(+)
create mode 100644 examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/FindOutMoreCoverageTest.java
create mode 100644 examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/FindOutMore.java
create mode 100644 examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/FindOutMoreTest.java
create mode 100644 examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/FindOutMoreCoverageTest.java
create mode 100644 examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/FindOutMore.java
create mode 100644 examples/ip-intelligence/shared/src/test/java/fiftyone/ipintelligence/examples/shared/FindOutMoreTest.java
diff --git a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/ExamplesWithoutSettingsTest.java b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/ExamplesWithoutSettingsTest.java
index aea911082..9c36dc2ea 100644
--- a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/ExamplesWithoutSettingsTest.java
+++ b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/ExamplesWithoutSettingsTest.java
@@ -23,6 +23,7 @@
package fiftyone.devicedetection.examples.console;
import fiftyone.devicedetection.examples.shared.DataFileHelper;
+import fiftyone.devicedetection.examples.shared.FindOutMore;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
@@ -75,6 +76,7 @@ private static String runWithoutSettings(Main main) throws Exception {
}
String output = buffer.toString("UTF-8");
out.println(output);
+ assertFindOutMore(output);
return output;
}
@@ -83,6 +85,20 @@ private static void assertContains(String output, String expected) {
output.contains(expected));
}
+ /**
+ * Checks that the output ends with the "Find out more" section, after
+ * the link to the contact page.
+ */
+ private static void assertFindOutMore(String output) {
+ int heading = output.lastIndexOf(FindOutMore.HEADING);
+ assertTrue("The output should have the '" + FindOutMore.HEADING +
+ "' section after the link to the contact page",
+ heading > output.lastIndexOf(CONTACT_US));
+ assertTrue("The '" + FindOutMore.HEADING +
+ "' section should end with its rule",
+ output.indexOf(FindOutMore.RULE, heading) > heading);
+ }
+
private static void assertOffer(String output) {
assertContains(output, "This example used the free Lite data file.");
assertContains(output, CONTACT_US);
diff --git a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/FindOutMoreCoverageTest.java b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/FindOutMoreCoverageTest.java
new file mode 100644
index 000000000..f090ac8fe
--- /dev/null
+++ b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/FindOutMoreCoverageTest.java
@@ -0,0 +1,74 @@
+/* *********************************************************************
+ * This Original Work is copyright of 51 Degrees Mobile Experts Limited.
+ * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
+ * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
+ *
+ * This Original Work is licensed under the European Union Public Licence
+ * (EUPL) v.1.2 and is subject to its terms as set out below.
+ *
+ * If a copy of the EUPL was not distributed with this file, You can obtain
+ * one at https://opensource.org/licenses/EUPL-1.2.
+ *
+ * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
+ * amended by the European Commission) shall be deemed incompatible for
+ * the purposes of the Work and the provisions of the compatibility
+ * clause in Article 5 of the EUPL shall not apply.
+ *
+ * If using the Work as, or as part of, a network application, by
+ * including the attribution notice(s) required under Article 5 of the EUPL
+ * in the end user terms of the application under an appropriate heading,
+ * such notice(s) shall fulfill the requirements of that article.
+ * ********************************************************************* */
+
+package fiftyone.devicedetection.examples.console;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Checks that every console example with a main method writes the "Find out
+ * more" section, including those that need a key or a paid data file and so
+ * do not run in ExamplesWithoutSettingsTest.
+ */
+public class FindOutMoreCoverageTest {
+
+ @Test
+ public void everyExample_WritesFindOutMore() throws IOException {
+ Path sources = Paths.get(System.getProperty("basedir", "."),
+ "src", "main", "java");
+ assertTrue(sources + " should hold the examples",
+ Files.isDirectory(sources));
+ List files;
+ try (Stream walk = Files.walk(sources)) {
+ files = walk.filter(f -> f.toString().endsWith(".java"))
+ .collect(Collectors.toList());
+ }
+ List examples = new ArrayList<>();
+ List missing = new ArrayList<>();
+ for (Path file : files) {
+ String text = new String(Files.readAllBytes(file),
+ StandardCharsets.UTF_8);
+ if (text.contains("static void main(")) {
+ examples.add(file.getFileName().toString());
+ if (text.contains("FindOutMore.write") == false) {
+ missing.add(file.getFileName().toString());
+ }
+ }
+ }
+ assertTrue("No examples were found in " + sources,
+ examples.isEmpty() == false);
+ assertTrue("These examples do not write the 'Find out more' " +
+ "section: " + missing, missing.isEmpty());
+ }
+}
diff --git a/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/FindOutMore.java b/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/FindOutMore.java
new file mode 100644
index 000000000..7d6bda5a5
--- /dev/null
+++ b/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/FindOutMore.java
@@ -0,0 +1,161 @@
+/* *********************************************************************
+ * This Original Work is copyright of 51 Degrees Mobile Experts Limited.
+ * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
+ * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
+ *
+ * This Original Work is licensed under the European Union Public Licence
+ * (EUPL) v.1.2 and is subject to its terms as set out below.
+ *
+ * If a copy of the EUPL was not distributed with this file, You can obtain
+ * one at https://opensource.org/licenses/EUPL-1.2.
+ *
+ * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
+ * amended by the European Commission) shall be deemed incompatible for
+ * the purposes of the Work and the provisions of the compatibility
+ * clause in Article 5 of the EUPL shall not apply.
+ *
+ * If using the Work as, or as part of, a network application, by
+ * including the attribution notice(s) required under Article 5 of the EUPL
+ * in the end user terms of the application under an appropriate heading,
+ * such notice(s) shall fulfill the requirements of that article.
+ * ********************************************************************* */
+
+package fiftyone.devicedetection.examples.shared;
+
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.regex.Pattern;
+
+/**
+ * Writes the "Find out more" section that ends every example's output, set
+ * apart from the example's own output by a rule above and below. It gives
+ * pages on 51degrees.com, each tagged with the example that wrote it, then
+ * the repositories behind the example.
+ */
+public final class FindOutMore {
+
+ /**
+ * The heading the section starts with.
+ */
+ public static final String HEADING = "Find out more";
+
+ /**
+ * The rule above and below the section.
+ */
+ public static final String RULE =
+ "----------------------------------------------------------------";
+
+ /**
+ * The repositories an on-premise example lists, being this SDK, the C
+ * engines it builds on and the free data files.
+ */
+ public static final String[] ON_PREMISE_REPOSITORIES = {
+ "https://github.com/51Degrees/51degrees-java",
+ "https://github.com/51Degrees/51degrees-cxx",
+ "https://github.com/51Degrees/data"
+ };
+
+ /**
+ * The repositories a cloud example lists.
+ */
+ public static final String[] CLOUD_REPOSITORIES = {
+ "https://github.com/51Degrees/51degrees-java"
+ };
+
+ /**
+ * Each page's label and its path on 51degrees.com.
+ */
+ private static final String[][] PAGES = {
+ {"Device Detection", "device-detection"},
+ {"Pricing", "pricing"},
+ {"Documentation", "documentation/index.html"}
+ };
+
+ /**
+ * What a campaign tag value may hold.
+ */
+ private static final Pattern SLUG =
+ Pattern.compile("[a-z0-9][a-z0-9._-]*");
+
+ private FindOutMore() {
+ }
+
+ /**
+ * Writes the section.
+ * @param output where to write, flushed afterwards
+ * @param example the example's tag, a lower case slug such as
+ * "console-gettingstartedonprem.java"
+ * @param repositories the repositories to list, such as
+ * {@link #ON_PREMISE_REPOSITORIES}
+ */
+ public static void write(
+ PrintWriter output,
+ String example,
+ String... repositories) {
+ output.println();
+ writeLines(output, "", example, repositories);
+ }
+
+ /**
+ * Writes the section with each line a comment, for output that has to
+ * stay valid in its own format, such as a YAML stream.
+ * @param output where to write, flushed afterwards
+ * @param comment what starts a comment line, such as "# "
+ * @param example the example's tag
+ * @param repositories the repositories to list
+ */
+ public static void writeCommented(
+ PrintWriter output,
+ String comment,
+ String example,
+ String... repositories) {
+ writeLines(output, comment, example, repositories);
+ }
+
+ private static void writeLines(
+ PrintWriter output,
+ String start,
+ String example,
+ String... repositories) {
+ output.println(start + RULE);
+ output.println(start + HEADING);
+ for (String[] page : PAGES) {
+ output.println(start + "\t" + page[0] + ": " +
+ link(page[1], example));
+ }
+ for (String repository : repositories) {
+ output.println(start + "\t" + repository);
+ }
+ output.println(start + RULE);
+ output.flush();
+ }
+
+ /**
+ * Writes the section to a stream. See
+ * {@link #write(PrintWriter, String, String...)}.
+ */
+ public static void write(
+ OutputStream output,
+ String example,
+ String... repositories) {
+ write(new PrintWriter(output), example, repositories);
+ }
+
+ /**
+ * The address of a page on 51degrees.com, tagged with the example.
+ * @param page the path of the page
+ * @param example the example's tag
+ * @return the address
+ * @throws IllegalArgumentException if the tag is not a lower case slug
+ */
+ public static String link(String page, String example) {
+ if (example == null || SLUG.matcher(example).matches() == false) {
+ throw new IllegalArgumentException(
+ "'" + example + "' is not a lower case slug.");
+ }
+ return "https://51degrees.com/" +
+ page + "?utm_source=code&utm_medium=example" +
+ "&utm_campaign=51degrees-java&utm_content=" + example +
+ "&utm_term=find-out-more";
+ }
+}
diff --git a/examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/FindOutMoreTest.java b/examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/FindOutMoreTest.java
new file mode 100644
index 000000000..2b3aef4ab
--- /dev/null
+++ b/examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/FindOutMoreTest.java
@@ -0,0 +1,120 @@
+/* *********************************************************************
+ * This Original Work is copyright of 51 Degrees Mobile Experts Limited.
+ * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
+ * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
+ *
+ * This Original Work is licensed under the European Union Public Licence
+ * (EUPL) v.1.2 and is subject to its terms as set out below.
+ *
+ * If a copy of the EUPL was not distributed with this file, You can obtain
+ * one at https://opensource.org/licenses/EUPL-1.2.
+ *
+ * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
+ * amended by the European Commission) shall be deemed incompatible for
+ * the purposes of the Work and the provisions of the compatibility
+ * clause in Article 5 of the EUPL shall not apply.
+ *
+ * If using the Work as, or as part of, a network application, by
+ * including the attribution notice(s) required under Article 5 of the EUPL
+ * in the end user terms of the application under an appropriate heading,
+ * such notice(s) shall fulfill the requirements of that article.
+ * ********************************************************************* */
+
+package fiftyone.devicedetection.examples.shared;
+
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.regex.Pattern;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class FindOutMoreTest {
+
+ /**
+ * The campaign tags common-ci's scripts/utm-lint.ps1 requires of a link
+ * printed by an example.
+ */
+ private static final Pattern TAGGED = Pattern.compile(
+ "https://51degrees\\.com/[^?\\s]+\\?utm_source=code" +
+ "&utm_medium=example&utm_campaign=51degrees-java" +
+ "&utm_content=[a-z0-9][a-z0-9._-]*" +
+ "&utm_term=[a-z0-9][a-z0-9._-]*");
+
+ private static String[] write(String example, String... repositories) {
+ StringWriter text = new StringWriter();
+ FindOutMore.write(new PrintWriter(text), example, repositories);
+ return text.toString().split("\r?\n");
+ }
+
+ /**
+ * The section is a blank line, a rule, the heading, the three tagged
+ * pages, the repositories and a closing rule.
+ */
+ @Test
+ public void write_LinksThenRepositories() {
+ String[] lines = write("console-example.java",
+ FindOutMore.ON_PREMISE_REPOSITORIES);
+ assertEquals(10, lines.length);
+ assertEquals("", lines[0]);
+ assertEquals(FindOutMore.RULE, lines[1]);
+ assertEquals(FindOutMore.HEADING, lines[2]);
+ assertTrue(lines[3].startsWith("\tDevice Detection: https://51degrees.com/device-detection?"));
+ assertTrue(lines[4].startsWith("\tPricing: https://51degrees.com/pricing?"));
+ assertTrue(lines[5].startsWith("\tDocumentation: https://51degrees.com/documentation/index.html?"));
+ for (int i = 3; i <= 5; i++) {
+ String link = lines[i].substring(lines[i].indexOf("https://"));
+ assertTrue(link, TAGGED.matcher(link).matches());
+ assertTrue(link, link.contains("&utm_content=console-example.java&"));
+ }
+ assertEquals("\thttps://github.com/51Degrees/51degrees-java", lines[6]);
+ assertEquals("\thttps://github.com/51Degrees/51degrees-cxx", lines[7]);
+ assertEquals("\thttps://github.com/51Degrees/data", lines[8]);
+ assertEquals(FindOutMore.RULE, lines[9]);
+ }
+
+ /**
+ * A cloud example lists this repository alone.
+ */
+ @Test
+ public void write_Cloud_ThisRepository() {
+ String[] lines = write("console-example.java",
+ FindOutMore.CLOUD_REPOSITORIES);
+ assertEquals(8, lines.length);
+ assertEquals("\thttps://github.com/51Degrees/51degrees-java", lines[6]);
+ }
+
+ /**
+ * Each line of the commented form starts with the comment marker, and
+ * there is no blank line, so a YAML stream stays valid.
+ */
+ @Test
+ public void writeCommented_EveryLineAComment() {
+ StringWriter text = new StringWriter();
+ FindOutMore.writeCommented(new PrintWriter(text), "# ",
+ "console-example.java", FindOutMore.CLOUD_REPOSITORIES);
+ String[] lines = text.toString().split("\\r?\\n");
+ assertEquals(7, lines.length);
+ for (String line : lines) {
+ assertTrue(line, line.startsWith("# "));
+ }
+ assertEquals("# " + FindOutMore.HEADING, lines[1]);
+ }
+
+ /**
+ * A tag that is not a lower case slug would fail the campaign tag
+ * convention, so it is refused.
+ */
+ @Test
+ public void link_NotASlug_Refused() {
+ assertThrows(IllegalArgumentException.class,
+ () -> FindOutMore.link("pricing", "Console-Example.java"));
+ assertThrows(IllegalArgumentException.class,
+ () -> FindOutMore.link("pricing", "console example"));
+ assertThrows(IllegalArgumentException.class,
+ () -> FindOutMore.link("pricing", null));
+ }
+}
diff --git a/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/ExamplesWithoutSettingsTest.java b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/ExamplesWithoutSettingsTest.java
index d14a522f6..14c6a4ec6 100644
--- a/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/ExamplesWithoutSettingsTest.java
+++ b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/ExamplesWithoutSettingsTest.java
@@ -23,6 +23,7 @@
package fiftyone.ipintelligence.examples.console;
import fiftyone.ipintelligence.examples.shared.DataFileHelper;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
@@ -92,6 +93,7 @@ private static String runWithoutSettings(Main main) throws Exception {
}
String output = buffer.toString("UTF-8");
out.println(output);
+ assertFindOutMore(output);
return output;
}
@@ -100,6 +102,20 @@ private static void assertContains(String output, String expected) {
output.contains(expected));
}
+ /**
+ * Checks that the output ends with the "Find out more" section, after
+ * the link to the contact page.
+ */
+ private static void assertFindOutMore(String output) {
+ int heading = output.lastIndexOf(FindOutMore.HEADING);
+ assertTrue("The output should have the '" + FindOutMore.HEADING +
+ "' section after the link to the contact page",
+ heading > output.lastIndexOf(CONTACT_US));
+ assertTrue("The '" + FindOutMore.HEADING +
+ "' section should end with its rule",
+ output.indexOf(FindOutMore.RULE, heading) > heading);
+ }
+
private static void assertStops(String output, String purpose) {
assertContains(output, purpose + " needs the ");
assertContains(output, "so the example stops here.");
diff --git a/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/FindOutMoreCoverageTest.java b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/FindOutMoreCoverageTest.java
new file mode 100644
index 000000000..3ad6721cf
--- /dev/null
+++ b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/FindOutMoreCoverageTest.java
@@ -0,0 +1,74 @@
+/* *********************************************************************
+ * This Original Work is copyright of 51 Degrees Mobile Experts Limited.
+ * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
+ * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
+ *
+ * This Original Work is licensed under the European Union Public Licence
+ * (EUPL) v.1.2 and is subject to its terms as set out below.
+ *
+ * If a copy of the EUPL was not distributed with this file, You can obtain
+ * one at https://opensource.org/licenses/EUPL-1.2.
+ *
+ * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
+ * amended by the European Commission) shall be deemed incompatible for
+ * the purposes of the Work and the provisions of the compatibility
+ * clause in Article 5 of the EUPL shall not apply.
+ *
+ * If using the Work as, or as part of, a network application, by
+ * including the attribution notice(s) required under Article 5 of the EUPL
+ * in the end user terms of the application under an appropriate heading,
+ * such notice(s) shall fulfill the requirements of that article.
+ * ********************************************************************* */
+
+package fiftyone.ipintelligence.examples.console;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Checks that every console example with a main method writes the "Find out
+ * more" section, including those that need a key or a paid data file and so
+ * do not run in ExamplesWithoutSettingsTest.
+ */
+public class FindOutMoreCoverageTest {
+
+ @Test
+ public void everyExample_WritesFindOutMore() throws IOException {
+ Path sources = Paths.get(System.getProperty("basedir", "."),
+ "src", "main", "java");
+ assertTrue(sources + " should hold the examples",
+ Files.isDirectory(sources));
+ List files;
+ try (Stream walk = Files.walk(sources)) {
+ files = walk.filter(f -> f.toString().endsWith(".java"))
+ .collect(Collectors.toList());
+ }
+ List examples = new ArrayList<>();
+ List missing = new ArrayList<>();
+ for (Path file : files) {
+ String text = new String(Files.readAllBytes(file),
+ StandardCharsets.UTF_8);
+ if (text.contains("static void main(")) {
+ examples.add(file.getFileName().toString());
+ if (text.contains("FindOutMore.write") == false) {
+ missing.add(file.getFileName().toString());
+ }
+ }
+ }
+ assertTrue("No examples were found in " + sources,
+ examples.isEmpty() == false);
+ assertTrue("These examples do not write the 'Find out more' " +
+ "section: " + missing, missing.isEmpty());
+ }
+}
diff --git a/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/FindOutMore.java b/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/FindOutMore.java
new file mode 100644
index 000000000..2647e2c35
--- /dev/null
+++ b/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/FindOutMore.java
@@ -0,0 +1,161 @@
+/* *********************************************************************
+ * This Original Work is copyright of 51 Degrees Mobile Experts Limited.
+ * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
+ * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
+ *
+ * This Original Work is licensed under the European Union Public Licence
+ * (EUPL) v.1.2 and is subject to its terms as set out below.
+ *
+ * If a copy of the EUPL was not distributed with this file, You can obtain
+ * one at https://opensource.org/licenses/EUPL-1.2.
+ *
+ * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
+ * amended by the European Commission) shall be deemed incompatible for
+ * the purposes of the Work and the provisions of the compatibility
+ * clause in Article 5 of the EUPL shall not apply.
+ *
+ * If using the Work as, or as part of, a network application, by
+ * including the attribution notice(s) required under Article 5 of the EUPL
+ * in the end user terms of the application under an appropriate heading,
+ * such notice(s) shall fulfill the requirements of that article.
+ * ********************************************************************* */
+
+package fiftyone.ipintelligence.examples.shared;
+
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.regex.Pattern;
+
+/**
+ * Writes the "Find out more" section that ends every example's output, set
+ * apart from the example's own output by a rule above and below. It gives
+ * pages on 51degrees.com, each tagged with the example that wrote it, then
+ * the repositories behind the example.
+ */
+public final class FindOutMore {
+
+ /**
+ * The heading the section starts with.
+ */
+ public static final String HEADING = "Find out more";
+
+ /**
+ * The rule above and below the section.
+ */
+ public static final String RULE =
+ "----------------------------------------------------------------";
+
+ /**
+ * The repositories an on-premise example lists, being this SDK, the C
+ * engines it builds on and the free data files.
+ */
+ public static final String[] ON_PREMISE_REPOSITORIES = {
+ "https://github.com/51Degrees/51degrees-java",
+ "https://github.com/51Degrees/51degrees-cxx",
+ "https://github.com/51Degrees/data"
+ };
+
+ /**
+ * The repositories a cloud example lists.
+ */
+ public static final String[] CLOUD_REPOSITORIES = {
+ "https://github.com/51Degrees/51degrees-java"
+ };
+
+ /**
+ * Each page's label and its path on 51degrees.com.
+ */
+ private static final String[][] PAGES = {
+ {"IP Intelligence", "ip-intelligence"},
+ {"Pricing", "pricing"},
+ {"Documentation", "documentation/index.html"}
+ };
+
+ /**
+ * What a campaign tag value may hold.
+ */
+ private static final Pattern SLUG =
+ Pattern.compile("[a-z0-9][a-z0-9._-]*");
+
+ private FindOutMore() {
+ }
+
+ /**
+ * Writes the section.
+ * @param output where to write, flushed afterwards
+ * @param example the example's tag, a lower case slug such as
+ * "console-gettingstartedonprem.java"
+ * @param repositories the repositories to list, such as
+ * {@link #ON_PREMISE_REPOSITORIES}
+ */
+ public static void write(
+ PrintWriter output,
+ String example,
+ String... repositories) {
+ output.println();
+ writeLines(output, "", example, repositories);
+ }
+
+ /**
+ * Writes the section with each line a comment, for output that has to
+ * stay valid in its own format, such as a YAML stream.
+ * @param output where to write, flushed afterwards
+ * @param comment what starts a comment line, such as "# "
+ * @param example the example's tag
+ * @param repositories the repositories to list
+ */
+ public static void writeCommented(
+ PrintWriter output,
+ String comment,
+ String example,
+ String... repositories) {
+ writeLines(output, comment, example, repositories);
+ }
+
+ private static void writeLines(
+ PrintWriter output,
+ String start,
+ String example,
+ String... repositories) {
+ output.println(start + RULE);
+ output.println(start + HEADING);
+ for (String[] page : PAGES) {
+ output.println(start + "\t" + page[0] + ": " +
+ link(page[1], example));
+ }
+ for (String repository : repositories) {
+ output.println(start + "\t" + repository);
+ }
+ output.println(start + RULE);
+ output.flush();
+ }
+
+ /**
+ * Writes the section to a stream. See
+ * {@link #write(PrintWriter, String, String...)}.
+ */
+ public static void write(
+ OutputStream output,
+ String example,
+ String... repositories) {
+ write(new PrintWriter(output), example, repositories);
+ }
+
+ /**
+ * The address of a page on 51degrees.com, tagged with the example.
+ * @param page the path of the page
+ * @param example the example's tag
+ * @return the address
+ * @throws IllegalArgumentException if the tag is not a lower case slug
+ */
+ public static String link(String page, String example) {
+ if (example == null || SLUG.matcher(example).matches() == false) {
+ throw new IllegalArgumentException(
+ "'" + example + "' is not a lower case slug.");
+ }
+ return "https://51degrees.com/" +
+ page + "?utm_source=code&utm_medium=example" +
+ "&utm_campaign=51degrees-java&utm_content=" + example +
+ "&utm_term=find-out-more";
+ }
+}
diff --git a/examples/ip-intelligence/shared/src/test/java/fiftyone/ipintelligence/examples/shared/FindOutMoreTest.java b/examples/ip-intelligence/shared/src/test/java/fiftyone/ipintelligence/examples/shared/FindOutMoreTest.java
new file mode 100644
index 000000000..5433e1265
--- /dev/null
+++ b/examples/ip-intelligence/shared/src/test/java/fiftyone/ipintelligence/examples/shared/FindOutMoreTest.java
@@ -0,0 +1,120 @@
+/* *********************************************************************
+ * This Original Work is copyright of 51 Degrees Mobile Experts Limited.
+ * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
+ * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
+ *
+ * This Original Work is licensed under the European Union Public Licence
+ * (EUPL) v.1.2 and is subject to its terms as set out below.
+ *
+ * If a copy of the EUPL was not distributed with this file, You can obtain
+ * one at https://opensource.org/licenses/EUPL-1.2.
+ *
+ * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
+ * amended by the European Commission) shall be deemed incompatible for
+ * the purposes of the Work and the provisions of the compatibility
+ * clause in Article 5 of the EUPL shall not apply.
+ *
+ * If using the Work as, or as part of, a network application, by
+ * including the attribution notice(s) required under Article 5 of the EUPL
+ * in the end user terms of the application under an appropriate heading,
+ * such notice(s) shall fulfill the requirements of that article.
+ * ********************************************************************* */
+
+package fiftyone.ipintelligence.examples.shared;
+
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.regex.Pattern;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class FindOutMoreTest {
+
+ /**
+ * The campaign tags common-ci's scripts/utm-lint.ps1 requires of a link
+ * printed by an example.
+ */
+ private static final Pattern TAGGED = Pattern.compile(
+ "https://51degrees\\.com/[^?\\s]+\\?utm_source=code" +
+ "&utm_medium=example&utm_campaign=51degrees-java" +
+ "&utm_content=[a-z0-9][a-z0-9._-]*" +
+ "&utm_term=[a-z0-9][a-z0-9._-]*");
+
+ private static String[] write(String example, String... repositories) {
+ StringWriter text = new StringWriter();
+ FindOutMore.write(new PrintWriter(text), example, repositories);
+ return text.toString().split("\r?\n");
+ }
+
+ /**
+ * The section is a blank line, a rule, the heading, the three tagged
+ * pages, the repositories and a closing rule.
+ */
+ @Test
+ public void write_LinksThenRepositories() {
+ String[] lines = write("console-example.java",
+ FindOutMore.ON_PREMISE_REPOSITORIES);
+ assertEquals(10, lines.length);
+ assertEquals("", lines[0]);
+ assertEquals(FindOutMore.RULE, lines[1]);
+ assertEquals(FindOutMore.HEADING, lines[2]);
+ assertTrue(lines[3].startsWith("\tIP Intelligence: https://51degrees.com/ip-intelligence?"));
+ assertTrue(lines[4].startsWith("\tPricing: https://51degrees.com/pricing?"));
+ assertTrue(lines[5].startsWith("\tDocumentation: https://51degrees.com/documentation/index.html?"));
+ for (int i = 3; i <= 5; i++) {
+ String link = lines[i].substring(lines[i].indexOf("https://"));
+ assertTrue(link, TAGGED.matcher(link).matches());
+ assertTrue(link, link.contains("&utm_content=console-example.java&"));
+ }
+ assertEquals("\thttps://github.com/51Degrees/51degrees-java", lines[6]);
+ assertEquals("\thttps://github.com/51Degrees/51degrees-cxx", lines[7]);
+ assertEquals("\thttps://github.com/51Degrees/data", lines[8]);
+ assertEquals(FindOutMore.RULE, lines[9]);
+ }
+
+ /**
+ * A cloud example lists this repository alone.
+ */
+ @Test
+ public void write_Cloud_ThisRepository() {
+ String[] lines = write("console-example.java",
+ FindOutMore.CLOUD_REPOSITORIES);
+ assertEquals(8, lines.length);
+ assertEquals("\thttps://github.com/51Degrees/51degrees-java", lines[6]);
+ }
+
+ /**
+ * Each line of the commented form starts with the comment marker, and
+ * there is no blank line, so a YAML stream stays valid.
+ */
+ @Test
+ public void writeCommented_EveryLineAComment() {
+ StringWriter text = new StringWriter();
+ FindOutMore.writeCommented(new PrintWriter(text), "# ",
+ "console-example.java", FindOutMore.CLOUD_REPOSITORIES);
+ String[] lines = text.toString().split("\\r?\\n");
+ assertEquals(7, lines.length);
+ for (String line : lines) {
+ assertTrue(line, line.startsWith("# "));
+ }
+ assertEquals("# " + FindOutMore.HEADING, lines[1]);
+ }
+
+ /**
+ * A tag that is not a lower case slug would fail the campaign tag
+ * convention, so it is refused.
+ */
+ @Test
+ public void link_NotASlug_Refused() {
+ assertThrows(IllegalArgumentException.class,
+ () -> FindOutMore.link("pricing", "Console-Example.java"));
+ assertThrows(IllegalArgumentException.class,
+ () -> FindOutMore.link("pricing", "console example"));
+ assertThrows(IllegalArgumentException.class,
+ () -> FindOutMore.link("pricing", null));
+ }
+}
From e35c35d1855461bfcf951744151d927e608add32 Mon Sep 17 00:00:00 2001
From: James Rosewell
Date: Sat, 14 Nov 2026 00:17:01 +0000
Subject: [PATCH 2/8] End every console example with a Find out more section
Each Device Detection and IP Intelligence console example now ends its
output with the section, after the link to the contact page where it gives
one, including the examples that stop early on a free data file. The
section lists the product, pricing and documentation pages, tagged with the
example, and the repositories behind it. OfflineProcessing writes it as
YAML comments so its output stays a valid YAML stream, and the examples
whose writer closes standard output write it before that.
---
.../examples/console/GettingStartedCloud.java | 9 +++++++++
.../examples/console/GettingStartedOnPrem.java | 9 +++++++++
.../examples/console/MatchMetrics.java | 9 +++++++++
.../examples/console/MetadataCloud.java | 9 +++++++++
.../examples/console/MetadataOnPrem.java | 9 +++++++++
.../examples/console/MinimalExample.java | 9 +++++++++
.../examples/console/NativeModelCloud.java | 11 +++++++++++
.../examples/console/OfflineProcessing.java | 11 +++++++++++
.../examples/console/PerformanceBenchmark.java | 9 +++++++++
.../examples/console/ShareUsageExample.java | 9 +++++++++
.../devicedetection/examples/console/TacCloud.java | 11 +++++++++++
.../examples/console/UpdateDataFile.java | 9 +++++++++
.../examples/console/comparison/Comparer.java | 9 +++++++++
.../examples/console/CompareOnPrem.java | 11 +++++++++++
.../examples/console/GetAllPropertiesCloud.java | 9 +++++++++
.../examples/console/GettingStartedCloud.java | 9 +++++++++
.../examples/console/GettingStartedCloudMixed.java | 9 +++++++++
.../examples/console/GettingStartedMixed.java | 9 +++++++++
.../examples/console/GettingStartedOnPrem.java | 9 +++++++++
.../examples/console/MetadataCloud.java | 9 +++++++++
.../examples/console/MetadataOnPrem.java | 9 +++++++++
.../examples/console/MetricsOnPrem.java | 11 +++++++++++
.../examples/console/OfflineProcessing.java | 11 +++++++++++
.../examples/console/PerformanceBenchmark.java | 9 +++++++++
.../examples/console/SuspiciousOnPrem.java | 9 +++++++++
.../examples/console/UpdateDataFile.java | 9 +++++++++
26 files changed, 246 insertions(+)
diff --git a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/GettingStartedCloud.java b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/GettingStartedCloud.java
index 846e1fcb4..533763807 100644
--- a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/GettingStartedCloud.java
+++ b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/GettingStartedCloud.java
@@ -22,6 +22,7 @@
package fiftyone.devicedetection.examples.console;
+import fiftyone.devicedetection.examples.shared.FindOutMore;
import fiftyone.devicedetection.DeviceDetectionPipelineBuilder;
import fiftyone.devicedetection.examples.shared.KeyHelper;
import fiftyone.devicedetection.examples.shared.EvidenceHelper;
@@ -53,6 +54,12 @@
*/
@SuppressWarnings("DuplicatedCode")
public class GettingStartedCloud {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-devicedetection-examples-console-gettingstartedcloud.java";
+
private static final Logger logger = LoggerFactory.getLogger(GettingStartedCloud.class);
/**
@@ -66,6 +73,8 @@ public static void main(String[] args) throws Exception {
// prepare 'evidence' for use in pipeline (see below)
List
*/
public class ShareUsageExample {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-devicedetection-examples-console-shareusageexample.java";
+
static final Logger logger = LoggerFactory.getLogger(ShareUsageExample.class);
// Evidence file with 20,000 examples, from the data repository in
@@ -104,6 +111,8 @@ public static void main(String[] args) throws Exception {
File evidenceFile = DataFileHelper.findDataFile(HEADER_EVIDENCE_YML);
run(Files.newInputStream(evidenceFile.toPath()));
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.CLOUD_REPOSITORIES);
}
/**
diff --git a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/TacCloud.java b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/TacCloud.java
index ee5ee1e0b..c5a87bc5a 100644
--- a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/TacCloud.java
+++ b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/TacCloud.java
@@ -22,6 +22,7 @@
package fiftyone.devicedetection.examples.console;
+import fiftyone.devicedetection.examples.shared.FindOutMore;
import fiftyone.devicedetection.cloud.data.MultiDeviceDataCloud;
import fiftyone.devicedetection.shared.Constants;
import fiftyone.devicedetection.shared.DeviceData;
@@ -56,6 +57,12 @@
* file tacCloud.xml.
*/
public class TacCloud {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-devicedetection-examples-console-taccloud.java";
+
// Example values to use when looking up device details from TACs.
static final String TAC_1 = "35925406";
static final String TAC_2 = "86386802";
@@ -100,6 +107,10 @@ public static void run(String resourceKey, OutputStream os) throws Exception {
analyseTac(TAC_1, pipeline, writer);
analyseTac(TAC_2, pipeline, writer);
}
+ // The writer closes the stream it was given, so the section is
+ // written before it.
+ FindOutMore.write(writer, EXAMPLE_TAG,
+ FindOutMore.CLOUD_REPOSITORIES);
}
}
diff --git a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/UpdateDataFile.java b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/UpdateDataFile.java
index ebffdef86..eae7ca9fd 100644
--- a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/UpdateDataFile.java
+++ b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/UpdateDataFile.java
@@ -22,6 +22,7 @@
package fiftyone.devicedetection.examples.console;
+import fiftyone.devicedetection.examples.shared.FindOutMore;
import fiftyone.devicedetection.DeviceDetectionPipelineBuilder;
import fiftyone.devicedetection.examples.shared.DataFileHelper;
import fiftyone.devicedetection.shared.testhelpers.KeyUtils;
@@ -189,6 +190,12 @@
*/
public class UpdateDataFile {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-devicedetection-examples-console-updatedatafile.java";
+
public static final String UPDATE_EXAMPLE_LICENSE_KEY_NAME = "LicenseKey";
public static final String DEFAULT_DATA_FILENAME =
System.getProperty("user.dir") + File.separator + ENTERPRISE_HASH_DATA_FILE_NAME;
@@ -209,6 +216,8 @@ public static void main(String[] args) throws Exception {
String licenseKey = args.length > 0 ? args[0] : null;
String dataFilename = args.length > 1 ? args[1] : null;
run(dataFilename, licenseKey, true);
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
}
/**
diff --git a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/comparison/Comparer.java b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/comparison/Comparer.java
index ba231800a..19b60ceba 100644
--- a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/comparison/Comparer.java
+++ b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/comparison/Comparer.java
@@ -22,6 +22,7 @@
package fiftyone.devicedetection.examples.console.comparison;
+import fiftyone.devicedetection.examples.shared.FindOutMore;
import fiftyone.common.testhelpers.LogbackHelper;
import fiftyone.devicedetection.examples.console.comparison.Detection.BenchmarkResult;
import fiftyone.devicedetection.examples.console.comparison.Detection.Request;
@@ -50,6 +51,12 @@
* code has not been tested and it is left to those interested to uncomment and get working.
*/
public class Comparer {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-devicedetection-examples-console-comparison-comparer.java";
+
public static final int DEFAULT_NUMBER_OF_THREADS = 4;
public static final int DEFAULT_NUMBER_OF_RESULTS = 2500;
@SuppressWarnings("resource")
@@ -69,6 +76,8 @@ public static void main(String[] args) throws Exception {
DEFAULT_NUMBER_OF_RESULTS,
new Reporting.Minimal(),
new PrintWriter(System.out, true));
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
}
/**
diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/CompareOnPrem.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/CompareOnPrem.java
index 0fc3c7bbf..0c3e98d68 100644
--- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/CompareOnPrem.java
+++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/CompareOnPrem.java
@@ -72,6 +72,7 @@
package fiftyone.ipintelligence.examples.console;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.IPIntelligenceOnPremisePipelineBuilder;
import fiftyone.ipintelligence.IPIntelligencePipelineBuilder;
import fiftyone.ipintelligence.engine.onpremise.flowelements.IPIntelligenceOnPremiseEngine;
@@ -115,6 +116,12 @@
* location is contained in the area returned are written to an output CSV.
*/
public class CompareOnPrem {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-ipintelligence-examples-console-compareonprem.java";
+
private static final Logger logger = LoggerFactory.getLogger(CompareOnPrem.class);
/**
@@ -216,6 +223,8 @@ args[3], new PrintWriter(System.out)) :
LOCATION_PROPERTIES,
new PrintWriter(System.out),
CONTACT_US)) {
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
return;
}
@@ -223,6 +232,8 @@ args[3], new PrintWriter(System.out)) :
run(dataFile, csvTruthFile, profile, writer);
}
logger.info("Output written to '{}'", outputFile);
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
}
/**
diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GetAllPropertiesCloud.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GetAllPropertiesCloud.java
index 3a55152aa..9d66ab330 100644
--- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GetAllPropertiesCloud.java
+++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GetAllPropertiesCloud.java
@@ -46,6 +46,7 @@
package fiftyone.ipintelligence.examples.console;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.IPIntelligenceCloudPipelineBuilder;
import fiftyone.ipintelligence.IPIntelligencePipelineBuilder;
import fiftyone.ipintelligence.examples.shared.KeyHelper;
@@ -76,6 +77,12 @@
* Cloud service.
*/
public class GetAllPropertiesCloud {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-ipintelligence-examples-console-getallpropertiescloud.java";
+
private static final Logger logger = LoggerFactory.getLogger(GetAllPropertiesCloud.class);
/**
@@ -95,6 +102,8 @@ public static void main(String[] args) throws Exception {
KeyUtils.getNamedKey(GettingStartedCloud.CLOUD_END_POINT_NAME);
run(resourceKey, endPoint, System.out);
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.CLOUD_REPOSITORIES);
}
/**
diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedCloud.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedCloud.java
index 684990a91..c9c8d475c 100644
--- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedCloud.java
+++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedCloud.java
@@ -46,6 +46,7 @@
package fiftyone.ipintelligence.examples.console;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.IPIntelligenceCloudPipelineBuilder;
import fiftyone.ipintelligence.IPIntelligencePipelineBuilder;
import fiftyone.ipintelligence.examples.shared.EvidenceHelper;
@@ -85,6 +86,12 @@
* The concepts of "pipeline", "flow data", "evidence" and "results" are illustrated.
*/
public class GettingStartedCloud {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-ipintelligence-examples-console-gettingstartedcloud.java";
+
private static final Logger logger = LoggerFactory.getLogger(GettingStartedCloud.class);
/**
@@ -107,6 +114,8 @@ public static void main(String[] args) throws Exception {
// prepare 'evidence' for use in pipeline (see below)
List> evidence = EvidenceHelper.setUpEvidence();
run(resourceKey, endPoint, evidence, System.out);
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.CLOUD_REPOSITORIES);
}
/**
diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedCloudMixed.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedCloudMixed.java
index efca03fdc..11628ca1c 100644
--- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedCloudMixed.java
+++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedCloudMixed.java
@@ -47,6 +47,7 @@
package fiftyone.ipintelligence.examples.console;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.devicedetection.cloud.flowelements.DeviceDetectionCloudEngine;
import fiftyone.devicedetection.cloud.flowelements.DeviceDetectionCloudEngineBuilder;
import fiftyone.devicedetection.shared.DeviceData;
@@ -90,6 +91,12 @@
* Cloud service.
*/
public class GettingStartedCloudMixed {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-ipintelligence-examples-console-gettingstartedcloudmixed.java";
+
private static final Logger logger = LoggerFactory.getLogger(GettingStartedCloudMixed.class);
public static void main(String[] args) throws Exception {
@@ -107,6 +114,8 @@ public static void main(String[] args) throws Exception {
// includes both a User-Agent header and an IP address.
List> evidence = GettingStartedMixed.setUpCombinedEvidence();
run(resourceKey, endPoint, evidence, System.out);
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.CLOUD_REPOSITORIES);
}
/**
diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedMixed.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedMixed.java
index f007e2fb3..771fde5e5 100644
--- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedMixed.java
+++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedMixed.java
@@ -47,6 +47,7 @@
package fiftyone.ipintelligence.examples.console;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.devicedetection.hash.engine.onpremise.flowelements.DeviceDetectionHashEngine;
import fiftyone.devicedetection.shared.DeviceData;
import fiftyone.ipintelligence.engine.onpremise.flowelements.IPIntelligenceOnPremiseEngine;
@@ -85,6 +86,12 @@
* The configuration file is src/main/resources/gettingStartedMixed.xml.
*/
public class GettingStartedMixed {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-ipintelligence-examples-console-gettingstartedmixed.java";
+
private static final Logger logger = LoggerFactory.getLogger(GettingStartedMixed.class);
public static final String DD_DATA_FILE_DEFAULT = "device-detection/51Degrees-LiteV4.1.hash";
@@ -105,6 +112,8 @@ public static void main(String[] args) throws Exception {
List> evidence = setUpCombinedEvidence();
run(ddDataFile, ipiDataFile, evidence, System.out);
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
}
/**
diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedOnPrem.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedOnPrem.java
index e25f3e109..ca39cc6fe 100644
--- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedOnPrem.java
+++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/GettingStartedOnPrem.java
@@ -44,6 +44,7 @@
package fiftyone.ipintelligence.examples.console;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.engine.onpremise.interop.swig.WeightedStringListValueSwig;
import fiftyone.ipintelligence.examples.shared.DataFileHelper;
import fiftyone.ipintelligence.examples.shared.EvidenceHelper;
@@ -82,6 +83,12 @@
* The concepts of "pipeline", "flow data", "evidence" and "results" are illustrated.
*/
public class GettingStartedOnPrem {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-ipintelligence-examples-console-gettingstartedonprem.java";
+
private static final Logger logger = LoggerFactory.getLogger(GettingStartedOnPrem.class);
/* In this example, by default, the free 51degrees Lite IP Intelligence data file is used from
@@ -104,6 +111,8 @@ public static void main(String[] args) throws Exception {
// prepare 'evidence' for use in pipeline (see below)
List> evidence = EvidenceHelper.setUpEvidence();
run(dataFile, evidence, System.out);
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
}
/**
diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetadataCloud.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetadataCloud.java
index 00da31c62..1af46c383 100644
--- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetadataCloud.java
+++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetadataCloud.java
@@ -54,6 +54,7 @@
package fiftyone.ipintelligence.examples.console;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.IPIntelligenceCloudPipelineBuilder;
import fiftyone.ipintelligence.IPIntelligencePipelineBuilder;
import fiftyone.ipintelligence.cloud.flowelements.IPIntelligenceCloudEngine;
@@ -85,6 +86,12 @@
* Cloud service.
*/
public class MetadataCloud {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-ipintelligence-examples-console-metadatacloud.java";
+
private static final Logger logger = LoggerFactory.getLogger(MetadataCloud.class);
public static void main(String[] args) throws Exception {
@@ -99,6 +106,8 @@ public static void main(String[] args) throws Exception {
KeyUtils.getNamedKey(GettingStartedCloud.CLOUD_END_POINT_NAME);
run(resourceKey, endPoint, System.out);
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.CLOUD_REPOSITORIES);
}
/**
diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetadataOnPrem.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetadataOnPrem.java
index 223121f1d..2619ca2cf 100644
--- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetadataOnPrem.java
+++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetadataOnPrem.java
@@ -21,6 +21,7 @@
* ********************************************************************* */
package fiftyone.ipintelligence.examples.console;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.pipeline.engines.fiftyone.data.FiftyOneAspectPropertyMetaData;
import fiftyone.ipintelligence.examples.shared.DataFileHelper;
import fiftyone.ipintelligence.engine.onpremise.flowelements.IPIntelligenceOnPremiseEngine;
@@ -61,6 +62,12 @@
* [contact us](https://51degrees.com/contact-us?utm_source=code&utm_medium=example&utm_campaign=51degrees-java&utm_content=console-src-main-java-fiftyone-ipintelligence-examples-console-metadataonprem.java&utm_term=header).
*/
public class MetadataOnPrem {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-ipintelligence-examples-console-metadataonprem.java";
+
private static final Logger logger = LoggerFactory.getLogger(GettingStartedOnPrem.class);
/* In this example, by default, the free 51degrees Lite IP Intelligence data file is used from
@@ -79,6 +86,8 @@ public static void main(String[] args) throws Exception {
configureLogback(getFilePath("logback.xml"));
String dataFile = args.length > 0 ? args[0] : LITE_DATA_FILE_REL_PATH;
run(dataFile, System.out);
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
}
diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetricsOnPrem.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetricsOnPrem.java
index 4730245ec..d287f69f9 100644
--- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetricsOnPrem.java
+++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/MetricsOnPrem.java
@@ -54,6 +54,7 @@
package fiftyone.ipintelligence.examples.console;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.IPIntelligencePipelineBuilder;
import fiftyone.ipintelligence.engine.onpremise.flowelements.IPIntelligenceOnPremiseEngine;
import fiftyone.ipintelligence.examples.console.areas.Calculations;
@@ -105,6 +106,12 @@
* are output in CSV format.
*/
public class MetricsOnPrem {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-ipintelligence-examples-console-metricsonprem.java";
+
private static final Logger logger = LoggerFactory.getLogger(MetricsOnPrem.class);
/**
@@ -334,6 +341,8 @@ args[3], new PrintWriter(System.out)) :
Arrays.asList(PROPERTIES),
new PrintWriter(System.out),
CONTACT_US)) {
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
return;
}
@@ -345,6 +354,8 @@ args[3], new PrintWriter(System.out)) :
run(dataFile, samplePercentage, 0, condition, profile, writer);
}
logger.info("Output written to '{}'", outputFile);
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
}
/**
diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/OfflineProcessing.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/OfflineProcessing.java
index 0357b64d8..87c67d177 100644
--- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/OfflineProcessing.java
+++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/OfflineProcessing.java
@@ -54,6 +54,7 @@
package fiftyone.ipintelligence.examples.console;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.IPIntelligencePipelineBuilder;
import fiftyone.ipintelligence.examples.shared.DataFileHelper;
import fiftyone.ipintelligence.engine.onpremise.flowelements.IPIntelligenceOnPremiseEngine;
@@ -96,6 +97,12 @@
* using Performance Profile settings.
*/
public class OfflineProcessing {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-ipintelligence-examples-console-offlineprocessing.java";
+
static final Logger logger = LoggerFactory.getLogger(OfflineProcessing.class);
// By default the free 51degrees Lite IP Intelligence data file is used from
// the data repository in cxx/data, and is downloaded if it is missing.
@@ -269,6 +276,10 @@ public static void run(String dataFile, InputStream is, OutputStream os) throws
}
// finish the last YAML document
writer.write("...\n");
+ // The section is written as comments, so the output stays a
+ // valid YAML stream.
+ FindOutMore.writeCommented(new PrintWriter(writer), "# ",
+ EXAMPLE_TAG, FindOutMore.ON_PREMISE_REPOSITORIES);
writer.flush();
logger.info("Finished processing {} records", count);
}
diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/PerformanceBenchmark.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/PerformanceBenchmark.java
index d3c03c4e2..6a535f868 100644
--- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/PerformanceBenchmark.java
+++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/PerformanceBenchmark.java
@@ -46,6 +46,7 @@
package fiftyone.ipintelligence.examples.console;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.common.testhelpers.LogbackHelper;
import fiftyone.ipintelligence.IPIntelligenceOnPremisePipelineBuilder;
import fiftyone.ipintelligence.IPIntelligencePipelineBuilder;
@@ -81,6 +82,12 @@
*
*/
public class PerformanceBenchmark {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-ipintelligence-examples-console-performancebenchmark.java";
+
// the default number of threads if one is not provided.
public static final int DEFAULT_NUMBER_OF_THREADS = 4;
// the number of tests to execute.
@@ -132,6 +139,8 @@ public static void main(String[] args) throws Exception {
evidenceFilename,
numberOfThreads,
new PrintWriter(System.out,true));
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
}
/**
diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/SuspiciousOnPrem.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/SuspiciousOnPrem.java
index 8f0e5e8f2..5ad8a3865 100644
--- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/SuspiciousOnPrem.java
+++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/SuspiciousOnPrem.java
@@ -45,6 +45,7 @@
package fiftyone.ipintelligence.examples.console;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.IPIntelligencePipelineBuilder;
import fiftyone.ipintelligence.engine.onpremise.flowelements.IPIntelligenceOnPremiseEngine;
import fiftyone.ipintelligence.examples.shared.DataFileHelper;
@@ -80,6 +81,12 @@
* in production without further testing and tuning.
*/
public class SuspiciousOnPrem {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-ipintelligence-examples-console-suspiciousonprem.java";
+
private static final Logger logger = LoggerFactory.getLogger(SuspiciousOnPrem.class);
/* In this example, by default, the free 51degrees Lite IP Intelligence data file is used from
@@ -116,6 +123,8 @@ public static void main(String[] args) throws Exception {
// prepare 'evidence' for use in pipeline (see below)
List> evidence = EvidenceHelper.setUpEvidence();
run(dataFile, evidence, System.out);
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
}
/**
diff --git a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/UpdateDataFile.java b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/UpdateDataFile.java
index a57a9b3e0..a396c73c9 100644
--- a/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/UpdateDataFile.java
+++ b/examples/ip-intelligence/console/src/main/java/fiftyone/ipintelligence/examples/console/UpdateDataFile.java
@@ -22,6 +22,7 @@
package fiftyone.ipintelligence.examples.console;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.IPIntelligencePipelineBuilder;
import fiftyone.ipintelligence.examples.shared.DataFileHelper;
import fiftyone.ipintelligence.shared.testhelpers.KeyUtils;
@@ -178,6 +179,12 @@
*/
public class UpdateDataFile {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-ipintelligence-examples-console-updatedatafile.java";
+
public static final String UPDATE_EXAMPLE_LICENSE_KEY_NAME = "LicenseKey";
public static final String DEFAULT_DATA_FILENAME =
System.getProperty("user.dir") + File.separator + ENTERPRISE_DATA_FILE_REL_PATH;
@@ -198,6 +205,8 @@ public static void main(String[] args) throws Exception {
String licenseKey = args.length > 0 ? args[0] : null;
String dataFilename = args.length > 1 ? args[1] : null;
run(dataFilename, licenseKey, true);
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
}
/**
From 2366f65edf4d11fb47d54927caecbe728d0cce43 Mon Sep 17 00:00:00 2001
From: James Rosewell
Date: Sat, 14 Nov 2026 00:22:28 +0000
Subject: [PATCH 3/8] Test that every web example's page ends with a Find out
more section
Adds the HTML form of the section to FindOutMore, with its own test, and
checks the page of each Device Detection and IP Intelligence web example
that serves HTML.
---
.../examples/shared/FindOutMore.java | 24 +++++++++++++++
.../examples/shared/FindOutMoreTest.java | 29 +++++++++++++++++++
.../web/GettingStartedWebCloudTest.java | 4 +++
.../web/GettingStartedWebOnPremTest.java | 14 +++++++--
.../examples/shared/FindOutMore.java | 24 +++++++++++++++
.../examples/shared/FindOutMoreTest.java | 29 +++++++++++++++++++
.../web/GettingStartedWebCloudMixedTest.java | 4 +++
.../web/GettingStartedWebCloudTest.java | 4 +++
.../web/GettingStartedWebMixedTest.java | 4 +++
.../web/GettingStartedWebOnPremTest.java | 4 +++
10 files changed, 138 insertions(+), 2 deletions(-)
diff --git a/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/FindOutMore.java b/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/FindOutMore.java
index 7d6bda5a5..2b8f918f3 100644
--- a/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/FindOutMore.java
+++ b/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/FindOutMore.java
@@ -141,6 +141,30 @@ public static void write(
write(new PrintWriter(output), example, repositories);
}
+ /**
+ * The section as HTML, for the end of a web example's page.
+ * @param example the example's tag
+ * @param repositories the repositories to list
+ * @return the markup
+ */
+ public static String html(String example, String... repositories) {
+ StringBuilder html = new StringBuilder();
+ html.append("\n");
+ html.append("
").append(HEADING).append("
\n");
+ html.append("
\n
\n");
+ return html.toString();
+ }
+
/**
* The address of a page on 51degrees.com, tagged with the example.
* @param page the path of the page
diff --git a/examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/FindOutMoreTest.java b/examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/FindOutMoreTest.java
index 2b3aef4ab..8ff97350a 100644
--- a/examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/FindOutMoreTest.java
+++ b/examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/FindOutMoreTest.java
@@ -104,6 +104,35 @@ public void writeCommented_EveryLineAComment() {
assertEquals("# " + FindOutMore.HEADING, lines[1]);
}
+ /**
+ * The HTML form has the heading, each tagged page as a link with its
+ * ampersands escaped, and each repository as a link.
+ */
+ @Test
+ public void html_LinksThenRepositories() {
+ String html = FindOutMore.html("web-example.java",
+ FindOutMore.ON_PREMISE_REPOSITORIES);
+ assertTrue(html, html.contains("" + FindOutMore.HEADING + "
"));
+ java.util.regex.Matcher href =
+ Pattern.compile("href=\"([^\"]+)\"").matcher(html);
+ int pages = 0;
+ int repositories = 0;
+ while (href.find()) {
+ String link = href.group(1);
+ if (link.startsWith("https://51degrees.com/")) {
+ assertTrue(link, link.contains("&"));
+ assertTrue(link,
+ TAGGED.matcher(link.replace("&", "&")).matches());
+ pages++;
+ } else {
+ assertTrue(link, link.startsWith("https://github.com/51Degrees/"));
+ repositories++;
+ }
+ }
+ assertEquals(3, pages);
+ assertEquals(3, repositories);
+ }
+
/**
* A tag that is not a lower case slug would fail the campaign tag
* convention, so it is refused.
diff --git a/examples/device-detection/web/getting-started.cloud/src/test/java/fiftyone/devicedetection/web/GettingStartedWebCloudTest.java b/examples/device-detection/web/getting-started.cloud/src/test/java/fiftyone/devicedetection/web/GettingStartedWebCloudTest.java
index 11fa1f57a..1ad8ddc50 100644
--- a/examples/device-detection/web/getting-started.cloud/src/test/java/fiftyone/devicedetection/web/GettingStartedWebCloudTest.java
+++ b/examples/device-detection/web/getting-started.cloud/src/test/java/fiftyone/devicedetection/web/GettingStartedWebCloudTest.java
@@ -22,6 +22,7 @@
package fiftyone.devicedetection.web;
+import fiftyone.devicedetection.examples.shared.FindOutMore;
import fiftyone.devicedetection.examples.shared.KeyHelper;
import fiftyone.devicedetection.examples.web.EmbedJetty;
import org.eclipse.jetty.server.Server;
@@ -90,6 +91,9 @@ public void testWebCloud() throws Exception {
body.contains("cloud device detection engine"));
assertDetected(body, "Browser Name", "BrowserName", "Chrome");
assertDetected(body, "Platform Name", "PlatformName", "Windows");
+ assertTrue("The page should end with the '" + FindOutMore.HEADING +
+ "' section. " + body, body.contains(
+ "" + FindOutMore.HEADING + "
"));
}
/**
diff --git a/examples/device-detection/web/getting-started.onprem/src/test/java/fiftyone/devicedetection/examples/web/GettingStartedWebOnPremTest.java b/examples/device-detection/web/getting-started.onprem/src/test/java/fiftyone/devicedetection/examples/web/GettingStartedWebOnPremTest.java
index d6a94896f..967773a14 100644
--- a/examples/device-detection/web/getting-started.onprem/src/test/java/fiftyone/devicedetection/examples/web/GettingStartedWebOnPremTest.java
+++ b/examples/device-detection/web/getting-started.onprem/src/test/java/fiftyone/devicedetection/examples/web/GettingStartedWebOnPremTest.java
@@ -22,6 +22,8 @@
package fiftyone.devicedetection.examples.web;
+import fiftyone.devicedetection.examples.shared.DataFileHelper;
+import fiftyone.devicedetection.examples.shared.FindOutMore;
import fiftyone.devicedetection.shared.testhelpers.FileUtils;
import org.eclipse.jetty.server.Server;
import org.junit.AfterClass;
@@ -36,14 +38,18 @@
import static fiftyone.devicedetection.examples.web.GettingStartedWebOnPrem.resourceBase;
import static fiftyone.pipeline.util.FileFinder.getFilePath;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
public class GettingStartedWebOnPremTest {
private static Server SERVER;
@BeforeClass
public static void startJetty() throws Exception {
+ // With no Enterprise file, the free Lite file is found, or
+ // downloaded, as the example finds it, so a fresh clone runs this.
System.setProperty("TestDataFile", FileUtils.getHashFile() == null
- ? FileUtils.LITE_HASH_DATA_FILE_PATH
+ ? DataFileHelper.findDataFile(
+ FileUtils.LITE_HASH_DATA_FILE_PATH).getAbsolutePath()
: FileUtils.getHashFile().getAbsolutePath());
SERVER = EmbedJetty.startWebApp(getFilePath(resourceBase).getAbsolutePath(), 8081);
}
@@ -55,13 +61,17 @@ public void testWebOnPrem() throws Exception {
(HttpURLConnection) new URL("http://localhost:8081/").openConnection();
InputStream response = connection.getInputStream();
+ String responseBody;
try (Scanner scanner = new Scanner(response)) {
- String responseBody = scanner.useDelimiter("\\A").next();
+ responseBody = scanner.useDelimiter("\\A").next();
System.out.println(responseBody);
}
int code = connection.getResponseCode();
connection.disconnect();
assertEquals(200, code);
+ assertTrue("The page should end with the '" + FindOutMore.HEADING +
+ "' section", responseBody.contains(
+ "" + FindOutMore.HEADING + "
"));
}
@AfterClass
diff --git a/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/FindOutMore.java b/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/FindOutMore.java
index 2647e2c35..b9354f6a8 100644
--- a/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/FindOutMore.java
+++ b/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/FindOutMore.java
@@ -141,6 +141,30 @@ public static void write(
write(new PrintWriter(output), example, repositories);
}
+ /**
+ * The section as HTML, for the end of a web example's page.
+ * @param example the example's tag
+ * @param repositories the repositories to list
+ * @return the markup
+ */
+ public static String html(String example, String... repositories) {
+ StringBuilder html = new StringBuilder();
+ html.append("\n");
+ html.append("
").append(HEADING).append("
\n");
+ html.append("
\n
\n");
+ return html.toString();
+ }
+
/**
* The address of a page on 51degrees.com, tagged with the example.
* @param page the path of the page
diff --git a/examples/ip-intelligence/shared/src/test/java/fiftyone/ipintelligence/examples/shared/FindOutMoreTest.java b/examples/ip-intelligence/shared/src/test/java/fiftyone/ipintelligence/examples/shared/FindOutMoreTest.java
index 5433e1265..bb1d7843f 100644
--- a/examples/ip-intelligence/shared/src/test/java/fiftyone/ipintelligence/examples/shared/FindOutMoreTest.java
+++ b/examples/ip-intelligence/shared/src/test/java/fiftyone/ipintelligence/examples/shared/FindOutMoreTest.java
@@ -104,6 +104,35 @@ public void writeCommented_EveryLineAComment() {
assertEquals("# " + FindOutMore.HEADING, lines[1]);
}
+ /**
+ * The HTML form has the heading, each tagged page as a link with its
+ * ampersands escaped, and each repository as a link.
+ */
+ @Test
+ public void html_LinksThenRepositories() {
+ String html = FindOutMore.html("web-example.java",
+ FindOutMore.ON_PREMISE_REPOSITORIES);
+ assertTrue(html, html.contains("" + FindOutMore.HEADING + "
"));
+ java.util.regex.Matcher href =
+ Pattern.compile("href=\"([^\"]+)\"").matcher(html);
+ int pages = 0;
+ int repositories = 0;
+ while (href.find()) {
+ String link = href.group(1);
+ if (link.startsWith("https://51degrees.com/")) {
+ assertTrue(link, link.contains("&"));
+ assertTrue(link,
+ TAGGED.matcher(link.replace("&", "&")).matches());
+ pages++;
+ } else {
+ assertTrue(link, link.startsWith("https://github.com/51Degrees/"));
+ repositories++;
+ }
+ }
+ assertEquals(3, pages);
+ assertEquals(3, repositories);
+ }
+
/**
* A tag that is not a lower case slug would fail the campaign tag
* convention, so it is refused.
diff --git a/examples/ip-intelligence/web/getting-started.cloud.mixed/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudMixedTest.java b/examples/ip-intelligence/web/getting-started.cloud.mixed/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudMixedTest.java
index ff49ca9e7..a5b7af546 100644
--- a/examples/ip-intelligence/web/getting-started.cloud.mixed/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudMixedTest.java
+++ b/examples/ip-intelligence/web/getting-started.cloud.mixed/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudMixedTest.java
@@ -22,6 +22,7 @@
package fiftyone.ipintelligence.examples.web;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.examples.shared.KeyHelper;
import org.eclipse.jetty.server.Server;
import org.junit.AfterClass;
@@ -90,6 +91,9 @@ public void testWebCloudMixed() throws Exception {
assertEquals("Expected HTTP 200 OK response", 200, code);
assertTrue("Response should not be empty", responseBody.length() > 0);
+ assertTrue("The page should end with the '" + FindOutMore.HEADING +
+ "' section", responseBody.contains(
+ "" + FindOutMore.HEADING + "
"));
}
@AfterClass
diff --git a/examples/ip-intelligence/web/getting-started.cloud/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudTest.java b/examples/ip-intelligence/web/getting-started.cloud/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudTest.java
index 06a7b8b48..0eb321002 100644
--- a/examples/ip-intelligence/web/getting-started.cloud/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudTest.java
+++ b/examples/ip-intelligence/web/getting-started.cloud/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudTest.java
@@ -22,6 +22,7 @@
package fiftyone.ipintelligence.examples.web;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.examples.shared.KeyHelper;
import org.eclipse.jetty.server.Server;
import org.junit.AfterClass;
@@ -91,6 +92,9 @@ public void testWebCloud() throws Exception {
// Assert we got a successful response
assertEquals("Expected HTTP 200 OK response", 200, code);
assertTrue("Response should not be empty", responseBody.length() > 0);
+ assertTrue("The page should end with the '" + FindOutMore.HEADING +
+ "' section", responseBody.contains(
+ "" + FindOutMore.HEADING + "
"));
}
@AfterClass
diff --git a/examples/ip-intelligence/web/getting-started.mixed/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebMixedTest.java b/examples/ip-intelligence/web/getting-started.mixed/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebMixedTest.java
index 4aa023bb3..276a418b9 100644
--- a/examples/ip-intelligence/web/getting-started.mixed/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebMixedTest.java
+++ b/examples/ip-intelligence/web/getting-started.mixed/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebMixedTest.java
@@ -22,6 +22,7 @@
package fiftyone.ipintelligence.examples.web;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.examples.shared.DataFileHelper;
import org.eclipse.jetty.server.Server;
import org.junit.AfterClass;
@@ -117,6 +118,9 @@ public void testWebMixed() throws Exception {
assertEquals("Expected HTTP 200 OK response", 200, code);
assertTrue("Response should not be empty", responseBody.length() > 0);
+ assertTrue("The page should end with the '" + FindOutMore.HEADING +
+ "' section", responseBody.contains(
+ "" + FindOutMore.HEADING + "
"));
}
@AfterClass
diff --git a/examples/ip-intelligence/web/getting-started.onprem/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebOnPremTest.java b/examples/ip-intelligence/web/getting-started.onprem/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebOnPremTest.java
index e1831b39b..f995a4ea6 100644
--- a/examples/ip-intelligence/web/getting-started.onprem/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebOnPremTest.java
+++ b/examples/ip-intelligence/web/getting-started.onprem/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebOnPremTest.java
@@ -22,6 +22,7 @@
package fiftyone.ipintelligence.examples.web;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.examples.shared.DataFileHelper;
import org.eclipse.jetty.server.Server;
import org.junit.AfterClass;
@@ -96,6 +97,9 @@ public void testWebOnPrem() throws Exception {
// Assert we got a successful response
assertEquals("Expected HTTP 200 OK response", 200, code);
assertTrue("Response should not be empty", responseBody.length() > 0);
+ assertTrue("The page should end with the '" + FindOutMore.HEADING +
+ "' section", responseBody.contains(
+ "" + FindOutMore.HEADING + "
"));
}
@AfterClass
From b1945dbf724074d2c97e0b2255900b9e05f092f2 Mon Sep 17 00:00:00 2001
From: James Rosewell
Date: Sat, 14 Nov 2026 00:23:36 +0000
Subject: [PATCH 4/8] End every web example's page with a Find out more section
The Device Detection pages add it after the contact-us banner, and the IP
Intelligence on-premise, mixed, cloud and cloud mixed pages through a
placeholder in their templates.
---
.../examples/web/HtmlContentHelper.java | 11 +++++++++++
.../examples/web/GettingStartedWebCloudMixed.java | 9 +++++++++
.../src/main/webapp/WEB-INF/html/index.html | 2 ++
.../examples/web/GettingStartedWebCloud.java | 9 +++++++++
.../src/main/webapp/WEB-INF/html/index.html | 2 ++
.../examples/web/GettingStartedWebMixed.java | 9 +++++++++
.../src/main/webapp/WEB-INF/html/index.html | 1 +
.../examples/web/GettingStartedWebOnPrem.java | 9 +++++++++
.../src/main/webapp/WEB-INF/html/index.html | 1 +
9 files changed, 53 insertions(+)
diff --git a/examples/device-detection/web/shared/src/main/java/fiftyone/devicedetection/examples/web/HtmlContentHelper.java b/examples/device-detection/web/shared/src/main/java/fiftyone/devicedetection/examples/web/HtmlContentHelper.java
index 2df9b35a9..d6184ce2a 100644
--- a/examples/device-detection/web/shared/src/main/java/fiftyone/devicedetection/examples/web/HtmlContentHelper.java
+++ b/examples/device-detection/web/shared/src/main/java/fiftyone/devicedetection/examples/web/HtmlContentHelper.java
@@ -23,6 +23,7 @@
package fiftyone.devicedetection.examples.web;
import fiftyone.devicedetection.examples.shared.DataFileHelper;
+import fiftyone.devicedetection.examples.shared.FindOutMore;
import fiftyone.devicedetection.hash.engine.onpremise.flowelements.DeviceDetectionHashEngine;
import fiftyone.devicedetection.shared.DeviceData;
import fiftyone.pipeline.cloudrequestengine.flowelements.CloudRequestEngine;
@@ -49,6 +50,12 @@
import static fiftyone.pipeline.util.FileFinder.getFilePath;
public class HtmlContentHelper {
+ /**
+ * The tag of the links in the pages' "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "web-shared-src-main-java-fiftyone-devicedetection-examples-web-htmlcontenthelper.java";
+
/**
* Helper to output HTML headers
* @param out the PrintWriter to write to
@@ -306,6 +313,10 @@ private static void doContactUsMessage(PrintWriter out, boolean cloud) {
public static void doHtmlPostamble(PrintWriter out, FlowData flowData) {
// language=html
doContactUsMessage(out, flowData);
+ out.append(FindOutMore.html(EXAMPLE_TAG,
+ getFlowElement(flowData) instanceof CloudRequestEngine ?
+ FindOutMore.CLOUD_REPOSITORIES :
+ FindOutMore.ON_PREMISE_REPOSITORIES));
out.append("\n\n