diff --git a/.gitignore b/.gitignore index 84e0b0b5..2ebac306 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ network_*.svg sorting_networks /stats-config.txt dependency-reduced-pom.xml -scripts +/scripts diff --git a/src/main/java/io/github/arrayv/groovyapi/ArrayVEventHandler.java b/src/main/java/io/github/arrayv/groovyapi/ArrayVEventHandler.java index 1e6afee3..07cca42e 100644 --- a/src/main/java/io/github/arrayv/groovyapi/ArrayVEventHandler.java +++ b/src/main/java/io/github/arrayv/groovyapi/ArrayVEventHandler.java @@ -4,7 +4,7 @@ public final class ArrayVEventHandler { public static enum EventType { - SCRIPTS_INSTALLED, + DEFAULT_SCRIPTS_INSTALLED, ARRAYV_FULLY_LOADED } diff --git a/src/main/java/io/github/arrayv/groovyapi/ScriptManager.java b/src/main/java/io/github/arrayv/groovyapi/ScriptManager.java index 6cb1cc2d..28578c3c 100644 --- a/src/main/java/io/github/arrayv/groovyapi/ScriptManager.java +++ b/src/main/java/io/github/arrayv/groovyapi/ScriptManager.java @@ -1,12 +1,18 @@ package io.github.arrayv.groovyapi; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URISyntaxException; +import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; +import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -51,7 +57,7 @@ public void run() { private final GroovyShell shell; private final Map> events; - private Map installedScripts; + private Map defaultScripts; public ScriptManager() { final CompilerConfiguration compilerConfig = new CompilerConfiguration(); @@ -65,7 +71,7 @@ public ScriptManager() { compilerConfig.getClasspath().add(INSTALLED_SCRIPTS_ROOT.getPath()); this.shell = new GroovyShell(compilerConfig); this.events = new EnumMap<>(ArrayVEventHandler.EventType.class); - this.installedScripts = null; + this.defaultScripts = null; } public GroovyShell getGroovyShell() { @@ -116,6 +122,17 @@ public Script loadScript(File path) throws IOException { return script; } + public Script loadScript(URL url) throws IOException { + Script script; + try { + script = shell.parse(url.toURI()); + } catch (URISyntaxException e) { + throw new Error(e); + } + script.run(); + return script; + } + public ScriptThread runInThread(File path) throws IOException { Script script = shell.parse(path); ScriptThread thread = new ScriptThread(path, script); @@ -123,30 +140,66 @@ public ScriptThread runInThread(File path) throws IOException { return thread; } - public Map loadInstalledScripts() throws IOException { - if (installedScripts != null) { - throw new IllegalStateException("Cannot load installed scripts more than once (i.e. you are not a privileged caller)"); + public Map loadDefaultScripts() throws IOException { + if (defaultScripts != null) { + throw new IllegalStateException("Cannot load default scripts more than once (i.e. you should not be calling this method)"); } + defaultScripts = new HashMap<>(); + loadBuiltinScripts(); + loadInstalledScripts(); + return defaultScripts; + } + + private void loadInstalledScripts() throws IOException { if (!INSTALLED_SCRIPTS_ROOT.exists()) { INSTALLED_SCRIPTS_ROOT.mkdir(); - return installedScripts = Collections.emptyMap(); // There are definitely no scripts in a newly created directory + return; } - installedScripts = new HashMap<>(); for (File subFile : INSTALLED_SCRIPTS_ROOT.listFiles()) { if (!subFile.isFile() || !subFile.getPath().endsWith(".groovy")) { continue; } Script script = loadScript(subFile); - installedScripts.put(script.getClass().getName(), script); + defaultScripts.put(script.getClass().getName(), script); + } + } + + public Map getDefaultScripts() { + if (defaultScripts == null) { + throw new Error("Cannot return default scripts before they're loaded"); + } + return defaultScripts; + } + + private void loadBuiltinScripts() throws IOException { + ClassLoader classLoader = getClass().getClassLoader(); + for (String scriptPath : findBuiltinScripts(classLoader)) { + URL scriptUrl = classLoader.getResource(scriptPath); + Script script = loadScript(scriptUrl); + defaultScripts.put(script.getClass().getName(), script); } - runEventHandlers(ArrayVEventHandler.EventType.SCRIPTS_INSTALLED); - return installedScripts; } - public Map getInstalledScripts() { - if (installedScripts == null) { - throw new Error("Cannot return installed scripts before they're loaded"); + // Modified from https://github.com/apache/groovy/blob/master/src/main/java/org/codehaus/groovy/control/SourceExtensionHandler.java + private static Set findBuiltinScripts(ClassLoader loader) throws IOException { + Set scripts = new LinkedHashSet(); + Enumeration globalServices = loader.getResources("META-INF/arrayv/io.github.arrayv.groovyapi.BuiltinScripts"); + if (!globalServices.hasMoreElements()) { + globalServices = loader.getResources("META-INF/arrayv/io.github.arrayv.groovyapi.BuiltinScripts"); + } + while (globalServices.hasMoreElements()) { + URL service = globalServices.nextElement(); + try (BufferedReader svcIn = new BufferedReader(new InputStreamReader(service.openStream()))) { + String scriptBasePath = svcIn.readLine(); + while (scriptBasePath != null) { + scriptBasePath = scriptBasePath.trim(); + if (!scriptBasePath.isEmpty() && !scriptBasePath.startsWith("#")) { + scripts.add(scriptBasePath); + } + scriptBasePath = svcIn.readLine(); + } + } } - return installedScripts; + return scripts; } } diff --git a/src/main/java/io/github/arrayv/main/ArrayVisualizer.java b/src/main/java/io/github/arrayv/main/ArrayVisualizer.java index b194d890..2da45fb4 100644 --- a/src/main/java/io/github/arrayv/main/ArrayVisualizer.java +++ b/src/main/java/io/github/arrayv/main/ArrayVisualizer.java @@ -499,8 +499,6 @@ public void run() { this.ANTI_Q_SORT = new AntiQSort(this); this.SCRIPT_MANAGER = new ScriptManager(); - SCRIPT_MANAGER.loadInstalledScripts(); - SoundFrame test = new SoundFrame(this.SOUNDS); test.setVisible(true); @@ -578,6 +576,9 @@ public void run() { this.ch = 0; this.cw = 0; + SCRIPT_MANAGER.loadDefaultScripts(); + SCRIPT_MANAGER.runEventHandlers(ArrayVEventHandler.EventType.DEFAULT_SCRIPTS_INSTALLED); + this.ArrayManager.initializeArray(this.array); //TODO: Overhaul visual code to properly reflect Swing (JavaFX?) style and conventions diff --git a/src/main/java/io/github/arrayv/main/SortAnalyzer.java b/src/main/java/io/github/arrayv/main/SortAnalyzer.java index 0980f930..5283ca70 100644 --- a/src/main/java/io/github/arrayv/main/SortAnalyzer.java +++ b/src/main/java/io/github/arrayv/main/SortAnalyzer.java @@ -129,10 +129,6 @@ public boolean didSortComeFromExtra(Class sort) { return EXTRA_SORTS.contains(sort); } - public SortInfo getSortByName(SortNameType nameType, String name) { - return SORTS_BY_NAME.getOrDefault(nameType, Collections.emptyMap()).get(name); - } - public SortInfo addSort(SortInfo sort) { sort = sort.withId(sorts.size()); sorts.add(sort); @@ -229,6 +225,10 @@ private void addSortByName(SortInfo sort) { getSortNameCategory(SortNameType.SHOWCASE_NAME).put(sort.getRunAllName(), sort); } + public SortInfo getSortByName(SortNameType nameType, String name) { + return getSortNameCategory(nameType).get(name); + } + private ClassGraph classGraph(boolean includeExtras) { ClassGraph classGraph = new ClassGraph() .acceptPackages("sorts", "io.github.arrayv.sorts") diff --git a/src/main/java/io/github/arrayv/prompts/SortPrompt.java b/src/main/java/io/github/arrayv/prompts/SortPrompt.java index 9fc89f09..0a743a36 100644 --- a/src/main/java/io/github/arrayv/prompts/SortPrompt.java +++ b/src/main/java/io/github/arrayv/prompts/SortPrompt.java @@ -6,7 +6,8 @@ import java.awt.event.ActionListener; import java.io.File; import java.util.ArrayList; -import java.util.Hashtable; +import java.util.HashMap; +import java.util.Map; import javax.swing.DefaultComboBoxModel; import javax.swing.GroupLayout.Alignment; @@ -28,18 +29,7 @@ import io.github.arrayv.main.SortAnalyzer; import io.github.arrayv.panes.JErrorPane; import io.github.arrayv.sortdata.SortInfo; -import io.github.arrayv.threads.MultipleSortThread; import io.github.arrayv.threads.RunAllSorts; -import io.github.arrayv.threads.RunConcurrentSorts; -import io.github.arrayv.threads.RunDistributionSorts; -import io.github.arrayv.threads.RunExchangeSorts; -import io.github.arrayv.threads.RunHybridSorts; -import io.github.arrayv.threads.RunImpracticalSorts; -import io.github.arrayv.threads.RunInsertionSorts; -import io.github.arrayv.threads.RunMergeSorts; -import io.github.arrayv.threads.RunMiscellaneousSorts; -import io.github.arrayv.threads.RunQuickSorts; -import io.github.arrayv.threads.RunSelectionSorts; import io.github.arrayv.threads.RunSort; /* @@ -108,7 +98,7 @@ public void setPlaceholder(final String s) { private static final long serialVersionUID = 1L; - private Hashtable categorySortThreads; + private static final Map CATEGORY_SORT_THREADS = new HashMap<>(); private int[] array; @@ -124,7 +114,6 @@ public SortPrompt(int[] array, ArrayVisualizer arrayVisualizer, JFrame frame, Ut setAlwaysOnTop(true); setUndecorated(true); - loadSortThreads(); initComponents(); if (lastCategory == -1) { for (lastCategory = 1; ; lastCategory++) { @@ -142,25 +131,17 @@ public SortPrompt(int[] array, ArrayVisualizer arrayVisualizer, JFrame frame, Ut setVisible(true); } + public static void setSortThreadForCategory(String category, Runnable sortThread) { + synchronized (CATEGORY_SORT_THREADS) { + CATEGORY_SORT_THREADS.put(category, sortThread); + } + } + @Override public void reposition() { setLocation(Frame.getX()+(Frame.getWidth()-getWidth())/2,Frame.getY()+(Frame.getHeight()-getHeight())/2); } - private void loadSortThreads() { - this.categorySortThreads = new Hashtable<>(); - categorySortThreads.put("Concurrent Sorts", new RunConcurrentSorts (ArrayVisualizer)); - categorySortThreads.put("Distribution Sorts", new RunDistributionSorts (ArrayVisualizer)); - categorySortThreads.put("Exchange Sorts", new RunExchangeSorts (ArrayVisualizer)); - categorySortThreads.put("Hybrid Sorts", new RunHybridSorts (ArrayVisualizer)); - categorySortThreads.put("Impractical Sorts", new RunImpracticalSorts (ArrayVisualizer)); - categorySortThreads.put("Insertion Sorts", new RunInsertionSorts (ArrayVisualizer)); - categorySortThreads.put("Merge Sorts", new RunMergeSorts (ArrayVisualizer)); - categorySortThreads.put("Miscellaneous Sorts", new RunMiscellaneousSorts(ArrayVisualizer)); - categorySortThreads.put("Quick Sorts", new RunQuickSorts (ArrayVisualizer)); - categorySortThreads.put("Selection Sorts", new RunSelectionSorts (ArrayVisualizer)); - } - @SuppressWarnings({ "unchecked", "rawtypes" }) // //GEN-BEGIN:initComponents private void initComponents() { @@ -383,13 +364,8 @@ public void run(){ }//GEN-LAST:event_jButton1ActionPerformed private void jButton3ActionPerformed() {//GEN-FIRST:event_jButton1ActionPerformed - if (categorySortThreads.containsKey(jComboBox1.getSelectedItem())) { - MultipleSortThread thread = categorySortThreads.get(jComboBox1.getSelectedItem()); - try { - thread.reportAllSorts(array, 1, thread.getSortCount()); - } catch (Exception e) { - JErrorPane.invokeErrorMessage(e); - } + if (CATEGORY_SORT_THREADS.containsKey(jComboBox1.getSelectedItem())) { + CATEGORY_SORT_THREADS.get(jComboBox1.getSelectedItem()).run(); } UtilFrame.jButton1ResetText(); dispose(); @@ -436,7 +412,7 @@ private void loadSorts() { jButton3.setEnabled(false); } else { jButton3.setText("Run All ".concat(category)); - jButton3.setEnabled(categorySortThreads.containsKey(category)); + jButton3.setEnabled(CATEGORY_SORT_THREADS.containsKey(category)); } } diff --git a/src/main/resources/META-INF/arrayv/io.github.arrayv.groovyapi.BuiltinScripts b/src/main/resources/META-INF/arrayv/io.github.arrayv.groovyapi.BuiltinScripts new file mode 100644 index 00000000..838481ca --- /dev/null +++ b/src/main/resources/META-INF/arrayv/io.github.arrayv.groovyapi.BuiltinScripts @@ -0,0 +1,23 @@ +# The MIT License (MIT) +# +# Copyright (c) 2022 ArrayV Team +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +scripts/test.groovy diff --git a/src/main/resources/scripts/test.groovy b/src/main/resources/scripts/test.groovy new file mode 100644 index 00000000..1f8435df --- /dev/null +++ b/src/main/resources/scripts/test.groovy @@ -0,0 +1 @@ +println "Groovy API builtin scripts loaded!"