diff --git a/scripts/cn1playground/README.md b/scripts/cn1playground/README.md index 46f1d0e35c..593f8b611f 100644 --- a/scripts/cn1playground/README.md +++ b/scripts/cn1playground/README.md @@ -206,7 +206,7 @@ The playground uses a customized version of [BeanShell](https://github.com/beans #### Class Declarations Have Limited Support -BeanShell's class generation is disabled in this playground, but single top-level classes are automatically unwrapped: +BeanShell's class generation is disabled in this playground, but top-level classes are automatically unwrapped: ```java // This works - playground unwraps the class: @@ -221,8 +221,7 @@ public class DemoApp { ``` **What doesn't work**: -- Nested classes -- Multiple top-level classes +- Nested classes are erased during unwrapping (references are treated as `Object` placeholders, with limited rewrite support for simple no-arg `String` methods) - Interfaces or enums - Static fields or methods that reference instance fields @@ -360,4 +359,4 @@ cd scripts/cn1playground ## Contributing -See the main Codename One repository for contribution guidelines. \ No newline at end of file +See the main Codename One repository for contribution guidelines. diff --git a/scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundRunner.java b/scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundRunner.java index 63452b2ec9..17e2aadcde 100644 --- a/scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundRunner.java +++ b/scripts/cn1playground/common/src/main/java/com/codenameone/playground/PlaygroundRunner.java @@ -216,23 +216,512 @@ private String unwrapSingleTopLevelClass(String script) { if (classBlock == null) { return null; } - if (findSingleTopLevelClass(script, classBlock.bodyEnd + 1) != null) { - return null; - } int classModifiersStart = findClassModifiersStart(script, classBlock.classStart); String prefix = script.substring(0, classModifiersStart); - String suffix = script.substring(classBlock.bodyEnd + 1); - if (containsTopLevelTypeDeclaration(classBlock.body)) { + StringBuilder body = new StringBuilder(); + int cursor = classModifiersStart; + while (classBlock != null) { + if (containsNonWhitespace(script.substring(cursor, classModifiersStart))) { + return null; + } + StripTypeDeclarationsResult stripped = stripTopLevelTypeDeclarations(classBlock.body); + if (stripped == null) { + return null; + } + String classBody = rewriteNestedTypeReferences(stripped.body, stripped); + if (containsFieldDeclaration(classBody)) { + classBody = transformFieldDeclarations(classBody); + } + if (body.length() > 0) { + body.append('\n'); + } + body.append(classBody); + cursor = classBlock.bodyEnd + 1; + classBlock = findSingleTopLevelClass(script, cursor); + if (classBlock != null) { + classModifiersStart = findClassModifiersStart(script, classBlock.classStart); + } + } + if (containsNonWhitespace(script.substring(cursor))) { return null; } - if (containsNonWhitespace(suffix)) { + return prefix + body.toString(); + } + + private String rewriteNestedTypeReferences(String body, StripTypeDeclarationsResult stripped) { + if (stripped.typeNames.length == 0) { + return body; + } + String rewritten = rewriteNestedTypeMethodCalls(body, stripped.nestedTypes); + StringBuilder out = new StringBuilder(); + int i = 0; + while (i < rewritten.length()) { + char ch = rewritten.charAt(i); + if (ch == '"' || ch == '\'') { + int end = skipQuoted(rewritten, i); + out.append(rewritten.substring(i, end + 1)); + i = end + 1; + continue; + } + if (startsLineComment(rewritten, i)) { + int end = skipLineComment(rewritten, i); + out.append(rewritten.substring(i, end + 1)); + i = end + 1; + continue; + } + if (startsBlockComment(rewritten, i)) { + int end = skipBlockComment(rewritten, i); + out.append(rewritten.substring(i, end + 1)); + i = end + 1; + continue; + } + if (startsWithWord(rewritten, i, "new")) { + int nameStart = skipWhitespace(rewritten, i + 3); + String matchedType = findMatchingTypeName(rewritten, nameStart, stripped.typeNames); + if (matchedType != null) { + int afterName = nameStart + matchedType.length(); + int openParen = skipWhitespace(rewritten, afterName); + if (openParen < rewritten.length() && rewritten.charAt(openParen) == '(') { + int closeParen = findMatchingParen(rewritten, openParen); + if (closeParen < 0) { + return rewritten; + } + String args = rewritten.substring(openParen + 1, closeParen); + out.append("__cn1_new_").append(matchedType).append("(").append(args).append(")"); + i = closeParen + 1; + continue; + } + } + } + String matchedType = findMatchingTypeName(rewritten, i, stripped.typeNames); + if (matchedType != null) { + out.append("Object"); + i += matchedType.length(); + continue; + } + out.append(ch); + i++; + } + return stripped.helperPrefix + out.toString(); + } + + private String rewriteNestedTypeMethodCalls(String body, NestedTypeDescriptor[] nestedTypes) { + String text = body; + for (int t = 0; t < nestedTypes.length; t++) { + NestedTypeDescriptor type = nestedTypes[t]; + for (int m = 0; m < type.simpleStringMethods.length; m++) { + SimpleStringMethod method = type.simpleStringMethods[m]; + StringBuilder out = new StringBuilder(); + int i = 0; + while (i < text.length()) { + char ch = text.charAt(i); + if (ch == '"' || ch == '\'') { + int end = skipQuoted(text, i); + out.append(text.substring(i, end + 1)); + i = end + 1; + continue; + } + if (startsLineComment(text, i)) { + int end = skipLineComment(text, i); + out.append(text.substring(i, end + 1)); + i = end + 1; + continue; + } + if (startsBlockComment(text, i)) { + int end = skipBlockComment(text, i); + out.append(text.substring(i, end + 1)); + i = end + 1; + continue; + } + if (!isIdentifierStart(ch)) { + out.append(ch); + i++; + continue; + } + int receiverStart = i; + while (i < text.length() && isIdentifierPart(text.charAt(i))) { + i++; + } + String receiver = text.substring(receiverStart, i); + int dotPos = skipWhitespace(text, i); + if (dotPos >= text.length() || text.charAt(dotPos) != '.') { + out.append(receiver); + continue; + } + int methodPos = skipWhitespace(text, dotPos + 1); + if (!startsWithWord(text, methodPos, method.name)) { + out.append(receiver); + continue; + } + int afterMethod = methodPos + method.name.length(); + int openParen = skipWhitespace(text, afterMethod); + if (openParen >= text.length() || text.charAt(openParen) != '(') { + out.append(receiver); + continue; + } + int closeParen = skipWhitespace(text, openParen + 1); + if (closeParen >= text.length() || text.charAt(closeParen) != ')') { + out.append(receiver); + continue; + } + String expr = method.expression; + for (int f = 0; f < type.fieldNames.length; f++) { + String field = type.fieldNames[f]; + expr = replaceIdentifierWord(expr, field, + "((java.util.Hashtable)" + receiver + ").get(\"" + field + "\")"); + } + out.append('(').append(expr).append(')'); + i = closeParen + 1; + } + text = out.toString(); + } + } + return text; + } + + private String replaceIdentifierWord(String text, String word, String replacement) { + StringBuilder out = new StringBuilder(); + int i = 0; + while (i < text.length()) { + if (startsWithWord(text, i, word)) { + out.append(replacement); + i += word.length(); + } else { + out.append(text.charAt(i)); + i++; + } + } + return out.toString(); + } + + private String findMatchingTypeName(String body, int index, String[] typeNames) { + for (int i = 0; i < typeNames.length; i++) { + String type = typeNames[i]; + if (startsWithWord(body, index, type)) { + return type; + } + } + return null; + } + + private StripTypeDeclarationsResult stripTopLevelTypeDeclarations(String body) { + StringBuilder out = new StringBuilder(); + List typeNames = new ArrayList(); + List nestedTypes = new ArrayList(); + int depth = 0; + int i = 0; + int last = 0; + while (i < body.length()) { + char ch = body.charAt(i); + if (ch == '"' || ch == '\'') { + i = skipQuoted(body, i) + 1; + continue; + } + if (startsLineComment(body, i)) { + i = skipLineComment(body, i) + 1; + continue; + } + if (startsBlockComment(body, i)) { + i = skipBlockComment(body, i) + 1; + continue; + } + if (ch == '{') { + depth++; + i++; + continue; + } + if (ch == '}') { + depth--; + i++; + continue; + } + if (depth == 0 && (startsWithWord(body, i, "class") + || startsWithWord(body, i, "interface") + || startsWithWord(body, i, "enum"))) { + int namePos = skipWhitespace(body, i + indexOfWordEnd(body, i)); + String typeName = readIdentifier(body, namePos); + if (typeName != null) { + typeNames.add(typeName); + } + int openingBrace = findOpeningBrace(body, i); + if (openingBrace < 0) { + return null; + } + int closingBrace = findMatchingBrace(body, openingBrace); + if (closingBrace < 0) { + return null; + } + if (typeName != null) { + nestedTypes.add(parseNestedTypeDescriptor(typeName, body.substring(openingBrace + 1, closingBrace))); + } + out.append(body.substring(last, i)); + i = closingBrace + 1; + while (i < body.length() && Character.isWhitespace(body.charAt(i))) { + i++; + } + if (i < body.length() && body.charAt(i) == ';') { + i++; + } + last = i; + continue; + } + i++; + } + if (last < body.length()) { + out.append(body.substring(last)); + } + return new StripTypeDeclarationsResult(out.toString(), typeNames, nestedTypes, + buildNestedTypeHelpers(nestedTypes.toArray(new NestedTypeDescriptor[nestedTypes.size()]))); + } + + private NestedTypeDescriptor parseNestedTypeDescriptor(String typeName, String typeBody) { + List fieldNames = new ArrayList(); + List methods = new ArrayList(); + ConstructorPlan constructor = null; + int depth = 0; + int stmtStart = 0; + for (int i = 0; i < typeBody.length(); i++) { + char ch = typeBody.charAt(i); + if (ch == '"' || ch == '\'') { + i = skipQuoted(typeBody, i); + continue; + } + if (startsLineComment(typeBody, i)) { + i = skipLineComment(typeBody, i); + continue; + } + if (startsBlockComment(typeBody, i)) { + i = skipBlockComment(typeBody, i); + continue; + } + if (ch == '{') { + if (depth == 0) { + String header = typeBody.substring(stmtStart, i).trim(); + int bodyEnd = findMatchingBrace(typeBody, i); + if (bodyEnd < 0) { + break; + } + String blockBody = typeBody.substring(i + 1, bodyEnd).trim(); + String memberName = parseMemberName(header); + if (typeName.equals(memberName)) { + constructor = parseConstructorPlan(header, blockBody); + } + SimpleStringMethod method = parseSimpleStringMethod(header, blockBody); + if (method != null) { + methods.add(method); + } + i = bodyEnd; + stmtStart = i + 1; + continue; + } + depth++; + } else if (ch == '}') { + depth--; + } else if (depth == 0 && ch == ';') { + String statement = typeBody.substring(stmtStart, i + 1).trim(); + String field = parseFieldName(statement); + if (field != null) { + fieldNames.add(field); + } + stmtStart = i + 1; + } + } + return new NestedTypeDescriptor(typeName, fieldNames, methods, constructor); + } + + private String parseMemberName(String header) { + int openParen = header.indexOf('('); + if (openParen < 0) { return null; } - String body = classBlock.body; - if (containsFieldDeclaration(body)) { - body = transformFieldDeclarations(body); + int nameEnd = openParen - 1; + while (nameEnd >= 0 && Character.isWhitespace(header.charAt(nameEnd))) { + nameEnd--; } - return prefix + body; + int nameStart = nameEnd; + while (nameStart >= 0 && isIdentifierPart(header.charAt(nameStart))) { + nameStart--; + } + nameStart++; + if (nameStart > nameEnd) { + return null; + } + return header.substring(nameStart, nameEnd + 1); + } + + private ConstructorPlan parseConstructorPlan(String header, String body) { + int openParen = header.indexOf('('); + int closeParen = header.indexOf(')', openParen + 1); + if (openParen < 0 || closeParen < 0) { + return new ConstructorPlan(new String[0], new String[0], new String[0]); + } + String params = header.substring(openParen + 1, closeParen).trim(); + List paramNames = new ArrayList(); + if (params.length() > 0) { + int start = 0; + while (start < params.length()) { + int comma = params.indexOf(',', start); + if (comma < 0) { + comma = params.length(); + } + String part = params.substring(start, comma).trim(); + int end = part.length() - 1; + while (end >= 0 && Character.isWhitespace(part.charAt(end))) { + end--; + } + int p = end; + while (p >= 0 && isIdentifierPart(part.charAt(p))) { + p--; + } + p++; + if (p <= end) { + paramNames.add(part.substring(p, end + 1)); + } + start = comma + 1; + } + } + List assignFields = new ArrayList(); + List assignParams = new ArrayList(); + int stmtStart = 0; + int depth = 0; + for (int i = 0; i < body.length(); i++) { + char ch = body.charAt(i); + if (ch == '"' || ch == '\'') { + i = skipQuoted(body, i); + continue; + } + if (startsLineComment(body, i)) { + i = skipLineComment(body, i); + continue; + } + if (startsBlockComment(body, i)) { + i = skipBlockComment(body, i); + continue; + } + if (ch == '{') { + depth++; + } else if (ch == '}') { + depth--; + } else if (depth == 0 && ch == ';') { + String stmt = body.substring(stmtStart, i).trim(); + if (stmt.startsWith("this.")) { + int eq = stmt.indexOf('='); + if (eq > 5) { + String field = stmt.substring(5, eq).trim(); + String expr = stmt.substring(eq + 1).trim(); + if (field.length() > 0 && expr.length() > 0) { + assignFields.add(field); + assignParams.add(expr); + } + } + } + stmtStart = i + 1; + } + } + return new ConstructorPlan( + paramNames.toArray(new String[paramNames.size()]), + assignFields.toArray(new String[assignFields.size()]), + assignParams.toArray(new String[assignParams.size()])); + } + + private String parseFieldName(String statement) { + if (statement.indexOf('(') >= 0 || statement.indexOf('=') >= 0) { + return null; + } + int end = statement.length() - 1; + while (end >= 0 && (statement.charAt(end) == ';' || Character.isWhitespace(statement.charAt(end)))) { + end--; + } + if (end < 0) { + return null; + } + int start = end; + while (start >= 0 && isIdentifierPart(statement.charAt(start))) { + start--; + } + start++; + if (start > end || !isIdentifierStart(statement.charAt(start))) { + return null; + } + return statement.substring(start, end + 1); + } + + private SimpleStringMethod parseSimpleStringMethod(String header, String body) { + int openParen = header.indexOf('('); + int closeParen = header.indexOf(')', openParen + 1); + if (openParen < 0 || closeParen < 0) { + return null; + } + String params = header.substring(openParen + 1, closeParen).trim(); + if (params.length() > 0) { + return null; + } + int nameEnd = openParen - 1; + while (nameEnd >= 0 && Character.isWhitespace(header.charAt(nameEnd))) { + nameEnd--; + } + int nameStart = nameEnd; + while (nameStart >= 0 && isIdentifierPart(header.charAt(nameStart))) { + nameStart--; + } + nameStart++; + if (nameStart > nameEnd) { + return null; + } + String methodName = header.substring(nameStart, nameEnd + 1); + if (methodName.length() == 0) { + return null; + } + String beforeName = header.substring(0, nameStart).trim(); + if (!beforeName.endsWith("String")) { + return null; + } + String trimmedBody = body.trim(); + if (!trimmedBody.startsWith("return ") || !trimmedBody.endsWith(";")) { + return null; + } + String expr = trimmedBody.substring(7, trimmedBody.length() - 1).trim(); + if (expr.length() == 0) { + return null; + } + return new SimpleStringMethod(methodName, expr); + } + + private String readIdentifier(String text, int start) { + if (start < 0 || start >= text.length() || !isIdentifierStart(text.charAt(start))) { + return null; + } + int i = start + 1; + while (i < text.length() && isIdentifierPart(text.charAt(i))) { + i++; + } + return text.substring(start, i); + } + + private String buildNestedTypeHelpers(NestedTypeDescriptor[] nestedTypes) { + StringBuilder out = new StringBuilder(); + for (int i = 0; i < nestedTypes.length; i++) { + NestedTypeDescriptor type = nestedTypes[i]; + out.append("java.util.Hashtable __cn1_new_").append(type.typeName).append('('); + ConstructorPlan ctor = type.constructorPlan; + String[] params = ctor != null ? ctor.paramNames : new String[0]; + for (int p = 0; p < params.length; p++) { + if (p > 0) { + out.append(", "); + } + out.append("Object ").append(params[p]); + } + out.append(") {\n"); + out.append("java.util.Hashtable __self = new java.util.Hashtable();\n"); + if (ctor != null) { + for (int a = 0; a < ctor.assignedFields.length; a++) { + out.append("__self.put(\"").append(ctor.assignedFields[a]).append("\", ") + .append(ctor.assignedExpressions[a]).append(");\n"); + } + } + out.append("return __self;\n}\n"); + } + return out.toString(); } private int findClassModifiersStart(String script, int classKeywordPos) { @@ -539,39 +1028,6 @@ private int findMatchingBrace(String script, int openBrace) { return -1; } - private boolean containsTopLevelTypeDeclaration(String body) { - int depth = 0; - for (int i = 0; i < body.length(); i++) { - char ch = body.charAt(i); - if (ch == '"' || ch == '\'') { - i = skipQuoted(body, i); - continue; - } - if (startsLineComment(body, i)) { - i = skipLineComment(body, i); - continue; - } - if (startsBlockComment(body, i)) { - i = skipBlockComment(body, i); - continue; - } - if (ch == '{') { - depth++; - continue; - } - if (ch == '}') { - depth--; - continue; - } - if (depth == 0 && (startsWithWord(body, i, "class") - || startsWithWord(body, i, "interface") - || startsWithWord(body, i, "enum"))) { - return true; - } - } - return false; - } - private boolean containsFieldDeclaration(String body) { int depth = 0; int i = 0; @@ -1805,4 +2261,56 @@ private static final class ClassBlock { this.body = body; } } + + private static final class StripTypeDeclarationsResult { + final String body; + final String[] typeNames; + final NestedTypeDescriptor[] nestedTypes; + final String helperPrefix; + + StripTypeDeclarationsResult(String body, List typeNames, List nestedTypes, + String helperPrefix) { + this.body = body; + this.typeNames = typeNames.toArray(new String[typeNames.size()]); + this.nestedTypes = nestedTypes.toArray(new NestedTypeDescriptor[nestedTypes.size()]); + this.helperPrefix = helperPrefix; + } + } + + private static final class NestedTypeDescriptor { + final String typeName; + final String[] fieldNames; + final SimpleStringMethod[] simpleStringMethods; + final ConstructorPlan constructorPlan; + + NestedTypeDescriptor(String typeName, List fieldNames, List methods, + ConstructorPlan constructorPlan) { + this.typeName = typeName; + this.fieldNames = fieldNames.toArray(new String[fieldNames.size()]); + this.simpleStringMethods = methods.toArray(new SimpleStringMethod[methods.size()]); + this.constructorPlan = constructorPlan; + } + } + + private static final class SimpleStringMethod { + final String name; + final String expression; + + SimpleStringMethod(String name, String expression) { + this.name = name; + this.expression = expression; + } + } + + private static final class ConstructorPlan { + final String[] paramNames; + final String[] assignedFields; + final String[] assignedExpressions; + + ConstructorPlan(String[] paramNames, String[] assignedFields, String[] assignedExpressions) { + this.paramNames = paramNames; + this.assignedFields = assignedFields; + this.assignedExpressions = assignedExpressions; + } + } } diff --git a/scripts/cn1playground/common/src/test/java/com/codenameone/playground/PlaygroundSmokeHarness.java b/scripts/cn1playground/common/src/test/java/com/codenameone/playground/PlaygroundSmokeHarness.java index c7ac006b9f..eab5793fb3 100644 --- a/scripts/cn1playground/common/src/test/java/com/codenameone/playground/PlaygroundSmokeHarness.java +++ b/scripts/cn1playground/common/src/test/java/com/codenameone/playground/PlaygroundSmokeHarness.java @@ -21,6 +21,9 @@ public static void main(String[] args) throws Exception { smokeLooseScriptListeners(); smokeLooseScriptListSnippet(); smokeLifecycleDemo(); + smokeNestedClassReferenceInLifecycleScript(); + smokeMultipleClassWrapperScript(); + smokeMultipleClassWrapperWithNestedTypeReference(); smokeRestScriptWithLambda(); smokeStringMethods(); smokeComponentTypeResolvesWithoutExplicitImport(); @@ -65,6 +68,89 @@ public void log(String message) { require(Display.getInstance().getCurrent() != shown, "Form.show() interception should not replace the host UI"); } + private static void smokeNestedClassReferenceInLifecycleScript() { + Display.init(null); + + Form host = new Form("Host", new BorderLayout()); + Container preview = new Container(new BorderLayout()); + host.add(BorderLayout.CENTER, preview); + host.show(); + + PlaygroundContext context = new PlaygroundContext(host, preview, null, + new PlaygroundContext.Logger() { + public void log(String message) { + } + }); + + PlaygroundRunner runner = new PlaygroundRunner(); + PlaygroundRunner.RunResult result = runner.run( + "import com.codename1.ui.*;\n" + + "import com.codename1.ui.layouts.*;\n" + + "public class DemoApp {\n" + + " private C last;\n" + + " public void init(Object context) {}\n" + + " public void start() {\n" + + " C c = new C(3, 4);\n" + + " last = c;\n" + + " Form form = new Form(\"Nested Ref\", BoxLayout.y());\n" + + " form.add(new Label(c.myString()));\n" + + " form.show();\n" + + " }\n" + + " class C {\n" + + " int x;\n" + + " int y;\n" + + " C(int x, int y) {\n" + + " this.x = x;\n" + + " this.y = y;\n" + + " }\n" + + " public String myString() {\n" + + " return \"X: \" + x;\n" + + " }\n" + + " }\n" + + "}\n", + context); + + require(result.getComponent() instanceof Form, + "Nested class reference script should still render: " + summarizeMessages(result)); + } + + private static void smokeMultipleClassWrapperWithNestedTypeReference() { + Display.init(null); + + Form host = new Form("Host", new BorderLayout()); + Container preview = new Container(new BorderLayout()); + host.add(BorderLayout.CENTER, preview); + host.show(); + + PlaygroundContext context = new PlaygroundContext(host, preview, null, + new PlaygroundContext.Logger() { + public void log(String message) { + } + }); + + PlaygroundRunner runner = new PlaygroundRunner(); + PlaygroundRunner.RunResult result = runner.run( + "import com.codename1.ui.*;\n" + + "import com.codename1.ui.layouts.*;\n" + + "public class Helpers {\n" + + " class Pair {\n" + + " Pair(int x, int y) {}\n" + + " }\n" + + " Pair make() { return new Pair(1, 2); }\n" + + "}\n" + + "public class Demo {\n" + + " public void start() {\n" + + " Form f = new Form(\"Multi Nested\", BoxLayout.y());\n" + + " f.add(new Label(\"ok\"));\n" + + " f.show();\n" + + " }\n" + + "}\n", + context); + + require(result.getComponent() instanceof Form, + "Multi class wrapper with nested type references should render: " + summarizeMessages(result)); + } + private static void smokeLifecycleWrapperScript() { Display.init(null); @@ -279,6 +365,54 @@ private static String summarizeMessages(PlaygroundRunner.RunResult result) { return out.toString(); } + private static void smokeMultipleClassWrapperScript() { + Display.init(null); + + Form host = new Form("Host", new BorderLayout()); + Container preview = new Container(new BorderLayout()); + host.add(BorderLayout.CENTER, preview); + host.show(); + + final List log = new ArrayList(); + PlaygroundContext context = new PlaygroundContext(host, preview, null, + new PlaygroundContext.Logger() { + public void log(String message) { + log.add(message); + } + }); + + PlaygroundRunner runner = new PlaygroundRunner(); + PlaygroundRunner.RunResult result = runner.run( + "import com.codename1.ui.*;\n" + + "import com.codename1.ui.layouts.*;\n" + + "public class Helpers {\n" + + "private String greeting;\n" + + "public void init(Object context) {\n" + + "greeting = \"Hello\";\n" + + "}\n" + + "public String message(String name) {\n" + + "return greeting + \" \" + name;\n" + + "}\n" + + "}\n" + + "public class Demo {\n" + + "public void start() {\n" + + "Form root = new Form(\"Multi Class\", BoxLayout.y());\n" + + "root.add(new Label(message(\"Playground\")));\n" + + "ctx.log(\"multi-class ok\");\n" + + "root.show();\n" + + "}\n" + + "}\n", + context); + + require(result.getComponent() != null, + "Multi class wrapper script did not produce a component: " + summarizeMessages(result)); + require(result.getComponent() instanceof Form, "Multi class wrapper script should return the shown Form"); + require("Multi Class".equals(((Form) result.getComponent()).getTitle()), + "Multi class wrapper script should preserve shown form title"); + require(log.size() == 1 && "multi-class ok".equals(log.get(0)), + "Multi class wrapper script did not run expected body"); + } + private static void smokeLifecycleDemo() { Display.init(null); @@ -307,6 +441,7 @@ public void log(String message) { + " public void init(Object context) {}\n" + "\n" + " public void start() {\n" + + " C c = new C(3, 4);\n" + " Form form = new Form(\"Lifecycle Demo\", BoxLayout.y());\n" + " status = new Label(\"Ready\");\n" + " Button button = new Button(\"Tap me\");\n" @@ -319,6 +454,14 @@ public void log(String message) { + " form.addAll(new Label(\"Lifecycle-style scripts are the easiest place to test listeners.\"), button, status);\n" + " form.show();\n" + " }\n" + + " class C {\n" + + " int x;\n" + + " int y;\n" + + " C(int x, int y) {\n" + + " this.x = x;\n" + + " this.y = y;\n" + + " }\n" + + " }\n" + "}\n", context);