Skip to content

Commit

Permalink
Add builtin script system
Browse files Browse the repository at this point in the history
  • Loading branch information
Gaming32 committed Feb 21, 2022
1 parent eb579f7 commit 064c696
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 58 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ network_*.svg
sorting_networks
/stats-config.txt
dependency-reduced-pom.xml
scripts
/scripts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

public final class ArrayVEventHandler {
public static enum EventType {
SCRIPTS_INSTALLED,
DEFAULT_SCRIPTS_INSTALLED,
ARRAYV_FULLY_LOADED
}

Expand Down
81 changes: 67 additions & 14 deletions src/main/java/io/github/arrayv/groovyapi/ScriptManager.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -51,7 +57,7 @@ public void run() {

private final GroovyShell shell;
private final Map<ArrayVEventHandler.EventType, Set<ArrayVEventHandler>> events;
private Map<String, Script> installedScripts;
private Map<String, Script> defaultScripts;

public ScriptManager() {
final CompilerConfiguration compilerConfig = new CompilerConfiguration();
Expand All @@ -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() {
Expand Down Expand Up @@ -116,37 +122,84 @@ 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);
thread.start();
return thread;
}

public Map<String, Script> 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<String, Script> 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<String, Script> 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<String, Script> 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<String> findBuiltinScripts(ClassLoader loader) throws IOException {
Set<String> scripts = new LinkedHashSet<String>();
Enumeration<URL> 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;
}
}
5 changes: 3 additions & 2 deletions src/main/java/io/github/arrayv/main/ArrayVisualizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/io/github/arrayv/main/SortAnalyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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")
Expand Down
48 changes: 12 additions & 36 deletions src/main/java/io/github/arrayv/prompts/SortPrompt.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/*
Expand Down Expand Up @@ -108,7 +98,7 @@ public void setPlaceholder(final String s) {

private static final long serialVersionUID = 1L;

private Hashtable<String, MultipleSortThread> categorySortThreads;
private static final Map<String, Runnable> CATEGORY_SORT_THREADS = new HashMap<>();

private int[] array;

Expand All @@ -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++) {
Expand All @@ -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" })
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions src/main/resources/scripts/test.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
println "Groovy API builtin scripts loaded!"

0 comments on commit 064c696

Please sign in to comment.