Browse Source

Merge pull request #464 from tonihele/feature/issue-371

Feature/issue 371
Toni Helenius 2 years ago
parent
commit
e649511112
30 changed files with 1593 additions and 285 deletions
  1. 8 0
      jme3-templates/nbproject/project.xml
  2. 13 9
      jme3-templates/src/com/jme3/gde/templates/files/freemarker/build.gradle.ftl
  3. 3 0
      jme3-templates/src/com/jme3/gde/templates/files/patchnotes/default.html
  4. 13 11
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameAdditionalLibrariesPanelVisual.java
  5. 6 6
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameGuiPanelVisual.form
  6. 16 20
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameGuiPanelVisual.java
  7. 4 4
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameJMEVersionPanelVisual.form
  8. 43 13
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameJMEVersionPanelVisual.java
  9. 12 9
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameWizardIterator.java
  10. 11 2
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameWizardPanel.java
  11. 59 48
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/AdditionalLibrary.java
  12. 336 0
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/CachedOptionsContainer.java
  13. 47 41
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/GUILibrary.java
  14. 31 7
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/JMEVersion.java
  15. 39 32
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/LWJGLLibrary.java
  16. 47 0
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/LibraryVersion.java
  17. 62 0
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/MavenArtifact.java
  18. 49 42
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/NetworkingLibrary.java
  19. 47 41
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/PhysicsLibrary.java
  20. 165 0
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/SemanticPlusTagVersionInfo.java
  21. 60 0
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/TemplateLibrary.java
  22. 76 0
      jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/VersionInfo.java
  23. 140 0
      jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/MavenApiVersionChecker.java
  24. 47 0
      jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/MavenVersionCheckException.java
  25. 46 0
      jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/MavenVersionChecker.java
  26. 38 0
      jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/models/DownloadLink.java
  27. 52 0
      jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/models/SearchDoc.java
  28. 42 0
      jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/models/SearchResponse.java
  29. 41 0
      jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/models/SearchResult.java
  30. 40 0
      jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/models/SearchSpellcheck.java

+ 8 - 0
jme3-templates/nbproject/project.xml

@@ -6,6 +6,14 @@
             <code-name-base>com.jme3.gde.templates</code-name-base>
             <suite-component/>
             <module-dependencies>
+                <dependency>
+                    <code-name-base>com.google.gson</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>2.8.9</specification-version>
+                    </run-dependency>
+                </dependency>
                 <dependency>
                     <code-name-base>com.jme3.gde.project.baselibs</code-name-base>
                     <run-dependency>

+ 13 - 9
jme3-templates/src/com/jme3/gde/templates/files/freemarker/build.gradle.ftl

@@ -38,7 +38,11 @@ dependencies {
   // Core JME
   implementation "org.jmonkeyengine:jme3-core:$jmeVer"
   implementation "org.jmonkeyengine:jme3-desktop:$jmeVer"
-  implementation "${lwjglArtifact}:$jmeVer"
+  <#if lwjglLibrary.isCoreJmeLibrary == true>
+  implementation "${lwjglLibrary.groupId}:${lwjglLibrary.artifactId}:$jmeVer"
+  <#else>
+  implementation "${lwjglLibrary.groupId}:${lwjglLibrary.artifactId}:${lwjglLibrary.versionInfo.versionString}"
+  </#if>
 
   // Suppress errors / warnings building in SDK
   implementation "org.jmonkeyengine:jme3-jogg:$jmeVer"
@@ -47,36 +51,36 @@ dependencies {
   
   // GUI Library
   <#if guiLibrary.isCoreJmeLibrary == true>
-  implementation "${guiLibrary.artifact}:$jmeVer"
+  implementation "${guiLibrary.groupId}:${guiLibrary.artifactId}:$jmeVer"
   <#else>
-  implementation "${guiLibrary.artifact}"
+  implementation "${guiLibrary.groupId}:${guiLibrary.artifactId}:${guiLibrary.versionInfo.versionString}"
   </#if>
   </#if>
   <#if physicsLibrary.label != "">
   
   // Physics Library
   <#if physicsLibrary.isCoreJmeLibrary == true>
-  implementation "${physicsLibrary.artifact}:$jmeVer"
+  implementation "${physicsLibrary.groupId}:${physicsLibrary.artifactId}:$jmeVer"
   <#else>
-  implementation "${physicsLibrary.artifact}"
+  implementation "${physicsLibrary.groupId}:${physicsLibrary.artifactId}:${physicsLibrary.versionInfo.versionString}"
   </#if>
   </#if>
   <#if networkingLibrary.label != "">
   
   // Networking Library
   <#if networkingLibrary.isCoreJmeLibrary == true>
-  implementation "${networkingLibrary.artifact}:$jmeVer"
+  implementation "${networkingLibrary.groupId}:${networkingLibrary.artifactId}:$jmeVer"
   <#else>
-  implementation "${networkingLibrary.artifact}"
+  implementation "${networkingLibrary.groupId}:${networkingLibrary.artifactId}:${networkingLibrary.versionInfo.versionString}"
   </#if>
   </#if>
 
   // Additional Libraries
   <#list additionalLibraries as additionalLibrary>
   <#if additionalLibrary.isCoreJmeLibrary == true>
-  implementation "${additionalLibrary.artifact}:$jmeVer"
+  implementation "${additionalLibrary.groupId}:${additionalLibrary.artifactId}:$jmeVer"
   <#else>
-  implementation "${additionalLibrary.artifact}"
+  implementation "${additionalLibrary.groupId}:${additionalLibrary.artifactId}:${additionalLibrary.versionInfo.versionString}"
   </#if>
   </#list>
 

+ 3 - 0
jme3-templates/src/com/jme3/gde/templates/files/patchnotes/default.html

@@ -0,0 +1,3 @@
+<html>
+    Check jMonkeyEngine <a href="https://github.com/jMonkeyEngine/jmonkeyengine/releases">release history</a>
+</html>

+ 13 - 11
jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameAdditionalLibrariesPanelVisual.java

@@ -32,7 +32,8 @@
 
 package com.jme3.gde.templates.gradledesktop;
 
-import com.jme3.gde.templates.gradledesktop.options.AdditionalLibrary;
+import com.jme3.gde.templates.gradledesktop.options.CachedOptionsContainer;
+import com.jme3.gde.templates.gradledesktop.options.TemplateLibrary;
 import java.awt.Dimension;
 import java.util.ArrayList;
 import java.util.List;
@@ -80,11 +81,13 @@ public class GradleDesktopGameAdditionalLibrariesPanelVisual extends JPanel
     }
 
     private void populateLibraryTable() {
-        int noRows = AdditionalLibrary.values().length;
+        List<TemplateLibrary> libraries = CachedOptionsContainer.getInstance().getAdditionalLibraries();
+
+        int noRows = libraries.size();
         Object[][] tableData = new Object[noRows][2];
 
         int row = 0;
-        for (AdditionalLibrary library : AdditionalLibrary.values()) {
+        for (TemplateLibrary library : libraries) {
             tableData[row][0] = Boolean.FALSE;
             tableData[row][1] = library;
             row++;
@@ -107,8 +110,7 @@ public class GradleDesktopGameAdditionalLibrariesPanelVisual extends JPanel
         if (selectedRow == -1) {
             libraryDescriptionTextArea.setText("");
         } else {
-            AdditionalLibrary selectedLibrary = (AdditionalLibrary)
-                    additionalLibraryTable.getValueAt(selectedRow, 1);
+            TemplateLibrary selectedLibrary = (TemplateLibrary)                    additionalLibraryTable.getValueAt(selectedRow, 1);
             libraryDescriptionTextArea.setText(selectedLibrary
                     .getDescription());
         }
@@ -117,8 +119,8 @@ public class GradleDesktopGameAdditionalLibrariesPanelVisual extends JPanel
     protected void store(WizardDescriptor d) {
         AdditionalLibraryTableModel model = (AdditionalLibraryTableModel)
                 additionalLibraryTable.getModel();
-        List<AdditionalLibrary> selectedLibraries =
-                model.getSelectedLibraries();
+        List<TemplateLibrary> selectedLibraries
+                =                model.getSelectedLibraries();
 
         d.putProperty("additionalLibraries", selectedLibraries);
     }
@@ -202,7 +204,7 @@ public class GradleDesktopGameAdditionalLibrariesPanelVisual extends JPanel
                 case 0:
                     return Boolean.class;
                 case 1:
-                    return String.class;
+                    return TemplateLibrary.class;
                 default:
                     return super.getColumnClass(columnIndex);
             }
@@ -213,11 +215,11 @@ public class GradleDesktopGameAdditionalLibrariesPanelVisual extends JPanel
             return column == 0;
         }
 
-        public List<AdditionalLibrary> getSelectedLibraries() {
-            List<AdditionalLibrary> selectedLibraries = new ArrayList<>();
+        public List<TemplateLibrary> getSelectedLibraries() {
+            List<TemplateLibrary> selectedLibraries = new ArrayList<>();
             for (int i = 0; i < getRowCount(); i++) {
                 if ((Boolean) getValueAt(i, 0)) {
-                    selectedLibraries.add((AdditionalLibrary) getValueAt(i, 1));
+                    selectedLibraries.add((TemplateLibrary) getValueAt(i, 1));
                 }
             }
 

+ 6 - 6
jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameGuiPanelVisual.form

@@ -95,14 +95,14 @@
     <Component class="javax.swing.JComboBox" name="guiComboBox">
       <Properties>
         <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
-          <Connection code="new DefaultComboBoxModel(GUILibrary.values())" type="code"/>
+          <Connection code="new DefaultComboBoxModel&lt;TemplateLibrary&gt;(CachedOptionsContainer.getInstance().getGuiLibraries().toArray(TemplateLibrary[]::new))" type="code"/>
         </Property>
       </Properties>
       <Events>
         <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="guiComboBoxActionPerformed"/>
       </Events>
       <AuxValues>
-        <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
+        <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;TemplateLibrary&gt;"/>
       </AuxValues>
     </Component>
     <Container class="javax.swing.JScrollPane" name="guiDescriptionScrollPane">
@@ -138,14 +138,14 @@
     <Component class="javax.swing.JComboBox" name="physicsEngineComboBox">
       <Properties>
         <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
-          <Connection code="new DefaultComboBoxModel(PhysicsLibrary.values())" type="code"/>
+          <Connection code="new DefaultComboBoxModel&lt;TemplateLibrary&gt;(CachedOptionsContainer.getInstance().getPhysicsLibraries().toArray(TemplateLibrary[]::new))" type="code"/>
         </Property>
       </Properties>
       <Events>
         <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="physicsEngineComboBoxActionPerformed"/>
       </Events>
       <AuxValues>
-        <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
+        <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;TemplateLibrary&gt;"/>
       </AuxValues>
     </Component>
     <Container class="javax.swing.JScrollPane" name="physicsEngineDescriptionScrollPane">
@@ -181,14 +181,14 @@
     <Component class="javax.swing.JComboBox" name="networkingComboBox">
       <Properties>
         <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
-          <Connection code="new DefaultComboBoxModel(NetworkingLibrary.values())" type="code"/>
+          <Connection code="new DefaultComboBoxModel&lt;TemplateLibrary&gt;(CachedOptionsContainer.getInstance().getNetworkingLibraries().toArray(TemplateLibrary[]::new))" type="code"/>
         </Property>
       </Properties>
       <Events>
         <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="networkingComboBoxActionPerformed"/>
       </Events>
       <AuxValues>
-        <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
+        <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;TemplateLibrary&gt;"/>
       </AuxValues>
     </Component>
     <Container class="javax.swing.JScrollPane" name="networkingDescriptionScrollPane">

+ 16 - 20
jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameGuiPanelVisual.java

@@ -31,9 +31,8 @@
  */
 package com.jme3.gde.templates.gradledesktop;
 
-import com.jme3.gde.templates.gradledesktop.options.GUILibrary;
-import com.jme3.gde.templates.gradledesktop.options.NetworkingLibrary;
-import com.jme3.gde.templates.gradledesktop.options.PhysicsLibrary;
+import com.jme3.gde.templates.gradledesktop.options.CachedOptionsContainer;
+import com.jme3.gde.templates.gradledesktop.options.TemplateLibrary;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import javax.swing.DefaultComboBoxModel;
@@ -50,45 +49,42 @@ import org.openide.awt.Mnemonics;
 import org.openide.util.NbBundle;
 
 /**
- * UI Compoment for the New Gradle Game Wizard GUI panel.
+ * UI Component for the New Gradle Game Wizard GUI panel.
  *
  * @author peedeeboy
  */
 public class GradleDesktopGameGuiPanelVisual extends JPanel {
 
-    private final GradleDesktopGameGuiPanel panel;
-
     /**
      * Creates new form GradleDesktopGameGuiPanelVisual
      */
     public GradleDesktopGameGuiPanelVisual(GradleDesktopGameGuiPanel panel) {
         initComponents();
+
         updateGuiLibraryDescription();
         updatePhysicsLibraryDescription();
         updateNetworkingLibraryDescription();
-
-        this.panel = panel;
     }
 
     private void updateGuiLibraryDescription() {
-        GUILibrary selectedGuiLibrary = (GUILibrary) guiComboBox.getSelectedItem();
+        TemplateLibrary selectedGuiLibrary = guiComboBox.getItemAt(guiComboBox.getSelectedIndex());
         guiDescriptionTextArea.setText(selectedGuiLibrary.getDescription());
     }
 
     private void updatePhysicsLibraryDescription() {
-        PhysicsLibrary selectedPhysicsLibrary = (PhysicsLibrary) physicsEngineComboBox.getSelectedItem();
+        TemplateLibrary selectedPhysicsLibrary = physicsEngineComboBox.getItemAt(physicsEngineComboBox.getSelectedIndex());
         physicsEngineDescriptionTextArea.setText(selectedPhysicsLibrary.getDescription());
     }
 
     private void updateNetworkingLibraryDescription() {
-        NetworkingLibrary selectedNetworkingLibrary = (NetworkingLibrary) networkingComboBox.getSelectedItem();
+        TemplateLibrary selectedNetworkingLibrary = networkingComboBox.getItemAt(networkingComboBox.getSelectedIndex());
         networkingDescriptionTextArea.setText(selectedNetworkingLibrary.getDescription());
     }
 
     protected void store(WizardDescriptor d) {
-        GUILibrary selectedGuiLibrary = (GUILibrary) guiComboBox.getSelectedItem();
-        PhysicsLibrary selectedPhysicsLibrary = (PhysicsLibrary) physicsEngineComboBox.getSelectedItem();
-        NetworkingLibrary selectedNetworkingLibrary = (NetworkingLibrary) networkingComboBox.getSelectedItem();
+        TemplateLibrary selectedGuiLibrary = guiComboBox.getItemAt(guiComboBox.getSelectedIndex());
+        TemplateLibrary selectedPhysicsLibrary = physicsEngineComboBox.getItemAt(physicsEngineComboBox.getSelectedIndex());
+        TemplateLibrary selectedNetworkingLibrary = networkingComboBox.getItemAt(networkingComboBox.getSelectedIndex());
 
         d.putProperty("guiLibrary", selectedGuiLibrary);
         d.putProperty("physicsLibrary", selectedPhysicsLibrary);
@@ -122,7 +118,7 @@ public class GradleDesktopGameGuiPanelVisual extends JPanel {
         guiLabel.setLabelFor(guiComboBox);
         Mnemonics.setLocalizedText(guiLabel, NbBundle.getMessage(GradleDesktopGameGuiPanelVisual.class, "GradleDesktopGameGuiPanelVisual.guiLabel.text")); // NOI18N
 
-        guiComboBox.setModel(new DefaultComboBoxModel(GUILibrary.values()));
+        guiComboBox.setModel(new DefaultComboBoxModel<TemplateLibrary>(CachedOptionsContainer.getInstance().getGuiLibraries().toArray(TemplateLibrary[]::new)));
         guiComboBox.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent evt) {
                 guiComboBoxActionPerformed(evt);
@@ -139,7 +135,7 @@ public class GradleDesktopGameGuiPanelVisual extends JPanel {
         physicsEngineLabel.setLabelFor(physicsEngineComboBox);
         Mnemonics.setLocalizedText(physicsEngineLabel, NbBundle.getMessage(GradleDesktopGameGuiPanelVisual.class, "GradleDesktopGameGuiPanelVisual.physicsEngineLabel.text")); // NOI18N
 
-        physicsEngineComboBox.setModel(new DefaultComboBoxModel(PhysicsLibrary.values()));
+        physicsEngineComboBox.setModel(new DefaultComboBoxModel<TemplateLibrary>(CachedOptionsContainer.getInstance().getPhysicsLibraries().toArray(TemplateLibrary[]::new)));
         physicsEngineComboBox.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent evt) {
                 physicsEngineComboBoxActionPerformed(evt);
@@ -156,7 +152,7 @@ public class GradleDesktopGameGuiPanelVisual extends JPanel {
         networkingLabel.setLabelFor(networkingComboBox);
         Mnemonics.setLocalizedText(networkingLabel, NbBundle.getMessage(GradleDesktopGameGuiPanelVisual.class, "GradleDesktopGameGuiPanelVisual.networkingLabel.text")); // NOI18N
 
-        networkingComboBox.setModel(new DefaultComboBoxModel(NetworkingLibrary.values()));
+        networkingComboBox.setModel(new DefaultComboBoxModel<TemplateLibrary>(CachedOptionsContainer.getInstance().getNetworkingLibraries().toArray(TemplateLibrary[]::new)));
         networkingComboBox.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent evt) {
                 networkingComboBoxActionPerformed(evt);
@@ -239,17 +235,17 @@ public class GradleDesktopGameGuiPanelVisual extends JPanel {
 
 
     // Variables declaration - do not modify//GEN-BEGIN:variables
-    private JComboBox<String> guiComboBox;
+    private JComboBox<TemplateLibrary> guiComboBox;
     private JScrollPane guiDescriptionScrollPane;
     private JTextArea guiDescriptionTextArea;
     private JLabel guiLabel;
     private JSeparator jSeparator1;
     private JSeparator jSeparator2;
-    private JComboBox<String> networkingComboBox;
+    private JComboBox<TemplateLibrary> networkingComboBox;
     private JScrollPane networkingDescriptionScrollPane;
     private JTextArea networkingDescriptionTextArea;
     private JLabel networkingLabel;
-    private JComboBox<String> physicsEngineComboBox;
+    private JComboBox<TemplateLibrary> physicsEngineComboBox;
     private JScrollPane physicsEngineDescriptionScrollPane;
     private JTextArea physicsEngineDescriptionTextArea;
     private JLabel physicsEngineLabel;

+ 4 - 4
jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameJMEVersionPanelVisual.form

@@ -91,7 +91,7 @@
     <Component class="javax.swing.JComboBox" name="jmeVersionComboBox">
       <Properties>
         <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
-          <Connection code="new DefaultComboBoxModel(JMEVersion.values())" type="code"/>
+          <Connection code="new DefaultComboBoxModel&lt;LibraryVersion&gt;(com.jme3.gde.templates.gradledesktop.options.JMEVersion.values())" type="code"/>
         </Property>
         <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
           <Dimension value="[100, 25]"/>
@@ -101,7 +101,7 @@
         <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jmeVersionComboBoxActionPerformed"/>
       </Events>
       <AuxValues>
-        <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
+        <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;LibraryVersion&gt;"/>
       </AuxValues>
     </Component>
     <Container class="javax.swing.JScrollPane" name="jmeVersionDescriptionScrollPane">
@@ -140,7 +140,7 @@
     <Component class="javax.swing.JComboBox" name="lwjglComboBox">
       <Properties>
         <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
-          <Connection code="new DefaultComboBoxModel(LWJGLVersion.values())" type="code"/>
+          <Connection code="new DefaultComboBoxModel&lt;TemplateLibrary&gt;(com.jme3.gde.templates.gradledesktop.options.LWJGLLibrary.values())" type="code"/>
         </Property>
         <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
           <Dimension value="[100, 25]"/>
@@ -150,7 +150,7 @@
         <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="lwjglComboBoxActionPerformed"/>
       </Events>
       <AuxValues>
-        <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
+        <AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;TemplateLibrary&gt;"/>
       </AuxValues>
     </Component>
     <Container class="javax.swing.JScrollPane" name="lwjglDescriptionScrollPane">

+ 43 - 13
jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameJMEVersionPanelVisual.java

@@ -32,8 +32,11 @@
 
 package com.jme3.gde.templates.gradledesktop;
 
+import com.jme3.gde.templates.gradledesktop.options.CachedOptionsContainer;
 import com.jme3.gde.templates.gradledesktop.options.JMEVersion;
-import com.jme3.gde.templates.gradledesktop.options.LWJGLVersion;
+import com.jme3.gde.templates.gradledesktop.options.LWJGLLibrary;
+import com.jme3.gde.templates.gradledesktop.options.LibraryVersion;
+import com.jme3.gde.templates.gradledesktop.options.TemplateLibrary;
 import java.awt.Desktop;
 import java.awt.Dimension;
 import java.awt.event.ActionEvent;
@@ -54,6 +57,8 @@ import javax.swing.JSeparator;
 import javax.swing.JTextArea;
 import javax.swing.JTextPane;
 import javax.swing.LayoutStyle;
+import javax.swing.event.AncestorEvent;
+import javax.swing.event.AncestorListener;
 import javax.swing.event.HyperlinkEvent;
 import org.openide.WizardDescriptor;
 import org.openide.awt.Mnemonics;
@@ -70,6 +75,8 @@ public class GradleDesktopGameJMEVersionPanelVisual extends JPanel {
     private static final Logger LOGGER = Logger.getLogger(
             GradleDesktopGameJMEVersionPanel.class.getName());
 
+    private boolean jmeVersionsInitialized = false;
+
     /**
      * Creates new form GradleDesktopGameJMEVersion
      */
@@ -78,6 +85,33 @@ public class GradleDesktopGameJMEVersionPanelVisual extends JPanel {
         initComponents();
         additionalComponentConfiguration();
 
+        addAncestorListener(new AncestorListener() {
+
+            @Override
+            public void ancestorAdded(AncestorEvent event) {
+
+                // Refresh the jME version selection
+                Object selection = jmeVersionComboBox.getSelectedItem();
+                jmeVersionComboBox.setModel(new DefaultComboBoxModel<>(CachedOptionsContainer.getInstance().getJmeVersions().toArray(LibraryVersion[]::new)));
+                if (selection != null && jmeVersionsInitialized) {
+                    jmeVersionComboBox.setSelectedItem(selection);
+                }
+
+                jmeVersionsInitialized = true;
+            }
+
+            @Override
+            public void ancestorRemoved(AncestorEvent event) {
+
+            }
+
+            @Override
+            public void ancestorMoved(AncestorEvent event) {
+
+            }
+
+        });
+
         loadPatchNotes();
         updateLWJGLdescription();
     }
@@ -109,8 +143,7 @@ public class GradleDesktopGameJMEVersionPanelVisual extends JPanel {
     }
 
     private void loadPatchNotes() {
-        JMEVersion jmeVersionSelected = (JMEVersion) jmeVersionComboBox
-                .getSelectedItem();
+        LibraryVersion jmeVersionSelected = jmeVersionComboBox.getItemAt(jmeVersionComboBox.getSelectedIndex());
         try {
             URL patchNotesURL = GradleDesktopGameJMEVersionPanelVisual.class
                     .getResource(jmeVersionSelected.getPatchNotesPath());
@@ -124,19 +157,16 @@ public class GradleDesktopGameJMEVersionPanelVisual extends JPanel {
     }
 
     private void updateLWJGLdescription() {
-        LWJGLVersion lwjglVersion = (LWJGLVersion) lwjglComboBox
-                .getSelectedItem();
+        TemplateLibrary lwjglVersion = lwjglComboBox.getItemAt(lwjglComboBox.getSelectedIndex());
         lwjglTextArea.setText(lwjglVersion.getDescription());
     }
 
     protected void store(WizardDescriptor d) {
         String jmeVersion = jmeVersionComboBox.getSelectedItem().toString();
-        LWJGLVersion lwjglVersion = (LWJGLVersion) lwjglComboBox
-                .getSelectedItem();
-        String lwjglArtifact = lwjglVersion.getArtifact();
+        TemplateLibrary lwjglLibrary = lwjglComboBox.getItemAt(lwjglComboBox.getSelectedIndex());
 
         d.putProperty("jmeVersion", jmeVersion);
-        d.putProperty("lwjglArtifact", lwjglArtifact);
+        d.putProperty("lwjglLibrary", lwjglLibrary);
     }
 
     /**
@@ -163,7 +193,7 @@ public class GradleDesktopGameJMEVersionPanelVisual extends JPanel {
         jmeVersionLabel.setLabelFor(jmeVersionComboBox);
         Mnemonics.setLocalizedText(jmeVersionLabel, NbBundle.getMessage(GradleDesktopGameJMEVersionPanelVisual.class, "GradleDesktopGameJMEVersionPanelVisual.jmeVersionLabel.text")); // NOI18N
 
-        jmeVersionComboBox.setModel(new DefaultComboBoxModel(JMEVersion.values()));
+        jmeVersionComboBox.setModel(new DefaultComboBoxModel<LibraryVersion>(JMEVersion.values()));
         jmeVersionComboBox.setMaximumSize(new Dimension(100, 25));
         jmeVersionComboBox.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent evt) {
@@ -184,7 +214,7 @@ public class GradleDesktopGameJMEVersionPanelVisual extends JPanel {
         lwjglVersionLabel.setLabelFor(lwjglComboBox);
         Mnemonics.setLocalizedText(lwjglVersionLabel, NbBundle.getMessage(GradleDesktopGameJMEVersionPanelVisual.class, "GradleDesktopGameJMEVersionPanelVisual.lwjglVersionLabel.text")); // NOI18N
 
-        lwjglComboBox.setModel(new DefaultComboBoxModel(LWJGLVersion.values()));
+        lwjglComboBox.setModel(new DefaultComboBoxModel<TemplateLibrary>(LWJGLLibrary.values()));
         lwjglComboBox.setMaximumSize(new Dimension(100, 25));
         lwjglComboBox.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent evt) {
@@ -253,11 +283,11 @@ public class GradleDesktopGameJMEVersionPanelVisual extends JPanel {
 
     // Variables declaration - do not modify//GEN-BEGIN:variables
     JSeparator jSeparator1;
-    JComboBox<String> jmeVersionComboBox;
+    JComboBox<LibraryVersion> jmeVersionComboBox;
     JScrollPane jmeVersionDescriptionScrollPane;
     JTextPane jmeVersionDescriptionTextPane;
     JLabel jmeVersionLabel;
-    JComboBox<String> lwjglComboBox;
+    JComboBox<TemplateLibrary> lwjglComboBox;
     JScrollPane lwjglDescriptionScrollPane;
     JTextArea lwjglTextArea;
     JLabel lwjglVersionLabel;

+ 12 - 9
jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameWizardIterator.java

@@ -31,7 +31,10 @@
  */
 package com.jme3.gde.templates.gradledesktop;
 
+import com.jme3.gde.templates.gradledesktop.options.CachedOptionsContainer;
 import java.awt.Component;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -42,7 +45,7 @@ import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.Reader;
 import java.io.Writer;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.text.MessageFormat;
 import java.util.Collections;
 import java.util.Enumeration;
@@ -84,6 +87,9 @@ public class GradleDesktopGameWizardIterator implements WizardDescriptor./*Progr
     private static final String TEMPLATE_BUILDFILE = "com/jme3/gde/templates/files/freemarker/build.gradle.ftl";
     
     public GradleDesktopGameWizardIterator() {
+
+        // Initiate the options getting...
+        CachedOptionsContainer.getInstance();
     }
 
     public static GradleDesktopGameWizardIterator createIterator() {
@@ -127,7 +133,7 @@ public class GradleDesktopGameWizardIterator implements WizardDescriptor./*Progr
         File gradleBuildFile = new File(dirF, "build.gradle");
         Map<String, Object> buildFileBindings = new HashMap<>();
         buildFileBindings.put("jmeVersion", wiz.getProperty("jmeVersion"));
-        buildFileBindings.put("lwjglArtifact", wiz.getProperty("lwjglArtifact"));
+        buildFileBindings.put("lwjglLibrary", wiz.getProperty("lwjglLibrary"));
         buildFileBindings.put("guiLibrary", wiz.getProperty("guiLibrary"));
         buildFileBindings.put("physicsLibrary", wiz.getProperty("physicsLibrary"));
         buildFileBindings.put("networkingLibrary", wiz.getProperty("networkingLibrary"));
@@ -245,13 +251,10 @@ public class GradleDesktopGameWizardIterator implements WizardDescriptor./*Progr
         // Process template           
         try {
             FileObject targetFO = FileUtil.toFileObject(target);
-            Writer os = new OutputStreamWriter(targetFO.getOutputStream(), Charset.forName("UTF-8"));
-            engine.getContext().setWriter(os);
-            Reader is = new InputStreamReader(GradleDesktopGameWizardIterator.class.getResourceAsStream("/" + templateResourcePath));
-            engine.eval(is);
-            
-            os.close();
-            is.close();
+            try (Writer os = new BufferedWriter(new OutputStreamWriter(targetFO.getOutputStream(), StandardCharsets.UTF_8)); Reader is = new BufferedReader(new InputStreamReader(GradleDesktopGameWizardIterator.class.getResourceAsStream("/" + templateResourcePath)));) {
+                engine.getContext().setWriter(os);
+                engine.eval(is);
+            }
         } catch (IOException | ScriptException ex) {
                 throw new IOException(ex.getMessage(), ex);
         }

+ 11 - 2
jme3-templates/src/com/jme3/gde/templates/gradledesktop/GradleDesktopGameWizardPanel.java

@@ -54,6 +54,7 @@ public class GradleDesktopGameWizardPanel implements WizardDescriptor.Panel,
     public GradleDesktopGameWizardPanel() {
     }
 
+    @Override
     public Component getComponent() {
         if (component == null) {
             component = new GradleDesktopGamePanelVisual(this);
@@ -62,22 +63,26 @@ public class GradleDesktopGameWizardPanel implements WizardDescriptor.Panel,
         return component;
     }
 
+    @Override
     public HelpCtx getHelp() {
         return new HelpCtx("sdk.project_creation");
     }
 
+    @Override
     public boolean isValid() {
         getComponent();
         return component.valid(wizardDescriptor);
     }
-    private final Set<ChangeListener> listeners = new HashSet<ChangeListener>(1); // or can use ChangeSupport in NB 6.0
+    private final Set<ChangeListener> listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0
 
+    @Override
     public final void addChangeListener(ChangeListener l) {
         synchronized (listeners) {
             listeners.add(l);
         }
     }
 
+    @Override
     public final void removeChangeListener(ChangeListener l) {
         synchronized (listeners) {
             listeners.remove(l);
@@ -87,7 +92,7 @@ public class GradleDesktopGameWizardPanel implements WizardDescriptor.Panel,
     protected final void fireChangeEvent() {
         Set<ChangeListener> ls;
         synchronized (listeners) {
-            ls = new HashSet<ChangeListener>(listeners);
+            ls = new HashSet<>(listeners);
         }
         ChangeEvent ev = new ChangeEvent(this);
         for (ChangeListener l : ls) {
@@ -95,20 +100,24 @@ public class GradleDesktopGameWizardPanel implements WizardDescriptor.Panel,
         }
     }
 
+    @Override
     public void readSettings(Object settings) {
         wizardDescriptor = (WizardDescriptor) settings;
         component.read(wizardDescriptor);
     }
 
+    @Override
     public void storeSettings(Object settings) {
         WizardDescriptor d = (WizardDescriptor) settings;
         component.store(d);
     }
 
+    @Override
     public boolean isFinishPanel() {
         return false;
     }
 
+    @Override
     public void validate() throws WizardValidationException {
         getComponent();
         component.validate(wizardDescriptor);

+ 59 - 48
jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/AdditionalLibrary.java

@@ -65,45 +65,53 @@ import org.openide.util.NbBundle;
  *
  * @author peedeeboy
  */
-public enum AdditionalLibrary {
+public enum AdditionalLibrary implements TemplateLibrary {
 
     JME3_EFFECTS("jMonkeyEngine Effects (jme3-effects)",
             NbBundle.getMessage(AdditionalLibrary.class,
             "additionalLibrary.jme3-effects.description"),
-            "org.jmonkeyengine:jme3-effects", true),
+            null, "jme3-effects",
+            null, true),
     JME3_TERRAIN("jMonkeyEngine TerraMonkey (jme3-terrain)",
             NbBundle.getMessage(AdditionalLibrary.class,
             "additionalLibrary.jme3-terrain.description"),
-            "org.jmonkeyengine:jme3-terrain", true),
+            null, "jme3-terrain",
+            null, true),
     JME3_TESTDATA("jMonkeyEngine Test Data (jme3-testdata)",
             NbBundle.getMessage(AdditionalLibrary.class,
             "additionalLibrary.jme3-testdata.description"),
-            "org.jmonkeyengine:jme3-testdata", true),
+            null, "jme3-testdata",
+            null, true),
     JME3_VR("jMonkeyEngine Virtual Reality (jme3-vr)",
             NbBundle.getMessage(AdditionalLibrary.class,
             "additionalLibrary.jme3-vr.description"),
-            "org.jmonkeyengine:jme3-vr", true),
+            null, "jme3-vr",
+            null, true),
     HEART("Heart Library", NbBundle.getMessage(AdditionalLibrary.class,
             "additionalLibrary.heart.description"),
-            "com.github.stephengold:Heart:8.1.0", false),
+            "com.github.stephengold", "Heart",
+            "8.1.0", false),
     PARTICLE_MONKEY("Particle Monkey",
             NbBundle.getMessage(AdditionalLibrary.class,
             "additionalLibrary.particlemonkey.description"),
-            "com.github.Jeddic:particlemonkey:1.0.2", false),
+            "com.github.Jeddic", "particlemonkey",
+            "1.0.2", false),
     SHADERBLOW_EX("ShaderBlowEx", NbBundle.getMessage(AdditionalLibrary.class,
             "additionalLibrary.shaderblowex.description"),
-            "com.github.polincdev:ShaderBlowEx:master-SNAPSHOT", false),
+            "com.github.polincdev", "ShaderBlowEx",
+            "master-SNAPSHOT", false),
     SIO2("SiO2", NbBundle.getMessage(AdditionalLibrary.class,
             "additionalLibrary.sio2.description"),
-            "com.simsilica:sio2:1.7.0", false),
+            "com.simsilica", "sio2",
+            "1.7.0", false),
     ZAY_ES("Zay-ES Entity Component System",
             NbBundle.getMessage(AdditionalLibrary.class,
             "additionalLibrary.zayes.description"),
-            "com.simsilica:zay-es:1.4.0", false),
+            "com.simsilica", "zay-es", "1.4.0", false),
     ZAY_ES_NET("Zay-ES-Net Networking Extension",
             NbBundle.getMessage(AdditionalLibrary.class,
             "additionalLibrary.zayesnet.description"),
-            "com.simsilica:zay-es-net:1.5.0", false),;
+            "com.simsilica", "zay-es-net", "1.5.0", false);
 
     /**
      * The name of the library. This will be displayed in the jComboBox in the
@@ -116,13 +124,17 @@ public enum AdditionalLibrary {
      */
     private final String description;
     /**
-     * Gradle artifact string. If this is <strong>not</strong> a core JME
-     * library, then the artifact string should include the version number. If
-     * the library <strong>is</strong> a core JME library, then the version
-     * should be omitted, as they jMonkeyEngine version will be appended
-     * automatically by the template.
+     * Maven artifact ID
      */
-    private final String artifact;
+    private final String artifactId;
+    /**
+     * Maven group ID
+     */
+    private final String groupId;
+    /**
+     * Default artifact version to be used
+     */
+    private final VersionInfo defaultVersion;
     /**
      * Is this library a core jMonkeyEngine library? True if the library is a
      * part of jMonkeyengine, false if it is 3rd party.
@@ -134,61 +146,60 @@ public enum AdditionalLibrary {
      *
      * @param label The name of the library.
      * @param description Long description of the library.
-     * @param artifact Gradle artifact string.
+     * @param groupId Maven group ID.
+     * @param artifactId Maven artifact ID.
+     * @param defaultVersion Default version is used if no version info is found
+     * from Maven
      * @param isCoreJmeLibrary Is this library a core jMonkeyEngine library?
      */
-    AdditionalLibrary(String label, String description, String artifact,
-            boolean isCoreJmeLibrary) {
+    AdditionalLibrary(String label, String description, String groupId,
+            String artifactId, String defaultVersion, boolean isCoreJmeLibrary) {
         this.label = label;
         this.description = description;
-        this.artifact = artifact;
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.defaultVersion = defaultVersion != null ? SemanticPlusTagVersionInfo.of(defaultVersion) : null;
         this.isCoreJmeLibrary = isCoreJmeLibrary;
     }
 
-    /**
-     * Get the label for this Additional Library.
-     *
-     * @return the label for this Additional Library.
-     */
+    @Override
     public String getLabel() {
         return label;
     }
 
-    /**
-     * Get the long description for this Additional Library.
-     *
-     * @return the long description for this Additional Library.
-     */
+    @Override
     public String getDescription() {
         return description;
     }
 
     /**
-     * Get the Gradle artifact string.
+     * Override the <code>toString()</code> method to return the label, so that
+     * this enum will display nicely in a jComboBox.
      *
-     * @return the Gradle artifact string.
+     * @return <code>label</code> as a String
      */
-    public String getArtifact() {
-        return artifact;
+    @Override
+    public String toString() {
+        return label;
     }
 
-    /**
-     * Is this a Core jMonkeyEngine library?
-     *
-     * @return true if this is a core jMonkeyEngine library.
-     */
+    @Override
+    public String getGroupId() {
+        return isCoreJmeLibrary ? JME_GROUP_ID : groupId;
+    }
+
+    @Override
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    @Override
     public boolean getIsCoreJmeLibrary() {
         return isCoreJmeLibrary;
     }
 
-    /**
-     * Override the <code>toString()</code> method to return the label, so that
-     * this enum will display nicely in a jComboBox.
-     *
-     * @return <code>label</code> as a String
-     */
     @Override
-    public String toString() {
-        return label;
+    public VersionInfo getVersionInfo() {
+        return defaultVersion;
     }
 }

+ 336 - 0
jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/CachedOptionsContainer.java

@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.gradledesktop.options;
+
+import com.jme3.gde.templates.utils.mavensearch.MavenApiVersionChecker;
+import com.jme3.gde.templates.utils.mavensearch.MavenVersionChecker;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import static java.util.Map.entry;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Singleton that contains all the options. Tries to go online to get all the
+ * latest options
+ */
+public class CachedOptionsContainer {
+
+    private static CachedOptionsContainer instance;
+
+    private static final Logger logger = Logger.getLogger(CachedOptionsContainer.class.getName());
+
+    private static final Map<TemplateLibrary, Predicate<VersionInfo>> LIBRARY_VERSION_FILTERS = Map.ofEntries(
+            entry(PhysicsLibrary.MINIE, (version) -> {
+                return version.getType() == null;
+            }),
+            entry(AdditionalLibrary.HEART, (version) -> {
+                return version.getType() == null;
+            })
+    );
+
+    private List<LibraryVersion> jmeVersions;
+    private List<TemplateLibrary> additionalLibraries;
+    private List<TemplateLibrary> guiLibraries;
+    private List<TemplateLibrary> networkingLibraries;
+    private List<TemplateLibrary> physicsLibraries;
+
+    private CachedOptionsContainer() {
+        initialize();
+    }
+
+    public static CachedOptionsContainer getInstance() {
+        if (instance == null) {
+            synchronized (CachedOptionsContainer.class) {
+                if (instance == null) {
+                    instance = new CachedOptionsContainer();
+                }
+            }
+        }
+        return instance;
+    }
+
+    private void initialize() {
+        MavenVersionChecker mavenVersionChecker = new MavenApiVersionChecker();
+
+        jmeVersions = initVersions(mavenVersionChecker,
+                MavenArtifact.JME_GROUP_ID,
+                JMEVersion.JME_ARTIFACT_ID,
+                (jmeVersion) -> {
+                    return "stable".equalsIgnoreCase(jmeVersion.getType());
+                },
+                JMEVersion.values(), (result) -> {
+            jmeVersions = result;
+        },
+                JMEVersion.DEFAULT_PATCH_NOTES_PATH);
+        additionalLibraries = initLibaries(mavenVersionChecker, AdditionalLibrary.values());
+        guiLibraries = initLibaries(mavenVersionChecker, GUILibrary.values());
+        networkingLibraries = initLibaries(mavenVersionChecker, NetworkingLibrary.values());
+        physicsLibraries = initLibaries(mavenVersionChecker, PhysicsLibrary.values());
+    }
+
+    private static List<TemplateLibrary> initLibaries(final MavenVersionChecker mavenVersionChecker, TemplateLibrary[] libraries) {
+        List<TemplateLibrary> libs = new ArrayList<>(libraries.length);
+        for (TemplateLibrary templateLibrary : libraries) {
+            libs.add(createTemplateLibrary(templateLibrary, mavenVersionChecker));
+        }
+
+        return Collections.unmodifiableList(libs);
+    }
+
+    private static TemplateLibrary createTemplateLibrary(TemplateLibrary templateLibrary, final MavenVersionChecker mavenVersionChecker) {
+        return new TemplateLibrary() {
+
+            private VersionInfo version;
+
+            {
+                if (templateLibrary.getGroupId() != null && templateLibrary.getArtifactId() != null) {
+                    if (LIBRARY_VERSION_FILTERS.containsKey(templateLibrary)) {
+                        mavenVersionChecker.getAllVersions(templateLibrary.getGroupId(), templateLibrary.getArtifactId())
+                                .whenComplete((result, exception) -> {
+                                    if (exception != null || result == null) {
+                                        logMavenCheckFailure(exception);
+
+                                        return;
+                                    }
+
+                                    Predicate<VersionInfo> versionFilter = LIBRARY_VERSION_FILTERS.get(templateLibrary);
+                                    Optional<VersionInfo> latestInfo = result.stream()
+                                            .map((versionString) -> {
+                                                return SemanticPlusTagVersionInfo.of(versionString);
+                                            })
+                                            .filter(versionFilter)
+                                            .max(Comparator.naturalOrder());
+                                    if (latestInfo.isPresent()) {
+                                        version = latestInfo.get();
+                                    }
+                                });
+                    } else {
+                        mavenVersionChecker.getLatestVersion(templateLibrary.getGroupId(), templateLibrary.getArtifactId())
+                                .whenComplete((result, exception) -> {
+                                    if (exception != null || result == null) {
+                                        logMavenCheckFailure(exception);
+
+                                        return;
+                                    }
+
+                                    version = SemanticPlusTagVersionInfo.of(result);
+                                });
+                    }
+                }
+            }
+
+            private void logMavenCheckFailure(Throwable exception) {
+                logger.log(Level.INFO, exception,
+                        () -> String.format("Failed to acquire version information for Maven artifact %s (%s:%s)", new Object[]{getLabel(), getGroupId(), getArtifactId()}));
+            }
+
+            @Override
+            public String getLabel() {
+                return templateLibrary.getLabel();
+            }
+
+            @Override
+            public String getDescription() {
+                return templateLibrary.getDescription();
+            }
+
+            @Override
+            public boolean getIsCoreJmeLibrary() {
+                return templateLibrary.getIsCoreJmeLibrary();
+            }
+
+            @Override
+            public String getGroupId() {
+                return templateLibrary.getGroupId();
+            }
+
+            @Override
+            public String getArtifactId() {
+                return templateLibrary.getArtifactId();
+            }
+
+            @Override
+            public String toString() {
+                return templateLibrary.getLabel();
+            }
+
+            @Override
+            public VersionInfo getVersionInfo() {
+                return version != null ? version : templateLibrary.getVersionInfo();
+            }
+
+        };
+    }
+
+    public List<TemplateLibrary> getAdditionalLibraries() {
+        return additionalLibraries;
+    }
+
+    public List<TemplateLibrary> getGuiLibraries() {
+        return guiLibraries;
+    }
+
+    public List<TemplateLibrary> getNetworkingLibraries() {
+        return networkingLibraries;
+    }
+
+    public List<TemplateLibrary> getPhysicsLibraries() {
+        return physicsLibraries;
+    }
+
+    public List<LibraryVersion> getJmeVersions() {
+        return jmeVersions;
+    }
+
+    /**
+     * Initialize a version listing from Maven (and the given hard coded list)
+     *
+     * @param <T> the type of version information should be used in comparison
+     * @param mavenVersionChecker access to Maven version information
+     * @param groupId Maven group ID
+     * @param artifactId Maven artifact ID
+     * @param versionFilter predicate for version inclusion, may be null (all
+     * versions are accepted)
+     * @param versions the hard coded list of versions, guaranteed to be
+     * included in the listing
+     * @param completedVersionsConsumer consumer for the versions listing that
+     * has been compiled from hard coded list and Maven version results. Only
+     * triggers if Maven version check is successful
+     * @param defaultPatchNotes for versions from Maven API, we don't get their
+     * release notes. Supply default patch notes
+     * @return returns a listing of hard coded versions immediately
+     */
+    private static List<LibraryVersion> initVersions(MavenVersionChecker mavenVersionChecker, String groupId,
+            String artifactId, Predicate<VersionInfo> versionFilter,
+            LibraryVersion[] versions, Consumer<List<LibraryVersion>> completedVersionsConsumer,
+            String defaultPatchNotes) {
+        mavenVersionChecker.getAllVersions(groupId, artifactId).whenComplete((result, exception) -> {
+
+            if (exception != null || result == null) {
+                logger.log(Level.INFO, exception,
+                        () -> String.format("Failed to acquire version information for Maven artifact %s:%s", new Object[]{groupId, artifactId}));
+
+                return;
+            }
+
+            initVersionList(result, versionFilter, versions, groupId, artifactId, completedVersionsConsumer, defaultPatchNotes);
+        });
+
+        return Collections.unmodifiableList(Arrays.asList(versions));
+    }
+
+    private static void initVersionList(List<String> result, Predicate<VersionInfo> versionFilter,
+            LibraryVersion[] versions, String groupId, String artifactId,
+            Consumer<List<LibraryVersion>> completedVersionsConsumer,
+            String defaultPatchNotes) {
+
+        // Filter the versions list
+        Stream<VersionInfo> versionStream = result.stream().map((versionString) -> SemanticPlusTagVersionInfo.of(versionString));
+        if (versionFilter != null) {
+            versionStream = versionStream.filter(versionFilter);
+        }
+        List<VersionInfo> versionInfoList = versionStream.collect(Collectors.toList());
+
+        // Compile the results
+        final SortedSet<LibraryVersion> allVersions = new TreeSet<>(Comparator.comparing(LibraryVersion::getVersionInfo, Comparator.reverseOrder()));
+        allVersions.addAll(Arrays.asList(versions));
+        for (VersionInfo versionInfo : versionInfoList) {
+            allVersions.add(createLibraryVersion(groupId, artifactId, defaultPatchNotes, versionInfo));
+        }
+
+        completedVersionsConsumer.accept(Collections.unmodifiableList(new ArrayList<>(allVersions)));
+    }
+
+    private static LibraryVersion createLibraryVersion(String groupId, String artifactId, String defaultPatchNotes, VersionInfo versionInfo) {
+        return new LibraryVersion() {
+
+            @Override
+            public String getGroupId() {
+                return groupId;
+            }
+
+            @Override
+            public String getArtifactId() {
+                return artifactId;
+            }
+
+            @Override
+            public String getPatchNotesPath() {
+                return defaultPatchNotes;
+            }
+
+            @Override
+            public String toString() {
+                return getVersionInfo().getVersionString();
+            }
+
+            @Override
+            public VersionInfo getVersionInfo() {
+                return versionInfo;
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hashCode(versionInfo.getVersionString());
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                if (this == obj) {
+                    return true;
+                }
+                if (obj == null) {
+                    return false;
+                }
+                if (!(obj instanceof LibraryVersion)) {
+                    return false;
+                }
+                final LibraryVersion other = (LibraryVersion) obj;
+
+                return Objects.equals(getVersionInfo().getVersionString(), other.getVersionInfo().getVersionString());
+            }
+        };
+    }
+}

+ 47 - 41
jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/GUILibrary.java

@@ -64,16 +64,19 @@ import org.openide.util.NbBundle;
  *
  * @author peedeeboy
  */
-public enum GUILibrary {
+public enum GUILibrary implements TemplateLibrary {
 
     NONE("", NbBundle.getMessage(GUILibrary.class,
-            "guilibrary.none.description"), "", false),
+            "guilibrary.none.description"), null, null,
+            null, false),
     NIFTY("Nifty", NbBundle.getMessage(GUILibrary.class, 
             "guilibrary.nifty.description"),
-            "org.jmonkeyengine:jme3-niftygui", true),
+            null, "jme3-niftygui",
+            null, true),
     LEMUR("Lemur", NbBundle.getMessage(GUILibrary.class,
             "guilibrary.lemur.description"),
-            "com.simsilica:lemur:1.16.0", false);
+            "com.simsilica", "lemur",
+            "1.16.0", false);
 
     /**
      * The name of the library. This will be displayed in the jComboBox in the
@@ -86,13 +89,17 @@ public enum GUILibrary {
      */
     private final String description;
     /**
-     * Gradle artifact string. If this is <strong>not</strong> a core JME
-     * library, then the artifact string should include the version number. If
-     * the library <strong>is</strong> a core JME library, then the version
-     * should be omitted, as they jMonkeyEngine version will be appended
-     * automatically by the template.
+     * Maven artifact ID
      */
-    private final String artifact;
+    private final String artifactId;
+    /**
+     * Maven group ID
+     */
+    private final String groupId;
+    /**
+     * Default artifact version to be used
+     */
+    private final VersionInfo defaultVersion;
     /**
      * Is this library a core jMonkeyEngine library? True if the library is a
      * part of jMonkeyengine, false if it is 3rd party.
@@ -104,61 +111,60 @@ public enum GUILibrary {
      *
      * @param label The name of the library.
      * @param description Long description of the library.
-     * @param artifact Gradle artifact string.
+     * @param groupId Maven group ID.
+     * @param artifactId Maven artifact ID.
+     * @param defaultVersion Default version is used if no version info is found
+     * from Maven
      * @param isCoreJmeLibrary Is this library a core jMonkeyEngine library?
      */
-    GUILibrary(String label, String description, String artifact,
-            boolean isCoreJmeLibrary) {
+    GUILibrary(String label, String description, String groupId,
+            String artifactId, String defaultVersion, boolean isCoreJmeLibrary) {
         this.label = label;
         this.description = description;
-        this.artifact = artifact;
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.defaultVersion = defaultVersion != null ? SemanticPlusTagVersionInfo.of(defaultVersion) : null;
         this.isCoreJmeLibrary = isCoreJmeLibrary;
     }
 
-    /**
-     * Get the label for this GUI Library.
-     *
-     * @return the label for this GUI Library.
-     */
+    @Override
     public String getLabel() {
         return label;
     }
 
-    /**
-     * Get the long description for this GUI Library.
-     *
-     * @return the long description for this GUI Library.
-     */
+    @Override
     public String getDescription() {
         return description;
     }
 
     /**
-     * Get the Gradle artifact string.
+     * Override the <code>toString()</code> method to return the label, so that
+     * this enum will display nicely in a jComboBox.
      *
-     * @return the Gradle artifact string.
+     * @return <code>label</code> as a String
      */
-    public String getArtifact() {
-        return artifact;
+    @Override
+    public String toString() {
+        return label;
     }
 
-    /**
-     * Is this a Core jMonkeyEngine library?
-     *
-     * @return true if this is a core jMonkeyEngine library.
-     */
+    @Override
+    public String getGroupId() {
+        return isCoreJmeLibrary ? JME_GROUP_ID : groupId;
+    }
+
+    @Override
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    @Override
     public boolean getIsCoreJmeLibrary() {
         return isCoreJmeLibrary;
     }
 
-    /**
-     * Override the <code>toString()</code> method to return the label, so that
-     * this enum will display nicely in a jComboBox.
-     *
-     * @return <code>label</code> as a String
-     */
     @Override
-    public String toString() {
-        return label;
+    public VersionInfo getVersionInfo() {
+        return defaultVersion;
     }
 }

+ 31 - 7
jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/JMEVersion.java

@@ -54,7 +54,7 @@ package com.jme3.gde.templates.gradledesktop.options;
  *
  * @author peedeeboy
  */
-public enum JMEVersion {
+public enum JMEVersion implements LibraryVersion {
 
     JME_3_5_2("3.5.2-stable",
             "/com/jme3/gde/templates/files/patchnotes/352-stable.html"),
@@ -71,6 +71,17 @@ public enum JMEVersion {
     JME_3_3_0("3.3.0-stable",
             "/com/jme3/gde/templates/files/patchnotes/330-stable.html");
 
+    /**
+     * Default artifact ID for jME that we use to check i.e. versions from
+     */
+    public static final String JME_ARTIFACT_ID = "jme3-core";
+
+    /**
+     * Patch notes for versions that are not hard coded like versions in this
+     * class
+     */
+    public static final String DEFAULT_PATCH_NOTES_PATH = "/com/jme3/gde/templates/files/patchnotes/default.html";
+
     /**
      * Name of the jMonkeyEngine version. This should match the Maven/Gradle
      * version.
@@ -82,6 +93,8 @@ public enum JMEVersion {
      */
     private final String patchNotesPath;
 
+    private final VersionInfo versionInfo;
+
     /**
      * Private constructor to create an instance of this enum.
      *
@@ -92,6 +105,7 @@ public enum JMEVersion {
     JMEVersion(String label, String patchNotesPath) {
         this.label = label;
         this.patchNotesPath = patchNotesPath;
+        this.versionInfo = new SemanticPlusTagVersionInfo(label);
     }
 
     /**
@@ -103,12 +117,7 @@ public enum JMEVersion {
         return label;
     }
 
-    /**
-     * Get the path to the .html file containing the Patch Notes for this
-     * jMonkeyEngine version.
-     *
-     * @return the path to the .html file containing the Patch Notes
-     */
+    @Override
     public String getPatchNotesPath() {
         return patchNotesPath;
     }
@@ -123,4 +132,19 @@ public enum JMEVersion {
     public String toString() {
         return label;
     }
+
+    @Override
+    public String getGroupId() {
+        return MavenArtifact.JME_GROUP_ID;
+    }
+
+    @Override
+    public String getArtifactId() {
+        return "core";
+    }
+
+    @Override
+    public VersionInfo getVersionInfo() {
+        return versionInfo;
+    }
 }

+ 39 - 32
jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/LWJGLVersion.java → jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/LWJGLLibrary.java

@@ -59,12 +59,12 @@ import org.openide.util.NbBundle;
  *
  * @author peedeeboy
  */
-public enum LWJGLVersion {
+public enum LWJGLLibrary implements TemplateLibrary {
 
-    LWJGL_3("LWJGL 3.x", NbBundle.getMessage(LWJGLVersion.class,
-            "lwjgl.lwjgl3.description"), "org.jmonkeyengine:jme3-lwjgl3"),
-    LWJGL_2("LWJGL 2.x", NbBundle.getMessage(LWJGLVersion.class,
-            "lwjgl.lwjgl2.description"), "org.jmonkeyengine:jme3-lwjgl");
+    LWJGL_3("LWJGL 3.x", NbBundle.getMessage(LWJGLLibrary.class,
+            "lwjgl.lwjgl3.description"), "jme3-lwjgl3"),
+    LWJGL_2("LWJGL 2.x", NbBundle.getMessage(LWJGLLibrary.class,
+            "lwjgl.lwjgl2.description"), "jme3-lwjgl");
 
     /**
      * The name of the LWJGL library. This will be displayed in the jComboBox in
@@ -77,51 +77,38 @@ public enum LWJGLVersion {
      */
     private final String description;
     /**
-     * Gradle artifact string. This should exclude the jMonkeyEngine version, as
-     * this will be added by the template.
+     * Maven artifact ID
      */
-    private final String artifact;
+    private final String artifactId;
 
+    /**
     /**
      * Private constructor to create an instance of this enum.
      *
-     * @param label The name of the LWJGL library.
-     * @param description Long description of the LWJGL version.
-     * @param artifact Gradle artifact string.
+     * @param label The name of the library.
+     * @param description Long description of the library.
+     * @param groupId Maven group ID.
+     * @param artifactId Maven artifact ID.
+     * @param defaultVersion Default version is used if no version info is found
+     * from Maven
      */
-    LWJGLVersion(String label, String description, String artifact) {
+    LWJGLLibrary(String label, String description,
+            String artifactId) {
         this.label = label;
         this.description = description;
-        this.artifact = artifact;
+        this.artifactId = artifactId;
     }
 
-    /**
-     * Get the label for this LWJGL version.
-     *
-     * @return the label for this LWJGL version.
-     */
+    @Override
     public String getLabel() {
         return label;
     }
 
-    /**
-     * Get the long description for this LWJGL version.
-     *
-     * @return the long description for this LWJGL version.
-     */
+    @Override
     public String getDescription() {
         return description;
     }
 
-    /**
-     * Get the Gradle artifact string.
-     *
-     * @return the Gradle artifact string.
-     */
-    public String getArtifact() {
-        return artifact;
-    }
-
     /**
      * Override the <code>toString()</code> method to return the label, so that
      * this enum will display nicely in a jComboBox.
@@ -132,4 +119,24 @@ public enum LWJGLVersion {
     public String toString() {
         return this.label;
     }
+
+    @Override
+    public boolean getIsCoreJmeLibrary() {
+        return true;
+    }
+
+    @Override
+    public String getGroupId() {
+        return JME_GROUP_ID;
+    }
+
+    @Override
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    @Override
+    public VersionInfo getVersionInfo() {
+        return null;
+    }
 }

+ 47 - 0
jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/LibraryVersion.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.gradledesktop.options;
+
+/**
+ * Represents a Maven library version info (with patch notes)
+ */
+public interface LibraryVersion extends MavenArtifact {
+
+    /**
+     * Get the path to the .html file containing the Patch Notes for this
+     * jMonkeyEngine version.
+     *
+     * @return the path to the .html file containing the Patch Notes
+     */
+    String getPatchNotesPath();
+
+}

+ 62 - 0
jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/MavenArtifact.java

@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.gradledesktop.options;
+
+/**
+ * Represents Maven artifact
+ */
+public interface MavenArtifact {
+
+    String JME_GROUP_ID = "org.jmonkeyengine";
+
+    /**
+     * Get the Maven group ID
+     *
+     * @return group ID
+     */
+    String getGroupId();
+
+    /**
+     * Get the Maven artifact ID
+     *
+     * @return artifact ID
+     */
+    String getArtifactId();
+
+    /**
+     * Get the artifact version info
+     *
+     * @return version info
+     */
+    VersionInfo getVersionInfo();
+
+}

+ 49 - 42
jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/NetworkingLibrary.java

@@ -63,19 +63,23 @@ import org.openide.util.NbBundle;
  *
  * @author peedeeboy
  */
-public enum NetworkingLibrary {
+public enum NetworkingLibrary implements TemplateLibrary {
 
     NONE("", NbBundle.getMessage(NetworkingLibrary.class,
-            "networkinglibrary.none.description"), "", false),
+            "networkinglibrary.none.description"), null, null,
+            null, false),
     SPIDERMONKEY("SpiderMonkey", NbBundle.getMessage(NetworkingLibrary.class,
             "networkinglibrary.spidermonkey.description"),
-            "org.jmonkeyengine:jme3-networking", true),
+            null, "jme3-networking",
+            null, true),
     MONKEYNETTY("MonkeyNetty", NbBundle.getMessage(NetworkingLibrary.class,
             "networkinglibrary.monkeynetty.description"),
-            "io.tlf.monkeynetty:monkey-netty:0.1.1", false),
+            "io.tlf.monkeynetty", "monkey-netty",
+            "0.1.1", false),
     SIMETHEREAL("SimEthereal", NbBundle.getMessage(NetworkingLibrary.class,
             "networkinglibrary.simethereal.description"),
-            "com.simsilica:sim-ethereal:1.7.0", false);
+            "com.simsilica", "sim-ethereal",
+            "1.7.0", false);
 
     /**
      * The name of the library. This will be displayed in the jComboBox in the
@@ -88,13 +92,17 @@ public enum NetworkingLibrary {
      */
     private final String description;
     /**
-     * Gradle artifact string. If this is <strong>not</strong> a core JME
-     * library, then the artifact string should include the version number. If
-     * the library <strong>is</strong> a core JME library, then the version
-     * should be omitted, as they jMonkeyEngine version will be appended
-     * automatically by the template.
+     * Maven artifact ID
      */
-    private final String artifact;
+    private final String artifactId;
+    /**
+     * Maven group ID
+     */
+    private final String groupId;
+    /**
+     * Default artifact version to be used
+     */
+    private final VersionInfo defaultVersion;
     /**
      * Is this library a core jMonkeyEngine library? True if the library is a
      * part of jMonkeyengine, false if it is 3rd party.
@@ -106,61 +114,60 @@ public enum NetworkingLibrary {
      *
      * @param label The name of the library.
      * @param description Long description of the library.
-     * @param artifact Gradle artifact string.
+     * @param groupId Maven group ID.
+     * @param artifactId Maven artifact ID.
+     * @param defaultVersion Default version is used if no version info is found
+     * from Maven
      * @param isCoreJmeLibrary Is this library a core jMonkeyEngine library?
      */
-    NetworkingLibrary(String label, String description, String artifact,
-            boolean isCoreJmeLibrary) {
+    NetworkingLibrary(String label, String description, String groupId,
+            String artifactId, String defaultVersion, boolean isCoreJmeLibrary) {
         this.label = label;
         this.description = description;
-        this.artifact = artifact;
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.defaultVersion = defaultVersion != null ? SemanticPlusTagVersionInfo.of(defaultVersion) : null;
         this.isCoreJmeLibrary = isCoreJmeLibrary;
     }
 
-    /**
-     * Get the label for this Networking Library.
-     *
-     * @return the label for this Networking Library.
-     */
+    @Override
     public String getLabel() {
         return label;
     }
 
-    /**
-     * Get the long description for this Networking Library.
-     *
-     * @return the long description for this Networking Library.
-     */
+    @Override
     public String getDescription() {
         return description;
     }
 
     /**
-     * Get the Gradle artifact string.
+     * Override the <code>toString()</code> method to return the label, so that
+     * this enum will display nicely in a jComboBox.
      *
-     * @return the Gradle artifact string.
+     * @return <code>label</code> as a String
      */
-    public String getArtifact() {
-        return artifact;
+    @Override
+    public String toString() {
+        return this.label;
     }
 
-    /**
-     * Is this a Core jMonkeyEngine library?
-     *
-     * @return true if this is a core jMonkeyEngine library
-     */
+    @Override
+    public String getGroupId() {
+        return isCoreJmeLibrary ? JME_GROUP_ID : groupId;
+    }
+
+    @Override
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    @Override
     public boolean getIsCoreJmeLibrary() {
         return isCoreJmeLibrary;
     }
 
-    /**
-     * Override the <code>toString()</code> method to return the label, so that
-     * this enum will display nicely in a jComboBox.
-     *
-     * @return <code>label</code> as a String
-     */
     @Override
-    public String toString() {
-        return this.label;
+    public VersionInfo getVersionInfo() {
+        return defaultVersion;
     }
 }

+ 47 - 41
jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/PhysicsLibrary.java

@@ -63,16 +63,19 @@ import org.openide.util.NbBundle;
  *
  * @author peedeeboy
  */
-public enum PhysicsLibrary {
+public enum PhysicsLibrary implements TemplateLibrary {
 
     NONE("", NbBundle.getMessage(GUILibrary.class,
-            "physicslibrary.none.description"), "", false),
+            "physicslibrary.none.description"), null, null,
+            null, false),
     JBULLET("jBullet", NbBundle.getMessage(GUILibrary.class,
             "physicslibrary.jbullet.description"),
-            "org.jmonkeyengine:jme3-jbullet", true),
+            null, "jme3-jbullet",
+            null, true),
     MINIE("Minie", NbBundle.getMessage(PhysicsLibrary.class,
             "physicslibrary.minie.description"),
-            "com.github.stephengold:Minie:5.0.0", false);
+            "com.github.stephengold", "Minie",
+            "5.0.0", false);
 
     /**
      * The name of the library. This will be displayed in the jComboBox in the
@@ -85,13 +88,17 @@ public enum PhysicsLibrary {
      */
     private final String description;
     /**
-     * Gradle artifact string. If this is <strong>not</strong> a core JME
-     * library, then the artifact string should include the version number. If
-     * the library <strong>is</strong> a core JME library, then the version
-     * should be omitted, as they jMonkeyEngine version will be appended
-     * automatically by the template.
+     * Maven artifact ID
      */
-    private final String artifact;
+    private final String artifactId;
+    /**
+     * Maven group ID
+     */
+    private final String groupId;
+    /**
+     * Default artifact version to be used
+     */
+    private final VersionInfo defaultVersion;
     /**
      * Is this library a core jMonkeyEngine library? True if the library is a
      * part of jMonkeyengine, false if it is 3rd party.
@@ -103,61 +110,60 @@ public enum PhysicsLibrary {
      *
      * @param label The name of the library.
      * @param description Long description of the library.
-     * @param artifact Gradle artifact string.
+     * @param groupId Maven group ID.
+     * @param artifactId Maven artifact ID.
+     * @param defaultVersion Default version is used if no version info is found
+     * from Maven
      * @param isCoreJmeLibrary Is this library a core jMonkeyEngine library?
      */
-    PhysicsLibrary(String label, String description, String artifact,
-            boolean isCoreJmeLibrary) {
+    PhysicsLibrary(String label, String description, String groupId,
+            String artifactId, String defaultVersion, boolean isCoreJmeLibrary) {
         this.label = label;
         this.description = description;
-        this.artifact = artifact;
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.defaultVersion = defaultVersion != null ? SemanticPlusTagVersionInfo.of(defaultVersion) : null;
         this.isCoreJmeLibrary = isCoreJmeLibrary;
     }
 
-    /**
-     * Get the label for this Physics Library.
-     *
-     * @return the label for this Physics Library.
-     */
+    @Override
     public String getLabel() {
         return label;
     }
 
-    /**
-     * Get the long description for this Physics Library.
-     *
-     * @return the long description for this Physics Library.
-     */
+    @Override
     public String getDescription() {
         return description;
     }
 
     /**
-     * Get the Gradle artifact string.
+     * Override the <code>toString()</code> method to return the label, so that
+     * this enum will display nicely in a jComboBox.
      *
-     * @return the Gradle artifact string.
+     * @return <code>label</code> as a String
      */
-    public String getArtifact() {
-        return artifact;
+    @Override
+    public String toString() {
+        return this.label;
     }
 
-    /**
-     * Is this a Core jMonkeyEngine library?
-     *
-     * @return true if this is a core jMonkeyEngine library
-     */
+    @Override
+    public String getGroupId() {
+        return isCoreJmeLibrary ? JME_GROUP_ID : groupId;
+    }
+
+    @Override
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    @Override
     public boolean getIsCoreJmeLibrary() {
         return isCoreJmeLibrary;
     }
 
-    /**
-     * Override the <code>toString()</code> method to return the label, so that
-     * this enum will display nicely in a jComboBox.
-     *
-     * @return <code>label</code> as a String
-     */
     @Override
-    public String toString() {
-        return this.label;
+    public VersionInfo getVersionInfo() {
+        return defaultVersion;
     }
 }

+ 165 - 0
jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/SemanticPlusTagVersionInfo.java

@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.gradledesktop.options;
+
+import java.text.Collator;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Versioning scheme like x.x.x with or without a tag/type. Tries to parse
+ * versions as broadly and leniently as possible
+ */
+public final class SemanticPlusTagVersionInfo implements VersionInfo {
+
+    private static final Logger logger = Logger.getLogger(SemanticPlusTagVersionInfo.class.getName());
+
+    private static final Pattern VERSION_PATTERN = Pattern.compile("(?<major>\\d+).?(?<minor>\\d*).?(?<release>\\d*)\\W?(?<tag>.*)");
+
+    private final Integer major;
+    private final Integer minor;
+    private final Integer release;
+    private final String tag;
+    private final String versionString;
+
+    public SemanticPlusTagVersionInfo(String versionString) {
+        this.versionString = versionString;
+
+        Matcher m = VERSION_PATTERN.matcher(versionString);
+        if (m.find()) {
+            String group = m.group("major");
+            this.major = group.isEmpty() ? null : Integer.valueOf(group);
+            group = m.group("minor");
+            this.minor = group.isEmpty() ? null : Integer.valueOf(group);
+            group = m.group("release");
+            this.release = group.isEmpty() ? null : Integer.valueOf(group);
+            group = m.group("tag");
+            this.tag = group.isEmpty() ? null : group;
+        } else {
+            this.major = null;
+            this.minor = null;
+            this.release = null;
+            this.tag = null;
+
+            logger.log(Level.INFO, "Failed to parse version information from version string {0}", versionString);
+        }
+    }
+
+    public static VersionInfo of(String versionString) {
+        return new SemanticPlusTagVersionInfo(versionString);
+    }
+
+    @Override
+    public Integer getMajor() {
+        return major;
+    }
+
+    @Override
+    public Integer getMinor() {
+        return minor;
+    }
+
+    @Override
+    public Integer getRelease() {
+        return release;
+    }
+
+    @Override
+    public String getType() {
+        return tag;
+    }
+
+    @Override
+    public String getVersionString() {
+        return versionString;
+    }
+
+    @Override
+    public int compareTo(VersionInfo o) {
+        int result = compareVersionDigit(getMajor(), o.getMajor());
+        if (result != 0) {
+            return result;
+        }
+        result = compareVersionDigit(getMinor(), o.getMinor());
+        if (result != 0) {
+            return result;
+        }
+        result = compareVersionDigit(getRelease(), o.getRelease());
+        if (result != 0) {
+            return result;
+        }
+
+        result = Collator.getInstance().compare(getType() != null ? getType() : "", o.getType() != null ? o.getType() : "");
+        if (result != 0) {
+            return result;
+        }
+
+        return Collator.getInstance().compare(getVersionString(), o.getVersionString());
+    }
+
+    private int compareVersionDigit(Integer versionDigit1, Integer versionDigit2) {
+        if (versionDigit1 == null || versionDigit2 == null) {
+            return 0;
+        }
+
+        return Integer.compare(versionDigit1, versionDigit2);
+    }
+
+    @Override
+    public String toString() {
+        return versionString;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(this.versionString);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof VersionInfo)) {
+            return false;
+        }
+        final VersionInfo other = (VersionInfo) obj;
+        return Objects.equals(this.versionString, other.getVersionString());
+    }
+
+}

+ 60 - 0
jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/TemplateLibrary.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.gradledesktop.options;
+
+/**
+ * Our Gradle template library
+ */
+public interface TemplateLibrary extends MavenArtifact {
+
+    /**
+     * Get the label for this library.
+     *
+     * @return the label for this library.
+     */
+    public String getLabel();
+
+    /**
+     * Get the long description for this library.
+     *
+     * @return the long description for this library.
+     */
+    public String getDescription();
+
+    /**
+     * Is this a Core jMonkeyEngine library?
+     *
+     * @return true if this is a core jMonkeyEngine library
+     */
+    boolean getIsCoreJmeLibrary();
+
+}

+ 76 - 0
jme3-templates/src/com/jme3/gde/templates/gradledesktop/options/VersionInfo.java

@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.gradledesktop.options;
+
+/**
+ * Represents version information that can be compared between each other (which
+ * one is more new etc.)
+ */
+public interface VersionInfo extends Comparable<VersionInfo> {
+
+    /**
+     * Get major version number. Maybe the only version indicator on some
+     * schemes
+     *
+     * @return major version number or <code>null</code> if not exists
+     */
+    Integer getMajor();
+
+    /**
+     * Get minor version number
+     *
+     * @return minor version number or <code>null</code> if not exists
+     */
+    Integer getMinor();
+
+    /**
+     * Get release version number
+     *
+     * @return release version number or <code>null</code> if not exists
+     */
+    Integer getRelease();
+
+    /**
+     * Get type version type identifier
+     *
+     * @return version type identifier or <code>null</code> if not exists
+     */
+    String getType();
+
+    /**
+     * Get the original or complete version string presentation
+     *
+     * @return version string presentation
+     */
+    String getVersionString();
+
+}

+ 140 - 0
jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/MavenApiVersionChecker.java

@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.utils.mavensearch;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializer;
+import com.jme3.gde.templates.utils.mavensearch.models.SearchResult;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import static java.util.stream.Collectors.joining;
+
+/**
+ * Checks versions from the Maven Search API
+ */
+public class MavenApiVersionChecker implements MavenVersionChecker {
+
+    private static final String API_URL = "https://search.maven.org/solrsearch/select";
+
+    private static final Gson GSON;
+
+    static {
+        JsonDeserializer<Instant> instantDeserializer = (jSon, typeOfT, context) -> jSon == null ? null : Instant.ofEpochMilli(jSon.getAsLong());
+
+        GSON = new GsonBuilder()
+                .registerTypeAdapter(Instant.class, instantDeserializer).create();
+    }
+
+    @Override
+    public CompletableFuture<List<String>> getAllVersions(String groupId, String artifactId) {
+        Map<String, String> queryParams = new HashMap<>();
+        queryParams.put("q", String.format("g:%s AND a:%s", groupId, artifactId));
+        queryParams.put("core", "gav");
+        queryParams.put("rows", "50");
+        queryParams.put("wt", "json");
+
+        try {
+            return callApi(queryParams, SearchResult.class).thenApply((result) -> {
+                if (result == null || result.response.docs.isEmpty()) {
+                    return null;
+                }
+
+                return result.response.docs.stream().map((doc) -> doc.v).collect(Collectors.toList());
+            });
+        } catch (InterruptedException | ExecutionException ex) {
+            throw new MavenVersionCheckException("Failed to get version info!", ex);
+        }
+    }
+
+    @Override
+    public CompletableFuture<String> getLatestVersion(String groupId, String artifactId) {
+        Map<String, String> queryParams = new HashMap<>();
+        queryParams.put("q", String.format("g:%s AND a:%s", groupId, artifactId));
+        queryParams.put("wt", "json");
+
+        try {
+            return callApi(queryParams, SearchResult.class).thenApply((result) -> {
+                if (result == null || result.response.docs.isEmpty()) {
+                    return null;
+                }
+
+                return result.response.docs.get(0).latestVersion;
+            });
+        } catch (InterruptedException | ExecutionException ex) {
+            throw new MavenVersionCheckException("Failed to get latest version info!", ex);
+        }
+    }
+
+    private static <T> CompletableFuture<T> callApi(Map<String, String> queryParams, Class<T> clazz) throws InterruptedException, ExecutionException {
+        String encodedURL = queryParams.keySet().stream()
+                .map(key -> key + "=" + encodeValue(queryParams.get(key)))
+                .collect(joining("&", API_URL + "?", ""));
+
+        HttpClient client = HttpClient.newHttpClient();
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create(encodedURL))
+                .build();
+
+        CompletableFuture<T> result = client.sendAsync(request, BodyHandlers.ofString())
+                .thenApply((t) -> {
+                    if (t.statusCode() != 200) {
+                        throw new MavenVersionCheckException("Calling " + encodedURL + " not OK. API response " + t.statusCode() + "!");
+                    }
+
+            return GSON.fromJson(t.body(), clazz);
+                });
+
+        return result;
+    }
+
+    private static String encodeValue(final String value) {
+        try {
+            return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
+        } catch (UnsupportedEncodingException ex) {
+            throw new MavenVersionCheckException("Failed to encode value!", ex);
+        }
+    }
+
+}

+ 47 - 0
jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/MavenVersionCheckException.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.utils.mavensearch;
+
+/**
+ * Exception with Maven version check
+ */
+public class MavenVersionCheckException extends RuntimeException {
+
+    public MavenVersionCheckException(String message) {
+        super(message);
+    }
+
+    public MavenVersionCheckException(String message, Exception cause) {
+        super(message, cause);
+    }
+
+}

+ 46 - 0
jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/MavenVersionChecker.java

@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.utils.mavensearch;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Search Maven Central for versions
+ */
+public interface MavenVersionChecker {
+
+    CompletableFuture<List<String>> getAllVersions(String groupId, String artifactId);
+
+    CompletableFuture<String> getLatestVersion(String groupId, String artifactId);
+
+}

+ 38 - 0
jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/models/DownloadLink.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.utils.mavensearch.models;
+
+public class DownloadLink {
+
+    public String name;
+    public String link;
+}

+ 52 - 0
jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/models/SearchDoc.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.utils.mavensearch.models;
+
+import java.time.Instant;
+import java.util.List;
+
+public class SearchDoc {
+
+    public String id;
+    public String g;
+    public String a;
+    public String v;
+    public String latestVersion;
+    public String repositoryId;
+    public String p;
+    public Instant timestamp;
+    public Integer versionCount;
+    public List<String> text;
+    public List<String> ec;
+    public List<DownloadLink> downloadLinks;
+
+}

+ 42 - 0
jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/models/SearchResponse.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.utils.mavensearch.models;
+
+import java.util.List;
+
+public class SearchResponse {
+
+    public List<SearchDoc> docs;
+    public Integer numFound;
+    public Integer start;
+
+}

+ 41 - 0
jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/models/SearchResult.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.utils.mavensearch.models;
+
+/**
+ * Main level response from Maven Search API
+ */
+public class SearchResult {
+    public SearchResponse response;
+    public Object responseHeader;
+    public SearchSpellcheck spellcheck;
+}

+ 40 - 0
jme3-templates/src/com/jme3/gde/templates/utils/mavensearch/models/SearchSpellcheck.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2009-2023 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.templates.utils.mavensearch.models;
+
+import java.util.List;
+
+public class SearchSpellcheck {
+
+    public List<Object> suggestions;
+
+}