diff --git a/scripts/cn1playground/README.md b/scripts/cn1playground/README.md index 75149fdb17..46f1d0e35c 100644 --- a/scripts/cn1playground/README.md +++ b/scripts/cn1playground/README.md @@ -151,13 +151,19 @@ Additional imports can be declared in scripts as needed. ### Shareable Links -The playground can generate shareable URLs that contain the script source code. Use the **"Copy Shareable Playground URL"** option in the side menu to copy a link to the clipboard. +The playground can generate shareable URLs that contain both the Java script source and CSS editor content. Use the **"Copy Shareable Playground URL"** option in the side menu to copy a link to the clipboard. + +**URL Format**: +- Java source is stored in the `code` query parameter. +- CSS source is stored in the `css` query parameter. +- Both values use URL-safe Base64 encoding. -**URL Format**: The generated URL uses a `code` query parameter with URL-safe Base64-encoded script content: ``` -https://example.com/playground?code= +https://example.com/playground?code=&css= ``` +If the CSS editor is empty, the `css` parameter is omitted. + The encoding uses URL-safe Base64 (replacing `+` with `-` and `/` with `_`, with padding removed). **Sample Links**: You can also link to built-in samples using the `sample` query parameter: diff --git a/scripts/cn1playground/common/pom.xml b/scripts/cn1playground/common/pom.xml index ee47ddee6f..5774d07df1 100644 --- a/scripts/cn1playground/common/pom.xml +++ b/scripts/cn1playground/common/pom.xml @@ -11,6 +11,7 @@ 1.0-SNAPSHOT jar + diff --git a/scripts/cn1playground/common/src/main/java/bsh/BshClassManager.java b/scripts/cn1playground/common/src/main/java/bsh/BshClassManager.java index 42540be542..9c5b0660e1 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/BshClassManager.java +++ b/scripts/cn1playground/common/src/main/java/bsh/BshClassManager.java @@ -2,7 +2,6 @@ import bsh.cn1.CN1AccessRegistry; import java.io.InputStream; -import java.io.PrintWriter; import java.util.Collections; import java.util.Hashtable; import java.util.Map; @@ -118,7 +117,7 @@ public void addClassPath(Object path) {} protected String getClassNameByUnqName(String name) throws UtilEvalError { throw cmUnavailable(); } public void addListener(Listener l) {} public void removeListener(Listener l) {} - public void dump(PrintWriter pw) { pw.println("BshClassManager: reduced CN1 runtime."); } + public void dump() {} public Class defineClass(String name, byte[] code) { throw new InterpreterError("Class generation is disabled."); } protected void classLoaderChanged() {} diff --git a/scripts/cn1playground/common/src/main/java/bsh/BshMethod.java b/scripts/cn1playground/common/src/main/java/bsh/BshMethod.java index 52ea412d5b..124f6cb855 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/BshMethod.java +++ b/scripts/cn1playground/common/src/main/java/bsh/BshMethod.java @@ -27,7 +27,6 @@ package bsh; import java.io.Serializable; -import java.util.Arrays; /** This represents an instance of a bsh method declaration in a particular @@ -592,8 +591,15 @@ private void reloadTypes() { /** {@inheritDoc} */ @Override public void classLoaderChanged() { - reload = Reflect.isGeneratedClass(creturnType) - || Arrays.asList(cparamTypes).stream() - .anyMatch(Reflect::isGeneratedClass); + reload = Reflect.isGeneratedClass(creturnType); + if (reload || cparamTypes == null) { + return; + } + for (Class paramType : cparamTypes) { + if (Reflect.isGeneratedClass(paramType)) { + reload = true; + return; + } + } } } diff --git a/scripts/cn1playground/common/src/main/java/bsh/FileReader.java b/scripts/cn1playground/common/src/main/java/bsh/FileReader.java index c41dd75fc8..7c37e06454 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/FileReader.java +++ b/scripts/cn1playground/common/src/main/java/bsh/FileReader.java @@ -1,12 +1,11 @@ package bsh; -import java.io.FileNotFoundException; import java.io.InputStream; import java.io.InputStreamReader; /** Reduced UTF-8 reader wrapper for CN1 runtime use. */ final public class FileReader extends InputStreamReader { - public FileReader(String path) throws FileNotFoundException { + public FileReader(String path) { this(openUnsupported(path)); } @@ -14,7 +13,7 @@ public FileReader(InputStream in) { super(in); } - private static InputStream openUnsupported(String path) throws FileNotFoundException { - throw new FileNotFoundException("File access is disabled in the reduced CN1 runtime: " + path); + private static InputStream openUnsupported(String path) { + throw new UnsupportedOperationException("File access is disabled in the reduced CN1 runtime: " + path); } } diff --git a/scripts/cn1playground/common/src/main/java/bsh/Interpreter.java b/scripts/cn1playground/common/src/main/java/bsh/Interpreter.java index 52798d4712..46f6e5f225 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/Interpreter.java +++ b/scripts/cn1playground/common/src/main/java/bsh/Interpreter.java @@ -28,7 +28,6 @@ package bsh; import java.io.IOException; -import java.io.ObjectInputStream; import java.io.PrintStream; import java.io.Reader; import java.io.Serializable; @@ -1115,20 +1114,6 @@ public Interpreter getParent() { return parent; } - /** - De-serialization setup. - Default out and err streams to stdout, stderr if they are null. - */ - private void readObject(ObjectInputStream stream) - throws IOException, ClassNotFoundException - { - stream.defaultReadObject(); - - // set transient fields - setOut( System.out ); - setErr( System.err ); - } - /** Get the prompt string defined by the getBshPrompt() method in the global namespace. This may be from the getBshPrompt() command or may diff --git a/scripts/cn1playground/common/src/main/java/bsh/JavaCharStream.java b/scripts/cn1playground/common/src/main/java/bsh/JavaCharStream.java index 464a6223be..2cad192f8e 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/JavaCharStream.java +++ b/scripts/cn1playground/common/src/main/java/bsh/JavaCharStream.java @@ -298,7 +298,7 @@ public JavaCharStream(final java.io.InputStream dstream, final int startcolumn, final int buffersize) { - this(new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); } /** Constructor. */ @@ -340,7 +340,7 @@ public void reInit(final java.io.InputStream dstream, final int startcolumn, final int buffersize) { - reInit(new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize); + reInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); } @Override diff --git a/scripts/cn1playground/common/src/main/java/bsh/LHS.java b/scripts/cn1playground/common/src/main/java/bsh/LHS.java index c3e4e0063b..510cb8b0b8 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/LHS.java +++ b/scripts/cn1playground/common/src/main/java/bsh/LHS.java @@ -25,9 +25,6 @@ *****************************************************************************/ package bsh; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Map.Entry; import java.util.Objects; @@ -327,29 +324,4 @@ public String toString() { +(nameSpace!=null ? " nameSpace = "+nameSpace.toString(): ""); } - /** Reflect Field is not serializable, hide it. - * @param s serializer - * @throws IOException mandatory throwing exception */ - private synchronized void writeObject(final ObjectOutputStream s) throws IOException { - if ( null != field ) { - this.object = field.getDeclaringClass(); - this.varName = field.getName(); - this.field = null; - } - s.defaultWriteObject(); - } - - /** Fetch field removed from serializer. - * @param in secializer. - * @throws IOException mandatory throwing exception - * @throws ClassNotFoundException mandatory throwing exception */ - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - if ( null == this.object ) - return; - Class cls = this.object.getClass(); - if ( this.object instanceof Class ) - cls = (Class) this.object; - this.field = BshClassManager.memberCache.get(cls).findField(varName); - } } diff --git a/scripts/cn1playground/common/src/main/java/bsh/NameSpace.java b/scripts/cn1playground/common/src/main/java/bsh/NameSpace.java index c33ce878f5..3e58d4f293 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/NameSpace.java +++ b/scripts/cn1playground/common/src/main/java/bsh/NameSpace.java @@ -25,9 +25,6 @@ *****************************************************************************/ package bsh; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; @@ -1135,25 +1132,6 @@ public String toString() { + (this.classInstance != null ? " (class instance) " : ""); } - /** Write object. - * @param s the s - * @throws IOException Signals that an I/O exception has occurred. For - * serialization. Don't serialize non-serializable objects. */ - private synchronized void writeObject(final ObjectOutputStream s) - throws IOException { - // clear name resolvers... don't know if this is necessary. - this.names.clear(); - s.defaultWriteObject(); - } - /** Re-initialize transient members. - * @param in the serializer - * @throws IOException mandatory throwing exception - * @throws ClassNotFoundException mandatory throwing exception */ - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - - this.classCache = new HashMap<>(); - } /** Invoke a method in this namespace with the specified args and * interpreter reference. No caller information or call stack is required. * The method will appear as if called externally from Java. diff --git a/scripts/cn1playground/common/src/main/java/bsh/SecurityError.java b/scripts/cn1playground/common/src/main/java/bsh/SecurityError.java index 9f00d943b5..171e969da0 100644 --- a/scripts/cn1playground/common/src/main/java/bsh/SecurityError.java +++ b/scripts/cn1playground/common/src/main/java/bsh/SecurityError.java @@ -25,7 +25,14 @@ private static String argsTypesString(Object[] args) { List typesString = new ArrayList(); for (Class typeClass: Types.getTypes(args)) typesString.add(typeClass != null ? Types.prettyName(typeClass) : "null"); - return String.join(", ", typesString); + StringBuilder out = new StringBuilder(); + for (int i = 0; i < typesString.size(); i++) { + if (i > 0) { + out.append(", "); + } + out.append(typesString.get(i)); + } + return out.toString(); } /** Create a error for when can't construct a instance */ diff --git a/scripts/cn1playground/common/src/main/java/com/codenameone/playground/CN1Playground.java b/scripts/cn1playground/common/src/main/java/com/codenameone/playground/CN1Playground.java index b6dc09d914..d7f82f7f45 100644 --- a/scripts/cn1playground/common/src/main/java/com/codenameone/playground/CN1Playground.java +++ b/scripts/cn1playground/common/src/main/java/com/codenameone/playground/CN1Playground.java @@ -64,7 +64,7 @@ public void runApp() { CN.setProperty("platformHint.javascript.beforeUnloadMessage", null); theme = Resources.getGlobalResources(); currentScript = resolveInitialScript(); - currentCss = PlaygroundStateStore.loadCurrentCss(); + currentCss = resolveInitialCss(); appForm = new Form("Playground", new BorderLayout()); appForm.setUIID("PlaygroundForm"); @@ -307,12 +307,17 @@ private Button createSideMenuButton(String text, Runnable action) { private String resolveInitialScript() { String sharedScript = scriptFromUrl(); if (sharedScript != null) { - PlaygroundStateStore.saveCurrentState(sharedScript, PlaygroundStateStore.loadCurrentCss(), PlaygroundStateStore.loadCurrentOutput()); + PlaygroundStateStore.saveCurrentState(sharedScript, resolveInitialCss(), PlaygroundStateStore.loadCurrentOutput()); return sharedScript; } return PlaygroundStateStore.loadCurrentScript(); } + private String resolveInitialCss() { + String sharedCss = cssFromUrl(); + return sharedCss == null ? PlaygroundStateStore.loadCurrentCss() : sharedCss; + } + private String scriptFromUrl() { String href = CN.getProperty("browser.window.location.href", null); if (href == null || href.isEmpty()) { @@ -332,6 +337,22 @@ private String scriptFromUrl() { return found == null ? null : found.script; } + private String cssFromUrl() { + String href = CN.getProperty("browser.window.location.href", null); + if (href == null || href.isEmpty()) { + return null; + } + + String css = queryParam(href, "css"); + if (css != null && !css.isEmpty()) { + String decoded = decodeSharedScript(css); + if (decoded != null && !decoded.trim().isEmpty()) { + return decoded; + } + } + return null; + } + private String queryParam(String href, String name) { int queryStart = href.indexOf('?'); if (queryStart < 0 || queryStart == href.length() - 1) { @@ -388,7 +409,13 @@ private void copyCurrentSourceUrl() { return; } - Display.getInstance().copyToClipboard(base + "?code=" + encoded); + StringBuilder shareUrl = new StringBuilder(base).append("?code=").append(encoded); + if (currentCss != null && !currentCss.isEmpty()) { + String encodedCss = encodeSharedScript(currentCss); + shareUrl.append("&css=").append(encodedCss); + } + + Display.getInstance().copyToClipboard(shareUrl.toString()); } private String encodeSharedScript(String script) {