# HG changeset patch # User Anton Epple # Date 1378558764 -7200 # Node ID 8f8f6b84138f28894d9506d835aa2d5873625eba # Parent ded9a1b4a69cf2ced483ba4e92bf96a363977228# Parent 7b379a47e3a991c7cdd4b0788c38b19cc1f14d9d merging with default diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype-test/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype-test/pom.xml Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,48 @@ + + + 4.0.0 + + org.apidesign.bck2brwsr + ko + 0.8-SNAPSHOT + + org.apidesign.bck2brwsr + ko-archetype-test + 0.8-SNAPSHOT + Knockout Bck2Brwsr Archetype Test + http://maven.apache.org + Verifies the Knockout & net.java.html.json archetype behaves properly. + + UTF-8 + + + + ${project.groupId} + knockout4j-archetype + ${project.version} + + + org.testng + testng + test + + + org.apache.maven.shared + maven-verifier + 1.4 + test + + + ${project.groupId} + ko-fx + ${project.version} + provided + + + ${project.groupId} + ko-bck2brwsr + ${project.version} + provided + + + diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype-test/src/test/java/org/apidesign/bck2brwsr/ko/archetype/test/ArchetypeVersionTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype-test/src/test/java/org/apidesign/bck2brwsr/ko/archetype/test/ArchetypeVersionTest.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,138 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.ko.archetype.test; + +import java.io.IOException; +import java.net.URL; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import javax.xml.xpath.XPathFactoryConfigurationException; +import org.testng.annotations.Test; +import static org.testng.Assert.*; +import org.testng.annotations.BeforeClass; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * + * @author Jaroslav Tulach + */ +public class ArchetypeVersionTest { + private String version; + + public ArchetypeVersionTest() { + } + + @BeforeClass public void readCurrentVersion() throws Exception { + version = findCurrentVersion(); + assertFalse(version.isEmpty(), "There should be some version string"); + } + + + @Test public void testComparePomDepsVersions() throws Exception { + final ClassLoader l = ArchetypeVersionTest.class.getClassLoader(); + URL r = l.getResource("archetype-resources/pom.xml"); + assertNotNull(r, "Archetype pom found"); + + final XPathFactory fact = XPathFactory.newInstance(); + XPathExpression xp2 = fact.newXPath().compile( + "//properties/net.java.html.version/text()" + ); + + Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(r.openStream()); + String arch = (String) xp2.evaluate(dom, XPathConstants.STRING); + + int snapshot = arch.indexOf("-SNAPSHOT"); + assertEquals(snapshot, -1, "Don't depend on snapshots: " + arch); + + assertTrue(arch.matches("[0-9\\.]+"), "net.java.html.json version seems valid: " + arch); + } + + @Test public void testCheckLauncher() throws Exception { + final ClassLoader l = ArchetypeVersionTest.class.getClassLoader(); + URL r = l.getResource("archetype-resources/pom.xml"); + assertNotNull(r, "Archetype pom found"); + + final XPathFactory fact = XPathFactory.newInstance(); + XPathExpression xp2 = fact.newXPath().compile( + "//properties/bck2brwsr.launcher.version/text()" + ); + + Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(r.openStream()); + String arch = (String) xp2.evaluate(dom, XPathConstants.STRING); + + assertEquals(arch, version, "launcher dependency is on more recent version"); + } + + @Test public void testCheckBck2Brwsr() throws Exception { + final ClassLoader l = ArchetypeVersionTest.class.getClassLoader(); + URL r = l.getResource("archetype-resources/pom.xml"); + assertNotNull(r, "Archetype pom found"); + + final XPathFactory fact = XPathFactory.newInstance(); + XPathExpression xp2 = fact.newXPath().compile( + "//properties/bck2brwsr.version/text()" + ); + + Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(r.openStream()); + String arch = (String) xp2.evaluate(dom, XPathConstants.STRING); + + assertEquals(arch, version, "bck2brwsr dependency is on more recent version"); + } + + @Test public void testNbActions() throws Exception { + final ClassLoader l = ArchetypeVersionTest.class.getClassLoader(); + URL r = l.getResource("archetype-resources/nbactions.xml"); + assertNotNull(r, "Archetype nb file found"); + + final XPathFactory fact = XPathFactory.newInstance(); + XPathExpression xp2 = fact.newXPath().compile( + "//goal/text()" + ); + + Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(r.openStream()); + NodeList goals = (NodeList) xp2.evaluate(dom, XPathConstants.NODESET); + + for (int i = 0; i < goals.getLength(); i++) { + String s = goals.item(i).getTextContent(); + if (s.contains("apidesign")) { + assertFalse(s.matches(".*apidesign.*[0-9].*"), "No numbers: " + s); + } + } + } + + static String findCurrentVersion() throws XPathExpressionException, IOException, ParserConfigurationException, SAXException, XPathFactoryConfigurationException { + final ClassLoader l = ArchetypeVersionTest.class.getClassLoader(); + URL u = l.getResource("META-INF/maven/org.apidesign.bck2brwsr/knockout4j-archetype/pom.xml"); + assertNotNull(u, "Own pom found: " + System.getProperty("java.class.path")); + + final XPathFactory fact = XPathFactory.newInstance(); + fact.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + + XPathExpression xp = fact.newXPath().compile("project/version/text()"); + + Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(u.openStream()); + return xp.evaluate(dom); + } +} diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype-test/src/test/java/org/apidesign/bck2brwsr/ko/archetype/test/VerifyArchetypeTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype-test/src/test/java/org/apidesign/bck2brwsr/ko/archetype/test/VerifyArchetypeTest.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,116 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.ko.archetype.test; + +import java.io.File; +import java.util.Properties; +import java.util.zip.ZipFile; +import org.apache.maven.it.Verifier; +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +/** + * + * @author Jaroslav Tulach + */ +public class VerifyArchetypeTest { + @Test public void fxBrwsrCompiles() throws Exception { + final File dir = new File("target/tests/fxcompile/").getAbsoluteFile(); + generateFromArchetype(dir); + + File created = new File(dir, "o-a-test"); + assertTrue(created.isDirectory(), "Project created"); + assertTrue(new File(created, "pom.xml").isFile(), "Pom file is in there"); + + Verifier v = new Verifier(created.getAbsolutePath()); + v.executeGoal("verify"); + + v.verifyErrorFreeLog(); + + for (String l : v.loadFile(v.getBasedir(), v.getLogFileName(), false)) { + if (l.contains("j2js")) { + fail("No pre-compilaton:\n" + l); + } + } + + v.verifyTextInLog("org.apidesign.bck2brwsr.launcher.FXBrwsrLauncher"); + v.verifyTextInLog("fxcompile/o-a-test/target/o-a-test-1.0-SNAPSHOT-fxbrwsr.zip"); + } + + @Test public void bck2BrwsrCompiles() throws Exception { + final File dir = new File("target/tests/b2bcompile/").getAbsoluteFile(); + generateFromArchetype(dir); + + File created = new File(dir, "o-a-test"); + assertTrue(created.isDirectory(), "Project created"); + assertTrue(new File(created, "pom.xml").isFile(), "Pom file is in there"); + + Verifier v = new Verifier(created.getAbsolutePath()); + Properties sysProp = v.getSystemProperties(); + if (Boolean.getBoolean("java.awt.headless")) { + sysProp.put("java.awt.headless", "true"); + } + v.addCliOption("-Pbck2brwsr"); + v.executeGoal("verify"); + + v.verifyErrorFreeLog(); + + // does pre-compilation to JavaScript + v.verifyTextInLog("j2js"); + // uses Bck2BrwsrLauncher + v.verifyTextInLog("BaseHTTPLauncher showBrwsr"); + // building zip: + v.verifyTextInLog("b2bcompile/o-a-test/target/o-a-test-1.0-SNAPSHOT-bck2brwsr.zip"); + + for (String l : v.loadFile(v.getBasedir(), v.getLogFileName(), false)) { + if (l.contains("fxbrwsr")) { + fail("No fxbrwsr:\n" + l); + } + } + + File zip = new File(new File(created, "target"), "o-a-test-1.0-SNAPSHOT-bck2brwsr.zip"); + assertTrue(zip.isFile(), "Zip file with website was created"); + + ZipFile zf = new ZipFile(zip); + assertNotNull(zf.getEntry("public_html/index.html"), "index.html found"); + assertNotNull(zf.getEntry("public_html/twitterExample.css"), "css file found"); + + } + + private Verifier generateFromArchetype(final File dir, String... params) throws Exception { + Verifier v = new Verifier(dir.getAbsolutePath()); + v.setAutoclean(false); + v.setLogFileName("generate.log"); + v.deleteDirectory(""); + dir.mkdirs(); + Properties sysProp = v.getSystemProperties(); + sysProp.put("groupId", "org.apidesign.test"); + sysProp.put("artifactId", "o-a-test"); + sysProp.put("package", "org.apidesign.test.oat"); + sysProp.put("archetypeGroupId", "org.apidesign.bck2brwsr"); + sysProp.put("archetypeArtifactId", "knockout4j-archetype"); + sysProp.put("archetypeVersion", ArchetypeVersionTest.findCurrentVersion()); + + for (String p : params) { + v.addCliOption(p); + } + v.executeGoal("archetype:generate"); + v.verifyErrorFreeLog(); + return v; + } +} diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/pom.xml Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,58 @@ + + + 4.0.0 + + ko + org.apidesign.bck2brwsr + 0.8-SNAPSHOT + + org.apidesign.bck2brwsr + knockout4j-archetype + 0.8-SNAPSHOT + jar + Knockout Bck2Brwsr Maven Archetype + + HTML page with Knockout.js bindings driven by application model + written in Java. Use your favorite language to code. Use + HTML as a lightweight rendering toolkit. Deploy using JavaFX or + bck2brwsr virtual machine. + + + + + src/main/resources + true + + **/pom.xml + + + + src/main/resources + false + + **/pom.xml + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-resources-plugin + 2.6 + + \ + 1.6 + + + + + diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/java/org/apidesign/bck2brwsr/ko/archetype/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/java/org/apidesign/bck2brwsr/ko/archetype/package-info.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,18 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.ko.archetype; diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/resources/META-INF/maven/archetype-metadata.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/META-INF/maven/archetype-metadata.xml Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,63 @@ + + + + + + src/main/java + + **/*.java + + + + src/main/resources + + **/*.xhtml + **/*.html + **/*.css + + + + src/test/java + + **/*Test.java + + + + src/main/assembly + + **/*.xml + + + + + + nbactions*.xml + + + + assembly + + fxbrwsr-assembly.xml + bck2brwsr-assembly.xml + + + + \ No newline at end of file diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/resources/archetype-resources/nbactions-bck2brwsr.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/nbactions-bck2brwsr.xml Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,14 @@ + + + + run + + package + bck2brwsr:brwsr + + + true + NONE + + + diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/resources/archetype-resources/nbactions-fxbrwsr.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/nbactions-fxbrwsr.xml Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,20 @@ + + + + run + + process-classes + bck2brwsr:brwsr + + + + debug + + process-classes + bck2brwsr:brwsr + + + maven + + + diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/resources/archetype-resources/nbactions.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/nbactions.xml Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,20 @@ + + + + run + + process-classes + bck2brwsr:brwsr + + + + debug + + process-classes + bck2brwsr:brwsr + + + maven + + + diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/resources/archetype-resources/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/pom.xml Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,266 @@ + + + 4.0.0 + + \${groupId} + \${artifactId} + \${version} + jar + + \${artifactId} + + + + java.net + Java.net + https://maven.java.net/content/repositories/releases/ + + true + + + + netbeans + NetBeans + http://bits.netbeans.org/maven2/ + + + + + java.net + Java.net + https://maven.java.net/content/repositories/releases/ + + true + + + + + + UTF-8 + ${net.java.html.version} + ${project.version} + ${project.version} + MINIMAL + \${package.replace('.','/')}/index.html + + + + + org.apidesign.bck2brwsr + bck2brwsr-maven-plugin + \${bck2brwsr.launcher.version} + + + + brwsr + + + + + \${brwsr.startpage} + \${brwsr} + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.14.1 + + + \${brwsr} + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + lib/ + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.7 + + true + + + + + + + + org.testng + testng + 6.5.2 + test + + + org.apidesign.bck2brwsr + vmtest + \${bck2brwsr.version} + test + + + org.apidesign.html + net.java.html.json + \${net.java.html.version} + jar + + + + + fxbrwsr + + true + + + fxbrwsr + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + org.apidesign.bck2brwsr.launcher.FXBrwsrLauncher + true + lib/ + + + \${brwsr.startpage} + + + + + + maven-assembly-plugin + 2.4 + + + distro-assembly + package + + single + + + + src/main/assembly/fxbrwsr.xml + + + + + + + + + + org.apidesign.html + ko-fx + \${net.java.html.version} + + + org.apidesign.bck2brwsr + launcher.fx + \${bck2brwsr.launcher.version} + runtime + + + + + bck2brwsr + + + brwsr + bck2brwsr + + + + + + org.apidesign.bck2brwsr + bck2brwsr-maven-plugin + + + + j2js + + + + + \${project.build.directory}/bck2brwsr.js + \${bck2brwsr.obfuscationlevel} + + + + org.apache.maven.plugins + maven-compiler-plugin + + + netbeans.ignore.jdk.bootclasspath + + + + + maven-assembly-plugin + 2.4 + + + distro-assembly + package + + single + + + + src/main/assembly/bck2brwsr.xml + + + + + + + + + + org.apidesign.bck2brwsr + emul + \${bck2brwsr.version} + rt + + + org.apidesign.bck2brwsr + ko-bck2brwsr + \${bck2brwsr.version} + runtime + + + org.apidesign.bck2brwsr + launcher.http + \${bck2brwsr.launcher.version} + test + + + + + diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/resources/archetype-resources/src/main/assembly/bck2brwsr.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/main/assembly/bck2brwsr.xml Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,43 @@ + + + + bck2brwsr + + zip + + public_html + + + false + runtime + lib + + *:jar + *:rt + + + + + + ${project.build.directory}/classes/${package.replace('.','/')}/ + + **/* + + + **/*.class + + / + + + + + ${project.build.directory}/${project.build.finalName}.jar + / + + + ${project.build.directory}/bck2brwsr.js + / + + + \ No newline at end of file diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/resources/archetype-resources/src/main/assembly/fxbrwsr.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/main/assembly/fxbrwsr.xml Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,23 @@ + + + + fxbrwsr + + zip + + ${project.build.finalName}-fxbrwsr + + + false + runtime + lib + + + + + ${project.build.directory}/${project.build.finalName}.jar + / + + + \ No newline at end of file diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/resources/archetype-resources/src/main/java/TwitterClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/main/java/TwitterClient.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,178 @@ +package ${package}; + +import java.util.Arrays; +import java.util.List; +import net.java.html.json.ComputedProperty; +import net.java.html.json.Function; +import net.java.html.json.Model; +import net.java.html.json.OnPropertyChange; +import net.java.html.json.OnReceive; +import net.java.html.json.Property; + +@Model(className="TwitterModel", properties={ + @Property(name="savedLists", type=Tweeters.class, array = true), + @Property(name="activeTweetersName", type=String.class), + @Property(name="activeTweeters", type=String.class, array = true), + @Property(name="userNameToAdd", type=String.class), + @Property(name="loading", type=boolean.class), + @Property(name="currentTweets", type=Tweet.class, array = true) +}) +public class TwitterClient { + @Model(className = "Tweeters", properties = { + @Property(name="name", type = String.class), + @Property(name="userNames", type = String.class, array = true) + }) + static class Twttrs { + } + @Model(className = "Tweet", properties = { + @Property(name = "from_user", type = String.class), + @Property(name = "from_user_id", type = int.class), + @Property(name = "profile_image_url", type = String.class), + @Property(name = "text", type = String.class), + @Property(name = "created_at", type = String.class), + }) + static final class Twt { + @ComputedProperty static String html(String text) { + StringBuilder sb = new StringBuilder(320); + for (int pos = 0;;) { + int http = text.indexOf("http", pos); + if (http == -1) { + sb.append(text.substring(pos)); + return sb.toString(); + } + int spc = text.indexOf(' ', http); + if (spc == -1) { + spc = text.length(); + } + sb.append(text.substring(pos, http)); + String url = text.substring(http, spc); + sb.append("").append(url).append(""); + pos = spc; + } + } + + @ComputedProperty static String userUrl(String from_user) { + return "http://twitter.com/" + from_user; + } + } + @Model(className = "TwitterQuery", properties = { + @Property(array = true, name = "results", type = Twt.class) + }) + public static final class TwttrQr { + } + + @OnReceive(url="{root}/search.json?{query}&callback={me}", jsonp="me") + static void queryTweets(TwitterModel page, TwitterQuery q) { + page.getCurrentTweets().clear(); + page.getCurrentTweets().addAll(q.getResults()); + page.setLoading(false); + } + + @OnPropertyChange("activeTweetersName") + static void changeTweetersList(TwitterModel model) { + Tweeters people = findByName(model.getSavedLists(), model.getActiveTweetersName()); + model.getActiveTweeters().clear(); + model.getActiveTweeters().addAll(people.getUserNames()); + } + + @OnPropertyChange({ "activeTweeters", "activeTweetersCount" }) + static void refreshTweets(TwitterModel model) { + StringBuilder sb = new StringBuilder(); + sb.append("rpp=25&q="); + String sep = ""; + for (String p : model.getActiveTweeters()) { + sb.append(sep); + sb.append("from:"); + sb.append(p); + sep = " OR "; + } + model.setLoading(true); + model.queryTweets("http://search.twitter.com", sb.toString()); + } + + static { + final TwitterModel model = new TwitterModel(); + final List svdLst = model.getSavedLists(); + svdLst.add(newTweeters("API Design", "JaroslavTulach")); + svdLst.add(newTweeters("Celebrities", "JohnCleese", "MCHammer", "StephenFry", "algore", "StevenSanderson")); + svdLst.add(newTweeters("Microsoft people", "BillGates", "shanselman", "ScottGu")); + svdLst.add(newTweeters("NetBeans", "GeertjanW","monacotoni", "NetBeans", "petrjiricka")); + svdLst.add(newTweeters("Tech pundits", "Scobleizer", "LeoLaporte", "techcrunch", "BoingBoing", "timoreilly", "codinghorror")); + + model.setActiveTweetersName("NetBeans"); + + model.applyBindings(); + } + + @ComputedProperty + static boolean hasUnsavedChanges(List activeTweeters, List savedLists, String activeTweetersName) { + Tweeters tw = findByName(savedLists, activeTweetersName); + if (activeTweeters == null) { + return false; + } + return !tw.getUserNames().equals(activeTweeters); + } + + @ComputedProperty + static int activeTweetersCount(List activeTweeters) { + return activeTweeters.size(); + } + + @ComputedProperty + static boolean userNameToAddIsValid( + String userNameToAdd, String activeTweetersName, List savedLists, List activeTweeters + ) { + return userNameToAdd != null && + userNameToAdd.matches("[a-zA-Z0-9_]{1,15}") && + !activeTweeters.contains(userNameToAdd); + } + + @Function + static void deleteList(TwitterModel model) { + final List sl = model.getSavedLists(); + sl.remove(findByName(sl, model.getActiveTweetersName())); + if (sl.isEmpty()) { + final Tweeters t = new Tweeters(); + t.setName("New"); + sl.add(t); + } + model.setActiveTweetersName(sl.get(0).getName()); + } + + @Function + static void saveChanges(TwitterModel model) { + Tweeters t = findByName(model.getSavedLists(), model.getActiveTweetersName()); + int indx = model.getSavedLists().indexOf(t); + if (indx != -1) { + t.setName(model.getActiveTweetersName()); + t.getUserNames().clear(); + t.getUserNames().addAll(model.getActiveTweeters()); + } + } + + @Function + static void addUser(TwitterModel model) { + String n = model.getUserNameToAdd(); + model.getActiveTweeters().add(n); + } + @Function + static void removeUser(String data, TwitterModel model) { + model.getActiveTweeters().remove(data); + } + + private static Tweeters findByName(List list, String name) { + for (Tweeters l : list) { + if (l.getName() != null && l.getName().equals(name)) { + return l; + } + } + return list.isEmpty() ? new Tweeters() : list.get(0); + } + + private static Tweeters newTweeters(String listName, String... userNames) { + Tweeters t = new Tweeters(); + t.setName(listName); + t.getUserNames().addAll(Arrays.asList(userNames)); + return t; + } +} diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/resources/archetype-resources/src/main/resources/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/main/resources/index.html Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,90 @@ + + + + + + + + Bck2Brwsr's Twitter + + + + + + + +

Bck2Brwsr's Twitter

+ +

+ This code is based on original + knockout.js + Twitter example and + uses almost unmodified HTML page. It just changes the model. The model + is written in Java language with the help of + + Knockout/Java binding library + . The Java source code has about 180 lines and seems more + dense and shorter than the original JavaScript model. +

+

+ The project has two profiles. Either it executes in real Java virtual + machine and renders using JavaFX's WebView (use fxbrwsr profile + - the default). It can also run directly in a browser via + Bck2Brwsr virtual machine + (use bck2brwsr profile). +

+ +
+
+
+ + + +
+ +

Currently viewing user(s):

+
+
    +
  • + +
    +
  • +
+
+ +
+ + + +
+
+
+
Loading...
+ + + + + +
+ + +
+
+
+
+ + + + + + + diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/resources/archetype-resources/src/main/resources/twitterExample.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/main/resources/twitterExample.css Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,32 @@ +/* + Copied from knockout.js Twitter example: + http://knockoutjs.com/examples/twitter.html +*/ + +.configuration, .tweets, .tweets td { font-family: Verdana; font-size: 13px; } +.configuration { background-color: #DEDEDE; border: 2px solid gray; float:left; height: 40em; width: 40%; padding: 0.5em; border-right-width:0; } +.tweets { width: 55%; border: 2px solid gray; height: 40em; overflow: scroll; overflow-x: hidden; background-color: Black; color: White; padding: 0.5em; position: relative; } +.tweets table { border-width: 0;} +.tweets tr { vertical-align: top; } +.tweets td { padding: 0.4em 0.3em 1em 0.4em; border-width: 0; } +.tweets img { width: 4em; } +.tweetInfo { color: Gray; font-size: 0.9em; } +.twitterUser { color: #77AAFF; text-decoration: none; font-size: 1.1em; font-weight: bold; } +input.invalid { border: 1px solid red !important; background-color: #FFAAAA !important; } + +.listChooser select, .listChooser button { vertical-align:top; } +.listChooser select { width: 60%; font-size:1.2em; height:1.4em; } +.listChooser button { width: 19%; height:1.68em; float:right; } + +.currentUsers { height: 28em; overflow-y: auto; overflow-x: hidden; } +.currentUsers button { float: right; height: 2.5em; margin: 0.1em; padding-left: 1em; padding-right: 1em; } +.currentUsers ul, .configuration li { list-style: none; margin: 0; padding: 0 } +.currentUsers li { height: 2.4em; font-size: 1.2em; background-color: #A7D0E3; border: 1px solid gray; margin-bottom: 0.3em; -webkit-border-radius: 5px; -moz-border-radius: 5px; -webkit-box-shadow: 0 0.2em 0.5em gray; -moz-box-shadow: 0 0.2em 0.5em gray; } +.currentUsers li div { padding: 0.6em; } +.currentUsers li:hover { background-color: #EEC; } + +.configuration form label { width: 25%; display: inline-block; text-align:right; overflow: hidden; } +.configuration form input { width:40%; font-size: 1.3em; border:1px solid silver; background-color: White; padding: 0.1em; } +.configuration form button { width: 20%; margin-left: 0.3em; height: 2em; } + +.loadingIndicator { position: absolute; top: 0.1em; left: 0.1em; font: 0.8em Arial; background-color: #229; color: White; padding: 0.2em 0.5em 0.2em 0.5em; } diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,31 @@ +package ${package}; + +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.HtmlFragment; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** Sometimes it is useful to run tests inside of the real browser. + * To do that just annotate your method with {@link org.apidesign.bck2brwsr.vmtest.BrwsrTest} + * and that is it. If your code references elements on the HTML page, + * you can pass in an {@link org.apidesign.bck2brwsr.vmtest.HtmlFragment} which + * will be made available on the page before your test starts. + */ +public class IntegrationTest { + + /** Write to testing code here. Use assert (but not TestNG's + * Assert, as TestNG is not compiled with target 1.6 yet). + */ + @HtmlFragment( + "

Put this snippet on the HTML page

\n" + ) + @BrwsrTest + public void runThisTestInABrowser() { + } + + @Factory + public static Object[] create() { + return VMTest.create(IntegrationTest.class); + } + +} diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/resources/archetype-resources/src/test/java/TwitterClientTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/test/java/TwitterClientTest.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,50 @@ +package ${package}; + +import java.util.List; +import net.java.html.BrwsrCtx; +import net.java.html.json.Models; +import static org.testng.Assert.*; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** We can unit test the TwitterModel smoothly. + */ +public class TwitterClientTest { + private TwitterModel model; + + + @BeforeMethod + public void initModel() { + model = Models.bind(new TwitterModel(), BrwsrCtx.EMPTY); + } + + @Test public void testIsValidToAdd() { + model.setUserNameToAdd("Joe"); + Tweeters t = Models.bind(new Tweeters(), BrwsrCtx.EMPTY); + t.setName("test"); + model.getSavedLists().add(t); + model.setActiveTweetersName("test"); + + assertTrue(model.isUserNameToAddIsValid(), "Joe is OK"); + TwitterClient.addUser(model); + assertFalse(model.isUserNameToAddIsValid(), "Can't add Joe for the 2nd time"); + assertEquals(t.getUserNames().size(), 0, "Original tweeters list remains empty"); + + List mod = model.getActiveTweeters(); + assertTrue(model.isHasUnsavedChanges(), "We have modifications"); + assertEquals(mod.size(), 1, "One element in the list"); + assertEquals(mod.get(0), "Joe", "Its name is Joe"); + + assertSame(model.getActiveTweeters(), mod, "Editing list is the modified one"); + + TwitterClient.saveChanges(model); + assertFalse(model.isHasUnsavedChanges(), "Does not have anything to save"); + + assertSame(model.getActiveTweeters(), mod, "Still editing the old modified one"); + } + + @Test public void httpAtTheEnd() { + String res = TwitterClient.Twt.html("Ahoj http://kuk"); + assertEquals(res, "Ahoj http://kuk"); + } +} diff -r ded9a1b4a69c -r 8f8f6b84138f ko/archetype/src/main/resources/archetype-resources/src/test/java/TwitterProtocolTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/test/java/TwitterProtocolTest.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,73 @@ +package ${package}; + +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.Http; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +public class TwitterProtocolTest { + private TwitterModel page; + @Http(@Http.Resource( + path = "/search.json", + mimeType = "application/json", + parameters = {"callback"}, + content = "$0({\"completed_in\":0.04,\"max_id\":320055706885689344,\"max_id_str\"" + + ":\"320055706885689344\",\"page\":1,\"query\":\"from%3AJaroslavTulach\",\"refresh_url\":" + + "\"?since_id=320055706885689344&q=from%3AJaroslavTulach\"," + + "\"results\":[{\"created_at\":\"Fri, 05 Apr 2013 06:10:01 +0000\"," + + "\"from_user\":\"JaroslavTulach\",\"from_user_id\":420944648,\"from_user_id_str\":" + + "\"420944648\",\"from_user_name\":\"Jaroslav Tulach\",\"geo\":null,\"id\":320055706885689344," + + "\"id_str\":\"320055706885689344\",\"iso_language_code\":\"en\",\"metadata\":{\"result_type\":" + + "\"recent\"},\"profile_image_url\":\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"source\":\"<a href="http:\\/\\/twitter.com\\/">web<\\/a>\",\"text\":" + + "\"@tom_enebo Amzng! Not that I would like #ruby, but I am really glad you guys stabilized the plugin + " + + "made it work in #netbeans 7.3! Gd wrk.\",\"to_user\":\"tom_enebo\",\"to_user_id\":14498747," + + "\"to_user_id_str\":\"14498747\",\"to_user_name\":\"tom_enebo\",\"in_reply_to_status_id\":319832359509839872," + + "\"in_reply_to_status_id_str\":\"319832359509839872\"},{\"created_at\":\"Thu, 04 Apr 2013 07:33:06 +0000\"," + + "\"from_user\":\"JaroslavTulach\",\"from_user_id\":420944648,\"from_user_id_str\":" + + "\"420944648\",\"from_user_name\":\"Jaroslav Tulach\",\"geo\":null,\"id\":319714227088678913," + + "\"id_str\":\"319714227088678913\",\"iso_language_code\":\"en\",\"metadata\":{\"result_type\":" + + "\"recent\"},\"profile_image_url\":\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"source\":\"<a href="http:\\/\\/twitter.com\\/">web<\\/a>\",\"text\":" + + "\"RT @drkrab: At #erlangfactory @joerl: Frameworks grow in complexity until nobody can use them.\"}," + + "{\"created_at\":\"Tue, 02 Apr 2013 07:44:34 +0000\",\"from_user\":\"JaroslavTulach\"," + + "\"from_user_id\":420944648,\"from_user_id_str\":\"420944648\",\"from_user_name\":\"Jaroslav Tulach\"," + + "\"geo\":null,\"id\":318992336145248256,\"id_str\":\"318992336145248256\",\"iso_language_code\":\"en\"," + + "\"metadata\":{\"result_type\":\"recent\"},\"profile_image_url\":" + + "\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"source\":\"<a href="http:\\/\\/twitter.com\\/">web<\\/a>\",\"text\":" + + "\"Twitter renamed to twttr http:\\/\\/t.co\\/tqaN4T1xlZ - good, I don't have to rename #bck2brwsr!\"}," + + "{\"created_at\":\"Sun, 31 Mar 2013 03:52:04 +0000\",\"from_user\":\"JaroslavTulach\",\"from_user_id\":420944648," + + "\"from_user_id_str\":\"420944648\",\"from_user_name\":\"Jaroslav Tulach\",\"geo\":null," + + "\"id\":318209051223789568,\"id_str\":\"318209051223789568\",\"iso_language_code\":\"en\",\"metadata\":" + + "{\"result_type\":\"recent\"},\"profile_image_url\":" + + "\"http:\\/\\/a0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"profile_image_url_https\":\"https:\\/\\/si0.twimg.com\\/profile_images\\/1656828312\\/jst_normal.gif\"," + + "\"source\":\"<a href="http:\\/\\/twitter.com\\/">web<\\/a>\",\"text\":" + + "\"Math proofs without words. Ingenious: http:\\/\\/t.co\\/sz7yVbfpGw\"}],\"results_per_page\":100," + + "\"since_id\":0,\"since_id_str\":\"0\"})" + )) + @BrwsrTest public void readFromTwttr() throws InterruptedException { + if (page == null) { + page = new TwitterModel(); + page.applyBindings(); + page.queryTweets("", "q=xyz"); + } + + if (page.getCurrentTweets().isEmpty()) { + throw new InterruptedException(); + } + + assert 4 == page.getCurrentTweets().size() : "Four tweets: " + page.getCurrentTweets(); + + String firstDate = page.getCurrentTweets().get(0).getCreated_at(); + assert "Fri, 05 Apr 2013 06:10:01 +0000".equals(firstDate) : "Date is OK: " + firstDate; + } + + @Factory public static Object[] create() { + return VMTest.create(TwitterProtocolTest.class); + } +} diff -r ded9a1b4a69c -r 8f8f6b84138f ko/bck2brwsr/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/bck2brwsr/pom.xml Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,102 @@ + + + 4.0.0 + + org.apidesign.bck2brwsr + ko + 0.8-SNAPSHOT + + org.apidesign.bck2brwsr + ko-bck2brwsr + 0.8-SNAPSHOT + Knockout.b2b + http://maven.apache.org + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-javadoc-plugin + + false + + + + + + + org.testng + testng + test + + + junit + junit + + + + + org.netbeans.api + org-openide-util-lookup + provided + + + org.apidesign.bck2brwsr + emul + ${project.version} + rt + jar + compile + + + org.apidesign.bck2brwsr + vm4brwsr + ${project.version} + jar + test + + + org.apidesign.bck2brwsr + vmtest + ${project.version} + test + + + org.apidesign.bck2brwsr + launcher.http + ${project.version} + test + + + org.apidesign.html + net.java.html.json + ${net.java.html.version} + + + org.apidesign.html + net.java.html.json.tck + ${net.java.html.version} + test + + + org.apidesign.bck2brwsr + core + ${project.version} + jar + + + org.apidesign.html + net.java.html.boot + 0.5 + jar + + + diff -r ded9a1b4a69c -r 8f8f6b84138f ko/bck2brwsr/src/main/java/org/apidesign/bck2brwsr/ko2brwsr/BrwsrCtxImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/bck2brwsr/src/main/java/org/apidesign/bck2brwsr/ko2brwsr/BrwsrCtxImpl.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,166 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.ko2brwsr; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import org.apidesign.html.json.spi.FunctionBinding; +import org.apidesign.html.json.spi.JSONCall; +import org.apidesign.html.json.spi.PropertyBinding; +import org.apidesign.html.json.spi.Technology; +import org.apidesign.html.json.spi.Transfer; +import org.apidesign.html.json.spi.WSTransfer; + +/** + * + * @author Jaroslav Tulach + */ +final class BrwsrCtxImpl implements Technology, Transfer, WSTransfer { + private BrwsrCtxImpl() {} + + public static final BrwsrCtxImpl DEFAULT = new BrwsrCtxImpl(); + + @Override + public void extract(Object obj, String[] props, Object[] values) { + ConvertTypes.extractJSON(obj, props, values); + } + + @Override + public void loadJSON(final JSONCall call) { + class R implements Runnable { + final boolean success; + + public R(boolean success) { + this.success = success; + } + + Object[] arr = { null }; + @Override + public void run() { + if (success) { + call.notifySuccess(arr[0]); + } else { + Throwable t; + if (arr[0] instanceof Throwable) { + t = (Throwable) arr[0]; + } else { + if (arr[0] == null) { + t = new IOException(); + } else { + t = new IOException(arr[0].toString()); + } + } + call.notifyError(t); + } + } + } + R success = new R(true); + R failure = new R(false); + if (call.isJSONP()) { + String me = ConvertTypes.createJSONP(success.arr, success); + ConvertTypes.loadJSONP(call.composeURL(me), me); + } else { + String data = null; + if (call.isDoOutput()) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + call.writeData(bos); + data = new String(bos.toByteArray(), "UTF-8"); + } catch (IOException ex) { + call.notifyError(ex); + } + } + ConvertTypes.loadJSON(call.composeURL(null), success.arr, success, failure, call.getMethod(), data); + } + } + + @Override + public Object wrapModel(Object model) { + return model; + } + + @Override + public void bind(PropertyBinding b, Object model, Object data) { + Knockout.bind(data, b, b.getPropertyName(), + "getValue__Ljava_lang_Object_2", + b.isReadOnly() ? null : "setValue__VLjava_lang_Object_2", + false, false + ); + } + + @Override + public void valueHasMutated(Object data, String propertyName) { + Knockout.valueHasMutated(data, propertyName); + } + + @Override + public void expose(FunctionBinding fb, Object model, Object d) { + Knockout.expose(d, fb, fb.getFunctionName(), "call__VLjava_lang_Object_2Ljava_lang_Object_2"); + } + + @Override + public void applyBindings(Object data) { + Knockout.applyBindings(data); + } + + @Override + public Object wrapArray(Object[] arr) { + return arr; + } + + @Override + public M toModel(Class modelClass, Object data) { + return modelClass.cast(data); + } + + @Override + public Object toJSON(InputStream is) throws IOException { + StringBuilder sb = new StringBuilder(); + InputStreamReader r = new InputStreamReader(is); + for (;;) { + int ch = r.read(); + if (ch == -1) { + break; + } + sb.append((char)ch); + } + return ConvertTypes.parse(sb.toString()); + } + + @Override + public void runSafe(Runnable r) { + r.run(); + } + + @Override + public LoadWS open(String url, JSONCall callback) { + return new LoadWS(callback, url); + } + + @Override + public void send(LoadWS socket, JSONCall data) { + socket.send(data); + } + + @Override + public void close(LoadWS socket) { + socket.close(); + } +} diff -r ded9a1b4a69c -r 8f8f6b84138f ko/bck2brwsr/src/main/java/org/apidesign/bck2brwsr/ko2brwsr/BrwsrCtxPrvdr.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/bck2brwsr/src/main/java/org/apidesign/bck2brwsr/ko2brwsr/BrwsrCtxPrvdr.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,51 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.ko2brwsr; + +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.html.context.spi.Contexts; +import org.apidesign.html.json.spi.Technology; +import org.apidesign.html.json.spi.Transfer; +import org.openide.util.lookup.ServiceProvider; + +/** This is an implementation package - just + * include its JAR on classpath and use official {@link Context} API + * to access the functionality. + *

+ * Provides binding between models and + * Bck2Brwsr VM. + * Registers {@link ContextProvider}, so {@link ServiceLoader} can find it. + * + * @author Jaroslav Tulach + */ +@ServiceProvider(service = Contexts.Provider.class) +public final class BrwsrCtxPrvdr implements Contexts.Provider { + + @Override + public void fillContext(Contexts.Builder context, Class requestor) { + if (bck2BrwsrVM()) { + context.register(Technology.class, BrwsrCtxImpl.DEFAULT, 50). + register(Transfer.class, BrwsrCtxImpl.DEFAULT, 50); + } + } + + @JavaScriptBody(args = { }, body = "return true;") + private static boolean bck2BrwsrVM() { + return false; + } +} diff -r ded9a1b4a69c -r 8f8f6b84138f ko/bck2brwsr/src/main/java/org/apidesign/bck2brwsr/ko2brwsr/ConvertTypes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/bck2brwsr/src/main/java/org/apidesign/bck2brwsr/ko2brwsr/ConvertTypes.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,157 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.ko2brwsr; + +import org.apidesign.bck2brwsr.core.JavaScriptBody; + +/** + * + * @author Jaroslav Tulach + */ +final class ConvertTypes { + ConvertTypes() { + } + + public static String toString(Object object, String property) { + Object ret = getProperty(object, property); + return ret == null ? null : ret.toString(); + } + + public static double toDouble(Object object, String property) { + Object ret = getProperty(object, property); + return ret instanceof Number ? ((Number)ret).doubleValue() : Double.NaN; + } + + public static int toInt(Object object, String property) { + Object ret = getProperty(object, property); + return ret instanceof Number ? ((Number)ret).intValue() : Integer.MIN_VALUE; + } + + public static T toModel(Class modelClass, Object object, String property) { + Object ret = getProperty(object, property); + if (ret == null || modelClass.isInstance(ret)) { + return modelClass.cast(ret); + } + throw new IllegalStateException("Value " + ret + " is not of type " + modelClass); + } + + public static String toJSON(Object value) { + if (value == null) { + return "null"; + } + if (value instanceof Enum) { + value = value.toString(); + } + if (value instanceof String) { + return '"' + + ((String)value). + replace("\"", "\\\""). + replace("\n", "\\n"). + replace("\r", "\\r"). + replace("\t", "\\t") + + '"'; + } + return value.toString(); + } + + @JavaScriptBody(args = { "object", "property" }, + body = + "if (property === null) return object;\n" + + "if (object === null) return null;\n" + + "var p = object[property]; return p ? p : null;" + ) + private static Object getProperty(Object object, String property) { + return null; + } + + public static String createJSONP(Object[] jsonResult, Runnable whenDone) { + int h = whenDone.hashCode(); + String name; + for (;;) { + name = "jsonp" + Integer.toHexString(h); + if (defineIfUnused(name, jsonResult, whenDone)) { + return name; + } + h++; + } + } + + @JavaScriptBody(args = { "name", "arr", "run" }, body = + "if (window[name]) return false;\n " + + "window[name] = function(data) {\n " + + " delete window[name];\n" + + " var el = window.document.getElementById(name);\n" + + " el.parentNode.removeChild(el);\n" + + " arr[0] = data;\n" + + " run.run__V();\n" + + "};\n" + + "return true;\n" + ) + private static boolean defineIfUnused(String name, Object[] arr, Runnable run) { + return true; + } + + @JavaScriptBody(args = { "s" }, body = "return eval('(' + s + ')');") + static Object parse(String s) { + return s; + } + + @JavaScriptBody(args = { "url", "arr", "callback", "onError", "method", "data" }, body = "" + + "var request = new XMLHttpRequest();\n" + + "if (!method) method = 'GET';\n" + + "request.open(method, url, true);\n" + + "request.setRequestHeader('Content-Type', 'application/json; charset=utf-8');\n" + + "request.onreadystatechange = function() {\n" + + " if (this.readyState!==4) return;\n" + + " try {\n" + + " arr[0] = eval('(' + this.response + ')');\n" + + " } catch (error) {;\n" + + " arr[0] = this.response;\n" + + " }\n" + + " callback.run__V();\n" + + "};\n" + + "request.onerror = function (e) {\n" + + " arr[0] = e; onError.run__V();\n" + + "}\n" + + "if (data) request.send(data);" + + "else request.send();" + ) + static void loadJSON( + String url, Object[] jsonResult, Runnable whenDone, Runnable whenErr, String method, String data + ) { + } + + @JavaScriptBody(args = { "url", "jsonp" }, body = + "var scrpt = window.document.createElement('script');\n " + + "scrpt.setAttribute('src', url);\n " + + "scrpt.setAttribute('id', jsonp);\n " + + "scrpt.setAttribute('type', 'text/javascript');\n " + + "var body = document.getElementsByTagName('body')[0];\n " + + "body.appendChild(scrpt);\n" + ) + static void loadJSONP(String url, String jsonp) { + + } + + public static void extractJSON(Object jsonObject, String[] props, Object[] values) { + for (int i = 0; i < props.length; i++) { + values[i] = getProperty(jsonObject, props[i]); + } + } + +} diff -r ded9a1b4a69c -r 8f8f6b84138f ko/bck2brwsr/src/main/java/org/apidesign/bck2brwsr/ko2brwsr/Knockout.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/bck2brwsr/src/main/java/org/apidesign/bck2brwsr/ko2brwsr/Knockout.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,131 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.ko2brwsr; + +import java.lang.reflect.Method; +import java.util.List; +import org.apidesign.bck2brwsr.core.ExtraJavaScript; +import org.apidesign.bck2brwsr.core.JavaScriptBody; + +/** Provides binding between models and bck2brwsr VM. + * + * @author Jaroslav Tulach + */ +@ExtraJavaScript(resource = "/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js") +final class Knockout { + /** used by tests */ + static Knockout next; + private final Object model; + + Knockout(Object model) { + this.model = model == null ? this : model; + } + + public static Knockout applyBindings( + Object model, String[] propsGettersAndSetters, + String[] methodsAndSignatures + ) { + applyImpl(propsGettersAndSetters, model.getClass(), model, model, methodsAndSignatures); + return new Knockout(model); + } + public static Knockout applyBindings( + Class modelClass, M model, String[] propsGettersAndSetters, + String[] methodsAndSignatures + ) { + Knockout bindings = next; + next = null; + if (bindings == null) { + bindings = new Knockout(null); + } + applyImpl(propsGettersAndSetters, modelClass, bindings, model, methodsAndSignatures); + applyBindings(bindings); + return bindings; + } + + public void valueHasMutated(String prop) { + valueHasMutated(model, prop); + } + @JavaScriptBody(args = { "self", "prop" }, body = + "var p = self[prop]; if (p) p.valueHasMutated();" + ) + public static void valueHasMutated(Object self, String prop) { + } + + + @JavaScriptBody(args = { "id", "ev" }, body = "ko.utils.triggerEvent(window.document.getElementById(id), ev.substring(2));") + public static void triggerEvent(String id, String ev) { + } + + @JavaScriptBody(args = { "bindings", "model", "prop", "getter", "setter", "primitive", "array" }, body = + "var bnd = {\n" + + " 'read': function() {\n" + + " var v = model[getter]();\n" + + " if (array) v = v.koArray(); else if (v !== null) v = v.valueOf();\n" + + " return v;\n" + + " },\n" + + " 'owner': bindings\n" + + "};\n" + + "if (setter != null) {\n" + + " bnd['write'] = function(val) {\n" + + " var v = val === null ? null : val.valueOf();" + + " model[setter](v);\n" + + " };\n" + + "}\n" + + "bindings[prop] = ko['computed'](bnd);" + ) + static void bind( + Object bindings, Object model, String prop, String getter, String setter, boolean primitive, boolean array + ) { + } + + @JavaScriptBody(args = { "bindings", "model", "prop", "sig" }, body = + "bindings[prop] = function(data, ev) { model[sig](data, ev); };" + ) + static void expose( + Object bindings, Object model, String prop, String sig + ) { + } + + @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);") + static void applyBindings(Object bindings) {} + + private static void applyImpl( + String[] propsGettersAndSetters, + Class modelClass, + Object bindings, + Object model, + String[] methodsAndSignatures + ) throws IllegalStateException, SecurityException { + for (int i = 0; i < propsGettersAndSetters.length; i += 4) { + try { + Method getter = modelClass.getMethod(propsGettersAndSetters[i + 3]); + bind(bindings, model, propsGettersAndSetters[i], + propsGettersAndSetters[i + 1], + propsGettersAndSetters[i + 2], + getter.getReturnType().isPrimitive(), + List.class.isAssignableFrom(getter.getReturnType())); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException(ex.getMessage()); + } + } + for (int i = 0; i < methodsAndSignatures.length; i += 2) { + expose( + bindings, model, methodsAndSignatures[i], methodsAndSignatures[i + 1]); + } + } +} diff -r ded9a1b4a69c -r 8f8f6b84138f ko/bck2brwsr/src/main/java/org/apidesign/bck2brwsr/ko2brwsr/LoadWS.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/bck2brwsr/src/main/java/org/apidesign/bck2brwsr/ko2brwsr/LoadWS.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,126 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.ko2brwsr; + +import net.java.html.js.JavaScriptBody; +import org.apidesign.html.json.spi.JSONCall; + +/** Communication with WebSockets for WebView 1.8. + * + * @author Jaroslav Tulach + */ +final class LoadWS { + private static final boolean SUPPORTED = isWebSocket(); + private final Object ws; + private final JSONCall call; + LoadWS(JSONCall first, String url) { + call = first; + ws = initWebSocket(this, url); + if (ws == null) { + first.notifyError(new IllegalArgumentException("Wrong URL: " + url)); + } + } + + static boolean isSupported() { + return SUPPORTED; + } + + void send(JSONCall call) { + push(call); + } + + private synchronized void push(JSONCall call) { + send(ws, call.getMessage()); + } + + void onOpen(Object ev) { + if (!call.isDoOutput()) { + call.notifySuccess(null); + } + } + + + @JavaScriptBody(args = { "data" }, body = "try {\n" + + " return eval('(' + data + ')');\n" + + " } catch (error) {;\n" + + " return data;\n" + + " }\n" + ) + private static native Object toJSON(String data); + + void onMessage(Object ev, String data) { + Object json = toJSON(data); + call.notifySuccess(json); + } + + void onError(Object ev) { + call.notifyError(new Exception(ev.toString())); + } + + void onClose(boolean wasClean, int code, String reason) { + call.notifyError(null); + } + + @JavaScriptBody(args = {}, body = "if (window.WebSocket) return true; else return false;") + private static boolean isWebSocket() { + return false; + } + + @JavaScriptBody(args = { "back", "url" }, javacall = true, body = "" + + "if (window.WebSocket) {\n" + + " try {\n" + + " var ws = new window.WebSocket(url);\n" + + " ws.onopen = function(ev) {\n" + + " back.@org.apidesign.bck2brwsr.ko2brwsr.LoadWS::onOpen(Ljava/lang/Object;)(ev);\n" + + " };\n" + + " ws.onmessage = function(ev) {\n" + + " back.@org.apidesign.bck2brwsr.ko2brwsr.LoadWS::onMessage(Ljava/lang/Object;Ljava/lang/String;)(ev, ev.data);\n" + + " };\n" + + " ws.onerror = function(ev) {\n" + + " back.@org.apidesign.bck2brwsr.ko2brwsr.LoadWS::onError(Ljava/lang/Object;)(ev);\n" + + " };\n" + + " ws.onclose = function(ev) {\n" + + " back.@org.apidesign.bck2brwsr.ko2brwsr.LoadWS::onClose(ZILjava/lang/String;)(ev.wasClean, ev.code, ev.reason);\n" + + " };\n" + + " return ws;\n" + + " } catch (ex) {\n" + + " return null;\n" + + " }\n" + + "} else {\n" + + " return null;\n" + + "}\n" + ) + private static Object initWebSocket(Object back, String url) { + return null; + } + + + @JavaScriptBody(args = { "ws", "msg" }, body = "" + + "ws.send(msg);" + ) + private void send(Object ws, String msg) { + } + + @JavaScriptBody(args = { "ws" }, body = "ws.close();") + private static void close(Object ws) { + } + + void close() { + close(ws); + } +} diff -r ded9a1b4a69c -r 8f8f6b84138f ko/bck2brwsr/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/bck2brwsr/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,3614 @@ +/* + * HTML via Java(tm) Language Bindings + * Copyright (C) 2013 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. apidesign.org + * designates this particular file as subject to the + * "Classpath" exception as provided by apidesign.org + * in the License file that accompanied this code. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException + */ +// Knockout JavaScript library v2.2.1 +// (c) Steven Sanderson - http://knockoutjs.com/ +// License: MIT (http://www.opensource.org/licenses/mit-license.php) + +(function(){ +var DEBUG=true; +(function(window,document,navigator,jQuery,undefined){ +!function(factory) { + // Support three module loading scenarios + if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') { + // [1] CommonJS/Node.js + var target = module['exports'] || exports; // module.exports is for Node.js + factory(target); + } else if (typeof define === 'function' && define['amd']) { + // [2] AMD anonymous module + define(['exports'], factory); + } else { + // [3] No module loader (plain "); + }; + + if (jQueryTmplVersion > 0) { + jQuery['tmpl']['tag']['ko_code'] = { + open: "__.push($1 || '');" + }; + jQuery['tmpl']['tag']['ko_with'] = { + open: "with($1) {", + close: "} " + }; + } + }; + + ko.jqueryTmplTemplateEngine.prototype = new ko.templateEngine(); + + // Use this one by default *only if jquery.tmpl is referenced* + var jqueryTmplTemplateEngineInstance = new ko.jqueryTmplTemplateEngine(); + if (jqueryTmplTemplateEngineInstance.jQueryTmplVersion > 0) + ko.setTemplateEngine(jqueryTmplTemplateEngineInstance); + + ko.exportSymbol('jqueryTmplTemplateEngine', ko.jqueryTmplTemplateEngine); +})(); +}); +})(window,document,navigator,window["jQuery"]); +})(); \ No newline at end of file diff -r ded9a1b4a69c -r 8f8f6b84138f ko/bck2brwsr/src/test/java/org/apidesign/bck2brwsr/ko2brwsr/Bck2BrwsrKnockoutTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/bck2brwsr/src/test/java/org/apidesign/bck2brwsr/ko2brwsr/Bck2BrwsrKnockoutTest.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,120 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.ko2brwsr; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Map; +import net.java.html.BrwsrCtx; +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.apidesign.html.context.spi.Contexts; +import org.apidesign.html.json.spi.Technology; +import org.apidesign.html.json.spi.Transfer; +import org.apidesign.html.json.spi.WSTransfer; +import org.apidesign.html.json.tck.KOTest; +import org.apidesign.html.json.tck.KnockoutTCK; +import org.openide.util.lookup.ServiceProvider; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +@ServiceProvider(service = KnockoutTCK.class) +public final class Bck2BrwsrKnockoutTest extends KnockoutTCK { + @Factory public static Object[] create() { + return VMTest.newTests(). + withClasses(testClasses()). + withLaunchers("bck2brwsr"). + withTestAnnotation(KOTest.class). + build(); + } + + @Override + public BrwsrCtx createContext() { + return Contexts.newBuilder(). + register(Transfer.class, BrwsrCtxImpl.DEFAULT, 9). + register(WSTransfer.class, BrwsrCtxImpl.DEFAULT, 9). + register(Technology.class, BrwsrCtxImpl.DEFAULT, 9).build(); + } + + + + @Override + public Object createJSON(Map values) { + Object json = createJSON(); + + for (Map.Entry entry : values.entrySet()) { + putValue(json, entry.getKey(), entry.getValue()); + } + return json; + } + + @JavaScriptBody(args = {}, body = "return new Object();") + private static native Object createJSON(); + + @JavaScriptBody(args = { "json", "key", "value" }, body = "json[key] = value;") + private static native void putValue(Object json, String key, Object value); + + @Override + public Object executeScript(String script, Object[] arguments) { + return execScript(script, arguments); + } + + @JavaScriptBody(args = { "s", "args" }, body = + "var f = new Function(s); return f.apply(null, args);" + ) + private static native Object execScript(String s, Object[] arguments); + + @JavaScriptBody(args = { }, body = + "var h;" + + "if (!!window && !!window.location && !!window.location.href)\n" + + " h = window.location.href;\n" + + "else " + + " h = null;" + + "return h;\n" + ) + private static native String findBaseURL(); + + @Override + public URI prepareURL(String content, String mimeType, String[] parameters) { + try { + final URL baseURL = new URL(findBaseURL()); + StringBuilder sb = new StringBuilder(); + sb.append("/dynamic?mimeType=").append(mimeType); + for (int i = 0; i < parameters.length; i++) { + sb.append("¶m" + i).append("=").append(parameters[i]); + } + String mangle = content.replace("\n", "%0a") + .replace("\"", "\\\"").replace(" ", "%20"); + sb.append("&content=").append(mangle); + + URL query = new URL(baseURL, sb.toString()); + String uri = (String) query.getContent(new Class[] { String.class }); + URI connectTo = new URI(uri.trim()); + return connectTo; + } catch (IOException ex) { + throw new IllegalStateException(ex); + } catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } +} diff -r ded9a1b4a69c -r 8f8f6b84138f ko/fx/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/fx/pom.xml Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,120 @@ + + + 4.0.0 + + org.apidesign.bck2brwsr + ko + 0.8-SNAPSHOT + + org.apidesign.bck2brwsr + ko-fx + 0.8-SNAPSHOT + Knockout.fx in Brwsr + http://maven.apache.org + + ${java.home}/lib/jfxrt.jar + UTF-8 + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + false + + + + + + + com.oracle + javafx + 2.2 + system + ${jfxrt.jar} + + + org.json + json + 20090211 + jar + + + org.apidesign.html + net.java.html.json + ${net.java.html.version} + + + org.testng + testng + test + + + org.apidesign.html + net.java.html.json.tck + ${net.java.html.version} + test + + + org.netbeans.api + org-openide-util + provided + + + org.apidesign.bck2brwsr + launcher.fx + ${project.version} + test + + + org.apidesign.html + net.java.html.boot + ${net.java.html.version} + jar + + + org.apidesign.html + ko-fx + ${net.java.html.version} + jar + + + org.apidesign.bck2brwsr + vmtest + ${project.version} + test + jar + + + org.apidesign.html + ko-ws-tyrus + ${net.java.html.version} + test + + + + + jdk8 + + + ${java.home}/lib/ext/jfxrt.jar + + + + ${java.home}/lib/ext/jfxrt.jar + + + + jdk7 + + + ${java.home}/lib/jfxrt.jar + + + + ${java.home}/lib/jfxrt.jar + + + + diff -r ded9a1b4a69c -r 8f8f6b84138f ko/fx/src/test/java/org/apidesign/bck2brwsr/kofx/KnockoutFXTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/fx/src/test/java/org/apidesign/bck2brwsr/kofx/KnockoutFXTest.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,132 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.kofx; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Map; +import net.java.html.BrwsrCtx; +import net.java.html.js.JavaScriptBody; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.apidesign.html.context.spi.Contexts; +import org.apidesign.html.json.spi.Technology; +import org.apidesign.html.json.spi.Transfer; +import org.apidesign.html.json.spi.WSTransfer; +import org.apidesign.html.json.tck.KOTest; +import org.apidesign.html.json.tck.KnockoutTCK; +import org.apidesign.html.kofx.FXContext; +import org.apidesign.html.wstyrus.TyrusContext; +import org.json.JSONException; +import org.json.JSONObject; +import org.openide.util.lookup.ServiceProvider; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +@ServiceProvider(service = KnockoutTCK.class) +public final class KnockoutFXTest extends KnockoutTCK { + public KnockoutFXTest() { + } + + @Factory public static Object[] compatibilityTests() { + return VMTest.newTests(). + withClasses(testClasses()). + withTestAnnotation(KOTest.class). + withLaunchers("fxbrwsr").build(); + } + + @Override + public BrwsrCtx createContext() { + FXContext fx = new FXContext(); + TyrusContext tc = new TyrusContext(); + Contexts.Builder b = Contexts.newBuilder(). + register(Technology.class, fx, 10). + register(Transfer.class, fx, 10); + try { + Class.forName("java.util.function.Function"); + // prefer WebView's WebSockets on JDK8 + b.register(WSTransfer.class, fx, 10); + } catch (ClassNotFoundException ex) { + // ok, JDK7 needs tyrus + b.register(WSTransfer.class, tc, 20); + } + return b.build(); + } + + @Override + public Object createJSON(Map values) { + JSONObject json = new JSONObject(); + for (Map.Entry entry : values.entrySet()) { + try { + json.put(entry.getKey(), entry.getValue()); + } catch (JSONException ex) { + throw new IllegalStateException(ex); + } + } + return json; + } + + @Override + @JavaScriptBody(args = { "s", "args" }, body = "" + + "var f = new Function(s); " + + "return f.apply(null, args);" + ) + public native Object executeScript(String script, Object[] arguments); + + @JavaScriptBody(args = { }, body = + "var h;" + + "if (!!window && !!window.location && !!window.location.href)\n" + + " h = window.location.href;\n" + + "else " + + " h = null;" + + "return h;\n" + ) + private static native String findBaseURL(); + + @Override + public URI prepareURL(String content, String mimeType, String[] parameters) { + try { + final URL baseURL = new URL(findBaseURL()); + StringBuilder sb = new StringBuilder(); + sb.append("/dynamic?mimeType=").append(mimeType); + for (int i = 0; i < parameters.length; i++) { + sb.append("¶m" + i).append("=").append(parameters[i]); + } + String mangle = content.replace("\n", "%0a") + .replace("\"", "\\\"").replace(" ", "%20"); + sb.append("&content=").append(mangle); + + URL query = new URL(baseURL, sb.toString()); + URLConnection c = query.openConnection(); + BufferedReader br = new BufferedReader(new InputStreamReader(c.getInputStream())); + URI connectTo = new URI(br.readLine()); + return connectTo; + } catch (IOException ex) { + throw new IllegalStateException(ex); + } catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } +} diff -r ded9a1b4a69c -r 8f8f6b84138f ko/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/pom.xml Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,20 @@ + + + 4.0.0 + org.apidesign.bck2brwsr + ko + 0.8-SNAPSHOT + pom + Bck2Brwsr Knockout Support + + org.apidesign + bck2brwsr + 0.8-SNAPSHOT + + + archetype + archetype-test + bck2brwsr + fx + + diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java --- a/launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java Tue May 28 13:33:12 2013 +0200 +++ b/launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java Sat Sep 07 14:59:24 2013 +0200 @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -95,7 +96,6 @@ wait.countDown(); } - static final class Resource { final InputStream httpContent; final String httpType; diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/pom.xml --- a/launcher/fx/pom.xml Tue May 28 13:33:12 2013 +0200 +++ b/launcher/fx/pom.xml Sat Sep 07 14:59:24 2013 +0200 @@ -18,8 +18,8 @@ maven-compiler-plugin 2.3.2 - 1.7 - 1.7 + 1.6 + 1.6 @@ -54,5 +54,47 @@ system ${jfxrt.jar} + + org.testng + testng + test + + + org.netbeans.modules + org-netbeans-bootstrap + RELEASE73 + + + ${project.groupId} + core + ${project.version} + compile + + + org.ow2.asm + asm + 4.1 + + + org.apidesign.html + net.java.html.boot + ${net.java.html.version} + + + org.glassfish.grizzly + grizzly-websockets-server + ${grizzly.version} + jar + + + org.glassfish.grizzly + grizzly-http-servlet + ${grizzly.version} + + + javax.servlet + javax.servlet-api + 3.1.0 + diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/BaseHTTPLauncher.java --- a/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/BaseHTTPLauncher.java Tue May 28 13:33:12 2013 +0200 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/BaseHTTPLauncher.java Sat Sep 07 14:59:24 2013 +0200 @@ -17,6 +17,8 @@ */ package org.apidesign.bck2brwsr.launcher; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.IOException; @@ -52,6 +54,11 @@ import org.glassfish.grizzly.http.server.ServerConfiguration; import org.glassfish.grizzly.http.util.HttpStatus; import org.glassfish.grizzly.threadpool.ThreadPoolConfig; +import org.glassfish.grizzly.websockets.WebSocket; +import org.glassfish.grizzly.websockets.WebSocketAddOn; +import org.glassfish.grizzly.websockets.WebSocketApplication; +import org.glassfish.grizzly.websockets.WebSocketEngine; +import org.openide.util.Exceptions; /** * Lightweight server to launch Bck2Brwsr applications and tests. @@ -61,8 +68,8 @@ abstract class BaseHTTPLauncher extends Launcher implements Closeable, Callable { static final Logger LOG = Logger.getLogger(BaseHTTPLauncher.class.getName()); private static final InvocationContext END = new InvocationContext(null, null, null); - private final Set loaders = new LinkedHashSet<>(); - private final BlockingQueue methods = new LinkedBlockingQueue<>(); + private final Set loaders = new LinkedHashSet(); + private final BlockingQueue methods = new LinkedBlockingQueue(); private long timeOut; private final Res resources = new Res(); private final String cmd; @@ -112,7 +119,7 @@ server = s; try { launchServerAndBrwsr(s, simpleName); - } catch (URISyntaxException | InterruptedException ex) { + } catch (Exception ex) { throw new IOException(ex); } } @@ -124,7 +131,7 @@ HttpServer s = initServer(dir.getPath(), false); try { launchServerAndBrwsr(s, startpage); - } catch (URISyntaxException | InterruptedException ex) { + } catch (Exception ex) { throw new IOException(ex); } } @@ -150,25 +157,29 @@ private HttpServer initServer(String path, boolean addClasses) throws IOException { HttpServer s = HttpServer.createSimpleServer(path, new PortRange(8080, 65535)); - + /* ThreadPoolConfig fewThreads = ThreadPoolConfig.defaultConfig().copy(). setPoolName("Fx/Bck2 Brwsr"). - setCorePoolSize(1). + setCorePoolSize(3). setMaxPoolSize(5); ThreadPoolConfig oneKernel = ThreadPoolConfig.defaultConfig().copy(). setPoolName("Kernel Fx/Bck2"). - setCorePoolSize(1). + setCorePoolSize(3). setMaxPoolSize(3); for (NetworkListener nl : s.getListeners()) { nl.getTransport().setWorkerThreadPoolConfig(fewThreads); nl.getTransport().setKernelThreadPoolConfig(oneKernel); } - + */ final ServerConfiguration conf = s.getServerConfiguration(); if (addClasses) { conf.addHttpHandler(new VM(), "/bck2brwsr.js"); conf.addHttpHandler(new Classes(resources), "/classes/"); } + final WebSocketAddOn addon = new WebSocketAddOn(); + for (NetworkListener listener : s.getListeners()) { + listener.registerAddOn(addon); + } return s; } @@ -179,22 +190,56 @@ class DynamicResourceHandler extends HttpHandler { private final InvocationContext ic; + private int resourcesCount; + DynamicResourceHandler delegate; public DynamicResourceHandler(InvocationContext ic) { - if (ic == null || ic.resources.isEmpty()) { - throw new NullPointerException(); - } this.ic = ic; for (Resource r : ic.resources) { conf.addHttpHandler(this, r.httpPath); } } - public void close() { + public void close(DynamicResourceHandler del) { conf.removeHttpHandler(this); + delegate = del; } @Override public void service(Request request, Response response) throws Exception { + if (delegate != null) { + delegate.service(request, response); + return; + } + + if ("/dynamic".equals(request.getRequestURI())) { + boolean webSocket = false; + String mimeType = request.getParameter("mimeType"); + List params = new ArrayList(); + for (int i = 0; ; i++) { + String p = request.getParameter("param" + i); + if (p == null) { + break; + } + params.add(p); + if ("protocol:ws".equals(p)) { + webSocket = true; + continue; + } } + final String cnt = request.getParameter("content"); + String mangle = cnt.replace("%20", " ").replace("%0A", "\n"); + ByteArrayInputStream is = new ByteArrayInputStream(mangle.getBytes("UTF-8")); + URI url; + final Resource res = new Resource(is, mimeType, "/dynamic/res" + ++resourcesCount, params.toArray(new String[params.size()])); + if (webSocket) { + url = registerWebSocket(res); + } else { + url = registerResource(res); + } + response.getWriter().write(url.toString()); + response.getWriter().write("\n"); + return; + } + for (Resource r : ic.resources) { if (r.httpPath.equals(request.getRequestURI())) { LOG.log(Level.INFO, "Serving HttpResource for {0}", request.getRequestURI()); @@ -231,13 +276,26 @@ } } } + + private URI registerWebSocket(Resource r) { + WebSocketEngine.getEngine().register("", r.httpPath, new WS(r)); + return pageURL("ws", server, r.httpPath); + } + + private URI registerResource(Resource r) { + if (!ic.resources.contains(r)) { + ic.resources.add(r); + conf.addHttpHandler(this, r.httpPath); + } + return pageURL("http", server, r.httpPath); + } } conf.addHttpHandler(new Page(resources, harnessResource()), "/execute"); conf.addHttpHandler(new HttpHandler() { int cnt; - List cases = new ArrayList<>(); + List cases = new ArrayList(); DynamicResourceHandler prev; @Override public void service(Request request, Response response) throws Exception { @@ -269,11 +327,6 @@ } } - if (prev != null) { - prev.close(); - prev = null; - } - if (mi == null) { mi = methods.take(); caseNmbr = cnt++; @@ -285,10 +338,12 @@ LOG.log(Level.INFO, "End of data reached. Exiting."); return; } - - if (!mi.resources.isEmpty()) { - prev = new DynamicResourceHandler(mi); + final DynamicResourceHandler newRH = new DynamicResourceHandler(mi); + if (prev != null) { + prev.close(newRH); } + prev = newRH; + conf.addHttpHandler(prev, "/dynamic"); cases.add(mi); final String cn = mi.clazz.getName(); @@ -381,10 +436,7 @@ private Object[] launchServerAndBrwsr(HttpServer server, final String page) throws IOException, URISyntaxException, InterruptedException { server.start(); - NetworkListener listener = server.getListeners().iterator().next(); - int port = listener.getPort(); - - URI uri = new URI("http://localhost:" + port + page); + URI uri = pageURL("http", server, page); return showBrwsr(uri); } private static String toUTF8(String value) throws UnsupportedEncodingException { @@ -496,6 +548,16 @@ abstract void generateBck2BrwsrJS(StringBuilder sb, Res loader) throws IOException; abstract String harnessResource(); + private static URI pageURL(String protocol, HttpServer server, final String page) { + NetworkListener listener = server.getListeners().iterator().next(); + int port = listener.getPort(); + try { + return new URI(protocol + "://localhost:" + port + page); + } catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } + class Res { public InputStream get(String resource) throws IOException { URL u = null; @@ -547,7 +609,8 @@ replace = args; } OutputStream os = response.getOutputStream(); - try (InputStream is = res.get(r)) { + try { + InputStream is = res.get(r); copyStream(is, os, request.getRequestURL().toString(), replace); } catch (IOException ex) { response.setDetailMessage(ex.getLocalizedMessage()); @@ -603,7 +666,9 @@ if (res.startsWith("/")) { res = res.substring(1); } - try (InputStream is = loader.get(res)) { + InputStream is = null; + try { + is = loader.get(res); response.setContentType("text/javascript"); Writer w = response.getWriter(); w.append("["); @@ -628,7 +693,32 @@ response.setStatus(HttpStatus.NOT_FOUND_404); response.setError(); response.setDetailMessage(ex.getMessage()); + } finally { + if (is != null) { + is.close(); + } } } } -} + private static class WS extends WebSocketApplication { + + private final Resource r; + + private WS(Resource r) { + this.r = r; + } + + @Override + public void onMessage(WebSocket socket, String text) { + try { + r.httpContent.reset(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + copyStream(r.httpContent, out, null, text); + String s = new String(out.toByteArray(), "UTF-8"); + socket.send(s); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + + }} diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/FXBrwsrLauncher.java --- a/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/FXBrwsrLauncher.java Tue May 28 13:33:12 2013 +0200 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/FXBrwsrLauncher.java Sat Sep 07 14:59:24 2013 +0200 @@ -24,6 +24,7 @@ import java.net.URI; import java.net.URL; import java.net.URLClassLoader; +import java.util.ArrayList; import java.util.Enumeration; import java.util.List; @@ -67,7 +68,17 @@ public void run() { LOG.log(Level.INFO, "In FX thread. Launching!"); try { - FXBrwsr.launch(FXBrwsr.class, url.toString()); + List params = new ArrayList(); + params.add(url.toString()); + if (isDebugged()) { + params.add("--toolbar=true"); + params.add("--firebug=true"); + String ud = System.getProperty("netbeans.user"); + if (ud != null) { + params.add("--userdir=" + ud); + } + } + FXBrwsr.launch(FXBrwsr.class, params.toArray(new String[params.size()])); LOG.log(Level.INFO, "Launcher is back. Closing"); close(); System.exit(0); @@ -87,17 +98,6 @@ sb.append("(function() {\n" + " var impl = this.bck2brwsr;\n" + " this.bck2brwsr = function() { return impl; };\n"); - if (isDebugged()) { - sb.append("var scr = window.document.createElement('script');\n"); - sb.append("scr.type = 'text/javascript';\n"); - sb.append("scr.src = 'https://getfirebug.com/firebug-lite.js';\n"); - sb.append("scr.text = '{ startOpened: true }';\n"); - sb.append("var head = window.document.getElementsByTagName('head')[0];"); - sb.append("head.appendChild(scr);\n"); - sb.append("var html = window.document.getElementsByTagName('html')[0];"); - sb.append("html.debug = true;\n"); - } - sb.append("})(window);\n"); JVMBridge.onBck2BrwsrLoad(); } @@ -129,8 +129,12 @@ while (en.hasMoreElements()) { URL url = en.nextElement(); Manifest mf; - try (InputStream is = url.openStream()) { + InputStream is = null; + try { + is = url.openStream(); mf = new Manifest(is); + } finally { + if (is != null) is.close(); } String sp = mf.getMainAttributes().getValue("StartPage"); if (sp != null) { diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/BrowserToolbar.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/BrowserToolbar.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,397 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.launcher.fximpl; + +import java.util.ArrayList; +import java.util.List; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.scene.control.ComboBox; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.Separator; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.control.ToolBar; +import javafx.scene.control.Tooltip; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Pane; +import javafx.scene.web.WebEngine; +import javafx.scene.web.WebView; + +final class BrowserToolbar extends ToolBar { + private final ArrayList resizeButtons; + private final WebView webView; + private final Pane container; + private final ToggleGroup resizeGroup = new ToggleGroup(); + private final ComboBox comboZoom = new ComboBox(); + + BrowserToolbar(WebView webView, Pane container, boolean useFirebug, final WebDebug webDebug) { + this.webView = webView; + this.container = container; + + List options = ResizeOption.loadAll(); + options.add( 0, ResizeOption.SIZE_TO_FIT ); + resizeButtons = new ArrayList( options.size() ); + + for( ResizeOption ro : options ) { + ResizeBtn button = new ResizeBtn(ro); + resizeButtons.add( button ); + resizeGroup.getToggles().add( button ); + getItems().add( button ); + } + resizeButtons.get( 0 ).setSelected( true ); + resizeGroup.selectedToggleProperty().addListener( new InvalidationListener() { + + @Override + public void invalidated( Observable o ) { + resize(); + } + }); + + getItems().add( new Separator() ); + + getItems().add( comboZoom ); + ArrayList zoomModel = new ArrayList( 6 ); + zoomModel.add( "200%" ); //NOI18N + zoomModel.add( "150%" ); //NOI18N + zoomModel.add( "100%" ); //NOI18N + zoomModel.add( "75%" ); //NOI18N + zoomModel.add( "50%" ); //NOI18N + comboZoom.setItems( FXCollections.observableList( zoomModel ) ); + comboZoom.setEditable( true ); + comboZoom.setValue( "100%" ); //NOI18N + comboZoom.valueProperty().addListener( new ChangeListener() { + + @Override + public void changed( ObservableValue ov, String t, String t1 ) { + String newZoom = zoom( t1 ); + comboZoom.setValue( newZoom ); + } + }); + + if (useFirebug) { + getItems().add(new Separator()); + + final ToggleButton firebug = new ToggleButton(null, new ImageView( + new Image(BrowserToolbar.class.getResourceAsStream("firebug.png")) + )); + firebug.setTooltip(new Tooltip("Show/Hide firebug")); + firebug.selectedProperty().addListener(new InvalidationListener() { + @Override + public void invalidated(Observable o) { + toggleFireBug(firebug.isSelected()); + } + }); + getItems().add(firebug); + } + + if (webDebug != null) { + final ToggleButton btnSelMode = new ToggleButton(null, new ImageView( + new Image(BrowserToolbar.class.getResourceAsStream("selectionMode.png")))); + btnSelMode.setTooltip(new Tooltip("Toggle selection mode")); + btnSelMode.selectedProperty().addListener(new InvalidationListener() { + @Override + public void invalidated(Observable o) { + toggleSelectionMode(webDebug, btnSelMode.isSelected()); + } + }); + getItems().add(btnSelMode); + } + } + + private String zoom( String zoomFactor ) { + if( zoomFactor.trim().isEmpty() ) + return null; + + try { + zoomFactor = zoomFactor.replaceAll( "\\%", ""); //NOI18N + zoomFactor = zoomFactor.trim(); + double zoom = Double.parseDouble( zoomFactor ); + zoom = Math.abs( zoom )/100; + if( zoom <= 0.0 ) + return null; + webView.impl_setScale( zoom ); + return (int)(100*zoom) + "%"; //NOI18N + } catch( NumberFormatException nfe ) { + //ignore + } + return null; + } + + private void resize() { + Toggle selection = resizeGroup.getSelectedToggle(); + if( selection instanceof ResizeBtn ) { + ResizeOption ro = ((ResizeBtn)selection).getResizeOption(); + if( ro == ResizeOption.SIZE_TO_FIT ) { + _autofit(); + } else { + _resize( ro.getWidth(), ro.getHeight() ); + } + } + + } + + private void _resize( final double width, final double height ) { + ScrollPane scroll; + if( !(container.getChildren().get( 0) instanceof ScrollPane) ) { + scroll = new ScrollPane(); + scroll.setContent( webView ); + container.getChildren().clear(); + container.getChildren().add( scroll ); + } else { + scroll = ( ScrollPane ) container.getChildren().get( 0 ); + } + scroll.setPrefViewportWidth( width ); + scroll.setPrefViewportHeight(height ); + webView.setMaxWidth( width ); + webView.setMaxHeight( height ); + webView.setMinWidth( width ); + webView.setMinHeight( height ); + } + + private void _autofit() { + if( container.getChildren().get( 0) instanceof ScrollPane ) { + container.getChildren().clear(); + container.getChildren().add( webView ); + } + webView.setMaxWidth( Integer.MAX_VALUE ); + webView.setMaxHeight( Integer.MAX_VALUE ); + webView.setMinWidth( -1 ); + webView.setMinHeight( -1 ); + webView.autosize(); + } + + private void toggleSelectionMode(WebDebug dbg, boolean selMode) { + // "inspect" + dbg.call("{\"message\":\"selection_mode\",\"selectionMode\":" + selMode + "}"); + } + + final void toggleFireBug(boolean enable) { + WebEngine eng = webView.getEngine(); + Object installed = eng.executeScript("window.Firebug"); + if ("undefined".equals(installed)) { + StringBuilder sb = new StringBuilder(); + sb.append("var scr = window.document.createElement('script');\n"); + sb.append("scr.type = 'text/javascript';\n"); + sb.append("scr.src = 'https://getfirebug.com/firebug-lite.js';\n"); + sb.append("scr.text = '{ startOpened: true }';\n"); + sb.append("var head = window.document.getElementsByTagName('head')[0];"); + sb.append("head.appendChild(scr);\n"); + sb.append("var html = window.document.getElementsByTagName('html')[0];"); + sb.append("html.debug = true;\n"); + eng.executeScript(sb.toString()); + } else { + if (enable) { + eng.executeScript("Firebug.chrome.open()"); + } else { + eng.executeScript("Firebug.chrome.close()"); + } + } + } + + /** + * Button to resize the browser window. + * Taken from NetBeans. Kept GPLwithCPEx license. + * Portions Copyrighted 2012 Sun Microsystems, Inc. + * + * @author S. Aubrecht + */ + static final class ResizeBtn extends ToggleButton { + + private final ResizeOption resizeOption; + + ResizeBtn(ResizeOption resizeOption) { + super(null, new ImageView(toImage(resizeOption))); + this.resizeOption = resizeOption; + setTooltip(new Tooltip(resizeOption.getToolTip())); + } + + ResizeOption getResizeOption() { + return resizeOption; + } + + static Image toImage(ResizeOption ro) { + if (ro == ResizeOption.SIZE_TO_FIT) { + return ResizeOption.Type.CUSTOM.getImage(); + } + return ro.getType().getImage(); + } + } + + /** + * Immutable value class describing a single button to resize web browser window. + * Taken from NetBeans. Kept GPLwithCPEx license. + * Portions Copyrighted 2012 Sun Microsystems, Inc. + * + * @author S. Aubrecht + */ + static final class ResizeOption { + + private final Type type; + private final String displayName; + private final int width; + private final int height; + private final boolean isDefault; + + enum Type { + DESKTOP("desktop.png"), + TABLET_PORTRAIT("tabletPortrait.png"), + TABLET_LANDSCAPE("tabletLandscape.png"), + SMARTPHONE_PORTRAIT("handheldPortrait.png"), + SMARTPHONE_LANDSCAPE("handheldLandscape.png"), + WIDESCREEN("widescreen.png"), + NETBOOK("netbook.png"), + CUSTOM("sizeToFit.png"); + + + private final String resource; + + private Type(String r) { + resource = r; + } + + public Image getImage() { + return new Image(Type.class.getResourceAsStream(resource)); + } + } + + private ResizeOption(Type type, String displayName, int width, int height, boolean showInToolbar, boolean isDefault) { + super(); + this.type = type; + this.displayName = displayName; + this.width = width; + this.height = height; + this.isDefault = isDefault; + } + + static List loadAll() { + List res = new ArrayList(10); + res.add(ResizeOption.create(ResizeOption.Type.DESKTOP, "Desktop", 1280, 1024, true, true)); + res.add(ResizeOption.create(ResizeOption.Type.TABLET_LANDSCAPE, "Tablet Landscape", 1024, 768, true, true)); + res.add(ResizeOption.create(ResizeOption.Type.TABLET_PORTRAIT, "Tablet Portrait", 768, 1024, true, true)); + res.add(ResizeOption.create(ResizeOption.Type.SMARTPHONE_LANDSCAPE, "Smartphone Landscape", 480, 320, true, true)); + res.add(ResizeOption.create(ResizeOption.Type.SMARTPHONE_PORTRAIT, "Smartphone Portrait", 320, 480, true, true)); + res.add(ResizeOption.create(ResizeOption.Type.WIDESCREEN, "Widescreen", 1680, 1050, false, true)); + res.add(ResizeOption.create(ResizeOption.Type.NETBOOK, "Netbook", 1024, 600, false, true)); + return res; + } + + /** + * Creates a new instance. + * @param type + * @param displayName Display name to show in tooltip, cannot be empty. + * @param width Screen width + * @param height Screen height + * @param showInToolbar True to show in web developer toolbar. + * @param isDefault True if this is a predefined option that cannot be removed. + * @return New instance. + */ + public static ResizeOption create(Type type, String displayName, int width, int height, boolean showInToolbar, boolean isDefault) { + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Invalid screen dimensions: " + width + " x " + height); //NOI18N + } + return new ResizeOption(type, displayName, width, height, showInToolbar, isDefault); + } + /** + * An extra option to size the browser content to fit its window. + */ + public static final ResizeOption SIZE_TO_FIT = new ResizeOption(Type.CUSTOM, "Size To Fit", -1, -1, true, true); + + public String getDisplayName() { + return displayName; + } + + public Type getType() { + return type; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public boolean isDefault() { + return isDefault; + } + + @Override + public String toString() { + return displayName; + } + + public String getToolTip() { + if (width < 0 || height < 0) { + return displayName; + } + StringBuilder sb = new StringBuilder(); + sb.append(width); + sb.append(" x "); //NOI18N + sb.append(height); + sb.append(" ("); //NOI18N + sb.append(displayName); + sb.append(')'); //NOI18N + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ResizeOption other = (ResizeOption) obj; + if (this.type != other.type) { + return false; + } + if ((this.displayName == null) ? (other.displayName != null) : !this.displayName.equals(other.displayName)) { + return false; + } + if (this.width != other.width) { + return false; + } + if (this.height != other.height) { + return false; + } + if (this.isDefault != other.isDefault) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 11 * hash + (this.type != null ? this.type.hashCode() : 0); + hash = 11 * hash + (this.displayName != null ? this.displayName.hashCode() : 0); + hash = 11 * hash + this.width; + hash = 11 * hash + this.height; + hash = 11 * hash + (this.isDefault ? 1 : 0); + return hash; + } + } +} diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java --- a/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java Tue May 28 13:33:12 2013 +0200 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java Sat Sep 07 14:59:24 2013 +0200 @@ -25,8 +25,8 @@ import java.lang.reflect.Modifier; import java.net.URL; import java.util.Enumeration; -import javafx.scene.web.WebEngine; import netscape.javascript.JSObject; +import org.apidesign.bck2brwsr.core.JavaScriptBody; /** * @@ -35,17 +35,15 @@ public final class Console { public Console() { } - - private static Object getAttr(Object elem, String attr) { - return InvokeJS.CObject.call("getAttr", elem, attr); - } - private static void setAttr(String id, String attr, Object value) { - InvokeJS.CObject.call("setAttrId", id, attr, value); - } - private static void setAttr(Object id, String attr, Object value) { - InvokeJS.CObject.call("setAttr", id, attr, value); - } + @JavaScriptBody(args = { "elem", "attr" }, body = "return elem[attr].toString();") + private static native Object getAttr(Object elem, String attr); + + @JavaScriptBody(args = { "id", "attr", "value" }, body = "window.document.getElementById(id)[attr] = value;") + private static native void setAttr(String id, String attr, Object value); + + @JavaScriptBody(args = { "elem", "attr", "value" }, body = "elem[attr] = value;") + private static native void setAttr(Object id, String attr, Object value); private static void closeWindow() {} @@ -78,7 +76,7 @@ textArea = null; } - private static final String BEGIN_TEST = + @JavaScriptBody(args = { "test", "c", "arr" }, body = "var ul = window.document.getElementById('bck2brwsr.result');\n" + "var li = window.document.createElement('li');\n" + "var span = window.document.createElement('span');" @@ -103,27 +101,24 @@ + "p.appendChild(pre);\n" + "ul.appendChild(li);\n" + "arr[0] = pre;\n" - + "arr[1] = status;\n"; - - private static void beginTest(String test, Case c, Object[] arr) { - InvokeJS.CObject.call("beginTest", test, c, arr); - } + + "arr[1] = status;\n" + ) + private static native void beginTest(String test, Case c, Object[] arr); - private static final String LOAD_TEXT = + @JavaScriptBody(args = { "url", "callback", "arr" }, body = "var request = new XMLHttpRequest();\n" + "request.open('GET', url, true);\n" + "request.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');\n" + "request.onreadystatechange = function() {\n" + " if (this.readyState!==4) return;\n" - + " try {" + + " try {\n" + " arr[0] = this.responseText;\n" - + " callback.run__V();\n" - + " } catch (e) { alert(e); }" - + "};" - + "request.send();"; - private static void loadText(String url, Runnable callback, String[] arr) throws IOException { - InvokeJS.CObject.call("loadText", url, new Run(callback), arr); - } + + " callback.run();\n" + + " } catch (e) { alert(e); }\n" + + "};\n" + + "request.send();\n" + ) + private static native void loadText(String url, Runnable callback, String[] arr) throws IOException; public static void runHarness(String url) throws IOException { new Console().harness(url); @@ -142,15 +137,16 @@ private Request(String url) throws IOException { this.url = url; - loadText(url, this, arr); + loadText(url, new Run(this), arr); } private Request(String url, String u) throws IOException { this.url = url; - loadText(u, this, arr); + loadText(u, new Run(this), arr); } @Override public void run() { + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); try { if (c == null) { String data = arr[0]; @@ -181,7 +177,7 @@ } catch (Exception ex) { if (ex instanceof InterruptedException) { log("Re-scheduling in 100ms"); - schedule(this, 100); + schedule(new Run(this), 100); return; } log(ex.getClass().getName() + ":" + ex.getMessage()); @@ -232,7 +228,9 @@ if (u == null) { throw new IOException("Can't find " + name); } - try (InputStream is = u.openStream()) { + InputStream is = null; + try { + is = u.openStream(); byte[] arr; arr = new byte[is.available()]; int offset = 0; @@ -244,15 +242,16 @@ offset += len; } return arr; + } finally { + if (is != null) is.close(); } } private static void turnAssetionStatusOn() { } - private static Object schedule(Runnable r, int time) { - return InvokeJS.CObject.call("schedule", new Run(r), time); - } + @JavaScriptBody(args = { "r", "time" }, body = "return window.setTimeout(function() { r.run(); }, time);") + private static native Object schedule(Runnable r, int time); private static final class Case { private final Object data; @@ -348,48 +347,16 @@ } return res; } - - private static Object toJSON(String s) { - return InvokeJS.CObject.call("toJSON", s); - } + + @JavaScriptBody(args = { "s" }, body = "return eval('(' + s + ')');") + private static native Object toJSON(String s); private static Object value(String p, Object d) { return ((JSObject)d).getMember(p); } } - private static String safe(String txt) { - return "try {" + txt + "} catch (err) { alert(err); }"; - } - static { turnAssetionStatusOn(); } - - private static final class InvokeJS { - static final JSObject CObject = initJS(); - - private static JSObject initJS() { - WebEngine web = (WebEngine) System.getProperties().get("webEngine"); - return (JSObject) web.executeScript("(function() {" - + "var CObject = {};" - - + "CObject.getAttr = function(elem, attr) { return elem[attr].toString(); };" - - + "CObject.setAttrId = function(id, attr, value) { window.document.getElementById(id)[attr] = value; };" - + "CObject.setAttr = function(elem, attr, value) { elem[attr] = value; };" - - + "CObject.beginTest = function(test, c, arr) {" + safe(BEGIN_TEST) + "};" - - + "CObject.loadText = function(url, callback, arr) {" + safe(LOAD_TEXT.replace("run__V", "run")) + "};" - - + "CObject.schedule = function(r, time) { return window.setTimeout(function() { r.run(); }, time); };" - - + "CObject.toJSON = function(s) { return eval('(' + s + ')'); };" - - + "return CObject;" - + "})(this)"); - } - } - } diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/FXBrwsr.java --- a/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/FXBrwsr.java Tue May 28 13:33:12 2013 +0200 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/FXBrwsr.java Sat Sep 07 14:59:24 2013 +0200 @@ -27,17 +27,13 @@ import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; -import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.geometry.VPos; -import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Button; -import javafx.scene.layout.ColumnConstraints; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.Pane; -import javafx.scene.layout.Priority; +import javafx.scene.control.ToolBar; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.text.Text; import javafx.scene.web.WebEngine; @@ -55,30 +51,52 @@ */ public class FXBrwsr extends Application { private static final Logger LOG = Logger.getLogger(FXBrwsr.class.getName()); - + @Override public void start(Stage primaryStage) throws Exception { - Pane root = new WebViewPane(getParameters().getUnnamed()); - primaryStage.setScene(new Scene(root, 1024, 768)); - LOG.info("Showing the stage"); + WebView view = new WebView(); + final String nbUserDir = this.getParameters().getNamed().get("userdir"); // NOI18N + WebController wc = new WebController(view, nbUserDir, getParameters().getUnnamed()); + + final VBox vbox = new VBox(); + vbox.setAlignment( Pos.CENTER ); + vbox.setStyle( "-fx-background-color: #808080;"); + + + HBox hbox = new HBox(); + hbox.setStyle( "-fx-background-color: #808080;"); + hbox.setAlignment(Pos.CENTER); + hbox.getChildren().add(vbox); + vbox.getChildren().add(view); + + BorderPane root = new BorderPane(); + final boolean showToolbar = "true".equals(this.getParameters().getNamed().get("toolbar")); // NOI18N + final boolean useFirebug = "true".equals(this.getParameters().getNamed().get("firebug")); // NOI18N + if (showToolbar) { + final ToolBar toolbar = new BrowserToolbar(view, vbox, useFirebug, wc.dbg); + root.setTop( toolbar ); + } + root.setCenter(hbox); + + Scene scene = new Scene(root, 800, 600); + + primaryStage.setTitle( "Device Emulator" ); + primaryStage.setScene( scene ); primaryStage.show(); - LOG.log(Level.INFO, "State shown: {0}", primaryStage.isShowing()); } /** * Create a resizable WebView pane */ - private class WebViewPane extends Pane { - private final JVMBridge bridge = new JVMBridge(); + private static class WebController { + private final JVMBridge bridge; + private final WebDebug dbg; + private final String ud; - public WebViewPane(List params) { + public WebController(WebView view, String ud, List params) { + this.bridge = new JVMBridge(view.getEngine()); + this.ud = ud; LOG.log(Level.INFO, "Initializing WebView with {0}", params); - VBox.setVgrow(this, Priority.ALWAYS); - setMaxWidth(Double.MAX_VALUE); - setMaxHeight(Double.MAX_VALUE); - WebView view = new WebView(); - view.setMinSize(500, 400); - view.setPrefSize(500, 400); final WebEngine eng = view.getEngine(); try { JVMBridge.addBck2BrwsrLoad(new InitBck2Brwsr(eng)); @@ -120,13 +138,15 @@ dialogStage.showAndWait(); } }); - GridPane grid = new GridPane(); - grid.setVgap(5); - grid.setHgap(5); - GridPane.setConstraints(view, 0, 1, 2, 1, HPos.CENTER, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS); - grid.getColumnConstraints().addAll(new ColumnConstraints(100, 100, Double.MAX_VALUE, Priority.ALWAYS, HPos.CENTER, true), new ColumnConstraints(40, 40, 40, Priority.NEVER, HPos.CENTER, true)); - grid.getChildren().addAll(view); - getChildren().add(grid); + WebDebug wd = null; + try { + if (ud != null) { + wd = WebDebug.create(eng.impl_getDebugger(), ud); + } + } catch (Exception ex) { + LOG.log(Level.WARNING, null, ex); + } + this.dbg = wd; } boolean initBck2Brwsr(WebEngine webEngine) { @@ -134,28 +154,12 @@ LOG.log(Level.FINE, "window: {0}", jsobj); Object prev = jsobj.getMember("bck2brwsr"); if ("undefined".equals(prev)) { - System.getProperties().put("webEngine", webEngine); jsobj.setMember("bck2brwsr", bridge); return true; } return false; } - @Override - protected void layoutChildren() { - List managed = getManagedChildren(); - double width = getWidth(); - double height = getHeight(); - double top = getInsets().getTop(); - double right = getInsets().getRight(); - double left = getInsets().getLeft(); - double bottom = getInsets().getBottom(); - for (int i = 0; i < managed.size(); i++) { - Node child = managed.get(i); - layoutInArea(child, left, top, width - left - right, height - top - bottom, 0, Insets.EMPTY, true, true, HPos.CENTER, VPos.CENTER); - } - } - private class InitBck2Brwsr implements ChangeListener, Runnable { private final WebEngine eng; diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/JVMBridge.java --- a/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/JVMBridge.java Tue May 28 13:33:12 2013 +0200 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/JVMBridge.java Sat Sep 07 14:59:24 2013 +0200 @@ -17,16 +17,38 @@ */ package org.apidesign.bck2brwsr.launcher.fximpl; +import java.io.BufferedReader; +import java.io.Reader; +import org.apidesign.html.boot.spi.Fn; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; import java.util.TooManyListenersException; import javafx.beans.value.ChangeListener; +import javafx.scene.web.WebEngine; +import netscape.javascript.JSObject; +import org.apidesign.html.boot.impl.FindResources; +import org.apidesign.html.boot.impl.FnUtils; /** * * @author Jaroslav Tulach */ public final class JVMBridge { + private final WebEngine engine; + private final ClassLoader cl; + private static ClassLoader[] ldrs; private static ChangeListener onBck2BrwsrLoad; + + JVMBridge(WebEngine eng) { + this.engine = eng; + final ClassLoader p = JVMBridge.class.getClassLoader().getParent(); + WebClassLoader wcl = new WebClassLoader(); + this.cl = FnUtils.newLoader(wcl, wcl, p); + } public static void registerClassLoaders(ClassLoader[] loaders) { ldrs = loaders.clone(); @@ -47,18 +69,81 @@ } public Class loadClass(String name) throws ClassNotFoundException { - System.err.println("trying to load " + name); - ClassNotFoundException ex = null; - if (ldrs != null) for (ClassLoader l : ldrs) { - try { - return Class.forName(name, true, l); - } catch (ClassNotFoundException ex2) { - ex = ex2; + return Class.forName(name, true, cl); + } + + private final class WebClassLoader implements FindResources, Fn.Presenter { + @Override + public void findResources(String name, Collection results, boolean oneIsEnough) { + if (ldrs != null) for (ClassLoader l : ldrs) { + URL u = l.getResource(name); + if (u != null) { + results.add(u); + } } } - if (ex == null) { - ex = new ClassNotFoundException("No loaders"); + + @Override + public Fn defineFn(String code, String... names) { + StringBuilder sb = new StringBuilder(); + sb.append("(function() {"); + sb.append(" return function("); + String sep = ""; + for (String n : names) { + sb.append(sep).append(n); + sep = ","; + } + sb.append(") {\n"); + sb.append(code); + sb.append("};"); + sb.append("})()"); + + JSObject x = (JSObject) engine.executeScript(sb.toString()); + return new JSFn(x); } - throw ex; + + @Override + public void displayPage(URL page, Runnable onPageLoad) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void loadScript(Reader code) throws Exception { + BufferedReader r = new BufferedReader(code); + StringBuilder sb = new StringBuilder(); + for (;;) { + String l = r.readLine(); + if (l == null) { + break; + } + sb.append(l).append('\n'); + } + engine.executeScript(sb.toString()); + } + } + + private static final class JSFn extends Fn { + private final JSObject fn; + + public JSFn(JSObject fn) { + this.fn = fn; + } + + @Override + public Object invoke(Object thiz, Object... args) throws Exception { + try { + List all = new ArrayList(args.length + 1); + all.add(thiz == null ? fn : thiz); + all.addAll(Arrays.asList(args)); + Object ret = fn.call("call", all.toArray()); // NOI18N + return ret == fn ? null : ret; + } catch (Error t) { + t.printStackTrace(); + throw t; + } catch (Exception t) { + t.printStackTrace(); + throw t; + } + } } } diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/WebDebug.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/WebDebug.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,189 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.launcher.fximpl; + +import com.sun.javafx.scene.web.Debugger; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Method; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; +import javafx.application.Platform; +import javafx.util.Callback; + +/** Simulates WebKit protocol over WebSockets. + * + * @author Jaroslav Tulach + */ +abstract class WebDebug extends OutputStream +implements Callback, Runnable { + private static final Logger LOG = Logger.getLogger(WebDebug.class.getName()); + + private final Debugger debug; + private final StringBuilder cmd = new StringBuilder(); + private final ToDbgInputStream toDbg; + private final String ud; + + WebDebug(Debugger debug, String ud) throws Exception { + this.debug = debug; + this.ud = ud; + toDbg = new ToDbgInputStream(); + debug.setEnabled(true); + debug.setMessageCallback(this); + } + + static WebDebug create(Debugger debug, String ud) throws Exception { + WebDebug web = new WebDebug(debug, ud) {}; + + Executors.newFixedThreadPool(1).execute(web); + + return web; + } + + @Override + public void run() { + try { + String p = System.getProperty("startpage.file"); + File f; + if (p != null && (f = new File(p)).exists()) { + String[] args = {"--livehtml", f.getAbsolutePath()}; + File dir = f.getParentFile(); + cliHandler(args, toDbg, this, System.err, dir); + } + } catch (Exception ex) { + LOG.log(Level.SEVERE, null, ex); + } + } + + @Override + public void close() { + try { + toDbg.close(); + } catch (IOException ex) { + LOG.log(Level.SEVERE, null, ex); + } + } + + @Override + public Void call(String p) { + assert p.indexOf('\n') == -1 : "No new line: " + p; + LOG.log(Level.INFO, "toDbgr: {0}", p); + toDbg.pushMsg(p); + return null; + } + + @Override + public void write(int b) throws IOException { + if (b == '\n') { + final String msg = cmd.toString(); + Platform.runLater(new Runnable() { + @Override + public void run() { + LOG.log(Level.INFO, "toView: {0}", msg); + debug.sendMessage(msg); + } + }); + cmd.setLength(0); + } else { + if (cmd.length() > 100000) { + LOG.log(Level.WARNING, "Too big:\n{0}", cmd); + } + cmd.append((char)b); + } + } + + private void cliHandler( + String[] args, InputStream is, OutputStream os, OutputStream err, File dir + ) { + try { + Class main = Class.forName("org.netbeans.MainImpl"); + Method m = main.getDeclaredMethod("execute", String[].class, InputStream.class, + OutputStream.class, OutputStream.class, AtomicReference.class + ); + m.setAccessible(true); + System.setProperty("netbeans.user", ud); + int ret = (Integer)m.invoke(null, args, is, os, err, null); + LOG.log(Level.INFO, "Return value: {0}", ret); + } catch (Exception ex) { + LOG.log(Level.SEVERE, null, ex); + } finally { + LOG.info("Communication is over"); + } + + } + + private class ToDbgInputStream extends InputStream { + private byte[] current; + private int currentPos; + private final ArrayBlockingQueue pending = new ArrayBlockingQueue(64); + + public ToDbgInputStream() { + } + + @Override + public int read() throws IOException { + return read(null, 0, 1); + } + + @Override + public int read(byte[] arr, int offset, int len) throws IOException { + if (current == null || current.length <= currentPos) { + for (;;) { + WebDebug.this.flush(); + try { + current = pending.poll(5, TimeUnit.MILLISECONDS); + if (current == null) { + return 0; + } + break; + } catch (InterruptedException ex) { + throw (InterruptedIOException)new InterruptedIOException().initCause(ex); + } + } + LOG.info("Will return: " + new String(current)); + currentPos = 0; + } + int cnt = 0; + while (len-- > 0 && currentPos < current.length) { + final byte nextByte = current[currentPos++]; + if (arr == null) { + return nextByte; + } + arr[offset + cnt++] = nextByte; + } + LOG.log(Level.INFO, "read returns: {0}", new String(arr, offset, cnt)); + return cnt; + } + + private void pushMsg(String p) { + try { + pending.offer((p + '\n').getBytes("UTF-8")); + } catch (UnsupportedEncodingException ex) { + throw new IllegalStateException(ex); + } + } + } +} diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/desktop.png Binary file launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/desktop.png has changed diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/firebug.png Binary file launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/firebug.png has changed diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/handheldLandscape.png Binary file launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/handheldLandscape.png has changed diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/handheldPortrait.png Binary file launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/handheldPortrait.png has changed diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/netbook.png Binary file launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/netbook.png has changed diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/selectionMode.png Binary file launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/selectionMode.png has changed diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/sizeToFit.png Binary file launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/sizeToFit.png has changed diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/tabletLandscape.png Binary file launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/tabletLandscape.png has changed diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/tabletPortrait.png Binary file launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/tabletPortrait.png has changed diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/widescreen.png Binary file launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/widescreen.png has changed diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/test/java/org/apidesign/bck2brwsr/launcher/fximpl/JsClassLoaderTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/test/java/org/apidesign/bck2brwsr/launcher/fximpl/JsClassLoaderTest.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,174 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.launcher.fximpl; + +import java.io.Reader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apidesign.html.boot.spi.Fn; +import org.apidesign.html.boot.impl.FindResources; +import org.apidesign.html.boot.impl.FnUtils; +import static org.testng.Assert.*; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public class JsClassLoaderTest { + private static ClassLoader loader; + private static Class methodClass; + + public JsClassLoaderTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + ScriptEngineManager sem = new ScriptEngineManager(); + final ScriptEngine eng = sem.getEngineByMimeType("text/javascript"); + + final URL my = JsClassLoaderTest.class.getProtectionDomain().getCodeSource().getLocation(); + ClassLoader parent = JsClassLoaderTest.class.getClassLoader().getParent(); + final URLClassLoader ul = new URLClassLoader(new URL[] { my }, parent); + class Fr implements FindResources, Fn.Presenter { + @Override + public void findResources(String path, Collection results, boolean oneIsEnough) { + URL u = ul.getResource(path); + if (u != null) { + results.add(u); + } + } + + @Override + public Fn defineFn(String code, String... names) { + StringBuilder sb = new StringBuilder(); + sb.append("(function() {"); + sb.append("return function("); + String sep = ""; + for (String n : names) { + sb.append(sep); + sb.append(n); + sep = ", "; + } + sb.append(") {"); + sb.append(code); + sb.append("};"); + sb.append("})()"); + try { + final Object val = eng.eval(sb.toString()); + return new Fn() { + @Override + public Object invoke(Object thiz, Object... args) throws Exception { + List all = new ArrayList(args.length + 1); + all.add(thiz == null ? val : thiz); + all.addAll(Arrays.asList(args)); + Invocable inv = (Invocable)eng; + Object ret = inv.invokeMethod(val, "call", all.toArray()); + return ret == val ? null : ret; + } + }; + } catch (ScriptException ex) { + throw new LinkageError("Can't parse: " + sb, ex); + } + } + + @Override + public void displayPage(URL page, Runnable onPageLoad) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void loadScript(Reader code) throws Exception { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + } + + loader = FnUtils.newLoader(new Fr(), new Fr(), parent); + methodClass = loader.loadClass(JsMethods.class.getName()); + } + + @Test public void noParamMethod() throws Throwable { + Method plus = methodClass.getMethod("fortyTwo"); + try { + final Object val = plus.invoke(null); + assertTrue(val instanceof Number, "A number returned " + val); + assertEquals(((Number)val).intValue(), 42); + } catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + + @Test public void testExecuteScript() throws Throwable { + Method plus = methodClass.getMethod("plus", int.class, int.class); + try { + assertEquals(plus.invoke(null, 10, 20), 30); + } catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + + @Test public void overloadedMethod() throws Throwable { + Method plus = methodClass.getMethod("plus", int.class); + try { + assertEquals(plus.invoke(null, 10), 10); + } catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + + @Test public void instanceMethod() throws Throwable { + Method plus = methodClass.getMethod("plusInst", int.class); + Object inst = methodClass.newInstance(); + try { + assertEquals(plus.invoke(inst, 10), 10); + } catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + + @Test public void staticThis() throws Throwable { + Method st = methodClass.getMethod("staticThis"); + try { + assertNull(st.invoke(null)); + } catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + + @Test public void getThis() throws Throwable { + Object th = methodClass.newInstance(); + Method st = methodClass.getMethod("getThis"); + try { + assertEquals(st.invoke(th), th); + } catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + +} \ No newline at end of file diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/fx/src/test/java/org/apidesign/bck2brwsr/launcher/fximpl/JsMethods.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/test/java/org/apidesign/bck2brwsr/launcher/fximpl/JsMethods.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,45 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.launcher.fximpl; + +import org.apidesign.bck2brwsr.core.JavaScriptBody; + +/** + * + * @author Jaroslav Tulach + */ +public class JsMethods { + @JavaScriptBody(args = {}, body = "return 42;") + public static Object fortyTwo() { + return -42; + } + + @JavaScriptBody(args = {"x", "y" }, body = "return x + y;") + public static native int plus(int x, int y); + + @JavaScriptBody(args = {"x"}, body = "return x;") + public static native int plus(int x); + + @JavaScriptBody(args = {}, body = "return this;") + public static native Object staticThis(); + + @JavaScriptBody(args = {}, body = "return this;") + public native Object getThis(); + @JavaScriptBody(args = {"x"}, body = "return x;") + public native int plusInst(int x); +} diff -r ded9a1b4a69c -r 8f8f6b84138f launcher/pom.xml --- a/launcher/pom.xml Tue May 28 13:33:12 2013 +0200 +++ b/launcher/pom.xml Sat Sep 07 14:59:24 2013 +0200 @@ -12,7 +12,7 @@ pom Launchers - 2.3.2 + 2.3.3 api diff -r ded9a1b4a69c -r 8f8f6b84138f pom.xml --- a/pom.xml Tue May 28 13:33:12 2013 +0200 +++ b/pom.xml Sat Sep 07 14:59:24 2013 +0200 @@ -15,12 +15,14 @@ UTF-8 RELEASE73 COPYING + 0.5 dew javaquery benchmarks ide + ko launcher rt @@ -90,6 +92,8 @@ rt/emul/compact/src/test/resources/** dew/src/main/resources/org/apidesign/bck2brwsr/dew/** javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout*.js + ko/archetype/src/main/resources/archetype-resources/** + ko/*/src/main/resources/org/apidesign/*/*/knockout-2.2.1.js diff -r ded9a1b4a69c -r 8f8f6b84138f rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/DoubleTest.java --- a/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/DoubleTest.java Tue May 28 13:33:12 2013 +0200 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/DoubleTest.java Sat Sep 07 14:59:24 2013 +0200 @@ -26,6 +26,10 @@ * @author Jaroslav Tulach */ public class DoubleTest { + @Compare public boolean parsedDoubleIsDouble() { + return Double.valueOf("1.1") instanceof Double; + } + @Compare public String integerToString() { return toStr(1); } diff -r ded9a1b4a69c -r 8f8f6b84138f rt/emul/mini/src/main/java/java/lang/Class.java --- a/rt/emul/mini/src/main/java/java/lang/Class.java Tue May 28 13:33:12 2013 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/Class.java Sat Sep 07 14:59:24 2013 +0200 @@ -155,11 +155,15 @@ } return arrType; } - Class c = loadCls(className, className.replace('.', '_')); - if (c == null) { - throw new ClassNotFoundException(className); + try { + Class c = loadCls(className, className.replace('.', '_')); + if (c == null) { + throw new ClassNotFoundException(className); + } + return c; + } catch (Throwable ex) { + throw new ClassNotFoundException(className, ex); } - return c; } diff -r ded9a1b4a69c -r 8f8f6b84138f rt/emul/mini/src/main/java/java/lang/Double.java --- a/rt/emul/mini/src/main/java/java/lang/Double.java Tue May 28 13:33:12 2013 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/Double.java Sat Sep 07 14:59:24 2013 +0200 @@ -502,10 +502,8 @@ * @throws NumberFormatException if the string does not contain a * parsable number. */ - @JavaScriptBody(args="s", body="return parseFloat(s);") public static Double valueOf(String s) throws NumberFormatException { - throw new UnsupportedOperationException(); -// return new Double(FloatingDecimal.readJavaFormatString(s).doubleValue()); + return new Double(parseDouble(s)); } /** diff -r ded9a1b4a69c -r 8f8f6b84138f rt/emul/mini/src/main/java/java/lang/Object.java --- a/rt/emul/mini/src/main/java/java/lang/Object.java Tue May 28 13:33:12 2013 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/Object.java Sat Sep 07 14:59:24 2013 +0200 @@ -79,9 +79,17 @@ * @see Class Literals, section 15.8.2 of * The Java™ Language Specification. */ - @JavaScriptBody(args={}, body="return this.constructor.$class;") - public final native Class getClass(); + public final Class getClass() { + Class c = getClassImpl(); + return c == null ? Object.class : c; + } + @JavaScriptBody(args={}, body= + "var c = this.constructor.$class;\n" + + "return c ? c : null;\n" + ) + private final native Class getClassImpl(); + /** * Returns a hash code value for the object. This method is * supported for the benefit of hash tables such as those provided by diff -r ded9a1b4a69c -r 8f8f6b84138f rt/emul/mini/src/main/java/java/lang/Throwable.java --- a/rt/emul/mini/src/main/java/java/lang/Throwable.java Tue May 28 13:33:12 2013 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/Throwable.java Sat Sep 07 14:59:24 2013 +0200 @@ -638,10 +638,9 @@ * ... 2 more * */ -// public void printStackTrace() { -// printStackTrace(System.err); -// } -// + @JavaScriptBody(args = { }, body = "console.warn(this.toString());") + public native void printStackTrace(); + // /** // * Prints this throwable and its backtrace to the specified print stream. // * diff -r ded9a1b4a69c -r 8f8f6b84138f rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/BrwsrMojo.java --- a/rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/BrwsrMojo.java Tue May 28 13:33:12 2013 +0200 +++ b/rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/BrwsrMojo.java Sat Sep 07 14:59:24 2013 +0200 @@ -29,15 +29,21 @@ import java.util.Collection; import java.util.List; import org.apache.maven.artifact.Artifact; +import org.apache.maven.model.Resource; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apidesign.bck2brwsr.launcher.Launcher; /** Executes given HTML page in a browser. */ -@Mojo(name="brwsr", defaultPhase=LifecyclePhase.NONE) +@Mojo( + name="brwsr", + requiresDependencyResolution = ResolutionScope.RUNTIME, + defaultPhase=LifecyclePhase.NONE +) public class BrwsrMojo extends AbstractMojo { public BrwsrMojo() { } @@ -70,14 +76,20 @@ if (startpage == null) { throw new MojoExecutionException("You have to provide a start page"); } - try { Closeable httpServer; if (directory != null) { httpServer = Launcher.showDir(directory, startpage); } else { - URLClassLoader url = buildClassLoader(classes, prj.getDependencyArtifacts()); + URLClassLoader url = buildClassLoader(classes, prj.getArtifacts()); try { + for (Resource r : prj.getResources()) { + File f = new File(r.getDirectory(), startpage().replace('/', File.separatorChar)); + if (f.exists()) { + System.setProperty("startpage.file", f.getPath()); + } + } + httpServer = Launcher.showURL(launcher, url, startpage()); } catch (Exception ex) { throw new MojoExecutionException("Can't open " + startpage(), ex); diff -r ded9a1b4a69c -r 8f8f6b84138f rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/Java2JavaScript.java --- a/rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/Java2JavaScript.java Tue May 28 13:33:12 2013 +0200 +++ b/rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/Java2JavaScript.java Sat Sep 07 14:59:24 2013 +0200 @@ -33,12 +33,16 @@ import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apidesign.vm4brwsr.Bck2Brwsr; import org.apidesign.vm4brwsr.ObfuscationLevel; /** Compiles classes into JavaScript. */ -@Mojo(name="j2js", defaultPhase=LifecyclePhase.PROCESS_CLASSES) +@Mojo(name="j2js", + requiresDependencyResolution = ResolutionScope.COMPILE, + defaultPhase=LifecyclePhase.PROCESS_CLASSES +) public class Java2JavaScript extends AbstractMojo { public Java2JavaScript() { } @@ -86,7 +90,7 @@ } try { - URLClassLoader url = buildClassLoader(classes, prj.getDependencyArtifacts()); + URLClassLoader url = buildClassLoader(classes, prj.getArtifacts()); FileWriter w = new FileWriter(javascript); Bck2Brwsr.newCompiler(). obfuscation(obfuscation). diff -r ded9a1b4a69c -r 8f8f6b84138f rt/vm/pom.xml --- a/rt/vm/pom.xml Tue May 28 13:33:12 2013 +0200 +++ b/rt/vm/pom.xml Sat Sep 07 14:59:24 2013 +0200 @@ -151,6 +151,12 @@ closure-compiler r2388 compile - + + + org.apidesign.html + net.java.html.boot + test + ${net.java.html.version} + diff -r ded9a1b4a69c -r 8f8f6b84138f rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Tue May 28 13:33:12 2013 +0200 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Sat Sep 07 14:59:24 2013 +0200 @@ -96,16 +96,34 @@ ); } byte[] arrData = jc.findAnnotationData(true); - String[] arr = findAnnotation(arrData, jc, - "org.apidesign.bck2brwsr.core.ExtraJavaScript", - "resource", "processByteCode" - ); - if (arr != null) { - if (!arr[0].isEmpty()) { - requireScript(arr[0]); + { + String[] arr = findAnnotation(arrData, jc, + "org.apidesign.bck2brwsr.core.ExtraJavaScript", + "resource", "processByteCode" + ); + if (arr != null) { + if (!arr[0].isEmpty()) { + requireScript(arr[0]); + } + if ("0".equals(arr[1])) { + return null; + } } - if ("0".equals(arr[1])) { - return null; + } + { + String[] arr = findAnnotation(arrData, jc, + "net.java.html.js.JavaScriptResource", + "value" + ); + if (arr != null) { + if (arr[0].startsWith("/")) { + requireScript(arr[0]); + } else { + int last = jc.getClassName().lastIndexOf('/'); + requireScript( + jc.getClassName().substring(0, last + 1).replace('.', '/') + arr[0] + ); + } } } String[] proto = findAnnotation(arrData, jc, @@ -190,14 +208,18 @@ out.append("\n ").append(destObject).append(".").append(mn).append(".cls = CLS;"); } out.append("\n c.constructor = CLS;"); + out.append("\n function fillInstOf(x) {"); String instOfName = "$instOf_" + className; - out.append("\n c.").append(instOfName).append(" = true;"); + out.append("\n x.").append(instOfName).append(" = true;"); + for (String superInterface : jc.getSuperInterfaces()) { + String intrfc = superInterface.replace('/', '_'); + out.append("\n vm.").append(intrfc).append("(false).fillInstOf(x);"); + requireReference(superInterface); + } + out.append("\n }"); + out.append("\n c.fillInstOf = fillInstOf;"); + out.append("\n fillInstOf(c);"); obfuscationDelegate.exportJSProperty(out, "c", instOfName); - for (String superInterface : jc.getSuperInterfaces()) { - instOfName = "$instOf_" + superInterface.replace('/', '_'); - out.append("\n c.").append(instOfName).append(" = true;"); - obfuscationDelegate.exportJSProperty(out, "c", instOfName); - } out.append("\n CLS.$class = 'temp';"); out.append("\n CLS.$class = "); out.append(accessClass("java_lang_Class(true);")); @@ -1575,6 +1597,7 @@ return null; } final String jvmType = "Lorg/apidesign/bck2brwsr/core/JavaScriptBody;"; + final String htmlType = "Lnet/java/html/js/JavaScriptBody;"; class P extends AnnotationParser { public P() { super(false, true); @@ -1583,6 +1606,7 @@ int cnt; String[] args = new String[30]; String body; + boolean javacall; @Override protected void visitAttr(String type, String attr, String at, String value) { @@ -1595,6 +1619,17 @@ throw new IllegalArgumentException(attr); } } + if (type.equals(htmlType)) { + if ("body".equals(attr)) { + body = value; + } else if ("args".equals(attr)) { + args[cnt++] = value; + } else if ("javacall".equals(attr)) { + javacall = "1".equals(value); + } else { + throw new IllegalArgumentException(attr); + } + } } } P p = new P(); @@ -1614,10 +1649,121 @@ index++; } out.append(") {").append("\n"); - out.append(p.body); + if (p.javacall) { + int lastSlash = jc.getClassName().lastIndexOf('/'); + final String pkg = jc.getClassName().substring(0, lastSlash); + out.append(mangleCallbacks(pkg, p.body)); + requireReference(pkg + "/$JsCallbacks$"); + } else { + out.append(p.body); + } out.append("\n}\n"); return mn; } + + private static CharSequence mangleCallbacks(String pkgName, String body) { + StringBuilder sb = new StringBuilder(); + int pos = 0; + for (;;) { + int next = body.indexOf(".@", pos); + if (next == -1) { + sb.append(body.substring(pos)); + body = sb.toString(); + break; + } + int ident = next; + while (ident > 0) { + if (!Character.isJavaIdentifierPart(body.charAt(--ident))) { + ident++; + break; + } + } + String refId = body.substring(ident, next); + + sb.append(body.substring(pos, ident)); + + int sigBeg = body.indexOf('(', next); + int sigEnd = body.indexOf(')', sigBeg); + int colon4 = body.indexOf("::", next); + if (sigBeg == -1 || sigEnd == -1 || colon4 == -1) { + throw new IllegalStateException("Malformed body " + body); + } + String fqn = body.substring(next + 2, colon4); + String method = body.substring(colon4 + 2, sigBeg); + String params = body.substring(sigBeg, sigEnd + 1); + + int paramBeg = body.indexOf('(', sigEnd + 1); + + sb.append("vm.").append(pkgName.replace('/', '_')).append("_$JsCallbacks$(false)._VM()."); + sb.append(mangle(fqn, method, params, false)); + sb.append("(").append(refId); + if (body.charAt(paramBeg + 1) != ')') { + sb.append(","); + } + pos = paramBeg + 1; + } + sb = null; + pos = 0; + for (;;) { + int next = body.indexOf("@", pos); + if (next == -1) { + if (sb == null) { + return body; + } + sb.append(body.substring(pos)); + return sb; + } + if (sb == null) { + sb = new StringBuilder(); + } + + sb.append(body.substring(pos, next)); + + int sigBeg = body.indexOf('(', next); + int sigEnd = body.indexOf(')', sigBeg); + int colon4 = body.indexOf("::", next); + if (sigBeg == -1 || sigEnd == -1 || colon4 == -1) { + throw new IllegalStateException("Malformed body " + body); + } + String fqn = body.substring(next + 1, colon4); + String method = body.substring(colon4 + 2, sigBeg); + String params = body.substring(sigBeg, sigEnd + 1); + + int paramBeg = body.indexOf('(', sigEnd + 1); + + sb.append("vm.").append(pkgName.replace('/', '_')).append("_$JsCallbacks$(false)._VM()."); + sb.append(mangle(fqn, method, params, true)); + sb.append("("); + pos = paramBeg + 1; + } + } + private static String mangle(String fqn, String method, String params, boolean isStatic) { + if (params.startsWith("(")) { + params = params.substring(1); + } + if (params.endsWith(")")) { + params = params.substring(0, params.length() - 1); + } + StringBuilder sb = new StringBuilder(); + final String rfqn = replace(fqn); + final String rm = replace(method); + final String rp = replace(params); + sb.append(rfqn).append("$").append(rm). + append('$').append(rp).append("__Ljava_lang_Object_2"); + if (!isStatic) { + sb.append('L').append(rfqn).append("_2"); + } + sb.append(rp); + return sb.toString(); + } + + private static String replace(String orig) { + return orig.replace("_", "_1"). + replace(";", "_2"). + replace("[", "_3"). + replace('.', '_').replace('/', '_'); + } + private static String className(ClassData jc) { //return jc.getName().getInternalName().replace('/', '_'); return jc.getClassName().replace('/', '_'); diff -r ded9a1b4a69c -r 8f8f6b84138f rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java Tue May 28 13:33:12 2013 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java Sat Sep 07 14:59:24 2013 +0200 @@ -215,4 +215,17 @@ ); } + @Test public void typeOfFn() throws Exception { + assertExec("Type of function is Object", Classes.class, + "typeOfFn__Ljava_lang_String_2", + "java.lang.Object" + ); + } + + @Test public void instanceOfSuperInterface() throws Exception { + assertExec("Is iof super interface", Classes.class, + "instanceOfSuperInterface__Z", + 1.0 + ); + } } diff -r ded9a1b4a69c -r 8f8f6b84138f rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java Tue May 28 13:33:12 2013 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java Sat Sep 07 14:59:24 2013 +0200 @@ -18,6 +18,7 @@ package org.apidesign.vm4brwsr; import java.io.IOException; +import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -233,4 +234,20 @@ public static String valueEnum(String v) { return ClassesMarker.E.valueOf(v).toString(); } + + public static String typeOfFn() { + return fn().getClass().getName(); + } + + @JavaScriptBody(args = { }, body = "return function() { alert('x'); };") + private native static Object fn(); + + public static boolean instanceOfSuperInterface() { + Object obj = new SuperSerial() { + }; + return obj instanceof Serializable; + } + + private static interface SuperSerial extends Serializable { + } } diff -r ded9a1b4a69c -r 8f8f6b84138f rt/vm/src/test/java/org/apidesign/vm4brwsr/HtmlAnnotations.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/HtmlAnnotations.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,86 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.vm4brwsr; + +import net.java.html.js.JavaScriptBody; +import net.java.html.js.JavaScriptResource; + +/** + * + * @author Jaroslav Tulach + */ +@JavaScriptResource("htmlannotations.js") +public class HtmlAnnotations { + private Object callback; + + + @JavaScriptBody(args = {}, body = "return 42;") + public static int fourtyTwo() { + return -1; + } + + @JavaScriptBody(args = { "x", "y" }, body = "return mul(x, y);") + public static native int useExternalMul(int x, int y); + + public static int callback() { + final int[] arr = { 0 }; + callback(new Runnable() { + @Override + public void run() { + arr[0]++; + } + }); + return arr[0]; + } + + @JavaScriptBody(args = { "r" }, javacall=true, body = "r.@java.lang.Runnable::run()()") + private static native void callback(Runnable r); + + @JavaScriptBody(args = { }, javacall = true, body = "return @org.apidesign.vm4brwsr.HtmlAnnotations::callback()();") + public static native int staticCallback(); + + + protected long chooseLong(boolean takeFirst, boolean takeSecond, long first, long second) { + long l = 0; + if (takeFirst) l += first; + if (takeSecond) l += second; + return l; + } + + protected void onError(Object obj) throws Exception { + callback = obj; + } + + Object getError() { + return callback; + } + + public static Object create() { + return new HtmlAnnotations(); + } + @JavaScriptBody(args = { "impl", "a", "b" }, javacall = true, body = + "return impl.@org.apidesign.vm4brwsr.HtmlAnnotations::chooseLong(ZZJJ)(true, false, a, b);" + ) + public static native long first(Object impl, long a, long b); + + @JavaScriptBody(args = { "impl", "d" }, javacall = true, body = + "impl.@org.apidesign.vm4brwsr.HtmlAnnotations::onError(Ljava/lang/Object;)(d);" + + "return impl.@org.apidesign.vm4brwsr.HtmlAnnotations::getError()();" + ) + public static native Double onError(Object impl, Double d); +} diff -r ded9a1b4a69c -r 8f8f6b84138f rt/vm/src/test/java/org/apidesign/vm4brwsr/HtmlAnnotationsTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/HtmlAnnotationsTest.java Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,91 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.vm4brwsr; + +import static org.testng.Assert.assertNotNull; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** Verify cooperation with net.java.html.js annotations. + * + * @author Jaroslav Tulach + */ +public class HtmlAnnotationsTest { + @Test public void fourtyTwo() throws Exception { + assertExec("Annotation used", HtmlAnnotations.class, + "fourtyTwo__I", + Double.valueOf(42) + ); + } + + @Test public void externalMul() throws Exception { + assertExec("mul function is loaded", HtmlAnnotations.class, + "useExternalMul__III", + Double.valueOf(42), + 7, 6 + ); + } + + @Test public void callRunnableFromJS() throws Exception { + assertExec("runnable called", HtmlAnnotations.class, + "callback__I", + Double.valueOf(1) + ); + } + + @Test public void callStaticMethodFromJS() throws Exception { + assertExec("runnable called", HtmlAnnotations.class, + "staticCallback__I", + Double.valueOf(1) + ); + } + + @Test public void callbackWithFourParamsAndReturnType() throws Exception { + Object instance = code.execCode("Get an HtmlAnnotations instance", HtmlAnnotations.class, "create__Ljava_lang_Object_2", null); + assertNotNull(instance, "Instance created"); + assertExec("runnable called", HtmlAnnotations.class, + "first__JLjava_lang_Object_2JJ", + Double.valueOf(42), instance, 42, 31 + ); + } + + @Test public void callbackWithObjectParamsAndReturnType() throws Exception { + Object instance = code.execCode("Get an HtmlAnnotations instance", HtmlAnnotations.class, "create__Ljava_lang_Object_2", null); + assertNotNull(instance, "Instance created"); + assertExec("called back and forth", HtmlAnnotations.class, + "onError__Ljava_lang_Double_2Ljava_lang_Object_2Ljava_lang_Double_2", + Double.valueOf(42), instance, 42 + ); + } + + private static TestVM code; + + @BeforeClass + public void compileTheCode() throws Exception { + code = TestVM.compileClass("org/apidesign/vm4brwsr/HtmlAnnotations"); + } + @AfterClass + public static void releaseTheCode() { + code = null; + } + private static void assertExec(String msg, Class clazz, String method, Object expRes, Object... args) throws Exception { + code.assertExec(msg, clazz, method, expRes, args); + } + +} diff -r ded9a1b4a69c -r 8f8f6b84138f rt/vm/src/test/resources/org/apidesign/vm4brwsr/htmlannotations.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/test/resources/org/apidesign/vm4brwsr/htmlannotations.js Sat Sep 07 14:59:24 2013 +0200 @@ -0,0 +1,19 @@ +/* + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ + +function mul(x, y) { return x * y; } diff -r ded9a1b4a69c -r 8f8f6b84138f rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/VMTest.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/VMTest.java Tue May 28 13:33:12 2013 +0200 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/VMTest.java Sat Sep 07 14:59:24 2013 +0200 @@ -17,6 +17,7 @@ */ package org.apidesign.bck2brwsr.vmtest; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -39,6 +40,7 @@ public final class VMTest { private final List classes = new ArrayList<>(); private final List launcher = new ArrayList<>(); + private Class annotation = BrwsrTest.class; private VMTest() { } @@ -104,6 +106,24 @@ this.launcher.addAll(Arrays.asList(launcher)); return this; } + + /** Specifies which annotation annotates the test methods + * to be executed. By + * default it is the {@link BrwsrTest} annotation. Methods in + * {@link #withClasses(java.lang.Class[]) test classes} annotated by + * this annotation will be executed. + * + * @param aClass an annotation class + * @return this + * @since 0.8 + */ + public final VMTest withTestAnnotation(Class aClass) { + if (!aClass.isAnnotation()) { + throw new IllegalStateException(); + } + this.annotation = aClass; + return this; + } /** Assembles the provided information into the final array of tests. * @return array of TestNG tests @@ -112,7 +132,8 @@ public final Object[] build() { return CompareCase.create( launcher.toArray(new String[0]), - classes.toArray(new Class[0]) + classes.toArray(new Class[0]), + annotation ); } } diff -r ded9a1b4a69c -r 8f8f6b84138f rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java Tue May 28 13:33:12 2013 +0200 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java Sat Sep 07 14:59:24 2013 +0200 @@ -17,6 +17,7 @@ */ package org.apidesign.bck2brwsr.vmtest.impl; +import java.lang.annotation.Annotation; import org.apidesign.bck2brwsr.vmtest.*; import java.lang.reflect.Method; import java.util.ArrayList; @@ -53,7 +54,7 @@ * @param clazz the class to inspect * @return the set of created tests */ - public static Object[] create(String[] brwsr, Class[] classes) { + public static Object[] create(String[] brwsr, Class[] classes, Class brwsrTest) { List ret = new ArrayList<>(); final LaunchSetup l = LaunchSetup.INSTANCE; @@ -70,7 +71,7 @@ Method[] arr = clazz.getMethods(); for (Method m : arr) { registerCompareCases(m, l, ret, brwsr); - registerBrwsrCases(m, l, ret, brwsr); + registerBrwsrCases(brwsrTest, m, l, ret, brwsr); } } return ret.toArray(); @@ -149,8 +150,8 @@ ret.add(new CompareCase(m, real, cse)); } } - private static void registerBrwsrCases(Method m, final LaunchSetup l, List ret, String[] brwsr) { - BrwsrTest c = m.getAnnotation(BrwsrTest.class); + private static void registerBrwsrCases(Class brwsrTest, Method m, final LaunchSetup l, List ret, String[] brwsr) { + Object c = m.getAnnotation(brwsrTest); if (c == null) { return; }