# HG changeset patch # User Martin Soch # Date 1381148458 -7200 # Node ID f73c1a0234fb9c494081a744b36924614e11911a # Parent 8264f07b1f460d1d52e2ce9cda6e680a017de442# Parent 70532b5324e2a4ef2a72d03ed70e435193bd934a merge with revision 1294 where issues with SHIFT operations on Long were detected diff -r 8264f07b1f46 -r f73c1a0234fb .hgtags --- a/.hgtags Wed Feb 27 17:50:47 2013 +0100 +++ b/.hgtags Mon Oct 07 14:20:58 2013 +0200 @@ -1,1 +1,13 @@ 0a115f1c6f3c70458fc479ae82b4d7fcdeb7e95a jdk7-b147_base +7367a296a9ec4a88e0292a41244c96283818e563 bck2brwsr-0.3 +caf1e66268fd4100d57922d973ae09a6bf3be847 release-${releaseVersion} +30e9ac29654fba6f67d0921e7e3aa21133442592 release-0.5 +caf1e66268fd4100d57922d973ae09a6bf3be847 release-0.4 +caf1e66268fd4100d57922d973ae09a6bf3be847 release-${releaseVersion} +0000000000000000000000000000000000000000 release-${releaseVersion} +52a4a5f868bccc67d50ad17f793b9ebabdf75d88 release-0.6 +6792dc0bafb9c76a099e45bfc9e967d6a2823827 release-0.7 +623816269b75e53fffb4b19960df7040a3c20056 release-0.7 +23572dc719bd630817d11eaabdee4565f63ef8e1 release-0.7.1 +56abd247f421febd8b2c5e59d666968692e11555 release-0.7.2 +a83e16b8b825399bb21461e578c32d86982e4ed3 release-0.8 diff -r 8264f07b1f46 -r f73c1a0234fb benchmarks/matrix-multiplication/pom.xml --- a/benchmarks/matrix-multiplication/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/benchmarks/matrix-multiplication/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -1,16 +1,15 @@ - + 4.0.0 org.apidesign.bck2brwsr matrix.multiplication - 0.3-SNAPSHOT + 0.9-SNAPSHOT jar benchmarks org.apidesign.bck2brwsr - 0.3-SNAPSHOT + 0.9-SNAPSHOT Matrix multiplication @@ -38,6 +37,36 @@ true + + org.codehaus.mojo + xml-maven-plugin + 1.0 + + + + transform + + install + + + + + + target/surefire-reports + target/surefire-reports + + TEST*.xml + + src/main/select-time.xsl + + + .csv + + + + + + @@ -45,7 +74,7 @@ org.apidesign.bck2brwsr emul.mini - 0.3-SNAPSHOT + 0.9-SNAPSHOT org.testng @@ -62,7 +91,13 @@ org.apidesign.bck2brwsr vmtest - 0.3-SNAPSHOT + 0.9-SNAPSHOT + test + + + org.apidesign.bck2brwsr + launcher.http + 0.9-SNAPSHOT test diff -r 8264f07b1f46 -r f73c1a0234fb benchmarks/matrix-multiplication/src/main/select-time.xsl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/benchmarks/matrix-multiplication/src/main/select-time.xsl Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,55 @@ + + + + + + + + End + + NaN + + + + + + + + + + , + + + + + + + + + + + , + + + + + + diff -r 8264f07b1f46 -r f73c1a0234fb benchmarks/matrix-multiplication/src/test/java/org/apidesign/benchmark/matrixmul/MatrixTest.java --- a/benchmarks/matrix-multiplication/src/test/java/org/apidesign/benchmark/matrixmul/MatrixTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/benchmarks/matrix-multiplication/src/test/java/org/apidesign/benchmark/matrixmul/MatrixTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -31,6 +31,22 @@ } @Compare(scripting = false) + public String oneIteration() throws IOException { + + Matrix m1 = new Matrix(5); + Matrix m2 = new Matrix(5); + + m1.generateData(); + m2.generateData(); + + Matrix res = m1.multiply(m2); + + StringBuilder sb = new StringBuilder(); + res.printOn(sb); + return sb.toString(); + } + + @Compare(scripting = false) public String tenThousandIterations() throws IOException { Matrix m1 = new Matrix(5); @@ -50,6 +66,27 @@ return sb.toString(); } + @Compare(scripting = false) + public String tenUselessIterations() throws IOException { + + Matrix m1 = new Matrix(5); + Matrix m2 = new Matrix(5); + + m1.generateData(); + m2.generateData(); + + Matrix res = null; + for (int i = 0; i < 10; i++) { + res = m1.multiply(m2); + m1 = res; + } + + StringBuilder sb = new StringBuilder(); + res.printOn(sb); + return sb.toString(); + } + + @Factory public static Object[] create() { return VMTest.create(MatrixTest.class); diff -r 8264f07b1f46 -r f73c1a0234fb benchmarks/pom.xml --- a/benchmarks/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/benchmarks/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -4,11 +4,11 @@ bck2brwsr org.apidesign - 0.3-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr benchmarks - 0.3-SNAPSHOT + 0.9-SNAPSHOT pom Performance benchmarks diff -r 8264f07b1f46 -r f73c1a0234fb dew/pom.xml --- a/dew/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/dew/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -1,15 +1,14 @@ - + 4.0.0 org.apidesign bck2brwsr - 0.3-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr dew - 0.3-SNAPSHOT + 0.9-SNAPSHOT Development Environment for Web http://maven.apache.org @@ -38,11 +37,19 @@ java -classpath - + org.apidesign.bck2brwsr.dew.Dew + + org.apache.maven.plugins + maven-deploy-plugin + 2.7 + + true + + diff -r 8264f07b1f46 -r f73c1a0234fb ide/editor/pom.xml --- a/ide/editor/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/ide/editor/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -4,19 +4,18 @@ ide org.apidesign.bck2brwsr - 0.3-SNAPSHOT + 0.9-SNAPSHOT - org.apidesign.bck2brwsr.ide.editor + org.apidesign.bck2brwsr.ide editor - 0.3-SNAPSHOT + 0.9-SNAPSHOT nbm Editor Support for Bck2Brwsr UTF-8 - RELEASE72 ${project.build.directory}/endorsed @@ -40,71 +39,59 @@ org.netbeans.api org-netbeans-api-annotations-common - ${netbeans.version} org.netbeans.api org-netbeans-modules-java-source - ${netbeans.version} org.netbeans.api org-netbeans-libs-javacapi - ${netbeans.version} org.netbeans.api org-netbeans-spi-java-hints - ${netbeans.version} org.netbeans.api org-netbeans-modules-parsing-api - ${netbeans.version} org.netbeans.api org-netbeans-spi-editor-hints - ${netbeans.version} org.netbeans.api org-openide-util - ${netbeans.version} org.netbeans.api org-netbeans-modules-java-lexer - ${netbeans.version} org.netbeans.api org-netbeans-modules-lexer - ${netbeans.version} org.apidesign.bck2brwsr core - 0.3-SNAPSHOT + 0.9-SNAPSHOT jar test org.netbeans.api org-netbeans-modules-java-hints-test - ${netbeans.version} test org.netbeans.api org-netbeans-libs-junit4 - ${netbeans.version} test org.netbeans.modules org-netbeans-lib-nbjavac - ${netbeans.version} test diff -r 8264f07b1f46 -r f73c1a0234fb ide/pom.xml --- a/ide/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/ide/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -4,14 +4,26 @@ bck2brwsr org.apidesign - 0.3-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr ide - 0.3-SNAPSHOT + 0.9-SNAPSHOT pom IDE Support editor + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.7 + + true + + + + diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/pom.xml --- a/javaquery/api/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -1,15 +1,14 @@ - + 4.0.0 org.apidesign.bck2brwsr javaquery - 0.3-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr javaquery.api - 0.3-SNAPSHOT + 0.9-SNAPSHOT JavaQuery API http://maven.apache.org @@ -19,8 +18,16 @@ maven-compiler-plugin 2.3.2 - 1.6 - 1.6 + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-javadoc-plugin + + org.apidesign.bck2brwsr.htmlpage.api + false @@ -66,5 +73,11 @@ ${project.version} test + + ${project.groupId} + launcher.http + ${project.version} + test + diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypes.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,155 @@ +/** + * 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.htmlpage; + +import org.apidesign.bck2brwsr.core.JavaScriptBody; + +/** + * + * @author Jaroslav Tulach + */ +public 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" + + "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 = { "url", "arr", "callback" }, body = "" + + "var request = new XMLHttpRequest();\n" + + "request.open('GET', 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" + + " throw 'Cannot parse' + error + ':' + this.response;\n" + + " };\n" + + " callback.run__V();\n" + + "};" + + "request.send();" + ) + private static void loadJSON( + String url, Object[] jsonResult, Runnable whenDone + ) { + } + + public static void loadJSON( + String url, Object[] jsonResult, Runnable whenDone, String jsonp + ) { + if (jsonp == null) { + loadJSON(url, jsonResult, whenDone); + } else { + loadJSONP(url, jsonp); + } + } + + @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" + ) + private 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 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/KOList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/KOList.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,167 @@ +/** + * 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.htmlpage; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import org.apidesign.bck2brwsr.core.JavaScriptOnly; + +/** + * + * @author Jaroslav Tulach + */ +public final class KOList extends ArrayList { + private final String name; + private final String[] deps; + private Knockout model; + private Runnable onchange; + + public KOList(String name, String... deps) { + this.name = name; + this.deps = deps; + } + + public void assign(Knockout model) { + if (this.model != model) { + this.model = model; + notifyChange(); + } + } + + public KOList onChange(Runnable r) { + if (this.onchange != null) { + throw new IllegalStateException(); + } + this.onchange = r; + return this; + } + + @Override + public boolean add(T e) { + boolean ret = super.add(e); + notifyChange(); + return ret; + } + + @Override + public boolean addAll(Collection c) { + boolean ret = super.addAll(c); + notifyChange(); + return ret; + } + + @Override + public boolean addAll(int index, Collection c) { + boolean ret = super.addAll(index, c); + notifyChange(); + return ret; + } + + @Override + public boolean remove(Object o) { + boolean ret = super.remove(o); + notifyChange(); + return ret; + } + + @Override + public void clear() { + super.clear(); + notifyChange(); + } + + @Override + public boolean removeAll(Collection c) { + boolean ret = super.removeAll(c); + notifyChange(); + return ret; + } + + @Override + public boolean retainAll(Collection c) { + boolean ret = super.retainAll(c); + notifyChange(); + return ret; + } + + @Override + public T set(int index, T element) { + T ret = super.set(index, element); + notifyChange(); + return ret; + } + + @Override + public void add(int index, T element) { + super.add(index, element); + notifyChange(); + } + + @Override + public T remove(int index) { + T ret = super.remove(index); + notifyChange(); + return ret; + } + + @Override + public String toString() { + Iterator it = iterator(); + if (!it.hasNext()) { + return "[]"; + } + String sep = ""; + StringBuilder sb = new StringBuilder(); + sb.append('['); + while (it.hasNext()) { + T t = it.next(); + sb.append(sep); + sb.append(ConvertTypes.toJSON(t)); + sep = ","; + } + sb.append(']'); + return sb.toString(); + } + + + @JavaScriptOnly(name = "koArray", value = "function() { return this.toArray___3Ljava_lang_Object_2(); }") + private static native int koArray(); + + private void notifyChange() { + Knockout m = model; + if (m != null) { + m.valueHasMutated(name); + for (String dependant : deps) { + m.valueHasMutated(dependant); + } + } + Runnable r = onchange; + if (r != null) { + r.run(); + } + } + + @Override + public KOList clone() { + KOList ko = (KOList)super.clone(); + ko.model = null; + return ko; + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/Knockout.java Mon Oct 07 14:20:58 2013 +0200 @@ -18,6 +18,7 @@ package org.apidesign.bck2brwsr.htmlpage; import java.lang.reflect.Method; +import java.util.List; import org.apidesign.bck2brwsr.core.ExtraJavaScript; import org.apidesign.bck2brwsr.core.JavaScriptBody; @@ -29,38 +30,40 @@ public class Knockout { /** used by tests */ static Knockout next; - - Knockout() { + private final Object model; + + Knockout(Object model) { + this.model = model == null ? this : model; } public static Knockout applyBindings( - Class modelClass, M model, String[] propsGettersAndSetters + 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(); + bindings = new Knockout(null); } - 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() - ); - } catch (NoSuchMethodException ex) { - throw new IllegalStateException(ex.getMessage()); - } - } + applyImpl(propsGettersAndSetters, modelClass, bindings, model, methodsAndSignatures); applyBindings(bindings); return bindings; } - @JavaScriptBody(args = { "prop" }, body = - "this[prop].valueHasMutated();" + public void valueHasMutated(String prop) { + valueHasMutated(model, prop); + } + @JavaScriptBody(args = { "self", "prop" }, body = + "self[prop].valueHasMutated();" ) - public void valueHasMutated(String prop) { + public void valueHasMutated(Object self, String prop) { } @@ -68,26 +71,60 @@ public static void triggerEvent(String id, String ev) { } - @JavaScriptBody(args = { "bindings", "model", "prop", "getter", "setter", "primitive" }, body = + @JavaScriptBody(args = { "bindings", "model", "prop", "getter", "setter", "primitive", "array" }, body = "var bnd = {\n" - + " read: function() {\n" + + " 'read': function() {\n" + " var v = model[getter]();\n" + + " if (array) v = v.koArray();\n" + " return v;\n" + " },\n" - + " owner: bindings\n" + + " 'owner': bindings\n" + "};\n" + "if (setter != null) {\n" - + " bnd.write = function(val) {\n" + + " bnd['write'] = function(val) {\n" + " model[setter](primitive ? new Number(val) : val);\n" + " };\n" + "}\n" - + "bindings[prop] = ko.computed(bnd);" + + "bindings[prop] = ko['computed'](bnd);" ) private static void bind( - Object bindings, Object model, String prop, String getter, String setter, boolean primitive + 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); };" + ) + private static void expose( + Object bindings, Object model, String prop, String sig ) { } @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);") private 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 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Mon Oct 07 14:20:58 2013 +0200 @@ -20,23 +20,30 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; +import java.io.StringWriter; import java.io.Writer; +import java.lang.annotation.AnnotationTypeMismatchException; +import java.lang.annotation.IncompleteAnnotationException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.WeakHashMap; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Completion; import javax.annotation.processing.Completions; +import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -44,13 +51,21 @@ import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; import javax.lang.model.type.MirroredTypeException; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; +import org.apidesign.bck2brwsr.htmlpage.api.Model; import org.apidesign.bck2brwsr.htmlpage.api.On; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; +import org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange; +import org.apidesign.bck2brwsr.htmlpage.api.OnReceive; import org.apidesign.bck2brwsr.htmlpage.api.Page; import org.apidesign.bck2brwsr.htmlpage.api.Property; import org.openide.util.lookup.ServiceProvider; @@ -62,89 +77,70 @@ */ @ServiceProvider(service=Processor.class) @SupportedAnnotationTypes({ + "org.apidesign.bck2brwsr.htmlpage.api.Model", "org.apidesign.bck2brwsr.htmlpage.api.Page", + "org.apidesign.bck2brwsr.htmlpage.api.OnFunction", + "org.apidesign.bck2brwsr.htmlpage.api.OnReceive", + "org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange", + "org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty", "org.apidesign.bck2brwsr.htmlpage.api.On" }) public final class PageProcessor extends AbstractProcessor { + private final Map models = new WeakHashMap<>(); + private final Map verify = new WeakHashMap<>(); @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) { - Page p = e.getAnnotation(Page.class); - if (p == null) { - continue; - } - PackageElement pe = (PackageElement)e.getEnclosingElement(); - String pkg = pe.getQualifiedName().toString(); - - ProcessPage pp; - try { - InputStream is = openStream(pkg, p.xhtml()); - pp = ProcessPage.readPage(is); - is.close(); - } catch (IOException iOException) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml(), e); - return false; - } - Writer w; - String className = p.className(); - if (className.isEmpty()) { - int indx = p.xhtml().indexOf('.'); - className = p.xhtml().substring(0, indx); - } - try { - FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e); - w = new OutputStreamWriter(java.openOutputStream()); - try { - w.append("package " + pkg + ";\n"); - w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n"); - w.append("final class ").append(className).append(" {\n"); - w.append(" private boolean locked;\n"); - if (!initializeOnClick(className, (TypeElement) e, w, pp)) { - return false; - } - for (String id : pp.ids()) { - String tag = pp.tagNameForId(id); - String type = type(tag); - w.append(" ").append("public final "). - append(type).append(' ').append(cnstnt(id)).append(" = new "). - append(type).append("(\"").append(id).append("\");\n"); - } - List propsGetSet = new ArrayList(); - Map> propsDeps = new HashMap>(); - generateComputedProperties(w, e.getEnclosedElements(), propsGetSet, propsDeps); - generateProperties(w, p.properties(), propsGetSet, propsDeps); - w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n"); - if (!propsGetSet.isEmpty()) { - w.write("public " + className + " applyBindings() {\n"); - w.write(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings("); - w.write(className + ".class, this, "); - w.write("new String[] {\n"); - String sep = ""; - for (String n : propsGetSet) { - w.write(sep); - if (n == null) { - w.write(" null"); - } else { - w.write(" \"" + n + "\""); - } - sep = ",\n"; - } - w.write("\n });\n return this;\n}\n"); - - w.write("public void triggerEvent(Element e, OnEvent ev) {\n"); - w.write(" org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n"); - w.write("}\n"); - } - w.append("}\n"); - } finally { - w.close(); - } - } catch (IOException ex) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e); - return false; + boolean ok = true; + for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) { + if (!processModel(e)) { + ok = false; } } - return true; + for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) { + if (!processPage(e)) { + ok = false; + } + } + if (roundEnv.processingOver()) { + models.clear(); + for (Map.Entry entry : verify.entrySet()) { + TypeElement te = (TypeElement)entry.getKey(); + String fqn = processingEnv.getElementUtils().getBinaryName(te).toString(); + Element finalElem = processingEnv.getElementUtils().getTypeElement(fqn); + if (finalElem == null) { + continue; + } + Prprt[] props; + Model m = finalElem.getAnnotation(Model.class); + if (m != null) { + props = Prprt.wrap(processingEnv, finalElem, m.properties()); + } else { + Page p = finalElem.getAnnotation(Page.class); + props = Prprt.wrap(processingEnv, finalElem, p.properties()); + } + for (Prprt p : props) { + boolean[] isModel = { false }; + boolean[] isEnum = { false }; + boolean[] isPrimitive = { false }; + String t = checkType(p, isModel, isEnum, isPrimitive); + if (isEnum[0]) { + continue; + } + if (isPrimitive[0]) { + continue; + } + if (isModel[0]) { + continue; + } + if ("java.lang.String".equals(t)) { + continue; + } + error("The type " + t + " should be defined by @Model annotation", entry.getKey()); + } + } + verify.clear(); + } + return ok; } private InputStream openStream(String pkg, String name) throws IOException { @@ -157,6 +153,236 @@ } } + private void error(String msg, Element e) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e); + } + + private boolean processModel(Element e) { + boolean ok = true; + Model m = e.getAnnotation(Model.class); + if (m == null) { + return true; + } + String pkg = findPkgName(e); + Writer w; + String className = m.className(); + models.put(e, className); + try { + StringWriter body = new StringWriter(); + List propsGetSet = new ArrayList<>(); + List functions = new ArrayList<>(); + Map> propsDeps = new HashMap<>(); + Map> functionDeps = new HashMap<>(); + Prprt[] props = createProps(e, m.properties()); + + if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) { + ok = false; + } + if (!generateOnChange(e, propsDeps, props, className, functionDeps)) { + ok = false; + } + if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) { + ok = false; + } + if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) { + ok = false; + } + FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e); + w = new OutputStreamWriter(java.openOutputStream()); + try { + w.append("package " + pkg + ";\n"); + w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n"); + w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n"); + w.append("import org.apidesign.bck2brwsr.core.JavaScriptOnly;\n"); + w.append("final class ").append(className).append(" implements Cloneable {\n"); + w.append(" private boolean locked;\n"); + w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n"); + w.append(body.toString()); + w.append(" private static Class<" + inPckName(e) + "> modelFor() { return null; }\n"); + w.append(" public ").append(className).append("() {\n"); + w.append(" intKnckt();\n"); + w.append(" };\n"); + w.append(" private void intKnckt() {\n"); + w.append(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(this, "); + writeStringArray(propsGetSet, w); + w.append(", "); + writeStringArray(functions, w); + w.append(" );\n"); + w.append(" };\n"); + w.append(" ").append(className).append("(Object json) {\n"); + int values = 0; + for (int i = 0; i < propsGetSet.size(); i += 4) { + Prprt p = findPrprt(props, propsGetSet.get(i)); + if (p == null) { + continue; + } + values++; + } + w.append(" Object[] ret = new Object[" + values + "];\n"); + w.append(" org.apidesign.bck2brwsr.htmlpage.ConvertTypes.extractJSON(json, new String[] {\n"); + for (int i = 0; i < propsGetSet.size(); i += 4) { + Prprt p = findPrprt(props, propsGetSet.get(i)); + if (p == null) { + continue; + } + w.append(" \"").append(propsGetSet.get(i)).append("\",\n"); + } + w.append(" }, ret);\n"); + for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) { + final String pn = propsGetSet.get(i); + Prprt p = findPrprt(props, pn); + if (p == null) { + continue; + } + boolean[] isModel = { false }; + boolean[] isEnum = { false }; + boolean isPrimitive[] = { false }; + String type = checkType(props[prop++], isModel, isEnum, isPrimitive); + if (p.array()) { + w.append("if (ret[" + cnt + "] instanceof Object[]) {\n"); + w.append(" for (Object e : ((Object[])ret[" + cnt + "])) {\n"); + if (isModel[0]) { + w.append(" this.prop_").append(pn).append(".add(new "); + w.append(type).append("(e));\n"); + } else if (isEnum[0]) { + w.append(" this.prop_").append(pn); + w.append(".add(e == null ? null : "); + w.append(type).append(".valueOf((String)e));\n"); + } else { + if (isPrimitive(type)) { + w.append(" this.prop_").append(pn).append(".add(((Number)e)."); + w.append(type).append("Value());\n"); + } else { + w.append(" this.prop_").append(pn).append(".add(("); + w.append(type).append(")e);\n"); + } + } + w.append(" }\n"); + w.append("}\n"); + } else { + if (isEnum[0]) { + w.append(" this.prop_").append(pn); + w.append(" = ret[" + cnt + "] == null ? null : "); + w.append(type).append(".valueOf((String)ret[" + cnt + "]);\n"); + } else if (isPrimitive(type)) { + w.append(" this.prop_").append(pn); + w.append(" = ((Number)").append("ret[" + cnt + "])."); + w.append(type).append("Value();\n"); + } else { + w.append(" this.prop_").append(pn); + w.append(" = (").append(type).append(')'); + w.append("ret[" + cnt + "];\n"); + } + } + cnt++; + } + w.append(" intKnckt();\n"); + w.append(" };\n"); + writeToString(props, w); + writeClone(className, props, w); + w.append("}\n"); + } finally { + w.close(); + } + } catch (IOException ex) { + error("Can't create " + className + ".java", e); + return false; + } + return ok; + } + + private boolean processPage(Element e) { + boolean ok = true; + Page p = e.getAnnotation(Page.class); + if (p == null) { + return true; + } + String pkg = findPkgName(e); + + ProcessPage pp; + try (InputStream is = openStream(pkg, p.xhtml())) { + pp = ProcessPage.readPage(is); + is.close(); + } catch (IOException iOException) { + error("Can't read " + p.xhtml() + " as " + iOException.getMessage(), e); + ok = false; + pp = null; + } + Writer w; + String className = p.className(); + if (className.isEmpty()) { + int indx = p.xhtml().indexOf('.'); + className = p.xhtml().substring(0, indx); + } + try { + StringWriter body = new StringWriter(); + List propsGetSet = new ArrayList<>(); + List functions = new ArrayList<>(); + Map> propsDeps = new HashMap<>(); + Map> functionDeps = new HashMap<>(); + + Prprt[] props = createProps(e, p.properties()); + if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) { + ok = false; + } + if (!generateOnChange(e, propsDeps, props, className, functionDeps)) { + ok = false; + } + if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) { + ok = false; + } + if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) { + ok = false; + } + if (!generateReceive(e, body, className, e.getEnclosedElements(), functions)) { + ok = false; + } + + FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e); + w = new OutputStreamWriter(java.openOutputStream()); + try { + w.append("package " + pkg + ";\n"); + w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n"); + w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n"); + w.append("final class ").append(className).append(" {\n"); + w.append(" private boolean locked;\n"); + if (!initializeOnClick(className, (TypeElement) e, w, pp)) { + ok = false; + } else { + if (pp != null) for (String id : pp.ids()) { + String tag = pp.tagNameForId(id); + String type = type(tag); + w.append(" ").append("public final "). + append(type).append(' ').append(cnstnt(id)).append(" = new "). + append(type).append("(\"").append(id).append("\");\n"); + } + } + w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n"); + w.append(body.toString()); + if (!propsGetSet.isEmpty()) { + w.write("public " + className + " applyBindings() {\n"); + w.write(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings("); + w.write(className + ".class, this, "); + writeStringArray(propsGetSet, w); + w.append(", "); + writeStringArray(functions, w); + w.write(");\n return this;\n}\n"); + + w.write("public void triggerEvent(Element e, OnEvent ev) {\n"); + w.write(" org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n"); + w.write("}\n"); + } + w.append("}\n"); + } finally { + w.close(); + } + } catch (IOException ex) { + error("Can't create " + className + ".java", e); + return false; + } + return ok; + } + private static String type(String tag) { if (tag.equals("title")) { return "Title"; @@ -177,12 +403,13 @@ } private static String cnstnt(String id) { - return id.toUpperCase(Locale.ENGLISH).replace('.', '_').replace('-', '_'); + return id.replace('.', '_').replace('-', '_'); } private boolean initializeOnClick( String className, TypeElement type, Writer w, ProcessPage pp ) throws IOException { + boolean ok = true; TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType(); { //for (Element clazz : pe.getEnclosedElements()) { // if (clazz.getKind() != ElementKind.CLASS) { @@ -195,46 +422,27 @@ On oc = method.getAnnotation(On.class); if (oc != null) { for (String id : oc.id()) { + if (pp == null) { + error("id = " + id + " not found in HTML page.", method); + ok = false; + continue; + } if (pp.tagNameForId(id) == null) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method); - return false; + error("id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method); + ok = false; + continue; } ExecutableElement ee = (ExecutableElement)method; - StringBuilder params = new StringBuilder(); - { - boolean first = true; - for (VariableElement ve : ee.getParameters()) { - if (!first) { - params.append(", "); - } - first = false; - if (ve.asType() == stringType) { - params.append('"').append(id).append('"'); - continue; - } - String rn = ve.asType().toString(); - int last = rn.lastIndexOf('.'); - if (last >= 0) { - rn = rn.substring(last + 1); - } - if (rn.equals(className)) { - params.append(className).append(".this"); - continue; - } - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, - "@On method can only accept String or " + className + " arguments", - ee - ); - return false; - } - } + CharSequence params = wrapParams(ee, id, className, "ev", null); if (!ee.getModifiers().contains(Modifier.STATIC)) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee); - return false; + error("@On method has to be static", ee); + ok = false; + continue; } if (ee.getModifiers().contains(Modifier.PRIVATE)) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee); - return false; + error("@On method can't be private", ee); + ok = false; + continue; } w.append(" OnEvent." + oc.event()).append(".of(").append(cnstnt(id)). append(").perform(new OnDispatch(" + dispatchCnt + "));\n"); @@ -252,10 +460,10 @@ } w.append(" }\n"); if (dispatchCnt > 0) { - w.append("class OnDispatch implements Runnable {\n"); + w.append("class OnDispatch implements OnHandler {\n"); w.append(" private final int dispatch;\n"); w.append(" OnDispatch(int d) { dispatch = d; }\n"); - w.append(" public void run() {\n"); + w.append(" public void onEvent(Object ev) {\n"); w.append(" switch (dispatch) {\n"); w.append(dispatch); w.append(" }\n"); @@ -265,7 +473,7 @@ } - return true; + return ok; } @Override @@ -279,8 +487,7 @@ Element cls = findClass(element); Page p = cls.getAnnotation(Page.class); - PackageElement pe = (PackageElement) cls.getEnclosingElement(); - String pkg = pe.getQualifiedName().toString(); + String pkg = findPkgName(cls); ProcessPage pp; try { InputStream is = openStream(pkg, p.xhtml()); @@ -290,7 +497,7 @@ return Collections.emptyList(); } - List cc = new ArrayList(); + List cc = new ArrayList<>(); userText = userText.substring(1); for (String id : pp.ids()) { if (id.startsWith(userText)) { @@ -311,44 +518,89 @@ return e.getEnclosingElement(); } - private static void generateProperties( - Writer w, Property[] properties, Collection props, - Map> deps + private boolean generateProperties( + Element where, + Writer w, Prprt[] properties, + Collection props, + Map> deps, + Map> functionDeps ) throws IOException { - for (Property p : properties) { - final String tn = typeName(p); - String[] gs = toGetSet(p.name(), tn); + boolean ok = true; + for (Prprt p : properties) { + final String tn; + tn = typeName(where, p); + String[] gs = toGetSet(p.name(), tn, p.array()); - w.write("private " + tn + " prop_" + p.name() + ";\n"); - w.write("public " + tn + " " + gs[0] + "() {\n"); - w.write(" if (locked) throw new IllegalStateException();\n"); - w.write(" return prop_" + p.name() + ";\n"); - w.write("}\n"); - w.write("public void " + gs[1] + "(" + tn + " v) {\n"); - w.write(" if (locked) throw new IllegalStateException();\n"); - w.write(" prop_" + p.name() + " = v;\n"); - w.write(" if (ko != null) {\n"); - w.write(" ko.valueHasMutated(\"" + p.name() + "\");\n"); - final Collection dependants = deps.get(p.name()); - if (dependants != null) { - for (String depProp : dependants) { - w.write(" ko.valueHasMutated(\"" + depProp + "\");\n"); + if (p.array()) { + w.write("private KOList<" + tn + "> prop_" + p.name() + " = new KOList<" + tn + ">(\"" + + p.name() + "\""); + Collection dependants = deps.get(p.name()); + if (dependants != null) { + for (String depProp : dependants) { + w.write(", "); + w.write('\"'); + w.write(depProp); + w.write('\"'); + } } + w.write(")"); + + dependants = functionDeps.get(p.name()); + if (dependants != null) { + w.write(".onChange(new Runnable() { public void run() {\n"); + for (String call : dependants) { + w.append(call); + } + w.write("}})"); + } + w.write(";\n"); + + w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n"); + w.write(" if (locked) throw new IllegalStateException();\n"); + w.write(" prop_" + p.name() + ".assign(ko);\n"); + w.write(" return prop_" + p.name() + ";\n"); + w.write("}\n"); + } else { + w.write("private " + tn + " prop_" + p.name() + ";\n"); + w.write("public " + tn + " " + gs[0] + "() {\n"); + w.write(" if (locked) throw new IllegalStateException();\n"); + w.write(" return prop_" + p.name() + ";\n"); + w.write("}\n"); + w.write("public void " + gs[1] + "(" + tn + " v) {\n"); + w.write(" if (locked) throw new IllegalStateException();\n"); + w.write(" prop_" + p.name() + " = v;\n"); + w.write(" if (ko != null) {\n"); + w.write(" ko.valueHasMutated(\"" + p.name() + "\");\n"); + Collection dependants = deps.get(p.name()); + if (dependants != null) { + for (String depProp : dependants) { + w.write(" ko.valueHasMutated(\"" + depProp + "\");\n"); + } + } + w.write(" }\n"); + dependants = functionDeps.get(p.name()); + if (dependants != null) { + for (String call : dependants) { + w.append(call); + } + } + w.write("}\n"); } - w.write(" }\n"); - w.write("}\n"); props.add(p.name()); props.add(gs[2]); props.add(gs[3]); props.add(gs[0]); } + return ok; } private boolean generateComputedProperties( - Writer w, Collection arr, Collection props, + Writer w, Prprt[] fixedProps, + Collection arr, Collection props, Map> deps ) throws IOException { + boolean ok = true; for (Element e : arr) { if (e.getKind() != ElementKind.METHOD) { continue; @@ -357,30 +609,43 @@ continue; } ExecutableElement ee = (ExecutableElement)e; - final String tn = ee.getReturnType().toString(); + final TypeMirror rt = ee.getReturnType(); + final Types tu = processingEnv.getTypeUtils(); + TypeMirror ert = tu.erasure(rt); + String tn = fqn(ert, ee); + boolean array = false; + if (tn.equals("java.util.List")) { + array = true; + } + final String sn = ee.getSimpleName().toString(); - String[] gs = toGetSet(sn, tn); + String[] gs = toGetSet(sn, tn, array); w.write("public " + tn + " " + gs[0] + "() {\n"); w.write(" if (locked) throw new IllegalStateException();\n"); int arg = 0; for (VariableElement pe : ee.getParameters()) { final String dn = pe.getSimpleName().toString(); - final String dt = pe.asType().toString(); - String[] call = toGetSet(dn, dt); + + if (!verifyPropName(pe, dn, fixedProps)) { + ok = false; + } + + final String dt = fqn(pe.asType(), ee); + String[] call = toGetSet(dn, dt, false); w.write(" " + dt + " arg" + (++arg) + " = "); w.write(call[0] + "();\n"); Collection depends = deps.get(dn); if (depends == null) { - depends = new LinkedHashSet(); + depends = new LinkedHashSet<>(); deps.put(dn, depends); } depends.add(sn); } w.write(" try {\n"); w.write(" locked = true;\n"); - w.write(" return " + e.getEnclosingElement().getSimpleName() + '.' + e.getSimpleName() + "("); + w.write(" return " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + e.getSimpleName() + "("); String sep = ""; for (int i = 1; i <= arg; i++) { w.write(sep); @@ -392,17 +657,17 @@ w.write(" locked = false;\n"); w.write(" }\n"); w.write("}\n"); - + props.add(e.getSimpleName().toString()); props.add(gs[2]); props.add(null); props.add(gs[0]); } - return true; + return ok; } - private static String[] toGetSet(String name, String type) { + private static String[] toGetSet(String name, String type, boolean array) { String n = Character.toUpperCase(name.charAt(0)) + name.substring(1); String bck2brwsrType = "L" + type.replace('.', '_') + "_2"; if ("int".equals(type)) { @@ -417,6 +682,14 @@ bck2brwsrType = "Z"; } final String nu = n.replace('.', '_'); + if (array) { + return new String[] { + "get" + n, + null, + "get" + nu + "__Ljava_util_List_2", + null + }; + } return new String[]{ pref + n, "set" + n, @@ -425,11 +698,702 @@ }; } - private static String typeName(Property p) { - try { - return p.type().getName(); - } catch (MirroredTypeException ex) { - return ex.getTypeMirror().toString(); + private String typeName(Element where, Prprt p) { + String ret; + boolean[] isModel = { false }; + boolean[] isEnum = { false }; + boolean isPrimitive[] = { false }; + ret = checkType(p, isModel, isEnum, isPrimitive); + if (p.array()) { + String bt = findBoxedType(ret); + if (bt != null) { + return bt; + } + } + return ret; + } + + private static String findBoxedType(String ret) { + if (ret.equals("boolean")) { + return Boolean.class.getName(); + } + if (ret.equals("byte")) { + return Byte.class.getName(); + } + if (ret.equals("short")) { + return Short.class.getName(); + } + if (ret.equals("char")) { + return Character.class.getName(); + } + if (ret.equals("int")) { + return Integer.class.getName(); + } + if (ret.equals("long")) { + return Long.class.getName(); + } + if (ret.equals("float")) { + return Float.class.getName(); + } + if (ret.equals("double")) { + return Double.class.getName(); + } + return null; + } + + private boolean verifyPropName(Element e, String propName, Prprt[] existingProps) { + StringBuilder sb = new StringBuilder(); + String sep = ""; + for (Prprt Prprt : existingProps) { + if (Prprt.name().equals(propName)) { + return true; + } + sb.append(sep); + sb.append('"'); + sb.append(Prprt.name()); + sb.append('"'); + sep = ", "; + } + error( + propName + " is not one of known properties: " + sb + , e + ); + return false; + } + + private static String findPkgName(Element e) { + for (;;) { + if (e.getKind() == ElementKind.PACKAGE) { + return ((PackageElement)e).getQualifiedName().toString(); + } + e = e.getEnclosingElement(); } } + + private boolean generateFunctions( + Element clazz, StringWriter body, String className, + List enclosedElements, List functions + ) { + for (Element m : enclosedElements) { + if (m.getKind() != ElementKind.METHOD) { + continue; + } + ExecutableElement e = (ExecutableElement)m; + OnFunction onF = e.getAnnotation(OnFunction.class); + if (onF == null) { + continue; + } + if (!e.getModifiers().contains(Modifier.STATIC)) { + error("@OnFunction method needs to be static", e); + return false; + } + if (e.getModifiers().contains(Modifier.PRIVATE)) { + error("@OnFunction method cannot be private", e); + return false; + } + if (e.getReturnType().getKind() != TypeKind.VOID) { + error("@OnFunction method should return void", e); + return false; + } + String n = e.getSimpleName().toString(); + body.append("private void ").append(n).append("(Object data, Object ev) {\n"); + body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("("); + body.append(wrapParams(e, null, className, "ev", "data")); + body.append(");\n"); + body.append("}\n"); + + functions.add(n); + functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2"); + } + return true; + } + + private boolean generateOnChange(Element clazz, Map> propDeps, + Prprt[] properties, String className, + Map> functionDeps + ) { + for (Element m : clazz.getEnclosedElements()) { + if (m.getKind() != ElementKind.METHOD) { + continue; + } + ExecutableElement e = (ExecutableElement) m; + OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class); + if (onPC == null) { + continue; + } + for (String pn : onPC.value()) { + if (findPrprt(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) { + error("No Prprt named '" + pn + "' in the model", clazz); + return false; + } + } + if (!e.getModifiers().contains(Modifier.STATIC)) { + error("@OnPrprtChange method needs to be static", e); + return false; + } + if (e.getModifiers().contains(Modifier.PRIVATE)) { + error("@OnPrprtChange method cannot be private", e); + return false; + } + if (e.getReturnType().getKind() != TypeKind.VOID) { + error("@OnPrprtChange method should return void", e); + return false; + } + String n = e.getSimpleName().toString(); + + + for (String pn : onPC.value()) { + StringBuilder call = new StringBuilder(); + call.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("("); + call.append(wrapPropName(e, className, "name", pn)); + call.append(");\n"); + + Collection change = functionDeps.get(pn); + if (change == null) { + change = new ArrayList<>(); + functionDeps.put(pn, change); + } + change.add(call.toString()); + for (String dpn : findDerivedFrom(propDeps, pn)) { + change = functionDeps.get(dpn); + if (change == null) { + change = new ArrayList<>(); + functionDeps.put(dpn, change); + } + change.add(call.toString()); + } + } + } + return true; + } + + private boolean generateReceive( + Element clazz, StringWriter body, String className, + List enclosedElements, List functions + ) { + for (Element m : enclosedElements) { + if (m.getKind() != ElementKind.METHOD) { + continue; + } + ExecutableElement e = (ExecutableElement)m; + OnReceive onR = e.getAnnotation(OnReceive.class); + if (onR == null) { + continue; + } + if (!e.getModifiers().contains(Modifier.STATIC)) { + error("@OnReceive method needs to be static", e); + return false; + } + if (e.getModifiers().contains(Modifier.PRIVATE)) { + error("@OnReceive method cannot be private", e); + return false; + } + if (e.getReturnType().getKind() != TypeKind.VOID) { + error("@OnReceive method should return void", e); + return false; + } + String modelClass = null; + boolean expectsList = false; + List args = new ArrayList<>(); + { + for (VariableElement ve : e.getParameters()) { + TypeMirror modelType = null; + if (ve.asType().toString().equals(className)) { + args.add(className + ".this"); + } else if (isModel(ve.asType())) { + modelType = ve.asType(); + } else if (ve.asType().getKind() == TypeKind.ARRAY) { + modelType = ((ArrayType)ve.asType()).getComponentType(); + expectsList = true; + } + if (modelType != null) { + if (modelClass != null) { + error("There can be only one model class among arguments", e); + } else { + modelClass = modelType.toString(); + if (expectsList) { + args.add("arr"); + } else { + args.add("arr[0]"); + } + } + } + } + } + if (modelClass == null) { + error("The method needs to have one @Model class as parameter", e); + } + String n = e.getSimpleName().toString(); + body.append("public void ").append(n).append("("); + StringBuilder assembleURL = new StringBuilder(); + String jsonpVarName = null; + { + String sep = ""; + boolean skipJSONP = onR.jsonp().isEmpty(); + for (String p : findParamNames(e, onR.url(), assembleURL)) { + if (!skipJSONP && p.equals(onR.jsonp())) { + skipJSONP = true; + jsonpVarName = p; + continue; + } + body.append(sep); + body.append("String ").append(p); + sep = ", "; + } + if (!skipJSONP) { + error( + "Name of jsonp attribute ('" + onR.jsonp() + + "') is not used in url attribute '" + onR.url() + "'", e + ); + } + } + body.append(") {\n"); + body.append(" final Object[] result = { null };\n"); + body.append( + " class ProcessResult implements Runnable {\n" + + " @Override\n" + + " public void run() {\n" + + " Object value = result[0];\n"); + body.append( + " " + modelClass + "[] arr;\n"); + body.append( + " if (value instanceof Object[]) {\n" + + " Object[] data = ((Object[])value);\n" + + " arr = new " + modelClass + "[data.length];\n" + + " for (int i = 0; i < data.length; i++) {\n" + + " arr[i] = new " + modelClass + "(data[i]);\n" + + " }\n" + + " } else {\n" + + " arr = new " + modelClass + "[1];\n" + + " arr[0] = new " + modelClass + "(value);\n" + + " }\n" + ); + { + body.append(clazz.getSimpleName()).append(".").append(n).append("("); + String sep = ""; + for (String arg : args) { + body.append(sep); + body.append(arg); + sep = ", "; + } + body.append(");\n"); + } + body.append( + " }\n" + + " }\n" + ); + body.append(" ProcessResult pr = new ProcessResult();\n"); + if (jsonpVarName != null) { + body.append(" String ").append(jsonpVarName). + append(" = org.apidesign.bck2brwsr.htmlpage.ConvertTypes.createJSONP(result, pr);\n"); + } + body.append(" org.apidesign.bck2brwsr.htmlpage.ConvertTypes.loadJSON(\n "); + body.append(assembleURL); + body.append(", result, pr, ").append(jsonpVarName).append("\n );\n"); +// body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("("); +// body.append(wrapParams(e, null, className, "ev", "data")); +// body.append(");\n"); + body.append("}\n"); + } + return true; + } + + private CharSequence wrapParams( + ExecutableElement ee, String id, String className, String evName, String dataName + ) { + TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType(); + StringBuilder params = new StringBuilder(); + boolean first = true; + for (VariableElement ve : ee.getParameters()) { + if (!first) { + params.append(", "); + } + first = false; + String toCall = null; + if (ve.asType() == stringType) { + if (ve.getSimpleName().contentEquals("id")) { + params.append('"').append(id).append('"'); + continue; + } + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString("; + } + if (ve.asType().getKind() == TypeKind.DOUBLE) { + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble("; + } + if (ve.asType().getKind() == TypeKind.INT) { + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt("; + } + if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) { + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toModel(" + ve.asType() + ".class, "; + } + + if (toCall != null) { + params.append(toCall); + if (dataName != null && ve.getSimpleName().contentEquals(dataName)) { + params.append(dataName); + params.append(", null"); + } else { + if (evName == null) { + final StringBuilder sb = new StringBuilder(); + sb.append("Unexpected string parameter name."); + if (dataName != null) { + sb.append(" Try \"").append(dataName).append("\""); + } + error(sb.toString(), ee); + } + params.append(evName); + params.append(", \""); + params.append(ve.getSimpleName().toString()); + params.append("\""); + } + params.append(")"); + continue; + } + String rn = fqn(ve.asType(), ee); + int last = rn.lastIndexOf('.'); + if (last >= 0) { + rn = rn.substring(last + 1); + } + if (rn.equals(className)) { + params.append(className).append(".this"); + continue; + } + error( + "@On method can only accept String named 'id' or " + className + " arguments", + ee + ); + } + return params; + } + + + private CharSequence wrapPropName( + ExecutableElement ee, String className, String propName, String propValue + ) { + TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType(); + StringBuilder params = new StringBuilder(); + boolean first = true; + for (VariableElement ve : ee.getParameters()) { + if (!first) { + params.append(", "); + } + first = false; + if (ve.asType() == stringType) { + if (propName != null && ve.getSimpleName().contentEquals(propName)) { + params.append('"').append(propValue).append('"'); + } else { + error("Unexpected string parameter name. Try \"" + propName + "\".", ee); + } + continue; + } + String rn = fqn(ve.asType(), ee); + int last = rn.lastIndexOf('.'); + if (last >= 0) { + rn = rn.substring(last + 1); + } + if (rn.equals(className)) { + params.append(className).append(".this"); + continue; + } + error( + "@OnPrprtChange method can only accept String or " + className + " arguments", + ee); + } + return params; + } + + private boolean isModel(TypeMirror tm) { + final Element e = processingEnv.getTypeUtils().asElement(tm); + if (e == null) { + return false; + } + for (Element ch : e.getEnclosedElements()) { + if (ch.getKind() == ElementKind.METHOD) { + ExecutableElement ee = (ExecutableElement)ch; + if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) { + return true; + } + } + } + return models.values().contains(e.getSimpleName().toString()); + } + + private void writeStringArray(List strings, Writer w) throws IOException { + w.write("new String[] {\n"); + String sep = ""; + for (String n : strings) { + w.write(sep); + if (n == null) { + w.write(" null"); + } else { + w.write(" \"" + n + "\""); + } + sep = ",\n"; + } + w.write("\n }"); + } + + private void writeToString(Prprt[] props, Writer w) throws IOException { + w.write(" public String toString() {\n"); + w.write(" StringBuilder sb = new StringBuilder();\n"); + w.write(" sb.append('{');\n"); + String sep = ""; + for (Prprt p : props) { + w.write(sep); + w.append(" sb.append('\"').append(\"" + p.name() + "\")"); + w.append(".append('\"').append(\":\");\n"); + w.append(" sb.append(org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toJSON(prop_"); + w.append(p.name()).append("));\n"); + sep = " sb.append(',');\n"; + } + w.write(" sb.append('}');\n"); + w.write(" return sb.toString();\n"); + w.write(" }\n"); + } + private void writeClone(String className, Prprt[] props, Writer w) throws IOException { + w.write(" public " + className + " clone() {\n"); + w.write(" " + className + " ret = new " + className + "();\n"); + for (Prprt p : props) { + if (!p.array()) { + boolean isModel[] = { false }; + boolean isEnum[] = { false }; + boolean isPrimitive[] = { false }; + checkType(p, isModel, isEnum, isPrimitive); + if (!isModel[0]) { + w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ";\n"); + continue; + } + w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n"); + } else { + w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n"); + } + } + + w.write(" return ret;\n"); + w.write(" }\n"); + } + + private String inPckName(Element e) { + StringBuilder sb = new StringBuilder(); + while (e.getKind() != ElementKind.PACKAGE) { + if (sb.length() == 0) { + sb.append(e.getSimpleName()); + } else { + sb.insert(0, '.'); + sb.insert(0, e.getSimpleName()); + } + e = e.getEnclosingElement(); + } + return sb.toString(); + } + + private String fqn(TypeMirror pt, Element relative) { + if (pt.getKind() == TypeKind.ERROR) { + final Elements eu = processingEnv.getElementUtils(); + PackageElement pckg = eu.getPackageOf(relative); + return pckg.getQualifiedName() + "." + pt.toString(); + } + return pt.toString(); + } + + private String checkType(Prprt p, boolean[] isModel, boolean[] isEnum, boolean[] isPrimitive) { + TypeMirror tm; + try { + String ret = p.typeName(processingEnv); + TypeElement e = processingEnv.getElementUtils().getTypeElement(ret); + if (e == null) { + isModel[0] = true; + isEnum[0] = false; + isPrimitive[0] = false; + return ret; + } + tm = e.asType(); + } catch (MirroredTypeException ex) { + tm = ex.getTypeMirror(); + } + tm = processingEnv.getTypeUtils().erasure(tm); + isPrimitive[0] = tm.getKind().isPrimitive(); + final Element e = processingEnv.getTypeUtils().asElement(tm); + final Model m = e == null ? null : e.getAnnotation(Model.class); + + String ret; + if (m != null) { + ret = findPkgName(e) + '.' + m.className(); + isModel[0] = true; + models.put(e, m.className()); + } else if (findModelForMthd(e)) { + ret = ((TypeElement)e).getQualifiedName().toString(); + isModel[0] = true; + } else { + ret = tm.toString(); + } + TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType(); + enm = processingEnv.getTypeUtils().erasure(enm); + isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm); + return ret; + } + + private static boolean findModelForMthd(Element clazz) { + if (clazz == null) { + return false; + } + for (Element e : clazz.getEnclosedElements()) { + if (e.getKind() == ElementKind.METHOD) { + ExecutableElement ee = (ExecutableElement)e; + if ( + ee.getSimpleName().contentEquals("modelFor") && + ee.getParameters().isEmpty() + ) { + return true; + } + } + } + return false; + } + + private Iterable findParamNames(Element e, String url, StringBuilder assembleURL) { + List params = new ArrayList<>(); + + for (int pos = 0; ;) { + int next = url.indexOf('{', pos); + if (next == -1) { + assembleURL.append('"') + .append(url.substring(pos)) + .append('"'); + return params; + } + int close = url.indexOf('}', next); + if (close == -1) { + error("Unbalanced '{' and '}' in " + url, e); + return params; + } + final String paramName = url.substring(next + 1, close); + params.add(paramName); + assembleURL.append('"') + .append(url.substring(pos, next)) + .append("\" + ").append(paramName).append(" + "); + pos = close + 1; + } + } + + private static Prprt findPrprt(Prprt[] properties, String propName) { + for (Prprt p : properties) { + if (propName.equals(p.name())) { + return p; + } + } + return null; + } + + private boolean isPrimitive(String type) { + return + "int".equals(type) || + "double".equals(type) || + "long".equals(type) || + "short".equals(type) || + "byte".equals(type) || + "float".equals(type); + } + + private static Collection findDerivedFrom(Map> propsDeps, String derivedProp) { + Set names = new HashSet<>(); + for (Map.Entry> e : propsDeps.entrySet()) { + if (e.getValue().contains(derivedProp)) { + names.add(e.getKey()); + } + } + return names; + } + + private Prprt[] createProps(Element e, Property[] arr) { + Prprt[] ret = Prprt.wrap(processingEnv, e, arr); + Prprt[] prev = verify.put(e, ret); + if (prev != null) { + error("Two sets of properties for ", e); + } + return ret; + } + + private static class Prprt { + private final Element e; + private final AnnotationMirror tm; + private final Property p; + + public Prprt(Element e, AnnotationMirror tm, Property p) { + this.e = e; + this.tm = tm; + this.p = p; + } + + String name() { + return p.name(); + } + + boolean array() { + return p.array(); + } + + String typeName(ProcessingEnvironment env) { + try { + return p.type().getName(); + } catch (IncompleteAnnotationException | AnnotationTypeMismatchException ex) { + for (Object v : getAnnoValues(env)) { + String s = v.toString().replace(" ", ""); + if (s.startsWith("type=") && s.endsWith(".class")) { + return s.substring(5, s.length() - 6); + } + } + throw ex; + } + } + + + static Prprt[] wrap(ProcessingEnvironment pe, Element e, Property[] arr) { + if (arr.length == 0) { + return new Prprt[0]; + } + + if (e.getKind() != ElementKind.CLASS) { + throw new IllegalStateException("" + e.getKind()); + } + TypeElement te = (TypeElement)e; + List val = null; + for (AnnotationMirror an : te.getAnnotationMirrors()) { + for (Map.Entry entry : an.getElementValues().entrySet()) { + if (entry.getKey().getSimpleName().contentEquals("properties")) { + val = (List)entry.getValue().getValue(); + break; + } + } + } + if (val == null || val.size() != arr.length) { + pe.getMessager().printMessage(Diagnostic.Kind.ERROR, "" + val, e); + return new Prprt[0]; + } + Prprt[] ret = new Prprt[arr.length]; + BIG: for (int i = 0; i < ret.length; i++) { + AnnotationMirror am = (AnnotationMirror)val.get(i).getValue(); + ret[i] = new Prprt(e, am, arr[i]); + + } + return ret; + } + + private List getAnnoValues(ProcessingEnvironment pe) { + try { + Class trees = Class.forName("com.sun.tools.javac.api.JavacTrees"); + Method m = trees.getMethod("instance", ProcessingEnvironment.class); + Object instance = m.invoke(null, pe); + m = instance.getClass().getMethod("getPath", Element.class, AnnotationMirror.class); + Object path = m.invoke(instance, e, tm); + m = path.getClass().getMethod("getLeaf"); + Object leaf = m.invoke(path); + m = leaf.getClass().getMethod("getArguments"); + return (List)m.invoke(leaf); + } catch (Exception ex) { + return Collections.emptyList(); + } + } + } + } diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Canvas.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Canvas.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Canvas.java Mon Oct 07 14:20:58 2013 +0200 @@ -18,6 +18,7 @@ package org.apidesign.bck2brwsr.htmlpage.api; import org.apidesign.bck2brwsr.core.JavaScriptBody; +import static org.apidesign.bck2brwsr.htmlpage.api.Element.getAttribute; /** * @@ -34,7 +35,8 @@ } public int getHeight() { - return (Integer) getAttribute(this, "height"); + Object ret = getAttribute(this, "height"); + return (ret instanceof Number) ? ((Number)ret).intValue(): Integer.MIN_VALUE; } public void setWidth(int width) { @@ -42,7 +44,8 @@ } public int getWidth() { - return (Integer) getAttribute(this, "width"); + Object ret = getAttribute(this, "width"); + return (ret instanceof Number) ? ((Number)ret).intValue(): Integer.MIN_VALUE; } @JavaScriptBody( diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/ComputedProperty.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/ComputedProperty.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/ComputedProperty.java Mon Oct 07 14:20:58 2013 +0200 @@ -22,16 +22,11 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** Can be used in classes annotated with {@link Page} annotation to - * define a derived property. Value of derived property is based on values - * of {@link Property} as enumerated by {@link Page#properties()}. - *

- * The name of the derived property is the name of the method. The arguments - * of the method define the property names (from {@link Page#properties()} list) - * the value of property depends on. - * +/** + * @deprecated Replaced by new {@link net.java.html.json.ComputedProperty net.java.html.json} API. * @author Jaroslav Tulach */ +@Deprecated @Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) public @interface ComputedProperty { diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Element.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Element.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Element.java Mon Oct 07 14:20:58 2013 +0200 @@ -61,14 +61,18 @@ /** Executes given runnable when user performs a "click" on the given * element. + * @param data an array of one element to fill with event parameter (if any) * @param r the runnable to execute, never null */ @JavaScriptBody( args={ "ev", "r" }, body="var e = window.document.getElementById(this._id());\n" - + "e[ev._id()] = function() { r.run__V(); };\n" + + "e[ev._id()] = function(ev) {\n" + + " var d = ev ? ev : null;\n" + + " r.onEvent__VLjava_lang_Object_2(d);\n" + + "};\n" ) - final void on(OnEvent ev, Runnable r) { + final void on(OnEvent ev, OnHandler r) { } /** Shows alert message dialog in a browser. diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/GraphicsContext.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/GraphicsContext.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/GraphicsContext.java Mon Oct 07 14:20:58 2013 +0200 @@ -134,7 +134,7 @@ @JavaScriptBody(args = {"ctx", "img", "x", "y"}, body = "ctx.drawImage(img,x,y);") private native static void drawImageImpl(Object ctx, Object img, double x, double y); - @JavaScriptBody(args = {"style"}, body = "this._context().fillStyle=style;") + @JavaScriptBody(args = {"style"}, body = "this._context().fillStyle=style.valueOf();") public native void setFillStyle(String style); @JavaScriptBody(args = {}, body = "return this._context().fillStyle;") @@ -155,7 +155,7 @@ @JavaScriptBody(args = {"context","obj"}, body = "context.fillStyle=obj;") private native void setFillStyleImpl(Object context, Object obj); - @JavaScriptBody(args = {"style"}, body = "this._context().strokeStyle=style;") + @JavaScriptBody(args = {"style"}, body = "this._context().strokeStyle=style.valueOf();") public native void setStrokeStyle(String style); public void setStrokeStyle(LinearGradient style) { @@ -174,7 +174,7 @@ @JavaScriptBody(args = {"context","obj"}, body = "context.strokeStyle=obj;") private native void setStrokeStyleImpl(Object context, Object obj); - @JavaScriptBody(args = {"color"}, body = "this._context().shadowColor=color;") + @JavaScriptBody(args = {"color"}, body = "this._context().shadowColor=color.valueOf();") public native void setShadowColor(String color); @JavaScriptBody(args = {"blur"}, body = "this._context().shadowBlur=blur;") @@ -204,19 +204,19 @@ @JavaScriptBody(args = {}, body = "return this._context().lineCap;") public native String getLineCap(); - @JavaScriptBody(args = {"style"}, body = "this._context().lineCap=style;") + @JavaScriptBody(args = {"style"}, body = "this._context().lineCap=style.valueOf();") public native void setLineCap(String style); @JavaScriptBody(args = {}, body = "return this._context().lineJoin;") public native String getLineJoin(); - @JavaScriptBody(args = {"style"}, body = "this._context().lineJoin=style;") + @JavaScriptBody(args = {"style"}, body = "this._context().lineJoin=style.valueOf();") public native void setLineJoin(String style) ; @JavaScriptBody(args = {}, body = "return this._context().lineWidth;") public native double getLineWidth(); - @JavaScriptBody(args = {"width"}, body = "this._context().lineJoin=width;") + @JavaScriptBody(args = {"width"}, body = "this._context().lineWidth=width;") public native void setLineWidth(double width); @JavaScriptBody(args = {}, body = "return this._context().miterLimit;") @@ -228,19 +228,19 @@ @JavaScriptBody(args = {}, body = "return this._context().font;") public native String getFont(); - @JavaScriptBody(args = {"font"}, body = "this._context().font=font;") + @JavaScriptBody(args = {"font"}, body = "this._context().font=font.valueOf();") public native void setFont(String font); @JavaScriptBody(args = {}, body = "return this._context().textAlign;") public native String getTextAlign(); - @JavaScriptBody(args = {"textalign"}, body = "this._context().textAlign=textalign;") + @JavaScriptBody(args = {"textalign"}, body = "this._context().textAlign=textalign.valueOf();") public native void setTextAlign(String textAlign); @JavaScriptBody(args = {}, body = "return this._context().textBaseline;") public native String getTextBaseline(); - @JavaScriptBody(args = {"textbaseline"}, body = "this._context().textBaseline=textbaseline;") + @JavaScriptBody(args = {"textbaseline"}, body = "this._context().textBaseline=textbaseline.valueOf();") public native void setTextBaseline(String textbaseline); @JavaScriptBody(args = {"text", "x", "y"}, body = "this._context().fillText(text,x,y);") @@ -306,7 +306,7 @@ @JavaScriptBody(args = {}, body = "return this._context().globalAlpha;") public native double getGlobalAlpha(); - @JavaScriptBody(args = {"operation"}, body = "this._context().globalCompositeOperation=operation;") + @JavaScriptBody(args = {"operation"}, body = "this._context().globalCompositeOperation=operation.valueOf();") public native void setGlobalCompositeOperation(String operation); @JavaScriptBody(args = {}, body = "return this._context().globalCompositeOperation;") diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Model.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Model.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,38 @@ +/** + * 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.htmlpage.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @deprecated Replaced by new {@link net.java.html.json.Model net.java.html.json} API. + * @author Jaroslav Tulach + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +@Deprecated +public @interface Model { + /** Name of the model class */ + String className(); + /** List of properties in the model. + */ + Property[] properties(); +} diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnController.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnController.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnController.java Mon Oct 07 14:20:58 2013 +0200 @@ -30,14 +30,33 @@ this.arr = arr; } + /** Registers an event handler on associated {@link OnEvent} + * and {@link Element} + * + * @param handler the handler to be called when the event occurs + */ + public void perform(final OnHandler handler) { + for (Element e : arr) { + e.on(event, handler); + } + } + /** Registers a runnable to be performed on associated {@link OnEvent} * and {@link Element}. * * @see OnEvent#of */ - public void perform(Runnable r) { + public void perform(final Runnable r) { + class W implements OnHandler { + @Override + public void onEvent(Object event) throws Exception { + r.run(); + } + } + perform(new W()); + OnHandler w = new W(); for (Element e : arr) { - e.on(event, r); + e.on(event, w); } } } diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnFunction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnFunction.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,33 @@ +/** + * 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.htmlpage.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @deprecated Replaced by new {@link net.java.html.json.Function net.java.html.json} API. + * @author Jaroslav Tulach + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.SOURCE) +@Deprecated +public @interface OnFunction { +} diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnHandler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnHandler.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,33 @@ +/** + * 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.htmlpage.api; + +/** Handler to be called when an event in an HTML {@link Page} appears. + * @see OnEvent + * @see OnController + * + * @author Jaroslav Tulach + */ +public interface OnHandler { + /** Called when a DOM event appears + * + * @param event the event as produced by the browser + * @throws Exception execution can throw exception + */ + public void onEvent(Object event) throws Exception; +} diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnPropertyChange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnPropertyChange.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,38 @@ +/** + * 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.htmlpage.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @deprecated Replaced by new {@link net.java.html.json.OnPropertyChange net.java.html.json} API. + * @author Jaroslav Tulach + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +@Deprecated +public @interface OnPropertyChange { + /** Name(s) of the properties. One wishes to observe. + * + * @return valid java identifier + */ + String[] value(); +} diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnReceive.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/OnReceive.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,53 @@ +/** + * 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.htmlpage.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @deprecated Replaced by new {@link net.java.html.json.OnReceive net.java.html.json} API. + * @author Jaroslav Tulach + * @since 0.6 + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +@Deprecated +public @interface OnReceive { + /** The URL to connect to. Can contain variable names surrounded by '{' and '}'. + * Those parameters will then become variables of the associated method. + * + * @return the (possibly parametrized) url to connect to + */ + String url(); + + /** Support for JSONP requires + * a callback from the server generated page to a function defined in the + * system. The name of such function is usually specified as a property + * (of possibly different names). By defining the jsonp attribute + * one turns on the JSONP + * transmission and specifies the name of the property. The property should + * also be used in the {@link #url()} attribute on appropriate place. + * + * @return name of a property to carry the name of JSONP + * callback function. + */ + String jsonp() default ""; +} diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java Mon Oct 07 14:20:58 2013 +0200 @@ -20,15 +20,36 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.List; -/** Represents a property in a generated model of an HTML - * {@link Page}. - * +/** + * @deprecated Replaced by new {@link net.java.html.json.Property net.java.html.json} API. * @author Jaroslav Tulach */ @Retention(RetentionPolicy.SOURCE) @Target({}) +@Deprecated public @interface Property { + /** Name of the property. Will be used to define proper getter and setter + * in the associated class. + * + * @return valid java identifier + */ String name(); + + /** Type of the property. Can either be primitive type (like int.class, + * double.class, etc.), {@link String} or complex model + * class (defined by {@link Model} property). + * + * @return the class of the property + */ Class type(); + + /** Is this property an array of the {@link #type()} or a single value? + * If the property is an array, only its getter (returning mutable {@link List} of + * the boxed {@link #type()}). + * + * @return true, if this is supposed to be an array of values. + */ + boolean array() default false; } diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js --- a/javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/main/resources/org/apidesign/bck2brwsr/htmlpage/knockout-2.2.1.js Mon Oct 07 14:20:58 2013 +0200 @@ -2193,7 +2193,14 @@ else element[attrName] = attrValue; } else if (!toRemove) { - element.setAttribute(attrName, attrValue.toString()); + try { + element.setAttribute(attrName, attrValue.toString()); + } catch (err) { + // ignore for now + if (console) { + console.log("Can't set attribute " + attrName + " to " + attrValue + " error: " + err); + } + } } // Treat "name" specially - although you can think of it as an attribute, it also needs diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/Compile.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/Compile.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,203 @@ +/** + * 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.htmlpage; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; + +/** + * + * @author Jaroslav Tulach + */ +final class Compile implements DiagnosticListener { + private final List> errors = new ArrayList<>(); + private final Map classes; + private final String pkg; + private final String cls; + private final String html; + + private Compile(String html, String code) throws IOException { + this.pkg = findPkg(code); + this.cls = findCls(code); + this.html = html; + classes = compile(html, code); + } + + /** Performs compilation of given HTML page and associated Java code + */ + public static Compile create(String html, String code) throws IOException { + return new Compile(html, code); + } + + /** Checks for given class among compiled resources */ + public byte[] get(String res) { + return classes.get(res); + } + + /** Obtains errors created during compilation. + */ + public List> getErrors() { + List> err = new ArrayList<>(); + for (Diagnostic diagnostic : errors) { + if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { + err.add(diagnostic); + } + } + return err; + } + + private Map compile(final String html, final String code) throws IOException { + StandardJavaFileManager sjfm = ToolProvider.getSystemJavaCompiler().getStandardFileManager(this, null, null); + + final Map class2BAOS = new HashMap<>(); + + JavaFileObject file = new SimpleJavaFileObject(URI.create("mem://mem"), Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return code; + } + }; + final JavaFileObject htmlFile = new SimpleJavaFileObject(URI.create("mem://mem2"), Kind.OTHER) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return html; + } + + @Override + public InputStream openInputStream() throws IOException { + return new ByteArrayInputStream(html.getBytes()); + } + }; + + final URI scratch; + try { + scratch = new URI("mem://mem3"); + } catch (URISyntaxException ex) { + throw new IOException(ex); + } + + JavaFileManager jfm = new ForwardingJavaFileManager(sjfm) { + @Override + public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { + if (kind == Kind.CLASS) { + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + class2BAOS.put(className.replace('.', '/') + ".class", buffer); + return new SimpleJavaFileObject(sibling.toUri(), kind) { + @Override + public OutputStream openOutputStream() throws IOException { + return buffer; + } + }; + } + + if (kind == Kind.SOURCE) { + return new SimpleJavaFileObject(scratch/*sibling.toUri()*/, kind) { + private final ByteArrayOutputStream data = new ByteArrayOutputStream(); + @Override + public OutputStream openOutputStream() throws IOException { + return data; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + data.close(); + return new String(data.toByteArray()); + } + }; + } + + throw new IllegalStateException(); + } + + @Override + public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { + if (location == StandardLocation.SOURCE_PATH) { + if (packageName.equals(pkg)) { + return htmlFile; + } + } + + return null; + } + + }; + + ToolProvider.getSystemJavaCompiler().getTask(null, jfm, this, /*XXX:*/Arrays.asList("-source", "1.7", "-target", "1.7"), null, Arrays.asList(file)).call(); + + Map result = new HashMap<>(); + + for (Map.Entry e : class2BAOS.entrySet()) { + result.put(e.getKey(), e.getValue().toByteArray()); + } + + return result; + } + + + @Override + public void report(Diagnostic diagnostic) { + errors.add(diagnostic); + } + private static String findPkg(String java) throws IOException { + Pattern p = Pattern.compile("package\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}*;", Pattern.MULTILINE); + Matcher m = p.matcher(java); + if (!m.find()) { + throw new IOException("Can't find package declaration in the java file"); + } + String pkg = m.group(1); + return pkg; + } + private static String findCls(String java) throws IOException { + Pattern p = Pattern.compile("class\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}", Pattern.MULTILINE); + Matcher m = p.matcher(java); + if (!m.find()) { + throw new IOException("Can't find package declaration in the java file"); + } + String cls = m.group(1); + return cls; + } + + String getHtml() { + String fqn = "'" + pkg + '.' + cls + "'"; + return html.replace("'${fqn}'", fqn); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypesTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ConvertTypesTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,63 @@ +/** + * 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.htmlpage; + +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class ConvertTypesTest { + @JavaScriptBody(args = { "includeSex" }, body = "var json = new Object();" + + "json.firstName = 'son';\n" + + "json.lastName = 'dj';\n" + + "if (includeSex) json.sex = 'MALE';\n" + + "return json;" + ) + private static native Object createJSON(boolean includeSex); + + @BrwsrTest + public void testConvertToPeople() throws Exception { + final Object o = createJSON(true); + + Person p = new Person(o); + + assert "son".equals(p.getFirstName()) : "First name: " + p.getFirstName(); + assert "dj".equals(p.getLastName()) : "Last name: " + p.getLastName(); + assert Sex.MALE.equals(p.getSex()) : "Sex: " + p.getSex(); + } + + @BrwsrTest + public void testConvertToPeopleWithoutSex() throws Exception { + final Object o = createJSON(false); + + Person p = new Person(o); + + assert "son".equals(p.getFirstName()) : "First name: " + p.getFirstName(); + assert "dj".equals(p.getLastName()) : "Last name: " + p.getLastName(); + assert p.getSex() == null : "No sex: " + p.getSex(); + } + + @Factory public static Object[] create() { + return VMTest.create(ConvertTypesTest.class); + } +} \ No newline at end of file diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/JSONTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/JSONTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,392 @@ +/** + * 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.htmlpage; + +import java.util.Arrays; +import java.util.Iterator; +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.htmlpage.api.OnReceive; +import org.apidesign.bck2brwsr.htmlpage.api.Page; +import org.apidesign.bck2brwsr.htmlpage.api.Property; +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.Http; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.testng.annotations.Test; +import static org.testng.Assert.*; +import org.testng.annotations.Factory; + +/** Need to verify that models produce reasonable JSON objects. + * + * @author Jaroslav Tulach + */ +@Page(xhtml = "Empty.html", className = "JSONik", properties = { + @Property(name = "fetched", type = PersonImpl.class), + @Property(name = "fetchedCount", type = int.class), + @Property(name = "fetchedSex", type = Sex.class, array = true) +}) +public class JSONTest { + private JSONik js; + private Integer orig; + + @Test public void personToString() throws JSONException { + Person p = new Person(); + p.setSex(Sex.MALE); + p.setFirstName("Jarda"); + p.setLastName("Tulach"); + + JSONTokener t = new JSONTokener(p.toString()); + JSONObject o; + try { + o = new JSONObject(t); + } catch (JSONException ex) { + throw new AssertionError("Can't parse " + p.toString(), ex); + } + + Iterator it = o.sortedKeys(); + assertEquals(it.next(), "firstName"); + assertEquals(it.next(), "lastName"); + assertEquals(it.next(), "sex"); + + assertEquals(o.getString("firstName"), "Jarda"); + assertEquals(o.getString("lastName"), "Tulach"); + assertEquals(o.getString("sex"), "MALE"); + } + + @BrwsrTest public void toJSONInABrowser() throws Throwable { + Person p = new Person(); + p.setSex(Sex.MALE); + p.setFirstName("Jarda"); + p.setLastName("Tulach"); + + Object json; + try { + json = parseJSON(p.toString()); + } catch (Throwable ex) { + throw new IllegalStateException("Can't parse " + p).initCause(ex); + } + + Person p2 = new Person(json); + + assert p2.getFirstName().equals(p.getFirstName()) : + "Should be the same: " + p.getFirstName() + " != " + p2.getFirstName(); + } + + @Test public void personWithWildCharactersAndNulls() throws JSONException { + Person p = new Person(); + p.setFirstName("'\"\n"); + p.setLastName("\t\r\u0002"); + + JSONTokener t = new JSONTokener(p.toString()); + JSONObject o; + try { + o = new JSONObject(t); + } catch (JSONException ex) { + throw new AssertionError("Can't parse " + p.toString(), ex); + } + + Iterator it = o.sortedKeys(); + assertEquals(it.next(), "firstName"); + assertEquals(it.next(), "lastName"); + assertEquals(it.next(), "sex"); + + assertEquals(o.getString("firstName"), p.getFirstName()); + assertEquals(o.getString("lastName"), p.getLastName()); + assertEquals(o.get("sex"), JSONObject.NULL); + } + + @Test public void personsInArray() throws JSONException { + Person p1 = new Person(); + p1.setFirstName("One"); + + Person p2 = new Person(); + p2.setFirstName("Two"); + + People arr = new People(); + arr.getInfo().add(p1); + arr.getInfo().add(p2); + arr.getNicknames().add("Prvn\u00ed k\u016f\u0148"); + final String n2 = "Druh\u00fd hlem\u00fd\u017e\u010f, star\u0161\u00ed"; + arr.getNicknames().add(n2); + arr.getAge().add(33); + arr.getAge().add(73); + + + final String json = arr.toString(); + + JSONTokener t = new JSONTokener(json); + JSONObject o; + try { + o = new JSONObject(t); + } catch (JSONException ex) { + throw new AssertionError("Can't parse " + json, ex); + } + + assertEquals(o.getJSONArray("info").getJSONObject(0).getString("firstName"), "One"); + assertEquals(o.getJSONArray("nicknames").getString(1), n2); + assertEquals(o.getJSONArray("age").getInt(1), 73); + } + + + @OnReceive(url="/{url}") + static void fetch(Person p, JSONik model) { + model.setFetched(p); + } + + @OnReceive(url="/{url}") + static void fetchArray(Person[] p, JSONik model) { + model.setFetchedCount(p.length); + model.setFetched(p[0]); + } + + @OnReceive(url="/{url}") + static void fetchPeople(People p, JSONik model) { + model.setFetchedCount(p.getInfo().size()); + model.setFetched(p.getInfo().get(0)); + } + + @OnReceive(url="/{url}") + static void fetchPeopleAge(People p, JSONik model) { + int sum = 0; + for (int a : p.getAge()) { + sum += a; + } + model.setFetchedCount(sum); + } + + @Http(@Http.Resource( + content = "{'firstName': 'Sitar', 'sex': 'MALE'}", + path="/person.json", + mimeType = "application/json" + )) + @BrwsrTest public void loadAndParseJSON() throws InterruptedException { + if (js == null) { + js = new JSONik(); + js.applyBindings(); + + js.fetch("person.json"); + } + + Person p = js.getFetched(); + if (p == null) { + throw new InterruptedException(); + } + + assert "Sitar".equals(p.getFirstName()) : "Expecting Sitar: " + p.getFirstName(); + assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex(); + } + + @OnReceive(url="/{url}?callme={me}", jsonp = "me") + static void fetchViaJSONP(Person p, JSONik model) { + model.setFetched(p); + } + + @Http(@Http.Resource( + content = "$0({'firstName': 'Mitar', 'sex': 'MALE'})", + path="/person.json", + mimeType = "application/javascript", + parameters = { "callme" } + )) + @BrwsrTest public void loadAndParseJSONP() throws InterruptedException { + + if (js == null) { + orig = scriptElements(); + assert orig > 0 : "There should be some scripts on the page"; + + js = new JSONik(); + js.applyBindings(); + + js.fetchViaJSONP("person.json"); + } + + Person p = js.getFetched(); + if (p == null) { + throw new InterruptedException(); + } + + assert "Mitar".equals(p.getFirstName()) : "Unexpected: " + p.getFirstName(); + assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex(); + + int now = scriptElements(); + + assert orig == now : "The set of elements is unchanged. Delta: " + (now - orig); + } + + @JavaScriptBody(args = { }, body = "return window.document.getElementsByTagName('script').length;") + private static native int scriptElements(); + + @JavaScriptBody(args = { "s" }, body = "return window.JSON.parse(s);") + private static native Object parseJSON(String s); + + @Http(@Http.Resource( + content = "{'firstName': 'Sitar', 'sex': 'MALE'}", + path="/person.json", + mimeType = "application/json" + )) + @BrwsrTest public void loadAndParseJSONSentToArray() throws InterruptedException { + if (js == null) { + js = new JSONik(); + js.applyBindings(); + + js.fetchArray("person.json"); + } + + Person p = js.getFetched(); + if (p == null) { + throw new InterruptedException(); + } + + assert p != null : "We should get our person back: " + p; + assert "Sitar".equals(p.getFirstName()) : "Expecting Sitar: " + p.getFirstName(); + assert Sex.MALE.equals(p.getSex()) : "Expecting MALE: " + p.getSex(); + } + + @Http(@Http.Resource( + content = "[{'firstName': 'Gitar', 'sex': 'FEMALE'}]", + path="/person.json", + mimeType = "application/json" + )) + @BrwsrTest public void loadAndParseJSONArraySingle() throws InterruptedException { + if (js == null) { + js = new JSONik(); + js.applyBindings(); + + js.fetch("person.json"); + } + + Person p = js.getFetched(); + if (p == null) { + throw new InterruptedException(); + } + + assert p != null : "We should get our person back: " + p; + assert "Gitar".equals(p.getFirstName()) : "Expecting Gitar: " + p.getFirstName(); + assert Sex.FEMALE.equals(p.getSex()) : "Expecting FEMALE: " + p.getSex(); + } + + @Http(@Http.Resource( + content = "{'info':[{'firstName': 'Gitar', 'sex': 'FEMALE'}]}", + path="/people.json", + mimeType = "application/json" + )) + @BrwsrTest public void loadAndParseArrayInPeople() throws InterruptedException { + if (js == null) { + js = new JSONik(); + js.applyBindings(); + + js.fetchPeople("people.json"); + } + + if (0 == js.getFetchedCount()) { + throw new InterruptedException(); + } + + assert js.getFetchedCount() == 1 : "One person loaded: " + js.getFetchedCount(); + + Person p = js.getFetched(); + + assert p != null : "We should get our person back: " + p; + assert "Gitar".equals(p.getFirstName()) : "Expecting Gitar: " + p.getFirstName(); + assert Sex.FEMALE.equals(p.getSex()) : "Expecting FEMALE: " + p.getSex(); + } + + @Http(@Http.Resource( + content = "{'age':[1, 2, 3]}", + path="/people.json", + mimeType = "application/json" + )) + @BrwsrTest public void loadAndParseArrayOfIntegers() throws InterruptedException { + if (js == null) { + js = new JSONik(); + js.applyBindings(); + + js.fetchPeopleAge("people.json"); + } + + if (0 == js.getFetchedCount()) { + throw new InterruptedException(); + } + + assert js.getFetchedCount() == 6 : "1 + 2 + 3 is " + js.getFetchedCount(); + } + + @OnReceive(url="/{url}") + static void fetchPeopleSex(People p, JSONik model) { + model.setFetchedCount(1); + model.getFetchedSex().addAll(p.getSex()); + } + + + @Http(@Http.Resource( + content = "{'sex':['FEMALE', 'MALE', 'MALE']}", + path="/people.json", + mimeType = "application/json" + )) + @BrwsrTest public void loadAndParseArrayOfEnums() throws InterruptedException { + if (js == null) { + js = new JSONik(); + js.applyBindings(); + + js.fetchPeopleSex("people.json"); + } + + if (0 == js.getFetchedCount()) { + throw new InterruptedException(); + } + + assert js.getFetchedCount() == 1 : "Loaded"; + + assert js.getFetchedSex().size() == 3 : "Three values " + js.getFetchedSex(); + assert js.getFetchedSex().get(0) == Sex.FEMALE : "Female first " + js.getFetchedSex(); + assert js.getFetchedSex().get(1) == Sex.MALE : "male 2nd " + js.getFetchedSex(); + assert js.getFetchedSex().get(2) == Sex.MALE : "male 3rd " + js.getFetchedSex(); + } + + @Http(@Http.Resource( + content = "[{'firstName': 'Gitar', 'sex': 'FEMALE'}," + + "{'firstName': 'Peter', 'sex': 'MALE'}" + + "]", + path="/person.json", + mimeType = "application/json" + )) + @BrwsrTest public void loadAndParseJSONArray() throws InterruptedException { + if (js == null) { + js = new JSONik(); + js.applyBindings(); + js.fetchArray("person.json"); + } + + + Person p = js.getFetched(); + if (p == null) { + throw new InterruptedException(); + } + + assert js.getFetchedCount() == 2 : "We got two values: " + js.getFetchedCount(); + assert p != null : "We should get our person back: " + p; + assert "Gitar".equals(p.getFirstName()) : "Expecting Gitar: " + p.getFirstName(); + assert Sex.FEMALE.equals(p.getSex()) : "Expecting FEMALE: " + p.getSex(); + } + + @Factory public static Object[] create() { + return VMTest.create(JSONTest.class); + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/KnockoutTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -17,21 +17,29 @@ */ package org.apidesign.bck2brwsr.htmlpage; +import java.util.List; +import org.apidesign.bck2brwsr.core.JavaScriptBody; import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; import org.apidesign.bck2brwsr.htmlpage.api.OnEvent; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; import org.apidesign.bck2brwsr.htmlpage.api.Page; import org.apidesign.bck2brwsr.htmlpage.api.Property; import org.apidesign.bck2brwsr.vmtest.BrwsrTest; import org.apidesign.bck2brwsr.vmtest.HtmlFragment; import org.apidesign.bck2brwsr.vmtest.VMTest; +import static org.testng.Assert.assertEquals; import org.testng.annotations.Factory; +import org.testng.annotations.Test; /** * * @author Jaroslav Tulach */ @Page(xhtml="Knockout.xhtml", className="KnockoutModel", properties={ - @Property(name="name", type=String.class) + @Property(name="name", type=String.class), + @Property(name="results", type=String.class, array = true), + @Property(name="callbackCount", type=int.class), + @Property(name="people", type=PersonImpl.class, array = true) }) public class KnockoutTest { @@ -44,19 +52,207 @@ KnockoutModel m = new KnockoutModel(); m.setName("Kukuc"); m.applyBindings(); - assert "Kukuc".equals(m.INPUT.getValue()) : "Value is really kukuc: " + m.INPUT.getValue(); - m.INPUT.setValue("Jardo"); - m.triggerEvent(m.INPUT, OnEvent.CHANGE); + assert "Kukuc".equals(m.input.getValue()) : "Value is really kukuc: " + m.input.getValue(); + m.input.setValue("Jardo"); + m.triggerEvent(m.input, OnEvent.CHANGE); assert "Jardo".equals(m.getName()) : "Name property updated: " + m.getName(); } + @HtmlFragment( + "

    \n" + + "
  • \n" + + "
\n" + ) + @BrwsrTest public void displayContentOfArray() { + KnockoutModel m = new KnockoutModel(); + m.getResults().add("Ahoj"); + m.applyBindings(); + + int cnt = countChildren("ul"); + assert cnt == 1 : "One child, but was " + cnt; + + m.getResults().add("Hi"); + + cnt = countChildren("ul"); + assert cnt == 2 : "Two children now, but was " + cnt; + + triggerChildClick("ul", 1); + + assert 1 == m.getCallbackCount() : "One callback " + m.getCallbackCount(); + assert "Hi".equals(m.getName()) : "We got callback from 2nd child " + m.getName(); + } + + @HtmlFragment( + "
    \n" + + "
  • \n" + + "
\n" + ) + @BrwsrTest public void displayContentOfDerivedArray() { + KnockoutModel m = new KnockoutModel(); + m.getResults().add("Ahoj"); + m.applyBindings(); + + int cnt = countChildren("ul"); + assert cnt == 1 : "One child, but was " + cnt; + + m.getResults().add("hello"); + + cnt = countChildren("ul"); + assert cnt == 2 : "Two children now, but was " + cnt; + } + + @HtmlFragment( + "
    \n" + + "
  • \n" + + "
\n" + ) + @BrwsrTest public void displayContentOfArrayOfPeople() { + KnockoutModel m = new KnockoutModel(); + + final Person first = new Person(); + first.setFirstName("first"); + m.getPeople().add(first); + + m.applyBindings(); + + int cnt = countChildren("ul"); + assert cnt == 1 : "One child, but was " + cnt; + + final Person second = new Person(); + second.setFirstName("second"); + m.getPeople().add(second); + + cnt = countChildren("ul"); + assert cnt == 2 : "Two children now, but was " + cnt; + + triggerChildClick("ul", 1); + + assert 1 == m.getCallbackCount() : "One callback " + m.getCallbackCount(); + + cnt = countChildren("ul"); + assert cnt == 1 : "Again one child, but was " + cnt; + + String txt = childText("ul", 0); + assert "first".equals(txt) : "Expecting 'first': " + txt; + + first.setFirstName("changed"); + + txt = childText("ul", 0); + assert "changed".equals(txt) : "Expecting 'changed': " + txt; + } + + @ComputedProperty + static Person firstPerson(List people) { + return people.isEmpty() ? null : people.get(0); + } + + @HtmlFragment( + "

\n" + + " \n" + + "

\n" + ) + @BrwsrTest public void accessFirstPersonWithOnFunction() { + trasfertToFemale(); + } + + @HtmlFragment( + "
    \n" + + "
  • \n" + + "
\n" + ) + @BrwsrTest public void onPersonFunction() { + trasfertToFemale(); + } + + private void trasfertToFemale() { + KnockoutModel m = new KnockoutModel(); + + final Person first = new Person(); + first.setFirstName("first"); + first.setSex(Sex.MALE); + m.getPeople().add(first); + + + m.applyBindings(); + + int cnt = countChildren("ul"); + assert cnt == 1 : "One child, but was " + cnt; + + + triggerChildClick("ul", 0); + + assert first.getSex() == Sex.FEMALE : "Transverted to female: " + first.getSex(); + } + + @Test public void cloneModel() { + Person model = new Person(); + + model.setFirstName("first"); + Person snd = model.clone(); + snd.setFirstName("clone"); + assertEquals("first", model.getFirstName(), "Value has not changed"); + assertEquals("clone", snd.getFirstName(), "Value has changed in clone"); + } + + + @Test public void deepCopyOnClone() { + People model = new People(); + model.getNicknames().add("Jarda"); + assertEquals(model.getNicknames().size(), 1, "One element"); + People snd = model.clone(); + snd.getNicknames().clear(); + assertEquals(snd.getNicknames().size(), 0, "Clone is empty"); + assertEquals(model.getNicknames().size(), 1, "Still one element"); + } + + + @OnFunction + static void call(KnockoutModel m, String data) { + m.setName(data); + m.setCallbackCount(m.getCallbackCount() + 1); + } + + @OnFunction + static void removePerson(KnockoutModel model, Person data) { + model.setCallbackCount(model.getCallbackCount() + 1); + model.getPeople().remove(data); + } + + @ComputedProperty static String helloMessage(String name) { return "Hello " + name + "!"; } + @ComputedProperty + static List cmpResults(List results) { + return results; + } + @Factory public static Object[] create() { return VMTest.create(KnockoutTest.class); } + + @JavaScriptBody(args = { "id" }, body = + "var e = window.document.getElementById(id);\n " + + "if (typeof e === 'undefined') return -2;\n " + + "return e.children.length;\n " + ) + private static native int countChildren(String id); + + @JavaScriptBody(args = { "id", "pos" }, body = + "var e = window.document.getElementById(id);\n " + + "var ev = window.document.createEvent('MouseEvents');\n " + + "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n " + + "e.children[pos].dispatchEvent(ev);\n " + ) + private static native void triggerChildClick(String id, int pos); + + @JavaScriptBody(args = { "id", "pos" }, body = + "var e = window.document.getElementById(id);\n " + + "var t = e.children[pos].innerHTML;\n " + + "return t ? t : null;" + ) + private static native String childText(String id, int pos); } diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -18,8 +18,13 @@ package org.apidesign.bck2brwsr.htmlpage; import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.ListIterator; import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; +import org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange; import org.apidesign.bck2brwsr.htmlpage.api.Page; import org.apidesign.bck2brwsr.htmlpage.api.Property; import static org.testng.Assert.*; @@ -30,17 +35,22 @@ * * @author Jaroslav Tulach */ -@Page(xhtml = "Empty.html", className = "Model", properties = { +@Page(xhtml = "Empty.html", className = "Modelik", properties = { @Property(name = "value", type = int.class), - @Property(name = "unrelated", type = long.class) + @Property(name = "count", type = int.class), + @Property(name = "unrelated", type = long.class), + @Property(name = "names", type = String.class, array = true), + @Property(name = "values", type = int.class, array = true), + @Property(name = "people", type = PersonImpl.class, array = true), + @Property(name = "changedProperty", type=String.class) }) public class ModelTest { - private Model model; - private static Model leakedModel; + private Modelik model; + private static Modelik leakedModel; @BeforeMethod public void createModel() { - model = new Model(); + model = new Modelik(); } @Test public void classGeneratedWithSetterGetter() { @@ -53,6 +63,75 @@ assertEquals(16, model.getPowerValue()); } + @Test public void arrayIsMutable() { + assertEquals(model.getNames().size(), 0, "Is empty"); + model.getNames().add("Jarda"); + assertEquals(model.getNames().size(), 1, "One element"); + } + + @Test public void arrayChangesNotified() { + MockKnockout my = new MockKnockout(); + MockKnockout.next = my; + + model.applyBindings(); + + model.getNames().add("Hello"); + + assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated); + assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated); + + my.mutated.clear(); + + Iterator it = model.getNames().iterator(); + assertEquals(it.next(), "Hello"); + it.remove(); + + assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated); + assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated); + + my.mutated.clear(); + + ListIterator lit = model.getNames().listIterator(); + lit.add("Jarda"); + + assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated); + assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated); + } + + @Test public void autoboxedArray() { + MockKnockout my = new MockKnockout(); + MockKnockout.next = my; + + model.applyBindings(); + + model.getValues().add(10); + + assertEquals(model.getValues().get(0), Integer.valueOf(10), "Really ten"); + } + + @Test public void derivedArrayProp() { + MockKnockout my = new MockKnockout(); + MockKnockout.next = my; + + model.applyBindings(); + + model.setCount(10); + + List arr = model.getRepeat(); + assertEquals(arr.size(), 10, "Ten items: " + arr); + + my.mutated.clear(); + + model.setCount(5); + + arr = model.getRepeat(); + assertEquals(arr.size(), 5, "Five items: " + arr); + + assertEquals(my.mutated.size(), 2, "Two properties changed: " + my.mutated); + assertTrue(my.mutated.contains("repeat"), "Array is in there: " + my.mutated); + assertTrue(my.mutated.contains("count"), "Count is in there: " + my.mutated); + } + @Test public void derivedPropertiesAreNotified() { MockKnockout my = new MockKnockout(); MockKnockout.next = my; @@ -61,6 +140,9 @@ model.setValue(33); + // not interested in change of this property + my.mutated.remove("changedProperty"); + assertEquals(my.mutated.size(), 2, "Two properties changed: " + my.mutated); assertTrue(my.mutated.contains("powerValue"), "Power value is in there: " + my.mutated); assertTrue(my.mutated.contains("value"), "Simple value is in there: " + my.mutated); @@ -68,7 +150,11 @@ my.mutated.clear(); model.setUnrelated(44); - assertEquals(my.mutated.size(), 1, "One property changed"); + + + // not interested in change of this property + my.mutated.remove("changedProperty"); + assertEquals(my.mutated.size(), 1, "One property changed: " + my.mutated); assertTrue(my.mutated.contains("unrelated"), "Its name is unrelated"); } @@ -92,11 +178,43 @@ } } + @OnFunction + static void doSomething() { + } + @ComputedProperty static int powerValue(int value) { return value * value; } + @OnPropertyChange({ "powerValue", "unrelated" }) + static void aPropertyChanged(Modelik m, String name) { + m.setChangedProperty(name); + } + + @OnPropertyChange({ "values" }) + static void anArrayPropertyChanged(String name, Modelik m) { + m.setChangedProperty(name); + } + + @Test public void changeAnything() { + model.setCount(44); + assertNull(model.getChangedProperty(), "No observed value change"); + } + @Test public void changeValue() { + model.setValue(33); + assertEquals(model.getChangedProperty(), "powerValue", "power property changed"); + } + @Test public void changeUnrelated() { + model.setUnrelated(333); + assertEquals(model.getChangedProperty(), "unrelated", "unrelated changed"); + } + + @Test public void changeInArray() { + model.getValues().add(10); + assertEquals(model.getChangedProperty(), "values", "Something added into the array"); + } + @ComputedProperty static String notAllowedRead() { return "Not allowed callback: " + leakedModel.getUnrelated(); @@ -108,12 +226,31 @@ return "Not allowed callback!"; } + @ComputedProperty + static List repeat(int count) { + return Collections.nCopies(count, "Hello"); + } + static class MockKnockout extends Knockout { - List mutated = new ArrayList(); + List mutated = new ArrayList<>(); + + MockKnockout() { + super(null); + } @Override public void valueHasMutated(String prop) { mutated.add(prop); } } + + public @Test void hasPersonPropertyAndComputedFullName() { + List arr = model.getPeople(); + assertEquals(arr.size(), 0, "By default empty"); + Person p = null; + if (p != null) { + String fullNameGenerated = p.getFullName(); + assertNotNull(fullNameGenerated); + } + } } diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageController.java --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageController.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageController.java Mon Oct 07 14:20:58 2013 +0200 @@ -50,7 +50,7 @@ if (PAGE != ref) { throw new IllegalStateException("Both references should be the same. " + ref + " != " + PAGE); } - ref.PG_TITLE.setText("You want this window to be named " + ref.PG_TEXT.getValue()); + ref.pg_title.setText("You want this window to be named " + ref.pg_text.getValue()); } @On(event = CLICK, id={ "pg.title", "pg.text" }) @@ -58,6 +58,11 @@ if (!id.equals("pg.title")) { throw new IllegalStateException(); } - PAGE.PG_TITLE.setText(id); + PAGE.pg_title.setText(id); + } + + @On(event = CLICK, id={ "pg.canvas" }) + static void clickCanvas(String id, double layerX) { + PAGE.pg_canvas.setWidth((int) layerX); } } diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PageTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,53 @@ +/** + * 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.htmlpage; + +import java.io.IOException; +import java.util.Locale; +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; +import static org.testng.Assert.*; +import org.testng.annotations.Test; + +/** Verify errors emitted by the processor. + * + * @author Jaroslav Tulach + */ +public class PageTest { + @Test public void verifyWrongType() throws IOException { + String html = "" + + ""; + String code = "package x.y.z;\n" + + "import org.apidesign.bck2brwsr.htmlpage.api.*;\n" + + "@Page(xhtml=\"index.xhtml\", className=\"Model\", properties={\n" + + " @Property(name=\"prop\", type=Runnable.class)\n" + + "})\n" + + "class X {\n" + + "}\n"; + + Compile c = Compile.create(html, code); + assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); + for (Diagnostic e : c.getErrors()) { + String msg = e.getMessage(Locale.ENGLISH); + if (!msg.contains("Runnable")) { + fail("Should contain warning about Runnable: " + msg); + } + } + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PersonImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PersonImpl.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,62 @@ +/** + * 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.htmlpage; + +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; +import org.apidesign.bck2brwsr.htmlpage.api.Model; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; +import org.apidesign.bck2brwsr.htmlpage.api.Property; + +/** + * + * @author Jaroslav Tulach + */ +@Model(className = "Person", properties = { + @Property(name = "firstName", type = String.class), + @Property(name = "lastName", type = String.class), + @Property(name = "sex", type = Sex.class) +}) +final class PersonImpl { + @ComputedProperty + public static String fullName(String firstName, String lastName) { + return firstName + " " + lastName; + } + + @ComputedProperty + public static String sexType(Sex sex) { + return sex == null ? "unknown" : sex.toString(); + } + + @OnFunction + static void changeSex(Person p) { + if (p.getSex() == Sex.MALE) { + p.setSex(Sex.FEMALE); + } else { + p.setSex(Sex.MALE); + } + } + + @Model(className = "People", properties = { + @Property(array = true, name = "info", type = Person.class), + @Property(array = true, name = "nicknames", type = String.class), + @Property(array = true, name = "age", type = int.class), + @Property(array = true, name = "sex", type = Sex.class) + }) + public class PeopleImpl { + } +} diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ProcessPageTest.java --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ProcessPageTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ProcessPageTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -36,11 +36,12 @@ assertNotNull(is, "Sample HTML page found"); ProcessPage res = ProcessPage.readPage(is); final Set ids = res.ids(); - assertEquals(ids.size(), 3, "Three ids found: " + ids); + assertEquals(ids.size(), 4, "Four ids found: " + ids); assertEquals(res.tagNameForId("pg.title"), "title"); assertEquals(res.tagNameForId("pg.button"), "button"); assertEquals(res.tagNameForId("pg.text"), "input"); + assertEquals(res.tagNameForId("pg.canvas"), "canvas"); } @Test public void testCompileAndRunPageController() throws Exception { @@ -53,11 +54,13 @@ + "doc.title.innerHTML = 'nothing';\n" + "doc.text = new Object();\n" + "doc.text.value = 'something';\n" + + "doc.canvas = new Object();\n" + "doc.getElementById = function(id) {\n" + " switch(id) {\n" + " case 'pg.button': return doc.button;\n" + " case 'pg.title': return doc.title;\n" + " case 'pg.text': return doc.text;\n" + + " case 'pg.canvas': return doc.canvas;\n" + " }\n" + " throw id;\n" + " }\n" @@ -92,11 +95,13 @@ + "doc.title.innerHTML = 'nothing';\n" + "doc.text = new Object();\n" + "doc.text.value = 'something';\n" + + "doc.canvas = new Object();\n" + "doc.getElementById = function(id) {\n" + " switch(id) {\n" + " case 'pg.button': return doc.button;\n" + " case 'pg.title': return doc.title;\n" + " case 'pg.text': return doc.text;\n" + + " case 'pg.canvas': return doc.canvas;\n" + " }\n" + " throw id;\n" + " }\n" @@ -123,12 +128,61 @@ assertEquals(ret, "pg.title", "Title has been passed to the method argument"); } + @Test public void clickWithArgumentAndParameterCalled() throws Exception { + StringBuilder sb = new StringBuilder(); + sb.append( + "var window = new Object();\n" + + "var doc = new Object();\n" + + "var eventObject = new Object();\n" + + "eventObject.layerX = 100;\n" + + "doc.button = new Object();\n" + + "doc.title = new Object();\n" + + "doc.title.innerHTML = 'nothing';\n" + + "doc.text = new Object();\n" + + "doc.text.value = 'something';\n" + + "doc.canvas = new Object();\n" + + "doc.canvas.width = 200;\n" + + "doc.getElementById = function(id) {\n" + + " switch(id) {\n" + + " case 'pg.button': return doc.button;\n" + + " case 'pg.title': return doc.title;\n" + + " case 'pg.text': return doc.text;\n" + + " case 'pg.canvas': return doc.canvas;\n" + + " }\n" + + " throw id;\n" + + " }\n" + + "\n" + + "function clickAndCheck() {\n" + + " doc.canvas.onclick(eventObject);\n" + + " return doc.canvas.width.toString();\n" + + "};\n" + + "\n" + + "window.document = doc;\n" + ); + Invocable i = compileClass(sb, + "org/apidesign/bck2brwsr/htmlpage/PageController" + ); + + Object ret = null; + try { + ret = i.invokeFunction("clickAndCheck"); + } catch (ScriptException ex) { + fail("Execution failed in " + sb, ex); + } catch (NoSuchMethodException ex) { + fail("Cannot find method in " + sb, ex); + } + assertEquals(ret, "100", "layerX has been passed to the method argument"); + } + static Invocable compileClass(StringBuilder sb, String... names) throws ScriptException, IOException { if (sb == null) { sb = new StringBuilder(); } Bck2Brwsr.generate(sb, ProcessPageTest.class.getClassLoader(), names); sb.append("var vm = this.bck2brwsr();\n"); + for (String c : names) { + sb.append("vm.loadClass('").append(c.replace('/', '.')).append("');\n"); + } ScriptEngineManager sem = new ScriptEngineManager(); ScriptEngine js = sem.getEngineByExtension("js"); diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/Sex.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/Sex.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,26 @@ +/** + * 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.htmlpage; + +/** + * + * @author Jaroslav Tulach + */ +public enum Sex { + MALE, FEMALE; +} diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/api/src/test/resources/org/apidesign/bck2brwsr/htmlpage/TestPage.html --- a/javaquery/api/src/test/resources/org/apidesign/bck2brwsr/htmlpage/TestPage.html Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/api/src/test/resources/org/apidesign/bck2brwsr/htmlpage/TestPage.html Mon Oct 07 14:20:58 2013 +0200 @@ -26,5 +26,6 @@ New title: + diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-calculator-dynamic/nbactions.xml --- a/javaquery/demo-calculator-dynamic/nbactions.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/demo-calculator-dynamic/nbactions.xml Mon Oct 07 14:20:58 2013 +0200 @@ -23,7 +23,7 @@ run process-classes - org.apidesign.bck2brwsr:mojo:0.3-SNAPSHOT:brwsr + bck2brwsr:brwsr diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-calculator-dynamic/pom.xml --- a/javaquery/demo-calculator-dynamic/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/demo-calculator-dynamic/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -1,11 +1,10 @@ - + 4.0.0 org.apidesign.bck2brwsr demo.calculator - 0.3-SNAPSHOT + 0.9-SNAPSHOT jar JavaQuery Demo - Calculator @@ -18,8 +17,8 @@ org.apidesign.bck2brwsr - mojo - 0.3-SNAPSHOT + bck2brwsr-maven-plugin + ${project.version} @@ -62,6 +61,14 @@ + org.apache.maven.plugins + maven-javadoc-plugin + 2.9 + + true + + + maven-assembly-plugin 2.4 @@ -86,13 +93,13 @@ org.apidesign.bck2brwsr emul - 0.3-SNAPSHOT + ${project.version} rt org.apidesign.bck2brwsr javaquery.api - 0.3-SNAPSHOT + ${project.version} org.testng @@ -105,7 +112,7 @@ vm4brwsr js zip - 0.3-SNAPSHOT + ${project.version} provided diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/Calc.java --- a/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/Calc.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/Calc.java Mon Oct 07 14:20:58 2013 +0200 @@ -17,9 +17,11 @@ */ package org.apidesign.bck2brwsr.demo.calc; +import java.util.List; import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; import org.apidesign.bck2brwsr.htmlpage.api.On; import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; import org.apidesign.bck2brwsr.htmlpage.api.Page; import org.apidesign.bck2brwsr.htmlpage.api.Property; @@ -33,11 +35,12 @@ @Property(name = "memory", type = double.class), @Property(name = "display", type = double.class), @Property(name = "operation", type = String.class), - @Property(name = "hover", type = boolean.class) + @Property(name = "hover", type = boolean.class), + @Property(name = "history", type = HistoryImpl.class, array = true) }) public class Calc { static { - new Calculator().applyBindings(); + new Calculator().applyBindings().setOperation("plus"); } @On(event = CLICK, id="clear") @@ -48,31 +51,48 @@ } @On(event = CLICK, id= { "plus", "minus", "mul", "div" }) - static void applyOp(Calculator c, String op) { + static void applyOp(Calculator c, String id) { c.setMemory(c.getDisplay()); - c.setOperation(op); + c.setOperation(id); c.setDisplay(0); } @On(event = MOUSE_OVER, id= { "result" }) - static void attemptingIn(Calculator c, String op) { + static void attemptingIn(Calculator c) { c.setHover(true); } @On(event = MOUSE_OUT, id= { "result" }) - static void attemptingOut(Calculator c, String op) { + static void attemptingOut(Calculator c) { c.setHover(false); } @On(event = CLICK, id="result") static void computeTheValue(Calculator c) { - c.setDisplay(compute( + final double newValue = compute( c.getOperation(), c.getMemory(), c.getDisplay() - )); + ); + c.setDisplay(newValue); + if (!containsValue(c.getHistory(), newValue)) { + History h = new History(); + h.setValue(newValue); + h.setOperation(c.getOperation()); + c.getHistory().add(h); + } c.setMemory(0); } + @OnFunction + static void recoverMemory(Calculator c, History data) { + c.setDisplay(data.getValue()); + } + + @OnFunction + static void removeMemory(Calculator c, History data) { + c.getHistory().remove(data); + } + private static double compute(String op, double memory, double display) { switch (op) { case "plus": return memory + display; @@ -84,18 +104,18 @@ } @On(event = CLICK, id={"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"}) - static void addDigit(String digit, Calculator c) { - digit = digit.substring(1); + static void addDigit(String id, Calculator c) { + id = id.substring(1); double v = c.getDisplay(); if (v == 0.0) { - c.setDisplay(Integer.parseInt(digit)); + c.setDisplay(Integer.parseInt(id)); } else { String txt = Double.toString(v); if (txt.endsWith(".0")) { txt = txt.substring(0, txt.length() - 2); } - txt = txt + digit; + txt = txt + id; c.setDisplay(Double.parseDouble(txt)); } } @@ -109,4 +129,18 @@ } return "Attempt to compute " + memory + " " + operation + " " + display + " = " + compute(operation, memory, display); } + + @ComputedProperty + static boolean emptyHistory(List history) { + return history.isEmpty(); + } + + private static boolean containsValue(List arr, final double newValue) { + for (History history : arr) { + if (history.getValue() == newValue) { + return true; + } + } + return false; + } } diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/HistoryImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-calculator-dynamic/src/main/java/org/apidesign/bck2brwsr/demo/calc/HistoryImpl.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,43 @@ +/** + * 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.demo.calc; + +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; +import org.apidesign.bck2brwsr.htmlpage.api.Model; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; +import org.apidesign.bck2brwsr.htmlpage.api.Property; + +/** + * + * @author Jaroslav Tulach + */ +@Model(className = "History", properties = { + @Property(name = "value", type = double.class), + @Property(name = "operation", type = String.class) +}) +public class HistoryImpl { + @ComputedProperty + static String resultOf(String operation) { + return "result of " + operation; + } + + @OnFunction + static void twice(History data) { + data.setValue(2.0 * data.getValue()); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml --- a/javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/demo-calculator-dynamic/src/main/resources/org/apidesign/bck2brwsr/demo/calc/Calculator.xhtml Mon Oct 07 14:20:58 2013 +0200 @@ -78,82 +78,25 @@
+

Previous Results

+ +
No results yet.
+ +
-
-    package org.apidesign.bck2brwsr.mavenhtml;
-
-    import org.apidesign.bck2brwsr.htmlpage.api.OnClick;
-    import org.apidesign.bck2brwsr.htmlpage.api.Page;
-
-    /** HTML5 & Java demo showing the power of annotation processors
-     * as well as other goodies, including type-safe association between
-     * an XHTML page and Java.
-     * 
-     * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
-     */
-    @Page(xhtml="Calculator.xhtml")
-    public class App {
-        private static double memory;
-        private static String operation;
-
-        @OnClick(id="clear")
-        static void clear() {
-            memory = 0;
-            operation = null;
-            Calculator.DISPLAY.setValue("0");
-        }
-
-        @OnClick(id= { "plus", "minus", "mul", "div" })
-        static void applyOp(String op) {
-            memory = getValue();
-            operation = op;
-            Calculator.DISPLAY.setValue("0");
-        }
-
-        @OnClick(id="result")
-        static void computeTheValue() {
-            switch (operation) {
-                case "plus": setValue(memory + getValue()); break;
-                case "minus": setValue(memory - getValue()); break;
-                case "mul": setValue(memory * getValue()); break;
-                case "div": setValue(memory / getValue()); break;
-                default: throw new IllegalStateException(operation);
-            }
-        }
-
-        @OnClick(id={"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"}) 
-        static void addDigit(String digit) {
-            digit = digit.substring(1);
-            String v = Calculator.DISPLAY.getValue();
-            if (getValue() == 0.0) {
-                Calculator.DISPLAY.setValue(digit);
-            } else {
-                Calculator.DISPLAY.setValue(v + digit);
-            }
-        }
-
-        private static void setValue(double v) {
-            StringBuilder sb = new StringBuilder();
-            sb.append(v);
-            Calculator.DISPLAY.setValue(sb.toString());
-        }
-
-        private static double getValue() {
-            try {
-                return Double.parseDouble(Calculator.DISPLAY.getValue());
-            } catch (NumberFormatException ex) {
-                Calculator.DISPLAY.setValue("err");
-                return 0.0;
-            }
-        }
-    }
-
-    
diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-calculator/nbactions.xml --- a/javaquery/demo-calculator/nbactions.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/demo-calculator/nbactions.xml Mon Oct 07 14:20:58 2013 +0200 @@ -23,7 +23,7 @@ run package - org.apidesign.bck2brwsr:mojo:0.3-SNAPSHOT:brwsr + bck2brwsr:brwsr true diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-calculator/pom.xml --- a/javaquery/demo-calculator/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/demo-calculator/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -1,11 +1,10 @@ - + 4.0.0 org.apidesign.bck2brwsr demo.static.calculator - 0.3-SNAPSHOT + 0.9-SNAPSHOT jar JavaQuery Demo - Calculator - Static Compilation @@ -13,16 +12,18 @@ UTF-8 + MINIMAL org.apidesign.bck2brwsr - mojo - 0.3-SNAPSHOT + bck2brwsr-maven-plugin + ${project.version} + j2js brwsr @@ -30,6 +31,8 @@ ${project.build.directory}/${project.build.finalName}-bck2brwsr/public_html/ index.xhtml + ${project.build.directory}/bck2brwsr.js + ${bck2brwsr.obfuscationlevel} @@ -80,6 +83,14 @@ true + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9 + + true + + @@ -87,21 +98,13 @@ org.apidesign.bck2brwsr emul - 0.3-SNAPSHOT + ${project.version} rt org.apidesign.bck2brwsr javaquery.api - 0.3-SNAPSHOT - - - org.apidesign.bck2brwsr - vm4brwsr - js - zip - 0.3-SNAPSHOT - provided + ${project.version} diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-calculator/src/main/assembly/bck2brwsr.xml --- a/javaquery/demo-calculator/src/main/assembly/bck2brwsr.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/demo-calculator/src/main/assembly/bck2brwsr.xml Mon Oct 07 14:20:58 2013 +0200 @@ -37,15 +37,6 @@ *:rt - - false - provided - - *:js - - true - / - @@ -53,6 +44,10 @@ / + ${project.build.directory}/bck2brwsr.js + / + + ${project.build.directory}/classes/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml / index.xhtml diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java --- a/javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/demo-calculator/src/main/java/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calc.java Mon Oct 07 14:20:58 2013 +0200 @@ -17,9 +17,11 @@ */ package org.apidesign.bck2brwsr.demo.calc.staticcompilation; +import java.util.List; import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; import org.apidesign.bck2brwsr.htmlpage.api.On; import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*; +import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; import org.apidesign.bck2brwsr.htmlpage.api.Page; import org.apidesign.bck2brwsr.htmlpage.api.Property; @@ -33,11 +35,12 @@ @Property(name = "memory", type = double.class), @Property(name = "display", type = double.class), @Property(name = "operation", type = String.class), - @Property(name = "hover", type = boolean.class) + @Property(name = "hover", type = boolean.class), + @Property(name = "history", type = double.class, array = true) }) public class Calc { static { - new Calculator().applyBindings(); + new Calculator().applyBindings().setOperation("plus"); } @On(event = CLICK, id="clear") @@ -48,31 +51,45 @@ } @On(event = CLICK, id= { "plus", "minus", "mul", "div" }) - static void applyOp(Calculator c, String op) { + static void applyOp(Calculator c, String id) { c.setMemory(c.getDisplay()); - c.setOperation(op); + c.setOperation(id); c.setDisplay(0); } @On(event = MOUSE_OVER, id= { "result" }) - static void attemptingIn(Calculator c, String op) { + static void attemptingIn(Calculator c) { c.setHover(true); } @On(event = MOUSE_OUT, id= { "result" }) - static void attemptingOut(Calculator c, String op) { + static void attemptingOut(Calculator c) { c.setHover(false); } @On(event = CLICK, id="result") static void computeTheValue(Calculator c) { - c.setDisplay(compute( + final double newValue = compute( c.getOperation(), c.getMemory(), c.getDisplay() - )); + ); + c.setDisplay(newValue); + if (!c.getHistory().contains(newValue)) { + c.getHistory().add(newValue); + } c.setMemory(0); } + @OnFunction + static void recoverMemory(Calculator c, double data) { + c.setDisplay(data); + } + + @OnFunction + static void removeMemory(Calculator c, double data) { + c.getHistory().remove(data); + } + private static double compute(String op, double memory, double display) { switch (op) { case "plus": return memory + display; @@ -84,18 +101,18 @@ } @On(event = CLICK, id={"n0", "n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9"}) - static void addDigit(String digit, Calculator c) { - digit = digit.substring(1); + static void addDigit(String id, Calculator c) { + id = id.substring(1); double v = c.getDisplay(); if (v == 0.0) { - c.setDisplay(Integer.parseInt(digit)); + c.setDisplay(Integer.parseInt(id)); } else { String txt = Double.toString(v); if (txt.endsWith(".0")) { txt = txt.substring(0, txt.length() - 2); } - txt = txt + digit; + txt = txt + id; c.setDisplay(Double.parseDouble(txt)); } } @@ -109,4 +126,9 @@ } return "Attempt to compute " + memory + " " + operation + " " + display + " = " + compute(operation, memory, display); } + + @ComputedProperty + static boolean emptyHistory(List history) { + return history.isEmpty(); + } } diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml --- a/javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/demo-calculator/src/main/resources/org/apidesign/bck2brwsr/demo/calc/staticcompilation/Calculator.xhtml Mon Oct 07 14:20:58 2013 +0200 @@ -76,10 +76,22 @@ + +

Previous Results

+ +
No results yet.
+ +
- diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-twitter/bck2brwsr-assembly.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-twitter/bck2brwsr-assembly.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,62 @@ + + + + + bck2brwsr + + zip + + public_html + + + false + runtime + lib + + *:jar + *:rt + + + + + + ${project.build.directory}/classes/org/apidesign/bck2brwsr/demo/twitter/ + + **/* + + + **/*.class + + / + + + + + ${project.build.directory}/${project.build.finalName}.jar + / + + + ${project.build.directory}/bck2brwsr.js + / + + + \ No newline at end of file diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-twitter/nb-configuration.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-twitter/nb-configuration.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,37 @@ + + + + + + + none + + diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-twitter/nbactions.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-twitter/nbactions.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,29 @@ + + + + + run + + process-classes + bck2brwsr:brwsr + + + diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-twitter/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-twitter/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,151 @@ + + + 4.0.0 + + javaquery + org.apidesign.bck2brwsr + 0.9-SNAPSHOT + + + org.apidesign.bck2brwsr + demo-twitter + 0.9-SNAPSHOT + jar + + Bck2Brwsr's Twttr + + Rewrite of knockoutjs example to use model written in Java and + execute using Bck2Brwsr virtual machine. + + + + + java.net + Java.net + https://maven.java.net/content/repositories/releases/ + + + + + netbeans + NetBeans + http://bits.netbeans.org/maven2/ + + + + + java.net + Java.net + https://maven.java.net/content/repositories/releases/ + + + + + + + UTF-8 + MINIMAL + + + + + org.apidesign.bck2brwsr + bck2brwsr-maven-plugin + ${project.version} + + + + brwsr + j2js + + + + + org/apidesign/bck2brwsr/demo/twitter/index.html + ${project.build.directory}/bck2brwsr.js + ${bck2brwsr.obfuscationlevel} + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + lib/ + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.7 + + true + + + + maven-assembly-plugin + 2.4 + + + distro-assembly + package + + single + + + + bck2brwsr-assembly.xml + + + + + + + + + + + org.apidesign.bck2brwsr + emul + ${project.version} + rt + + + org.apidesign.bck2brwsr + javaquery.api + ${project.version} + + + org.testng + testng + 6.5.2 + test + + + org.apidesign.bck2brwsr + vmtest + ${project.version} + test + + + org.apidesign.bck2brwsr + launcher.http + ${project.version} + runtime + + + diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-twitter/src/main/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-twitter/src/main/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClient.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,194 @@ +/** + * 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.demo.twitter; + +import java.util.Arrays; +import java.util.List; +import org.apidesign.bck2brwsr.htmlpage.api.*; +import org.apidesign.bck2brwsr.htmlpage.api.Page; +import org.apidesign.bck2brwsr.htmlpage.api.Property; +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; + +/** Controller class for access to Twitter. + * + * @author Jaroslav Tulach + */ +@Page(xhtml="index.html", 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="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()); + } + + @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.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); + } + + @OnFunction + 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()); + } + + @OnFunction + 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()); + } + } + + @OnFunction + static void addUser(TwitterModel model) { + String n = model.getUserNameToAdd(); + model.getActiveTweeters().add(n); + } + @OnFunction + 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 8264f07b1f46 -r f73c1a0234fb javaquery/demo-twitter/src/main/resources/org/apidesign/bck2brwsr/demo/twitter/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-twitter/src/main/resources/org/apidesign/bck2brwsr/demo/twitter/index.html Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,99 @@ + + + + + + + + + Bck2Brwsr's Twitter + + + + + + + +

Bck2Brwsr's Twitter

+ +

+ This code based on original knockout.js Twitter example and + uses almost unmodified HTML code. It just changes the model. It + is written in Java language and it is executed using Bck2Brwsr + virtual machine. The Java source code has about 190 lines and is available + here + - in fact it may even be more dense than the original JavaScript model. +

+ +
+
+
+ + + +
+ +

Currently viewing user(s):

+
+
    +
  • + +
    +
  • +
+
+ +
+ + + +
+
+
+
Loading...
+ + + + + +
+ + +
+
+
+
+ + + + + + + diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-twitter/src/main/resources/org/apidesign/bck2brwsr/demo/twitter/twitterExample.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-twitter/src/main/resources/org/apidesign/bck2brwsr/demo/twitter/twitterExample.css Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,50 @@ +/** + * 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. + */ + +/* + 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; display: none; } diff -r 8264f07b1f46 -r f73c1a0234fb javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClientTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClientTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,67 @@ +/** + * 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.demo.twitter; + +import java.util.List; +import static org.testng.Assert.*; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** We can unit test the TwitterModel smoothly. + * + * @author Jaroslav Tulach + */ +public class TwitterClientTest { + private TwitterModel model; + + + @BeforeMethod + public void initModel() { + model = new TwitterModel().applyBindings(); + } + + @Test public void testIsValidToAdd() { + model.setUserNameToAdd("Joe"); + Tweeters t = new Tweeters(); + 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 8264f07b1f46 -r f73c1a0234fb javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterProtocolTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterProtocolTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,94 @@ +/** + * 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.demo.twitter; + +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.Http; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +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 8264f07b1f46 -r f73c1a0234fb javaquery/pom.xml --- a/javaquery/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/javaquery/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -4,16 +4,17 @@ bck2brwsr org.apidesign - 0.3-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr javaquery - 0.3-SNAPSHOT + 0.9-SNAPSHOT pom JavaQuery API and Demo api demo-calculator demo-calculator-dynamic - -
+ demo-twitter + +
\ No newline at end of file diff -r 8264f07b1f46 -r f73c1a0234fb ko/archetype-test/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype-test/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,48 @@ + + + 4.0.0 + + org.apidesign.bck2brwsr + ko + 0.9-SNAPSHOT + + org.apidesign.bck2brwsr + ko-archetype-test + 0.9-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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,140 @@ +/** + * 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"); + if (snapshot >= 0) { + arch = arch.substring(0, snapshot); + } + + 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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,133 @@ +/** + * 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.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.apache.maven.it.Verifier; +import org.testng.annotations.Test; +import static org.testng.Assert.*; +import org.testng.reporters.Files; + +/** + * + * @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); + final ZipEntry index = zf.getEntry("public_html/index.html"); + assertNotNull(index, "index.html found"); + + String txt = readText(zf.getInputStream(index)); + final int beg = txt.indexOf("${"); + if (beg >= 0) { + int end = txt.indexOf("}", beg); + if (end < beg) { + end = txt.length(); + } + fail("No substitutions in index.html. Found: " + txt.substring(beg, end)); + } + } + + 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; + } + + private static String readText(InputStream is) throws IOException { + return Files.readFile(is); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb ko/archetype/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,58 @@ + + + 4.0.0 + + ko + org.apidesign.bck2brwsr + 0.9-SNAPSHOT + + org.apidesign.bck2brwsr + knockout4j-archetype + 0.9-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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,63 @@ + + + + + + src/main/java + + **/*.java + + + + src/main/webapp/pages + + **/*.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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,14 @@ + + + + run + + package + bck2brwsr:brwsr + + + true + NONE + + + diff -r 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,20 @@ + + + + run + + process-classes + bck2brwsr:brwsr + + + + debug + + process-classes + bck2brwsr:brwsr + + + maven + + + diff -r 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,20 @@ + + + + run + + process-classes + bck2brwsr:brwsr + + + + debug + + process-classes + bck2brwsr:brwsr + + + maven + + + diff -r 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,267 @@ + + + 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 + pages/index.html + + + + + org.apidesign.bck2brwsr + bck2brwsr-maven-plugin + \${bck2brwsr.launcher.version} + + + + brwsr + + + + + \${basedir}/src/main/webapp/ + ${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 + launcher.http + \${bck2brwsr.launcher.version} + 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 + + + + + diff -r 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,48 @@ + + + + bck2brwsr + + zip + + public_html + + + false + runtime + lib + + *:jar + *:rt + + + + + + ${project.build.directory}/classes/${package.replace('.','/')}/ + + **/* + + + **/*.class + + / + + + src/main/webapp/pages + / + true + + + + + ${project.build.directory}/${project.build.finalName}.jar + / + + + ${project.build.directory}/bck2brwsr.js + / + + + \ No newline at end of file diff -r 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,33 @@ + + + + fxbrwsr + + zip + + ${project.build.finalName}-fxbrwsr + + + false + runtime + lib + + + + + ${project.build.directory}/${project.build.finalName}.jar + / + + + + + src/main/webapp/ + / + + pages/** + + true + + + \ No newline at end of file diff -r 8264f07b1f46 -r f73c1a0234fb ko/archetype/src/main/resources/archetype-resources/src/main/java/DataModel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/main/java/DataModel.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,31 @@ +package ${package}; + +import net.java.html.json.ComputedProperty; +import net.java.html.json.Function; +import net.java.html.json.Model; +import net.java.html.json.Property; + +/** Model annotation generates class Data with + * one message property, boolean property and read only words property + */ +@Model(className = "Data", properties = { + @Property(name = "message", type = String.class), + @Property(name = "on", type = boolean.class) +}) +final class DataModel { + @ComputedProperty static java.util.List words(String message) { + String[] arr = new String[6]; + String[] words = message == null ? new String[0] : message.split(" ", 6); + for (int i = 0; i < 6; i++) { + arr[i] = words.length > i ? words[i] : "!"; + } + return java.util.Arrays.asList(arr); + } + + @Function static void turnOn(Data model) { + model.setOn(true); + } + @Function static void turnOff(Data model) { + model.setOn(false); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb ko/archetype/src/main/resources/archetype-resources/src/main/java/Main.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/main/java/Main.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,15 @@ +package ${package}; + +public final class Main { + private Main() { + } + + /** + * Called when the page is ready. + */ + static { + Data d = new Data(); + d.setMessage("Hello World from HTML and Java!"); + d.applyBindings(); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb ko/archetype/src/main/resources/archetype-resources/src/main/webapp/pages/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/main/webapp/pages/index.html Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,63 @@ + + + + + + + + + + +

Words Demo

+ +
+ + + +
+ + + +
+ + + + diff -r 8264f07b1f46 -r f73c1a0234fb ko/archetype/src/main/resources/archetype-resources/src/test/java/DataModelTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/test/java/DataModelTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,16 @@ +package ${package}; + +import static org.testng.Assert.*; +import org.testng.annotations.Test; + +public class DataModelTest { + @Test public void areHelloWorldTwoWords() { + Data model = new Data(); + model.setMessage("Hello World!"); + + java.util.List arr = model.getWords(); + assertEquals(arr.size(), 6, "Six words always"); + assertEquals("Hello", arr.get(0), "Hello is the first word"); + assertEquals("World!", arr.get(1), "World is the second word"); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb ko/archetype/src/main/resources/archetype-resources/src/test/java/InconsistencyTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/test/java/InconsistencyTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,38 @@ +package ${package}; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** Bck2brwsr cares about compatibility with real Java. Whatever API is + * supported by bck2brwsr, it needs to behave the same way as when running + * in HotSpot VM. + *

+ * There can be bugs, however. To help us fix them, we kindly ask you to + * write an "inconsistency" test. A test that compares behavior of the API + * between real VM and bck2brwsr VM. This class is skeleton of such test. + */ +public class InconsistencyTest { + /** A method to demonstrate inconsistency between bck2brwsr and HotSpot. + * Make calls to an API that behaves strangely, return some result at + * the end. No need to use any assert. + * + * @return value to compare between HotSpot and bck2brwsr + */ + @Compare + public int checkStringHashCode() throws Exception { + return "Is string hashCode the same?".hashCode(); + } + + /** Factory method that creates a three tests for each method annotated with + * {@link org.apidesign.bck2brwsr.vmtest.Compare}. One executes the code in + * HotSpot, one in Rhino and the last one compares the results. + * + * @see org.apidesign.bck2brwsr.vmtest.VMTest + */ + @Factory + public static Object[] create() { + return VMTest.create(InconsistencyTest.class); + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 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 8264f07b1f46 -r f73c1a0234fb ko/bck2brwsr/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/bck2brwsr/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,102 @@ + + + 4.0.0 + + org.apidesign.bck2brwsr + ko + 0.9-SNAPSHOT + + org.apidesign.bck2brwsr + ko-bck2brwsr + 0.9-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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,53 @@ +/** + * 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.apidesign.html.json.spi.WSTransfer; +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). + register(WSTransfer.class, BrwsrCtxImpl.DEFAULT, 50); + } + } + + @JavaScriptBody(args = { }, body = "return true;") + private static boolean bck2BrwsrVM() { + return false; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 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 8264f07b1f46 -r f73c1a0234fb ko/fx/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/fx/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,119 @@ + + + 4.0.0 + + org.apidesign.bck2brwsr + ko + 0.9-SNAPSHOT + + org.apidesign.bck2brwsr + ko-fx + 0.9-SNAPSHOT + Knockout.fx in Brwsr + http://maven.apache.org + + 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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,133 @@ +/** + * 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.boot.impl.FnUtils; +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(FnUtils.currentPresenter()); + 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 8264f07b1f46 -r f73c1a0234fb ko/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,20 @@ + + + 4.0.0 + org.apidesign.bck2brwsr + ko + 0.9-SNAPSHOT + pom + Bck2Brwsr Knockout Support + + org.apidesign + bck2brwsr + 0.9-SNAPSHOT + + + archetype + archetype-test + bck2brwsr + fx + + diff -r 8264f07b1f46 -r f73c1a0234fb launcher/api/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/api/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,31 @@ + + + 4.0.0 + + org.apidesign.bck2brwsr + launcher-pom + 0.9-SNAPSHOT + + org.apidesign.bck2brwsr + launcher + 0.9-SNAPSHOT + Launcher API + http://maven.apache.org + + UTF-8 + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + org.apidesign.bck2brwsr.launcher + false + + + + + + + diff -r 8264f07b1f46 -r f73c1a0234fb launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,115 @@ +/** + * 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; + +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; +import java.util.concurrent.TimeUnit; + +/** Represents individual method invocation, its context and its result. + * + * @author Jaroslav Tulach + */ +public final class InvocationContext { + final CountDownLatch wait = new CountDownLatch(1); + final Class clazz; + final String methodName; + private final Launcher launcher; + private String result; + private Throwable exception; + String html; + final List resources = new ArrayList<>(); + + InvocationContext(Launcher launcher, Class clazz, String methodName) { + this.launcher = launcher; + this.clazz = clazz; + this.methodName = methodName; + } + + /** An HTML fragment to be available for the execution. Useful primarily when + * executing in a browser via {@link Launcher#createBrowser(java.lang.String)}. + * @param html the html fragment + */ + public void setHtmlFragment(String html) { + this.html = html; + } + + /** HTTP resource to be available during execution. An invocation may + * perform an HTTP query and obtain a resource relative to the page. + */ + public void addHttpResource(String relativePath, String mimeType, String[] parameters, InputStream content) { + if (relativePath == null || mimeType == null || content == null || parameters == null) { + throw new NullPointerException(); + } + resources.add(new Resource(content, mimeType, relativePath, parameters)); + } + + /** Invokes the associated method. + * @return the textual result of the invocation + */ + public String invoke() throws IOException { + launcher.runMethod(this); + return toString(); + } + + /** Obtains textual result of the invocation. + * @return text representing the exception or result value + */ + @Override + public String toString() { + if (exception != null) { + return exception.toString(); + } + return result; + } + + /** + * @param timeOut + * @throws InterruptedException + */ + void await(long timeOut) throws InterruptedException { + wait.await(timeOut, TimeUnit.MILLISECONDS); + } + + void result(String r, Throwable e) { + this.result = r; + this.exception = e; + wait.countDown(); + } + + static final class Resource { + final InputStream httpContent; + final String httpType; + final String httpPath; + final String[] parameters; + + Resource(InputStream httpContent, String httpType, String httpPath, + String[] parameters + ) { + httpContent.mark(Integer.MAX_VALUE); + this.httpContent = httpContent; + this.httpType = httpType; + this.httpPath = httpPath; + this.parameters = parameters; + } + } +} diff -r 8264f07b1f46 -r f73c1a0234fb launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,186 @@ +/** + * 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; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; + +/** An abstraction for executing tests in a Bck2Brwsr virtual machine. + * Either in {@link Launcher#createJavaScript JavaScript engine}, + * or in {@link Launcher#createBrowser external browser}. + *

+ * There also are methods to {@link #showDir(java.io.File, java.lang.String) display pages} + * in an external browser served by internal HTTP server. + * + * @author Jaroslav Tulach + */ +public abstract class Launcher { + + Launcher() { + } + + /** Initializes the launcher. This may mean starting a web browser or + * initializing execution engine. + * @throws IOException if something goes wrong + */ + public abstract void initialize() throws IOException; + + /** Shuts down the launcher. + * @throws IOException if something goes wrong + */ + public abstract void shutdown() throws IOException; + + + /** Builds an invocation context. The context can later be customized + * and {@link InvocationContext#invoke() invoked}. + * + * @param clazz the class to execute method from + * @param method the method to execute + * @return the context pointing to the selected method + */ + public InvocationContext createInvocation(Class clazz, String method) { + return new InvocationContext(this, clazz, method); + } + + + /** Creates launcher that uses internal JavaScript engine (Rhino). + * @return the launcher + */ + public static Launcher createJavaScript() { + try { + Class c = loadClass("org.apidesign.bck2brwsr.launcher.JSLauncher"); + return (Launcher) c.newInstance(); + } catch (Exception ex) { + throw new IllegalStateException("Please include org.apidesign.bck2brwsr:launcher.html dependency!", ex); + } + } + + /** Creates launcher that is using external browser. + * + * @param cmd null to use java.awt.Desktop to show the launcher + * or a string to execute in an external process (with a parameter to the URL) + * @return launcher executing in external browser. + */ + public static Launcher createBrowser(String cmd) { + String msg = "Trying to create browser '" + cmd + "'"; + try { + Class c; + if ("fxbrwsr".equals(cmd)) { // NOI18N + msg = "Please include org.apidesign.bck2brwsr:launcher.fx dependency!"; + c = loadClass("org.apidesign.bck2brwsr.launcher.FXBrwsrLauncher"); // NOI18N + } else { + msg = "Please include org.apidesign.bck2brwsr:launcher.html dependency!"; + c = loadClass("org.apidesign.bck2brwsr.launcher.Bck2BrwsrLauncher"); // NOI18N + if ("bck2brwsr".equals(cmd)) { // NOI18N + // use default executable + cmd = null; + } + } + Constructor cnstr = c.getConstructor(String.class); + return (Launcher) cnstr.newInstance(cmd); + } catch (Exception ex) { + throw new IllegalStateException(msg, ex); + } + } + + /** Starts an HTTP server which provides access to classes and resources + * available in the classes URL and shows a start page + * available as {@link ClassLoader#getResource(java.lang.String)} from the + * provide classloader. Opens a browser with URL showing the start page. + * + * @param classes classloader offering access to classes and resources + * @param startpage page to show in the browser + * @return interface that allows one to stop the server + * @throws IOException if something goes wrong + */ + public static Closeable showURL(ClassLoader classes, String startpage) throws IOException { + return showURL(null, classes, startpage); + } + /** Starts an HTTP server which provides access to classes and resources + * available in the classes URL and shows a start page + * available as {@link ClassLoader#getResource(java.lang.String)} from the + * provide classloader. Opens a browser with URL showing the start page. + * + * @param brwsr name of browser to use or null + * @param classes classloader offering access to classes and resources + * @param startpage page to show in the browser + * @return interface that allows one to stop the server + * @throws IOException if something goes wrong + * @since 0.7 + */ + public static Closeable showURL(String brwsr, ClassLoader classes, String startpage) throws IOException { + Launcher l = createBrowser(brwsr); + l.addClassLoader(classes); + l.showURL(startpage); + return (Closeable) l; + } + /** Starts an HTTP server which provides access to certain directory. + * The startpage should be relative location inside the root + * directory. Opens a browser with URL showing the start page. + * + * @param brwsr type of the browser to use + * @param directory the root directory on disk + * @param classes additional classloader with access to classes or null + * @param startpage relative path from the root to the page + * @return instance of server that can be closed + * @exception IOException if something goes wrong. + * @since 0.8 + */ + public static Closeable showDir(String brwsr, File directory, ClassLoader classes, String startpage) throws IOException { + Launcher l = createBrowser(brwsr); + if (classes != null) { + l.addClassLoader(classes); + } + l.showDirectory(directory, startpage, classes != null); + return (Closeable) l; + } + + /** Starts an HTTP server which provides access to certain directory. + * The startpage should be relative location inside the root + * directory. Opens a browser with URL showing the start page. + * + * @param directory the root directory on disk + * @param startpage relative path from the root to the page + * @return instance of server that can be closed + * @exception IOException if something goes wrong. + */ + public static Closeable showDir(File directory, String startpage) throws IOException { + return showDir(null, directory, null, startpage); + } + + abstract InvocationContext runMethod(InvocationContext c) throws IOException; + + + private static Class loadClass(String cn) throws ClassNotFoundException { + return Launcher.class.getClassLoader().loadClass(cn); + } + + void showDirectory(File directory, String startpage, boolean addClasses) throws IOException { + throw new UnsupportedOperationException(); + } + + void showURL(String startpage) throws IOException { + throw new UnsupportedOperationException(); + } + + void addClassLoader(ClassLoader classes) { + throw new UnsupportedOperationException(); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb launcher/fx/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,99 @@ + + + 4.0.0 + + org.apidesign.bck2brwsr + launcher-pom + 0.9-SNAPSHOT + + org.apidesign.bck2brwsr + launcher.fx + 0.9-SNAPSHOT + FXBrwsr Launcher + http://maven.apache.org + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-javadoc-plugin + + org.apidesign.bck2brwsr.launcher.fx + false + + + + + + UTF-8 + + + + ${project.groupId} + launcher + ${project.version} + + + org.glassfish.grizzly + grizzly-http-server + ${grizzly.version} + + + com.oracle + javafx + 2.2 + 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 8264f07b1f46 -r f73c1a0234fb launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/BaseHTTPLauncher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/BaseHTTPLauncher.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,751 @@ +/** + * 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; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apidesign.bck2brwsr.launcher.InvocationContext.Resource; +import org.glassfish.grizzly.PortRange; +import org.glassfish.grizzly.http.server.HttpHandler; +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.grizzly.http.server.NetworkListener; +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; +import org.glassfish.grizzly.http.server.ServerConfiguration; +import org.glassfish.grizzly.http.server.StaticHttpHandler; +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. + * Supports execution in native browser as well as Java's internal + * execution engine. + */ +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 long timeOut; + private final Res resources = new Res(); + private final String cmd; + private Object[] brwsr; + private HttpServer server; + private CountDownLatch wait; + + public BaseHTTPLauncher(String cmd) { + this.cmd = cmd; + addClassLoader(BaseHTTPLauncher.class.getClassLoader()); + setTimeout(180000); + } + + @Override + InvocationContext runMethod(InvocationContext c) throws IOException { + loaders.add(c.clazz.getClassLoader()); + methods.add(c); + try { + c.await(timeOut); + } catch (InterruptedException ex) { + throw new IOException(ex); + } + return c; + } + + public void setTimeout(long ms) { + timeOut = ms; + } + + public void addClassLoader(ClassLoader url) { + this.loaders.add(url); + } + + ClassLoader[] loaders() { + return loaders.toArray(new ClassLoader[loaders.size()]); + } + + public void showURL(String startpage) throws IOException { + if (!startpage.startsWith("/")) { + startpage = "/" + startpage; + } + HttpServer s = initServer(".", true, ""); + int last = startpage.lastIndexOf('/'); + String prefix = startpage.substring(0, last); + String simpleName = startpage.substring(last); + s.getServerConfiguration().addHttpHandler(new SubTree(resources, prefix), "/"); + server = s; + try { + launchServerAndBrwsr(s, simpleName); + } catch (Exception ex) { + throw new IOException(ex); + } + } + + void showDirectory(File dir, String startpage, boolean addClasses) throws IOException { + if (!startpage.startsWith("/")) { + startpage = "/" + startpage; + } + String prefix = ""; + int last = startpage.lastIndexOf('/'); + if (last >= 0) { + prefix = startpage.substring(0, last); + } + HttpServer s = initServer(dir.getPath(), addClasses, prefix); + try { + launchServerAndBrwsr(s, startpage); + } catch (Exception ex) { + throw new IOException(ex); + } + } + + @Override + public void initialize() throws IOException { + try { + executeInBrowser(); + } catch (InterruptedException ex) { + final InterruptedIOException iio = new InterruptedIOException(ex.getMessage()); + iio.initCause(ex); + throw iio; + } catch (Exception ex) { + if (ex instanceof IOException) { + throw (IOException)ex; + } + if (ex instanceof RuntimeException) { + throw (RuntimeException)ex; + } + throw new IOException(ex); + } + } + + private HttpServer initServer(String path, boolean addClasses, String vmPrefix) throws IOException { + HttpServer s = HttpServer.createSimpleServer(null, new PortRange(8080, 65535)); + /* + ThreadPoolConfig fewThreads = ThreadPoolConfig.defaultConfig().copy(). + setPoolName("Fx/Bck2 Brwsr"). + setCorePoolSize(3). + setMaxPoolSize(5); + ThreadPoolConfig oneKernel = ThreadPoolConfig.defaultConfig().copy(). + setPoolName("Kernel Fx/Bck2"). + setCorePoolSize(3). + setMaxPoolSize(3); + for (NetworkListener nl : s.getListeners()) { + nl.getTransport().setWorkerThreadPoolConfig(fewThreads); + nl.getTransport().setKernelThreadPoolConfig(oneKernel); + } + */ + final ServerConfiguration conf = s.getServerConfiguration(); + VMAndPages vm = new VMAndPages(); + conf.addHttpHandler(vm, "/"); + if (vmPrefix != null) { + vm.registerVM(vmPrefix + "/bck2brwsr.js"); + } + if (path != null) { + vm.addDocRoot(path); + } + if (addClasses) { + conf.addHttpHandler(new Classes(resources), "/classes/"); + } + final WebSocketAddOn addon = new WebSocketAddOn(); + for (NetworkListener listener : s.getListeners()) { + listener.registerAddOn(addon); + } + return s; + } + + private void executeInBrowser() throws InterruptedException, URISyntaxException, IOException { + wait = new CountDownLatch(1); + server = initServer(".", true, ""); + final ServerConfiguration conf = server.getServerConfiguration(); + + class DynamicResourceHandler extends HttpHandler { + private final InvocationContext ic; + private int resourcesCount; + DynamicResourceHandler delegate; + public DynamicResourceHandler(InvocationContext ic) { + this.ic = ic; + for (Resource r : ic.resources) { + conf.addHttpHandler(this, r.httpPath); + } + } + + 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()); + response.setContentType(r.httpType); + r.httpContent.reset(); + String[] params = null; + if (r.parameters.length != 0) { + params = new String[r.parameters.length]; + for (int i = 0; i < r.parameters.length; i++) { + params[i] = request.getParameter(r.parameters[i]); + if (params[i] == null) { + if ("http.method".equals(r.parameters[i])) { + params[i] = request.getMethod().toString(); + } else if ("http.requestBody".equals(r.parameters[i])) { + Reader rdr = request.getReader(); + StringBuilder sb = new StringBuilder(); + for (;;) { + int ch = rdr.read(); + if (ch == -1) { + break; + } + sb.append((char)ch); + } + params[i] = sb.toString(); + } + } + if (params[i] == null) { + params[i] = "null"; + } + } + } + + copyStream(r.httpContent, response.getOutputStream(), null, params); + } + } + } + + 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(); + DynamicResourceHandler prev; + @Override + public void service(Request request, Response response) throws Exception { + String id = request.getParameter("request"); + String value = request.getParameter("result"); + if (value != null && value.indexOf((char)0xC5) != -1) { + value = toUTF8(value); + } + + + InvocationContext mi = null; + int caseNmbr = -1; + + if (id != null && value != null) { + LOG.log(Level.INFO, "Received result for case {0} = {1}", new Object[]{id, value}); + value = decodeURL(value); + int indx = Integer.parseInt(id); + cases.get(indx).result(value, null); + if (++indx < cases.size()) { + mi = cases.get(indx); + LOG.log(Level.INFO, "Re-executing case {0}", indx); + caseNmbr = indx; + } + } else { + if (!cases.isEmpty()) { + LOG.info("Re-executing test cases"); + mi = cases.get(0); + caseNmbr = 0; + } + } + + if (mi == null) { + mi = methods.take(); + caseNmbr = cnt++; + } + if (mi == END) { + response.getWriter().write(""); + wait.countDown(); + cnt = 0; + LOG.log(Level.INFO, "End of data reached. Exiting."); + return; + } + 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(); + final String mn = mi.methodName; + LOG.log(Level.INFO, "Request for {0} case. Sending {1}.{2}", new Object[]{caseNmbr, cn, mn}); + response.getWriter().write("{" + + "className: '" + cn + "', " + + "methodName: '" + mn + "', " + + "request: " + caseNmbr + ); + if (mi.html != null) { + response.getWriter().write(", html: '"); + response.getWriter().write(encodeJSON(mi.html)); + response.getWriter().write("'"); + } + response.getWriter().write("}"); + } + }, "/data"); + + this.brwsr = launchServerAndBrwsr(server, "/execute"); + } + + private static String encodeJSON(String in) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < in.length(); i++) { + char ch = in.charAt(i); + if (ch < 32 || ch == '\'' || ch == '"') { + sb.append("\\u"); + String hs = "0000" + Integer.toHexString(ch); + hs = hs.substring(hs.length() - 4); + sb.append(hs); + } else { + sb.append(ch); + } + } + return sb.toString(); + } + + @Override + public void shutdown() throws IOException { + methods.offer(END); + for (;;) { + int prev = methods.size(); + try { + if (wait != null && wait.await(timeOut, TimeUnit.MILLISECONDS)) { + break; + } + } catch (InterruptedException ex) { + throw new IOException(ex); + } + if (prev == methods.size()) { + LOG.log( + Level.WARNING, + "Timeout and no test has been executed meanwhile (at {0}). Giving up.", + methods.size() + ); + break; + } + LOG.log(Level.INFO, + "Timeout, but tests got from {0} to {1}. Trying again.", + new Object[]{prev, methods.size()} + ); + } + stopServerAndBrwsr(server, brwsr); + } + + static void copyStream(InputStream is, OutputStream os, String baseURL, String... params) throws IOException { + for (;;) { + int ch = is.read(); + if (ch == -1) { + break; + } + if (ch == '$' && params.length > 0) { + int cnt = is.read() - '0'; + if (baseURL != null && cnt == 'U' - '0') { + os.write(baseURL.getBytes("UTF-8")); + } else { + if (cnt >= 0 && cnt < params.length) { + os.write(params[cnt].getBytes("UTF-8")); + } else { + os.write('$'); + os.write(cnt + '0'); + } + } + } else { + os.write(ch); + } + } + } + + private Object[] launchServerAndBrwsr(HttpServer server, final String page) throws IOException, URISyntaxException, InterruptedException { + server.start(); + URI uri = pageURL("http", server, page); + return showBrwsr(uri); + } + private static String toUTF8(String value) throws UnsupportedEncodingException { + byte[] arr = new byte[value.length()]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (byte)value.charAt(i); + } + return new String(arr, "UTF-8"); + } + + private static String decodeURL(String s) { + for (;;) { + int pos = s.indexOf('%'); + if (pos == -1) { + return s; + } + int i = Integer.parseInt(s.substring(pos + 1, pos + 2), 16); + s = s.substring(0, pos) + (char)i + s.substring(pos + 2); + } + } + + private void stopServerAndBrwsr(HttpServer server, Object[] brwsr) throws IOException { + if (brwsr == null) { + return; + } + Process process = (Process)brwsr[0]; + + server.stop(); + InputStream stdout = process.getInputStream(); + InputStream stderr = process.getErrorStream(); + drain("StdOut", stdout); + drain("StdErr", stderr); + process.destroy(); + int res; + try { + res = process.waitFor(); + } catch (InterruptedException ex) { + throw new IOException(ex); + } + LOG.log(Level.INFO, "Exit code: {0}", res); + + deleteTree((File)brwsr[1]); + } + + private static void drain(String name, InputStream is) throws IOException { + int av = is.available(); + if (av > 0) { + StringBuilder sb = new StringBuilder(); + sb.append("v== ").append(name).append(" ==v\n"); + while (av-- > 0) { + sb.append((char)is.read()); + } + sb.append("\n^== ").append(name).append(" ==^"); + LOG.log(Level.INFO, sb.toString()); + } + } + + private void deleteTree(File file) { + if (file == null) { + return; + } + File[] arr = file.listFiles(); + if (arr != null) { + for (File s : arr) { + deleteTree(s); + } + } + file.delete(); + } + + @Override + public HttpServer call() throws Exception { + return server; + } + + @Override + public void close() throws IOException { + shutdown(); + } + + protected Object[] showBrwsr(URI uri) throws IOException { + LOG.log(Level.INFO, "Showing {0}", uri); + if (cmd == null) { + try { + LOG.log(Level.INFO, "Trying Desktop.browse on {0} {2} by {1}", new Object[] { + System.getProperty("java.vm.name"), + System.getProperty("java.vm.vendor"), + System.getProperty("java.vm.version"), + }); + java.awt.Desktop.getDesktop().browse(uri); + LOG.log(Level.INFO, "Desktop.browse successfully finished"); + return null; + } catch (UnsupportedOperationException ex) { + LOG.log(Level.INFO, "Desktop.browse not supported: {0}", ex.getMessage()); + LOG.log(Level.FINE, null, ex); + } + } + { + String cmdName = cmd == null ? "xdg-open" : cmd; + String[] cmdArr = { + cmdName, uri.toString() + }; + LOG.log(Level.INFO, "Launching {0}", Arrays.toString(cmdArr)); + final Process process = Runtime.getRuntime().exec(cmdArr); + return new Object[] { process, null }; + } + } + + 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; + for (ClassLoader l : loaders) { + Enumeration en = l.getResources(resource); + while (en.hasMoreElements()) { + u = en.nextElement(); + if (u.toExternalForm().matches("^.*emul.*rt\\.jar.*$")) { + return u.openStream(); + } + } + } + if (u != null) { + if (u.toExternalForm().contains("rt.jar")) { + LOG.log(Level.WARNING, "Fallback to bootclasspath for {0}", u); + } + return u.openStream(); + } + throw new IOException("Can't find " + resource); + } + } + + private static class Page extends HttpHandler { + final String resource; + private final String[] args; + private final Res res; + + public Page(Res res, String resource, String... args) { + this.res = res; + this.resource = resource; + this.args = args.length == 0 ? new String[] { "$0" } : args; + } + + @Override + public void service(Request request, Response response) throws Exception { + String r = computePage(request); + if (r.startsWith("/")) { + r = r.substring(1); + } + String[] replace = {}; + if (r.endsWith(".html")) { + response.setContentType("text/html"); + LOG.info("Content type text/html"); + replace = args; + } + if (r.endsWith(".xhtml")) { + response.setContentType("application/xhtml+xml"); + LOG.info("Content type application/xhtml+xml"); + replace = args; + } + OutputStream os = response.getOutputStream(); + try { + InputStream is = res.get(r); + copyStream(is, os, request.getRequestURL().toString(), replace); + } catch (IOException ex) { + response.setDetailMessage(ex.getLocalizedMessage()); + response.setError(); + response.setStatus(404); + } + } + + protected String computePage(Request request) { + String r = resource; + if (r == null) { + r = request.getHttpHandlerPath(); + } + return r; + } + } + + private static class SubTree extends Page { + + public SubTree(Res res, String resource, String... args) { + super(res, resource, args); + } + + @Override + protected String computePage(Request request) { + return resource + request.getHttpHandlerPath(); + } + + + } + + private class VMAndPages extends StaticHttpHandler { + private String vmResource; + + public VMAndPages() { + super((String[]) null); + } + + @Override + public void service(Request request, Response response) throws Exception { + if (request.getRequestURI().equals(vmResource)) { + response.setCharacterEncoding("UTF-8"); + response.setContentType("text/javascript"); + StringBuilder sb = new StringBuilder(); + generateBck2BrwsrJS(sb, BaseHTTPLauncher.this.resources); + response.getWriter().write(sb.toString()); + } else { + super.service(request, response); + } + } + + private void registerVM(String vmResource) { + this.vmResource = vmResource; + } + } + + private static class Classes extends HttpHandler { + private final Res loader; + + public Classes(Res loader) { + this.loader = loader; + } + + @Override + public void service(Request request, Response response) throws Exception { + String res = request.getHttpHandlerPath(); + if (res.startsWith("/")) { + res = res.substring(1); + } + InputStream is = null; + try { + is = loader.get(res); + response.setContentType("text/javascript"); + Writer w = response.getWriter(); + w.append("["); + for (int i = 0;; i++) { + int b = is.read(); + if (b == -1) { + break; + } + if (i > 0) { + w.append(", "); + } + if (i % 20 == 0) { + w.write("\n"); + } + if (b > 127) { + b = b - 256; + } + w.append(Integer.toString(b)); + } + w.append("\n]"); + } catch (IOException ex) { + 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 8264f07b1f46 -r f73c1a0234fb launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/FXBrwsrLauncher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/FXBrwsrLauncher.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,186 @@ +/** + * 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; + +import java.io.File; +import org.apidesign.bck2brwsr.launcher.fximpl.FXBrwsr; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.JarURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import java.util.concurrent.Executors; +import java.util.jar.Manifest; +import java.util.logging.Level; +import java.util.logging.Logger; +import javafx.application.Platform; +import org.apidesign.bck2brwsr.launcher.fximpl.JVMBridge; +import org.openide.util.Exceptions; + +/** + * + * @author Jaroslav Tulach + */ +final class FXBrwsrLauncher extends BaseHTTPLauncher { + private static final Logger LOG = Logger.getLogger(FXBrwsrLauncher.class.getName()); + static { + try { + Method m = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + m.setAccessible(true); + URL l = new URL("file://" + System.getProperty("java.home") + "/lib/jfxrt.jar"); + LOG.log(Level.INFO, "url : {0}", l); + m.invoke(ClassLoader.getSystemClassLoader(), l); + } catch (Exception ex) { + throw new LinkageError("Can't add jfxrt.jar on the classpath", ex); + } + } + + public FXBrwsrLauncher(String ignore) { + super(null); + } + + @Override + protected Object[] showBrwsr(final URI url) throws IOException { + try { + LOG.log(Level.INFO, "showBrwsr for {0}", url); + JVMBridge.registerClassLoaders(loaders()); + LOG.info("About to launch WebView"); + Executors.newSingleThreadExecutor().submit(new Runnable() { + @Override + public void run() { + LOG.log(Level.INFO, "In FX thread. Launching!"); + try { + 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); + } catch (Throwable ex) { + LOG.log(Level.WARNING, "Error launching Web View", ex); + } + } + }); + } catch (Throwable ex) { + LOG.log(Level.WARNING, "Can't open WebView", ex); + } + return null; + } + + @Override + void generateBck2BrwsrJS(StringBuilder sb, Res loader) throws IOException { + sb.append("(function() {\n" + + " var impl = this.bck2brwsr;\n" + + " this.bck2brwsr = function() { return impl; };\n"); + sb.append("})(window);\n"); + JVMBridge.onBck2BrwsrLoad(); + } + + @Override + public void close() throws IOException { + super.close(); + Platform.exit(); + } + + String harnessResource() { + return "org/apidesign/bck2brwsr/launcher/fximpl/harness.xhtml"; + } + + public static void main(String... args) throws IOException { + String startPage = null; + + final ClassLoader cl = FXBrwsrLauncher.class.getClassLoader(); + URL[] manifestURL = { null }; + startPage = findStartPage(cl, startPage, manifestURL); + if (startPage == null) { + throw new NullPointerException("Can't find StartPage tag in manifests!"); + } + + File dir = new File("."); + if (manifestURL[0].getProtocol().equals("jar")) { + try { + dir = new File( + ((JarURLConnection)manifestURL[0].openConnection()).getJarFileURL().toURI() + ).getParentFile(); + } catch (URISyntaxException ex) { + LOG.log(Level.WARNING, "Can't find root directory", ex); + } + } + + Launcher.showDir("fxbrwsr", dir, cl, startPage); + } + + private static String findStartPage( + final ClassLoader cl, String startPage, URL[] startURL + ) throws IOException { + Enumeration en = cl.getResources("META-INF/MANIFEST.MF"); + while (en.hasMoreElements()) { + URL url = en.nextElement(); + Manifest mf; + 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) { + startPage = sp; + if (startURL != null) { + startURL[0] = url; + } + break; + } + } + return startPage; + } + + private static boolean isDebugged() { + try { + return isDebuggedImpl(); + } catch (LinkageError e) { + return false; + } + } + + private static boolean isDebuggedImpl() { + java.lang.management.RuntimeMXBean runtime; + runtime = java.lang.management.ManagementFactory.getRuntimeMXBean(); + List args = runtime.getInputArguments(); + if (args.contains("-Xdebug")) { // NOI18N + return true; + } + return false; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fx/LauncherFX.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fx/LauncherFX.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,32 @@ +/** + * 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.fx; + +import org.apidesign.bck2brwsr.launcher.Launcher; + +/** This is a launcher for the Bck2Brwsr + * project that is using JavaFX's WebView to display the browser inside + * real Java virtual machine. Use {@link Launcher} methods to access this + * functionality via public, supported methods. + * + * @author Jaroslav Tulach + */ +public final class LauncherFX { + private LauncherFX() { + } +} diff -r 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,379 @@ +/** + * 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) { + 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); + } + } + + 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(); + } + + 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 8264f07b1f46 -r f73c1a0234fb launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,362 @@ +/** + * 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.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.util.Enumeration; +import netscape.javascript.JSObject; +import org.apidesign.bck2brwsr.core.JavaScriptBody; + +/** + * + * @author Jaroslav Tulach + */ +public final class Console { + public Console() { + } + + @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() {} + + private static Object textArea; + private static Object statusArea; + + private static void log(String newText) { + if (textArea == null) { + return; + } + String attr = "value"; + setAttr(textArea, attr, getAttr(textArea, attr) + "\n" + newText); + setAttr(textArea, "scrollTop", getAttr(textArea, "scrollHeight")); + } + + private static void beginTest(Case c) { + Object[] arr = new Object[2]; + beginTest(c.getClassName() + "." + c.getMethodName(), c, arr); + textArea = arr[0]; + statusArea = arr[1]; + } + + private static void finishTest(Case c, Object res) { + if ("null".equals(res)) { + setAttr(statusArea, "innerHTML", "Success"); + } else { + setAttr(statusArea, "innerHTML", "Result " + res); + } + statusArea = null; + textArea = null; + } + + @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');" + + "span.innerHTML = test + ' - ';\n" + + "var details = window.document.createElement('a');\n" + + "details.innerHTML = 'Details';\n" + + "details.href = '#';\n" + + "var p = window.document.createElement('p');\n" + + "var status = window.document.createElement('a');\n" + + "status.innerHTML = 'running';" + + "details.onclick = function() { li.appendChild(p); li.removeChild(details); status.innerHTML = 'Run Again'; status.href = '#'; };\n" + + "status.onclick = function() { c.again(arr); }\n" + + "var pre = window.document.createElement('textarea');\n" + + "pre.cols = 100;" + + "pre.rows = 10;" + + "li.appendChild(span);\n" + + "li.appendChild(status);\n" + + "var span = window.document.createElement('span');" + + "span.innerHTML = ' ';\n" + + "li.appendChild(span);\n" + + "li.appendChild(details);\n" + + "p.appendChild(pre);\n" + + "ul.appendChild(li);\n" + + "arr[0] = pre;\n" + + "arr[1] = status;\n" + ) + private static native void beginTest(String test, Case c, Object[] arr); + + @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 {\n" + + " arr[0] = this.responseText;\n" + + " 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); + } + + public void harness(String url) throws IOException { + log("Connecting to " + url); + Request r = new Request(url); + } + + private static class Request implements Runnable { + private final String[] arr = { null }; + private final String url; + private Case c; + private int retries; + + private Request(String url) throws IOException { + this.url = url; + loadText(url, new Run(this), arr); + } + private Request(String url, String u) throws IOException { + this.url = url; + loadText(u, new Run(this), arr); + } + + @Override + public void run() { + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + try { + if (c == null) { + String data = arr[0]; + + if (data == null) { + log("Some error exiting"); + closeWindow(); + return; + } + + if (data.isEmpty()) { + log("No data, exiting"); + closeWindow(); + return; + } + + c = Case.parseData(data); + beginTest(c); + log("Got \"" + data + "\""); + } else { + log("Processing \"" + arr[0] + "\" for " + retries + " time"); + } + Object result = retries++ >= 10 ? "java.lang.InterruptedException:timeout" : c.runTest(); + finishTest(c, result); + + String u = url + "?request=" + c.getRequestId() + "&result=" + result; + new Request(url, u); + } catch (Exception ex) { + if (ex instanceof InterruptedException) { + log("Re-scheduling in 100ms"); + schedule(new Run(this), 100); + return; + } + log(ex.getClass().getName() + ":" + ex.getMessage()); + } + } + } + + private static String encodeURL(String r) throws UnsupportedEncodingException { + final String SPECIAL = "%$&+,/:;=?@"; + StringBuilder sb = new StringBuilder(); + byte[] utf8 = r.getBytes("UTF-8"); + for (int i = 0; i < utf8.length; i++) { + int ch = utf8[i] & 0xff; + if (ch < 32 || ch > 127 || SPECIAL.indexOf(ch) >= 0) { + final String numbers = "0" + Integer.toHexString(ch); + sb.append("%").append(numbers.substring(numbers.length() - 2)); + } else { + if (ch == 32) { + sb.append("+"); + } else { + sb.append((char)ch); + } + } + } + return sb.toString(); + } + + static String invoke(String clazz, String method) throws + ClassNotFoundException, InvocationTargetException, IllegalAccessException, + InstantiationException, InterruptedException { + final Object r = new Case(null).invokeMethod(clazz, method); + return r == null ? "null" : r.toString().toString(); + } + + /** Helper method that inspects the classpath and loads given resource + * (usually a class file). Used while running tests in Rhino. + * + * @param name resource name to find + * @return the array of bytes in the given resource + * @throws IOException I/O in case something goes wrong + */ + public static byte[] read(String name) throws IOException { + URL u = null; + Enumeration en = Console.class.getClassLoader().getResources(name); + while (en.hasMoreElements()) { + u = en.nextElement(); + } + if (u == null) { + throw new IOException("Can't find " + name); + } + InputStream is = null; + try { + is = u.openStream(); + byte[] arr; + arr = new byte[is.available()]; + int offset = 0; + while (offset < arr.length) { + int len = is.read(arr, offset, arr.length - offset); + if (len == -1) { + throw new IOException("Can't read " + name); + } + offset += len; + } + return arr; + } finally { + if (is != null) is.close(); + } + } + + private static void turnAssetionStatusOn() { + } + + @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; + private Object inst; + + private Case(Object data) { + this.data = data; + } + + public static Case parseData(String s) { + return new Case(toJSON(s)); + } + + public String getMethodName() { + return (String) value("methodName", data); + } + + public String getClassName() { + return (String) value("className", data); + } + + public int getRequestId() { + Object v = value("request", data); + if (v instanceof Number) { + return ((Number)v).intValue(); + } + return Integer.parseInt(v.toString()); + } + + public String getHtmlFragment() { + return (String) value("html", data); + } + + void again(Object[] arr) { + try { + textArea = arr[0]; + statusArea = arr[1]; + setAttr(textArea, "value", ""); + runTest(); + } catch (Exception ex) { + log(ex.getClass().getName() + ":" + ex.getMessage()); + } + } + + private Object runTest() throws IllegalAccessException, + IllegalArgumentException, ClassNotFoundException, UnsupportedEncodingException, + InvocationTargetException, InstantiationException, InterruptedException { + if (this.getHtmlFragment() != null) { + setAttr("bck2brwsr.fragment", "innerHTML", this.getHtmlFragment()); + } + log("Invoking " + this.getClassName() + '.' + this.getMethodName() + " as request: " + this.getRequestId()); + Object result = invokeMethod(this.getClassName(), this.getMethodName()); + setAttr("bck2brwsr.fragment", "innerHTML", ""); + log("Result: " + result); + result = encodeURL("" + result); + log("Sending back: ...?request=" + this.getRequestId() + "&result=" + result); + return result; + } + + private Object invokeMethod(String clazz, String method) + throws ClassNotFoundException, InvocationTargetException, + InterruptedException, IllegalAccessException, IllegalArgumentException, + InstantiationException { + Method found = null; + Class c = Class.forName(clazz); + for (Method m : c.getMethods()) { + if (m.getName().equals(method)) { + found = m; + } + } + Object res; + if (found != null) { + try { + if ((found.getModifiers() & Modifier.STATIC) != 0) { + res = found.invoke(null); + } else { + if (inst == null) { + inst = c.newInstance(); + } + res = found.invoke(inst); + } + } catch (Throwable ex) { + if (ex instanceof InvocationTargetException) { + ex = ((InvocationTargetException) ex).getTargetException(); + } + if (ex instanceof InterruptedException) { + throw (InterruptedException)ex; + } + res = ex.getClass().getName() + ":" + ex.getMessage(); + } + } else { + res = "Can't find method " + method + " in " + clazz; + } + return res; + } + + @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); + } + } + + static { + turnAssetionStatusOn(); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/FXBrwsr.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/FXBrwsr.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,177 @@ +/** + * 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.List; +import java.util.TooManyListenersException; +import java.util.logging.Level; +import java.util.logging.Logger; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Button; +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; +import javafx.scene.web.WebEvent; +import javafx.scene.web.WebView; +import javafx.stage.Modality; +import javafx.stage.Stage; +import netscape.javascript.JSObject; + +/** + * Demonstrates a WebView object accessing a web page. + * + * @see javafx.scene.web.WebView + * @see javafx.scene.web.WebEngine + */ +public class FXBrwsr extends Application { + private static final Logger LOG = Logger.getLogger(FXBrwsr.class.getName()); + + @Override + public void start(Stage primaryStage) throws Exception { + WebView view = new WebView(); + WebController wc = new WebController(view, getParameters().getUnnamed()); + + FXInspect.initialize(view.getEngine()); + + 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); + root.setTop( toolbar ); + } + root.setCenter(hbox); + + Scene scene = new Scene(root, 800, 600); + + primaryStage.setTitle( "Device Emulator" ); + primaryStage.setScene( scene ); + primaryStage.show(); + } + + /** + * Create a resizable WebView pane + */ + private static class WebController { + private final JVMBridge bridge; + + public WebController(WebView view, List params) { + this.bridge = new JVMBridge(view.getEngine()); + LOG.log(Level.INFO, "Initializing WebView with {0}", params); + final WebEngine eng = view.getEngine(); + try { + JVMBridge.addBck2BrwsrLoad(new InitBck2Brwsr(eng)); + } catch (TooManyListenersException ex) { + LOG.log(Level.SEVERE, null, ex); + } + + if (params.size() > 0) { + LOG.log(Level.INFO, "loading page {0}", params.get(0)); + eng.load(params.get(0)); + LOG.fine("back from load"); + } + eng.setOnAlert(new EventHandler>() { + @Override + public void handle(WebEvent t) { + final Stage dialogStage = new Stage(); + dialogStage.initModality(Modality.WINDOW_MODAL); + dialogStage.setTitle("Warning"); + final Button button = new Button("Close"); + final Text text = new Text(t.getData()); + + VBox box = new VBox(); + box.setAlignment(Pos.CENTER); + box.setSpacing(10); + box.setPadding(new Insets(10)); + box.getChildren().addAll(text, button); + + dialogStage.setScene(new Scene(box)); + + button.setCancelButton(true); + button.setOnAction(new EventHandler() { + @Override + public void handle(ActionEvent t) { + dialogStage.close(); + } + }); + + dialogStage.centerOnScreen(); + dialogStage.showAndWait(); + } + }); + } + + boolean initBck2Brwsr(WebEngine webEngine) { + JSObject jsobj = (JSObject) webEngine.executeScript("window"); + LOG.log(Level.FINE, "window: {0}", jsobj); + Object prev = jsobj.getMember("bck2brwsr"); + if ("undefined".equals(prev)) { + jsobj.setMember("bck2brwsr", bridge); + return true; + } + return false; + } + + private class InitBck2Brwsr implements ChangeListener, Runnable { + private final WebEngine eng; + + public InitBck2Brwsr(WebEngine eng) { + this.eng = eng; + } + + @Override + public synchronized void changed(ObservableValue ov, Void t, Void t1) { + Platform.runLater(this); + try { + wait(); + } catch (InterruptedException ex) { + LOG.log(Level.SEVERE, null, ex); + } + } + + @Override + public synchronized void run() { + initBck2Brwsr(eng); + notifyAll(); + } + } + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/FXInspect.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/FXInspect.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,112 @@ +/** + * 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.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.logging.Level; +import java.util.logging.Logger; +import javafx.application.Platform; +import javafx.scene.web.WebEngine; +import javafx.util.Callback; +import org.openide.util.Exceptions; + +/** + * + * @author Jaroslav Tulach + */ +final class FXInspect implements Runnable { + private static final Logger LOG = Logger.getLogger(FXInspect.class.getName()); + + + private final WebEngine engine; + private final ObjectInputStream input; + + private FXInspect(WebEngine engine, int port) throws IOException { + this.engine = engine; + + Socket socket = new Socket(InetAddress.getByName(null), port); + ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); + this.input = new ObjectInputStream(socket.getInputStream()); + initializeDebugger(output); + } + + static boolean initialize(WebEngine engine) { + final int inspectPort = Integer.getInteger("netbeans.inspect.port", -1); // NOI18N + if (inspectPort != -1) { + try { + FXInspect inspector = new FXInspect(engine, inspectPort); + Thread t = new Thread(inspector, "FX<->NetBeans Inspector"); + t.start(); + return true; + } catch (IOException ex) { + LOG.log(Level.INFO, "Cannot connect to NetBeans IDE to port " + inspectPort, ex); // NOI18N + } + } + return false; + } + + private void initializeDebugger(final ObjectOutputStream output) { + Platform.runLater(new Runnable() { + @Override + public void run() { + Debugger debugger = engine.impl_getDebugger(); + debugger.setEnabled(true); + debugger.setMessageCallback(new Callback() { + @Override + public Void call(String message) { + try { + byte[] bytes = message.getBytes(StandardCharsets.UTF_8); + output.writeInt(bytes.length); + output.write(bytes); + output.flush(); + } catch (IOException ioex) { + ioex.printStackTrace(); + } + return null; + } + }); + } + }); + } + + @Override + public void run() { + try { + while (true) { + int length = input.readInt(); + byte[] bytes = new byte[length]; + input.readFully(bytes); + final String message = new String(bytes, StandardCharsets.UTF_8); + Platform.runLater(new Runnable() { + @Override + public void run() { + engine.impl_getDebugger().sendMessage(message); + } + }); + } + } catch (IOException ioex) { + ioex.printStackTrace(); + } + } +} diff -r 8264f07b1f46 -r f73c1a0234fb launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/JVMBridge.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/JVMBridge.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,152 @@ +/** + * 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.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 final WebPresenter presenter; + + private static ClassLoader[] ldrs; + private static ChangeListener onBck2BrwsrLoad; + + JVMBridge(WebEngine eng) { + this.engine = eng; + final ClassLoader p = JVMBridge.class.getClassLoader().getParent(); + this.presenter = new WebPresenter(); + this.cl = FnUtils.newLoader(presenter, presenter, p); + } + + public static void registerClassLoaders(ClassLoader[] loaders) { + ldrs = loaders.clone(); + } + + public static void addBck2BrwsrLoad(ChangeListener l) throws TooManyListenersException { + if (onBck2BrwsrLoad != null) { + throw new TooManyListenersException(); + } + onBck2BrwsrLoad = l; + } + + public static void onBck2BrwsrLoad() { + ChangeListener l = onBck2BrwsrLoad; + if (l != null) { + l.changed(null, null, null); + } + } + + public Class loadClass(String name) throws ClassNotFoundException { + FnUtils.currentPresenter(presenter); + return Class.forName(name, true, cl); + } + + private final class WebPresenter 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); + } + } + } + + @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(this, x); + } + + @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; + + private JSFn(WebPresenter cl, JSObject fn) { + super(cl); + 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 8264f07b1f46 -r f73c1a0234fb launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Run.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Run.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,35 @@ +/** + * 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; + +/** + * + * @author Jaroslav Tulach + */ +public final class Run implements Runnable { + private final Runnable r; + Run(Runnable r) { + this.r = r; + } + + @Override + public void run() { + r.run(); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb 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 8264f07b1f46 -r f73c1a0234fb 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 8264f07b1f46 -r f73c1a0234fb 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 8264f07b1f46 -r f73c1a0234fb 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 8264f07b1f46 -r f73c1a0234fb launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/harness.xhtml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/main/resources/org/apidesign/bck2brwsr/launcher/fximpl/harness.xhtml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,52 @@ + + + + + + Bck2Brwsr Harness + + + + + +

Bck2Brwsr Execution Harness

+ +
    +
+ +
+ + + + diff -r 8264f07b1f46 -r f73c1a0234fb 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 8264f07b1f46 -r f73c1a0234fb 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 8264f07b1f46 -r f73c1a0234fb 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 8264f07b1f46 -r f73c1a0234fb 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 8264f07b1f46 -r f73c1a0234fb 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 8264f07b1f46 -r f73c1a0234fb 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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,182 @@ +/** + * 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.BeforeMethod; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public class JsClassLoaderTest { + private static ClassLoader loader; + private static Class methodClass; + private static Fn.Presenter presenter; + + 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(this) { + @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. + } + } + + Fr fr = new Fr(); + presenter = fr; + loader = FnUtils.newLoader(fr, fr, parent); + methodClass = loader.loadClass(JsMethods.class.getName()); + } + + @BeforeMethod public void registerPresenter() { + FnUtils.currentPresenter(presenter); + } + + @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 8264f07b1f46 -r f73c1a0234fb 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 Mon Oct 07 14:20:58 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 8264f07b1f46 -r f73c1a0234fb launcher/http/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/http/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,60 @@ + + + 4.0.0 + + org.apidesign.bck2brwsr + launcher-pom + 0.9-SNAPSHOT + + org.apidesign.bck2brwsr + launcher.http + 0.9-SNAPSHOT + Bck2Brwsr Launcher + 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 + + org.apidesign.bck2brwsr.launcher.b2b + false + + + + + + UTF-8 + + + + ${project.groupId} + launcher + ${project.version} + + + ${project.groupId} + launcher.fx + ${project.version} + + + org.glassfish.grizzly + grizzly-http-server + ${grizzly.version} + + + ${project.groupId} + vm4brwsr + ${project.version} + + + diff -r 8264f07b1f46 -r f73c1a0234fb launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,70 @@ +/** + * 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; + +import java.io.IOException; +import java.io.InputStream; +import org.apidesign.vm4brwsr.Bck2Brwsr; + +/** + * Lightweight server to launch Bck2Brwsr applications and tests. + * Supports execution in native browser as well as Java's internal + * execution engine. + */ +final class Bck2BrwsrLauncher extends BaseHTTPLauncher { + + public Bck2BrwsrLauncher(String cmd) { + super(cmd); + } + + @Override + String harnessResource() { + return "org/apidesign/bck2brwsr/launcher/harness.xhtml"; + } + + @Override + void generateBck2BrwsrJS(StringBuilder sb, final Res loader) throws IOException { + class R implements Bck2Brwsr.Resources { + @Override + public InputStream get(String resource) throws IOException { + return loader.get(resource); + } + } + + Bck2Brwsr.generate(sb, new R()); + sb.append( + "(function WrapperVM(global) {" + + " function ldCls(res) {\n" + + " var request = new XMLHttpRequest();\n" + + " request.open('GET', '/classes/' + res, false);\n" + + " request.send();\n" + + " if (request.status !== 200) return null;\n" + + " var arr = eval('(' + request.responseText + ')');\n" + + " return arr;\n" + + " }\n" + + " var prevvm = global.bck2brwsr;\n" + + " global.bck2brwsr = function() {\n" + + " var args = Array.prototype.slice.apply(arguments);\n" + + " args.unshift(ldCls);\n" + + " return prevvm.apply(null, args);\n" + + " };\n" + + "})(this);\n" + ); + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/JSLauncher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/JSLauncher.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,135 @@ +/** + * 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; + +import org.apidesign.bck2brwsr.launcher.impl.Console; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apidesign.vm4brwsr.Bck2Brwsr; + +/** + * Tests execution in Java's internal scripting engine. + */ +final class JSLauncher extends Launcher { + private static final Logger LOG = Logger.getLogger(JSLauncher.class.getName()); + private Set loaders = new LinkedHashSet<>(); + private final Res resources = new Res(); + private Invocable code; + private StringBuilder codeSeq; + private Object console; + + JSLauncher() { + addClassLoader(Bck2Brwsr.class.getClassLoader()); + } + + @Override InvocationContext runMethod(InvocationContext mi) { + loaders.add(mi.clazz.getClassLoader()); + try { + long time = System.currentTimeMillis(); + LOG.log(Level.FINE, "Invoking {0}.{1}", new Object[]{mi.clazz.getName(), mi.methodName}); + String res = code.invokeMethod( + console, + "invoke__Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2", + mi.clazz.getName(), mi.methodName).toString(); + time = System.currentTimeMillis() - time; + LOG.log(Level.FINE, "Resut of {0}.{1} = {2} in {3} ms", new Object[]{mi.clazz.getName(), mi.methodName, res, time}); + mi.result(res, null); + } catch (ScriptException | NoSuchMethodException ex) { + mi.result(null, ex); + } + return mi; + } + + public void addClassLoader(ClassLoader url) { + this.loaders.add(url); + } + + @Override + public void initialize() throws IOException { + try { + initRhino(); + } catch (Exception ex) { + if (ex instanceof IOException) { + throw (IOException)ex; + } + if (ex instanceof RuntimeException) { + throw (RuntimeException)ex; + } + throw new IOException(ex); + } + } + + private void initRhino() throws IOException, ScriptException, NoSuchMethodException { + StringBuilder sb = new StringBuilder(); + Bck2Brwsr.generate(sb, new Res()); + + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine mach = sem.getEngineByExtension("js"); + + sb.append( + "\nvar vm = new bck2brwsr(org.apidesign.bck2brwsr.launcher.impl.Console.read);" + + "\nfunction initVM() { return vm; };" + + "\n"); + + Object res = mach.eval(sb.toString()); + if (!(mach instanceof Invocable)) { + throw new IOException("It is invocable object: " + res); + } + code = (Invocable) mach; + codeSeq = sb; + + Object vm = code.invokeFunction("initVM"); + console = code.invokeMethod(vm, "loadClass", Console.class.getName()); + } + + @Override + public void shutdown() throws IOException { + } + + @Override + public String toString() { + return codeSeq.toString(); + } + + private class Res implements Bck2Brwsr.Resources { + @Override + public InputStream get(String resource) throws IOException { + for (ClassLoader l : loaders) { + URL u = null; + Enumeration en = l.getResources(resource); + while (en.hasMoreElements()) { + u = en.nextElement(); + } + if (u != null) { + return u.openStream(); + } + } + throw new IOException("Can't find " + resource); + } + } +} diff -r 8264f07b1f46 -r f73c1a0234fb launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/b2b/LauncherBck2Brwsr.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/b2b/LauncherBck2Brwsr.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,33 @@ +/** + * 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.b2b; + +import java.awt.Desktop; +import org.apidesign.bck2brwsr.launcher.Launcher; + +/** This is a launcher for the Bck2Brwsr + * project that is using {@link Desktop} (or {@link Process}) to display the + * external browser in separate process. Use {@link Launcher} methods to access this + * functionality via public, supported methods. + * + * @author Jaroslav Tulach + */ +public final class LauncherBck2Brwsr { + private LauncherBck2Brwsr() { + } +} diff -r 8264f07b1f46 -r f73c1a0234fb launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,356 @@ +/** + * 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.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.util.Enumeration; +import org.apidesign.bck2brwsr.core.JavaScriptBody; + +/** + * + * @author Jaroslav Tulach + */ +public class Console { + private Console() { + } + static { + turnAssetionStatusOn(); + } + + @JavaScriptBody(args = {"id", "attr"}, body = + "return window.document.getElementById(id)[attr].toString();") + private static native Object getAttr(String id, String attr); + @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); + + @JavaScriptBody(args = {}, body = "return; window.close();") + private static native void closeWindow(); + + private static Object textArea; + private static Object statusArea; + + private static void log(String newText) { + if (textArea == null) { + return; + } + String attr = "value"; + setAttr(textArea, attr, getAttr(textArea, attr) + "\n" + newText); + setAttr(textArea, "scrollTop", getAttr(textArea, "scrollHeight")); + } + + private static void beginTest(Case c) { + Object[] arr = new Object[2]; + beginTest(c.getClassName() + "." + c.getMethodName(), c, arr); + textArea = arr[0]; + statusArea = arr[1]; + } + + private static void finishTest(Case c, Object res) { + if ("null".equals(res)) { + setAttr(statusArea, "innerHTML", "Success"); + } else { + setAttr(statusArea, "innerHTML", "Result " + res); + } + statusArea = null; + textArea = null; + } + + @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');" + + "span.innerHTML = test + ' - ';\n" + + "var details = window.document.createElement('a');\n" + + "details.innerHTML = 'Details';\n" + + "details.href = '#';\n" + + "var p = window.document.createElement('p');\n" + + "var status = window.document.createElement('a');\n" + + "status.innerHTML = 'running';" + + "details.onclick = function() { li.appendChild(p); li.removeChild(details); status.innerHTML = 'Run Again'; status.href = '#'; };\n" + + "status.onclick = function() { c.again__V_3Ljava_lang_Object_2(arr); }\n" + + "var pre = window.document.createElement('textarea');\n" + + "pre.cols = 100;" + + "pre.rows = 10;" + + "li.appendChild(span);\n" + + "li.appendChild(status);\n" + + "var span = window.document.createElement('span');" + + "span.innerHTML = ' ';\n" + + "li.appendChild(span);\n" + + "li.appendChild(details);\n" + + "p.appendChild(pre);\n" + + "ul.appendChild(li);\n" + + "arr[0] = pre;\n" + + "arr[1] = status;\n" + ) + private static native void beginTest(String test, Case c, Object[] arr); + + @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" + + " arr[0] = this.responseText;\n" + + " callback.run__V();\n" + + "};" + + "request.send();" + ) + private static native void loadText(String url, Runnable callback, String[] arr) throws IOException; + + public static void harness(String url) throws IOException { + log("Connecting to " + url); + Request r = new Request(url); + } + + private static class Request implements Runnable { + private final String[] arr = { null }; + private final String url; + private Case c; + private int retries; + + private Request(String url) throws IOException { + this.url = url; + loadText(url, this, arr); + } + private Request(String url, String u) throws IOException { + this.url = url; + loadText(u, this, arr); + } + + @Override + public void run() { + try { + if (c == null) { + String data = arr[0]; + + if (data == null) { + log("Some error exiting"); + closeWindow(); + return; + } + + if (data.isEmpty()) { + log("No data, exiting"); + closeWindow(); + return; + } + + c = Case.parseData(data); + beginTest(c); + log("Got \"" + data + "\""); + } else { + log("Processing \"" + arr[0] + "\" for " + retries + " time"); + } + Object result = retries++ >= 10 ? "java.lang.InterruptedException:timeout" : c.runTest(); + finishTest(c, result); + + String u = url + "?request=" + c.getRequestId() + "&result=" + result; + new Request(url, u); + } catch (Exception ex) { + if (ex instanceof InterruptedException) { + log("Re-scheduling in 100ms"); + schedule(this, 100); + return; + } + log(ex.getClass().getName() + ":" + ex.getMessage()); + } + } + } + + private static String encodeURL(String r) throws UnsupportedEncodingException { + final String SPECIAL = "%$&+,/:;=?@"; + StringBuilder sb = new StringBuilder(); + byte[] utf8 = r.getBytes("UTF-8"); + for (int i = 0; i < utf8.length; i++) { + int ch = utf8[i] & 0xff; + if (ch < 32 || ch > 127 || SPECIAL.indexOf(ch) >= 0) { + final String numbers = "0" + Integer.toHexString(ch); + sb.append("%").append(numbers.substring(numbers.length() - 2)); + } else { + if (ch == 32) { + sb.append("+"); + } else { + sb.append((char)ch); + } + } + } + return sb.toString(); + } + + static String invoke(String clazz, String method) throws + ClassNotFoundException, InvocationTargetException, IllegalAccessException, + InstantiationException, InterruptedException { + final Object r = new Case(null).invokeMethod(clazz, method); + return r == null ? "null" : r.toString().toString(); + } + + /** Helper method that inspects the classpath and loads given resource + * (usually a class file). Used while running tests in Rhino. + * + * @param name resource name to find + * @return the array of bytes in the given resource + * @throws IOException I/O in case something goes wrong + */ + public static byte[] read(String name) throws IOException { + URL u = null; + Enumeration en = Console.class.getClassLoader().getResources(name); + while (en.hasMoreElements()) { + u = en.nextElement(); + } + if (u == null) { + throw new IOException("Can't find " + name); + } + try (InputStream is = u.openStream()) { + byte[] arr; + arr = new byte[is.available()]; + int offset = 0; + while (offset < arr.length) { + int len = is.read(arr, offset, arr.length - offset); + if (len == -1) { + throw new IOException("Can't read " + name); + } + offset += len; + } + return arr; + } + } + + @JavaScriptBody(args = {}, body = "vm.desiredAssertionStatus = true;") + private static void turnAssetionStatusOn() { + } + + @JavaScriptBody(args = {"r", "time"}, body = + "return window.setTimeout(function() { r.run__V(); }, time);") + private static native Object schedule(Runnable r, int time); + + private static final class Case { + private final Object data; + private Object inst; + + private Case(Object data) { + this.data = data; + } + + public static Case parseData(String s) { + return new Case(toJSON(s)); + } + + public String getMethodName() { + return value("methodName", data); + } + + public String getClassName() { + return value("className", data); + } + + public String getRequestId() { + return value("request", data); + } + + public String getHtmlFragment() { + return value("html", data); + } + + void again(Object[] arr) { + try { + textArea = arr[0]; + statusArea = arr[1]; + setAttr(textArea, "value", ""); + runTest(); + } catch (Exception ex) { + log(ex.getClass().getName() + ":" + ex.getMessage()); + } + } + + private Object runTest() throws IllegalAccessException, + IllegalArgumentException, ClassNotFoundException, UnsupportedEncodingException, + InvocationTargetException, InstantiationException, InterruptedException { + if (this.getHtmlFragment() != null) { + setAttr("bck2brwsr.fragment", "innerHTML", this.getHtmlFragment()); + } + log("Invoking " + this.getClassName() + '.' + this.getMethodName() + " as request: " + this.getRequestId()); + Object result = invokeMethod(this.getClassName(), this.getMethodName()); + setAttr("bck2brwsr.fragment", "innerHTML", ""); + log("Result: " + result); + result = encodeURL("" + result); + log("Sending back: ...?request=" + this.getRequestId() + "&result=" + result); + return result; + } + + private Object invokeMethod(String clazz, String method) + throws ClassNotFoundException, InvocationTargetException, + InterruptedException, IllegalAccessException, IllegalArgumentException, + InstantiationException { + Method found = null; + Class c = Class.forName(clazz); + for (Method m : c.getMethods()) { + if (m.getName().equals(method)) { + found = m; + } + } + Object res; + if (found != null) { + try { + if ((found.getModifiers() & Modifier.STATIC) != 0) { + res = found.invoke(null); + } else { + if (inst == null) { + inst = c.newInstance(); + } + res = found.invoke(inst); + } + } catch (Throwable ex) { + if (ex instanceof InvocationTargetException) { + ex = ((InvocationTargetException) ex).getTargetException(); + } + if (ex instanceof InterruptedException) { + throw (InterruptedException)ex; + } + res = ex.getClass().getName() + ":" + ex.getMessage(); + } + } else { + res = "Can't find method " + method + " in " + clazz; + } + return res; + } + + @JavaScriptBody(args = "s", body = "return eval('(' + s + ')');") + private static native Object toJSON(String s); + + @JavaScriptBody(args = {"p", "d"}, body = + "var v = d[p];\n" + + "if (typeof v === 'undefined') return null;\n" + + "return v.toString();" + ) + private static native String value(String p, Object d); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb launcher/http/src/main/resources/org/apidesign/bck2brwsr/launcher/harness.xhtml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/http/src/main/resources/org/apidesign/bck2brwsr/launcher/harness.xhtml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,43 @@ + + + + + + Bck2Brwsr Harness + + + + + +

Bck2Brwsr Execution Harness

+ +
    +
+ +
+ + + + diff -r 8264f07b1f46 -r f73c1a0234fb launcher/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,22 @@ + + + 4.0.0 + + bck2brwsr + org.apidesign + 0.9-SNAPSHOT + + org.apidesign.bck2brwsr + launcher-pom + 0.9-SNAPSHOT + pom + Launchers + + 2.3.3 + + + api + http + fx + + \ No newline at end of file diff -r 8264f07b1f46 -r f73c1a0234fb pom.xml --- a/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -3,7 +3,7 @@ 4.0.0 org.apidesign bck2brwsr - 0.3-SNAPSHOT + 0.9-SNAPSHOT pom Back 2 Browser @@ -11,11 +11,19 @@ jvnet-parent 3 + + UTF-8 + RELEASE73 + COPYING + 0.6 + dew javaquery benchmarks ide + ko + launcher rt @@ -33,6 +41,7 @@ scm:hg:http://source.apidesign.org/hg/bck2brwsr scm:hg:https://source.apidesign.org/hg/bck2brwsr http://source.apidesign.org/hg/bck2brwsr + HEAD @@ -78,14 +87,26 @@ * .*/** rt/emul/*/src/main/** - rt/javap/** - rt/mojo/src/main/resources/archetype-resources/** - rt/vmtest/src/test/resources/** + rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java + rt/archetype/src/main/resources/archetype-resources/** + 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 + + maven-release-plugin + 2.4 + + forked-path + false + -Pjvnet-release -Pgpg + release-${releaseVersion} + + @@ -94,6 +115,23 @@ maven-surefire-plugin 2.13 + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9 + + true + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.7 + 1.7 + + @@ -114,19 +152,137 @@ org.netbeans.api org-netbeans-modules-classfile - RELEASE72 + ${netbeans.version} jar org.netbeans.api org-openide-util-lookup - RELEASE72 + ${netbeans.version} compile jar + + org.netbeans.api + org-netbeans-api-annotations-common + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-java-source + ${netbeans.version} + + + org.netbeans.api + org-netbeans-libs-javacapi + ${netbeans.version} + + + org.netbeans.api + org-netbeans-spi-java-hints + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-parsing-api + ${netbeans.version} + + + org.netbeans.api + org-netbeans-spi-editor-hints + ${netbeans.version} + + + org.netbeans.api + org-openide-util + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-java-lexer + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-lexer + ${netbeans.version} + + + org.netbeans.api + org-netbeans-modules-java-hints-test + ${netbeans.version} + + + org.netbeans.api + org-netbeans-libs-junit4 + ${netbeans.version} + + + org.netbeans.modules + org-netbeans-lib-nbjavac + ${netbeans.version} + + + org.netbeans.modules + org-netbeans-modules-web-browser-api + ${netbeans.version} + + + org-netbeans-core + org.netbeans.modules + + + org-netbeans-core-multiview + org.netbeans.api + + + org-netbeans-libs-lucene + org.netbeans.api + + + org-netbeans-modules-diff + org.netbeans.api + + + org-netbeans-modules-editor-fold + org.netbeans.api + + + org-netbeans-modules-editor-guards + org.netbeans.api + + + + + org-netbeans-modules-projectapi + org.netbeans.api + jar + ${netbeans.version} + - - COPYING - - \ No newline at end of file + + + 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 8264f07b1f46 -r f73c1a0234fb rt/core/pom.xml --- a/rt/core/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/core/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -1,15 +1,14 @@ - + 4.0.0 org.apidesign.bck2brwsr rt - 0.3-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr core - 0.3-SNAPSHOT + 0.9-SNAPSHOT Bck2Brwsr Native Annotations http://maven.apache.org @@ -23,6 +22,14 @@ 1.7 + + org.apache.maven.plugins + maven-javadoc-plugin + + org.apidesign.bck2brwsr.core.impl + false + + diff -r 8264f07b1f46 -r f73c1a0234fb rt/core/src/main/java/org/apidesign/bck2brwsr/core/ExtraJavaScript.java --- a/rt/core/src/main/java/org/apidesign/bck2brwsr/core/ExtraJavaScript.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/core/src/main/java/org/apidesign/bck2brwsr/core/ExtraJavaScript.java Mon Oct 07 14:20:58 2013 +0200 @@ -22,14 +22,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** +/** A way to include pre-made JavaScript scripts and libraries. + * The {@link #resource()} is loaded into the JavaScript VM and its object + * can be referenced from the class annotated by this annotation. * * @author Jaroslav Tulach */ @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface ExtraJavaScript { - /** location of a script to load */ + /** fully qualified location of a script to load. Start the path with slash. */ String resource(); /** should the class file still be processed or not? */ boolean processByteCode() default true; diff -r 8264f07b1f46 -r f73c1a0234fb rt/core/src/main/java/org/apidesign/bck2brwsr/core/JavaScriptBody.java --- a/rt/core/src/main/java/org/apidesign/bck2brwsr/core/JavaScriptBody.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/core/src/main/java/org/apidesign/bck2brwsr/core/JavaScriptBody.java Mon Oct 07 14:20:58 2013 +0200 @@ -22,22 +22,17 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** Put this method on a method in case it should have a special - * body in the JavaScript. +/** Put this annotation on a method to provide its special implementation + * in JavaScript. This is a way to define native methods that + * interact with the surrounding environment. * * @author Jaroslav Tulach */ @Retention(RetentionPolicy.CLASS) @Target({ ElementType.METHOD, ElementType.CONSTRUCTOR }) public @interface JavaScriptBody { - /** Names of parameters for the method. - * - * + /** Names of parameters for the method generated method that can + * be referenced from {@link #body()}. * * @return array of the names of parameters for the method * in JavaScript @@ -46,6 +41,12 @@ /** The actual body of the method in JavaScript. This string will be * put into generated header (ends with '{') and footer (ends with '}'). + * The body can reference provided arguments. In case of non-static + * instance method it may reference this. It can also + * call methods and access fields - if + * proper mangling + * is used. Methods that return some value should end with return + * statement. */ public String body(); } diff -r 8264f07b1f46 -r f73c1a0234fb rt/core/src/main/java/org/apidesign/bck2brwsr/core/JavaScriptOnly.java --- a/rt/core/src/main/java/org/apidesign/bck2brwsr/core/JavaScriptOnly.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/core/src/main/java/org/apidesign/bck2brwsr/core/JavaScriptOnly.java Mon Oct 07 14:20:58 2013 +0200 @@ -22,16 +22,17 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** Don't include given field or method in generated JavaScript. Rather - * generate completely independent JavaScript code. +/** Don't include given method in the generated JavaScript at all. Rather + * generate completely independent JavaScript code consisting of + * "{@link #name()}" = "{@link #value()}". * * @author Jaroslav Tulach */ @Retention(RetentionPolicy.CLASS) -@Target({ ElementType.METHOD, ElementType.FIELD }) +@Target({ ElementType.METHOD }) public @interface JavaScriptOnly { /** name of the variable to assign given value to */ String name() default ""; - /** value to assign to given field */ + /** value to assign to the {@link #name()} variable */ String value() default ""; } diff -r 8264f07b1f46 -r f73c1a0234fb rt/core/src/main/java/org/apidesign/bck2brwsr/core/JavaScriptPrototype.java --- a/rt/core/src/main/java/org/apidesign/bck2brwsr/core/JavaScriptPrototype.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/core/src/main/java/org/apidesign/bck2brwsr/core/JavaScriptPrototype.java Mon Oct 07 14:20:58 2013 +0200 @@ -22,7 +22,14 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** Controls how JavaScript inheritance should be handled. +/** Influence the inheritance of your class when converted to JavaScript. + * Sometimes one does not want + * to mimic the Java hierarchy, but modify it a bit. For example it makes + * sense to treat every (JavaScript) string literal as {@link String}. + * One can do it by making {@link String} subclass JavaScript String + * and use String.prototype as a container for all {@link String} + * methods. + * * @author Jaroslav Tulach */ @Retention(RetentionPolicy.CLASS) diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/brwsrtest/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/brwsrtest/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,52 @@ + + + 4.0.0 + + org.apidesign.bck2brwsr + emul.pom + 0.9-SNAPSHOT + + org.apidesign.bck2brwsr + brwsrtest + 0.9-SNAPSHOT + Tests Inside Real Browser + http://maven.apache.org + + UTF-8 + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.7 + + true + + + + org.apache.maven.plugins + maven-surefire-plugin + + + brwsr + + + + + + + + ${project.groupId} + vmtest + ${project.version} + test + + + ${project.groupId} + launcher.http + ${project.version} + provided + + + diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/brwsrtest/src/test/java/org/apidesign/bck2brwsr/brwsrtest/BooleanTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/brwsrtest/src/test/java/org/apidesign/bck2brwsr/brwsrtest/BooleanTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,49 @@ +/** + * 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.brwsrtest; + +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class BooleanTest { + @JavaScriptBody(args = { "tr" }, body = "return tr ? true : false;") + private static native Object trueFalse(boolean tr); + + @BrwsrTest public void isTrueInstanceOfBoolean() { + Object t = trueFalse(true); + assert t instanceof Boolean : "Should be boolean: " + t; + assert ((boolean)t) : "and is true"; + } + + @BrwsrTest public void isFalseInstanceOfBoolean() { + Object t = trueFalse(false); + assert t instanceof Boolean : "Should be boolean: " + t; + assert !((boolean)t) : "and is false: " + t; + } + + @Factory public static Object[] create() { + return VMTest.create(BooleanTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/brwsrtest/src/test/java/org/apidesign/bck2brwsr/brwsrtest/DoubleBitsTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/brwsrtest/src/test/java/org/apidesign/bck2brwsr/brwsrtest/DoubleBitsTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,42 @@ +/** + * 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.brwsrtest; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class DoubleBitsTest { + + @Compare public String doubleToBits() { + long val = Double.doubleToLongBits(333.456); + return Long.toString(val); + } + + @Compare public int floatToBits() { + return Float.floatToIntBits(333.456f); + } + + @Factory public static Object[] create() { + return VMTest.create(DoubleBitsTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/pom.xml --- a/rt/emul/compact/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/compact/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -1,15 +1,14 @@ - + 4.0.0 org.apidesign.bck2brwsr emul.pom - 0.3-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr emul - 0.3-SNAPSHOT + 0.9-SNAPSHOT Bck2Brwsr API Profile http://maven.apache.org @@ -29,6 +28,18 @@ test + ${project.groupId} + launcher.http + ${project.version} + test + + + com.oracle + javafx + + + + org.netbeans.api org-openide-util-lookup test @@ -48,6 +59,15 @@ 1.7 + + org.apache.maven.plugins + maven-javadoc-plugin + + org.apidesign.bck2brwsr.emul.* + false + true + + maven-assembly-plugin 2.4 diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/io/BufferedWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/BufferedWriter.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,271 @@ +/* + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + + +/** + * Writes text to a character-output stream, buffering characters so as to + * provide for the efficient writing of single characters, arrays, and strings. + * + *

The buffer size may be specified, or the default size may be accepted. + * The default is large enough for most purposes. + * + *

A newLine() method is provided, which uses the platform's own notion of + * line separator as defined by the system property line.separator. + * Not all platforms use the newline character ('\n') to terminate lines. + * Calling this method to terminate each output line is therefore preferred to + * writing a newline character directly. + * + *

In general, a Writer sends its output immediately to the underlying + * character or byte stream. Unless prompt output is required, it is advisable + * to wrap a BufferedWriter around any Writer whose write() operations may be + * costly, such as FileWriters and OutputStreamWriters. For example, + * + *

+ * PrintWriter out
+ *   = new PrintWriter(new BufferedWriter(new FileWriter("foo.out")));
+ * 
+ * + * will buffer the PrintWriter's output to the file. Without buffering, each + * invocation of a print() method would cause characters to be converted into + * bytes that would then be written immediately to the file, which can be very + * inefficient. + * + * @see PrintWriter + * @see FileWriter + * @see OutputStreamWriter + * @see java.nio.file.Files#newBufferedWriter + * + * @author Mark Reinhold + * @since JDK1.1 + */ + +public class BufferedWriter extends Writer { + + private Writer out; + + private char cb[]; + private int nChars, nextChar; + + private static int defaultCharBufferSize = 8192; + + /** + * Line separator string. This is the value of the line.separator + * property at the moment that the stream was created. + */ + private String lineSeparator; + + /** + * Creates a buffered character-output stream that uses a default-sized + * output buffer. + * + * @param out A Writer + */ + public BufferedWriter(Writer out) { + this(out, defaultCharBufferSize); + } + + /** + * Creates a new buffered character-output stream that uses an output + * buffer of the given size. + * + * @param out A Writer + * @param sz Output-buffer size, a positive integer + * + * @exception IllegalArgumentException If sz is <= 0 + */ + public BufferedWriter(Writer out, int sz) { + super(out); + if (sz <= 0) + throw new IllegalArgumentException("Buffer size <= 0"); + this.out = out; + cb = new char[sz]; + nChars = sz; + nextChar = 0; + + lineSeparator = "\n"; + } + + /** Checks to make sure that the stream has not been closed */ + private void ensureOpen() throws IOException { + if (out == null) + throw new IOException("Stream closed"); + } + + /** + * Flushes the output buffer to the underlying character stream, without + * flushing the stream itself. This method is non-private only so that it + * may be invoked by PrintStream. + */ + void flushBuffer() throws IOException { + synchronized (lock) { + ensureOpen(); + if (nextChar == 0) + return; + out.write(cb, 0, nextChar); + nextChar = 0; + } + } + + /** + * Writes a single character. + * + * @exception IOException If an I/O error occurs + */ + public void write(int c) throws IOException { + synchronized (lock) { + ensureOpen(); + if (nextChar >= nChars) + flushBuffer(); + cb[nextChar++] = (char) c; + } + } + + /** + * Our own little min method, to avoid loading java.lang.Math if we've run + * out of file descriptors and we're trying to print a stack trace. + */ + private int min(int a, int b) { + if (a < b) return a; + return b; + } + + /** + * Writes a portion of an array of characters. + * + *

Ordinarily this method stores characters from the given array into + * this stream's buffer, flushing the buffer to the underlying stream as + * needed. If the requested length is at least as large as the buffer, + * however, then this method will flush the buffer and write the characters + * directly to the underlying stream. Thus redundant + * BufferedWriters will not copy data unnecessarily. + * + * @param cbuf A character array + * @param off Offset from which to start reading characters + * @param len Number of characters to write + * + * @exception IOException If an I/O error occurs + */ + public void write(char cbuf[], int off, int len) throws IOException { + synchronized (lock) { + ensureOpen(); + if ((off < 0) || (off > cbuf.length) || (len < 0) || + ((off + len) > cbuf.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + + if (len >= nChars) { + /* If the request length exceeds the size of the output buffer, + flush the buffer and then write the data directly. In this + way buffered streams will cascade harmlessly. */ + flushBuffer(); + out.write(cbuf, off, len); + return; + } + + int b = off, t = off + len; + while (b < t) { + int d = min(nChars - nextChar, t - b); + System.arraycopy(cbuf, b, cb, nextChar, d); + b += d; + nextChar += d; + if (nextChar >= nChars) + flushBuffer(); + } + } + } + + /** + * Writes a portion of a String. + * + *

If the value of the len parameter is negative then no + * characters are written. This is contrary to the specification of this + * method in the {@linkplain java.io.Writer#write(java.lang.String,int,int) + * superclass}, which requires that an {@link IndexOutOfBoundsException} be + * thrown. + * + * @param s String to be written + * @param off Offset from which to start reading characters + * @param len Number of characters to be written + * + * @exception IOException If an I/O error occurs + */ + public void write(String s, int off, int len) throws IOException { + synchronized (lock) { + ensureOpen(); + + int b = off, t = off + len; + while (b < t) { + int d = min(nChars - nextChar, t - b); + s.getChars(b, b + d, cb, nextChar); + b += d; + nextChar += d; + if (nextChar >= nChars) + flushBuffer(); + } + } + } + + /** + * Writes a line separator. The line separator string is defined by the + * system property line.separator, and is not necessarily a single + * newline ('\n') character. + * + * @exception IOException If an I/O error occurs + */ + public void newLine() throws IOException { + write(lineSeparator); + } + + /** + * Flushes the stream. + * + * @exception IOException If an I/O error occurs + */ + public void flush() throws IOException { + synchronized (lock) { + flushBuffer(); + out.flush(); + } + } + + public void close() throws IOException { + synchronized (lock) { + if (out == null) { + return; + } + try { + flushBuffer(); + } finally { + out.close(); + out = null; + cb = null; + } + } + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/io/File.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/File.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,1927 @@ +/* + * Copyright (c) 1994, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +import java.net.URI; +import java.net.URL; +import java.net.MalformedURLException; +import java.net.URISyntaxException; + +/** + * An abstract representation of file and directory pathnames. + * + *

User interfaces and operating systems use system-dependent pathname + * strings to name files and directories. This class presents an + * abstract, system-independent view of hierarchical pathnames. An + * abstract pathname has two components: + * + *

    + *
  1. An optional system-dependent prefix string, + * such as a disk-drive specifier, "/" for the UNIX root + * directory, or "\\\\" for a Microsoft Windows UNC pathname, and + *
  2. A sequence of zero or more string names. + *
+ * + * The first name in an abstract pathname may be a directory name or, in the + * case of Microsoft Windows UNC pathnames, a hostname. Each subsequent name + * in an abstract pathname denotes a directory; the last name may denote + * either a directory or a file. The empty abstract pathname has no + * prefix and an empty name sequence. + * + *

The conversion of a pathname string to or from an abstract pathname is + * inherently system-dependent. When an abstract pathname is converted into a + * pathname string, each name is separated from the next by a single copy of + * the default separator character. The default name-separator + * character is defined by the system property file.separator, and + * is made available in the public static fields {@link + * #separator} and {@link #separatorChar} of this class. + * When a pathname string is converted into an abstract pathname, the names + * within it may be separated by the default name-separator character or by any + * other name-separator character that is supported by the underlying system. + * + *

A pathname, whether abstract or in string form, may be either + * absolute or relative. An absolute pathname is complete in + * that no other information is required in order to locate the file that it + * denotes. A relative pathname, in contrast, must be interpreted in terms of + * information taken from some other pathname. By default the classes in the + * java.io package always resolve relative pathnames against the + * current user directory. This directory is named by the system property + * user.dir, and is typically the directory in which the Java + * virtual machine was invoked. + * + *

The parent of an abstract pathname may be obtained by invoking + * the {@link #getParent} method of this class and consists of the pathname's + * prefix and each name in the pathname's name sequence except for the last. + * Each directory's absolute pathname is an ancestor of any File + * object with an absolute abstract pathname which begins with the directory's + * absolute pathname. For example, the directory denoted by the abstract + * pathname "/usr" is an ancestor of the directory denoted by the + * pathname "/usr/local/bin". + * + *

The prefix concept is used to handle root directories on UNIX platforms, + * and drive specifiers, root directories and UNC pathnames on Microsoft Windows platforms, + * as follows: + * + *

    + * + *
  • For UNIX platforms, the prefix of an absolute pathname is always + * "/". Relative pathnames have no prefix. The abstract pathname + * denoting the root directory has the prefix "/" and an empty + * name sequence. + * + *
  • For Microsoft Windows platforms, the prefix of a pathname that contains a drive + * specifier consists of the drive letter followed by ":" and + * possibly followed by "\\" if the pathname is absolute. The + * prefix of a UNC pathname is "\\\\"; the hostname and the share + * name are the first two names in the name sequence. A relative pathname that + * does not specify a drive has no prefix. + * + *
+ * + *

Instances of this class may or may not denote an actual file-system + * object such as a file or a directory. If it does denote such an object + * then that object resides in a partition. A partition is an + * operating system-specific portion of storage for a file system. A single + * storage device (e.g. a physical disk-drive, flash memory, CD-ROM) may + * contain multiple partitions. The object, if any, will reside on the + * partition named by some ancestor of the absolute + * form of this pathname. + * + *

A file system may implement restrictions to certain operations on the + * actual file-system object, such as reading, writing, and executing. These + * restrictions are collectively known as access permissions. The file + * system may have multiple sets of access permissions on a single object. + * For example, one set may apply to the object's owner, and another + * may apply to all other users. The access permissions on an object may + * cause some methods in this class to fail. + * + *

Instances of the File class are immutable; that is, once + * created, the abstract pathname represented by a File object + * will never change. + * + *

Interoperability with {@code java.nio.file} package

+ * + *

The {@code java.nio.file} + * package defines interfaces and classes for the Java virtual machine to access + * files, file attributes, and file systems. This API may be used to overcome + * many of the limitations of the {@code java.io.File} class. + * The {@link #toPath toPath} method may be used to obtain a {@link + * Path} that uses the abstract path represented by a {@code File} object to + * locate a file. The resulting {@code Path} may be used with the {@link + * java.nio.file.Files} class to provide more efficient and extensive access to + * additional file operations, file attributes, and I/O exceptions to help + * diagnose errors when an operation on a file fails. + * + * @author unascribed + * @since JDK1.0 + */ + +public class File + implements Serializable, Comparable +{ + + /** + * The FileSystem object representing the platform's local file system. + */ + static private FileSystem fs = new FileSystem(); + private static class FileSystem { + + private char getSeparator() { + return '/'; + } + + private String resolve(String path, String child) { + return path + '/' + child; + } + + private String normalize(String pathname) { + return pathname; + } + + private int prefixLength(String path) { + return 0; + } + + private String getDefaultParent() { + return "/"; + } + + private String fromURIPath(String p) { + return p; + } + + private boolean isAbsolute(File aThis) { + return aThis.getPath().startsWith("/"); + } + + private int compare(File one, File two) { + return one.getPath().compareTo(two.getPath()); + } + + private int hashCode(File aThis) { + return aThis.getPath().hashCode(); + } + + private char getPathSeparator() { + return ':'; + } + + } + + /** + * This abstract pathname's normalized pathname string. A normalized + * pathname string uses the default name-separator character and does not + * contain any duplicate or redundant separators. + * + * @serial + */ + private String path; + + /** + * The length of this abstract pathname's prefix, or zero if it has no + * prefix. + */ + private transient int prefixLength; + + /** + * Returns the length of this abstract pathname's prefix. + * For use by FileSystem classes. + */ + int getPrefixLength() { + return prefixLength; + } + + /** + * The system-dependent default name-separator character. This field is + * initialized to contain the first character of the value of the system + * property file.separator. On UNIX systems the value of this + * field is '/'; on Microsoft Windows systems it is '\\'. + * + * @see java.lang.System#getProperty(java.lang.String) + */ + public static final char separatorChar = fs.getSeparator(); + + /** + * The system-dependent default name-separator character, represented as a + * string for convenience. This string contains a single character, namely + * {@link #separatorChar}. + */ + public static final String separator = "" + separatorChar; + + /** + * The system-dependent path-separator character. This field is + * initialized to contain the first character of the value of the system + * property path.separator. This character is used to + * separate filenames in a sequence of files given as a path list. + * On UNIX systems, this character is ':'; on Microsoft Windows systems it + * is ';'. + * + * @see java.lang.System#getProperty(java.lang.String) + */ + public static final char pathSeparatorChar = fs.getPathSeparator(); + + /** + * The system-dependent path-separator character, represented as a string + * for convenience. This string contains a single character, namely + * {@link #pathSeparatorChar}. + */ + public static final String pathSeparator = "" + pathSeparatorChar; + + + /* -- Constructors -- */ + + /** + * Internal constructor for already-normalized pathname strings. + */ + private File(String pathname, int prefixLength) { + this.path = pathname; + this.prefixLength = prefixLength; + } + + /** + * Internal constructor for already-normalized pathname strings. + * The parameter order is used to disambiguate this method from the + * public(File, String) constructor. + */ + private File(String child, File parent) { + assert parent.path != null; + assert (!parent.path.equals("")); + this.path = fs.resolve(parent.path, child); + this.prefixLength = parent.prefixLength; + } + + /** + * Creates a new File instance by converting the given + * pathname string into an abstract pathname. If the given string is + * the empty string, then the result is the empty abstract pathname. + * + * @param pathname A pathname string + * @throws NullPointerException + * If the pathname argument is null + */ + public File(String pathname) { + if (pathname == null) { + throw new NullPointerException(); + } + this.path = fs.normalize(pathname); + this.prefixLength = fs.prefixLength(this.path); + } + + /* Note: The two-argument File constructors do not interpret an empty + parent abstract pathname as the current user directory. An empty parent + instead causes the child to be resolved against the system-dependent + directory defined by the FileSystem.getDefaultParent method. On Unix + this default is "/", while on Microsoft Windows it is "\\". This is required for + compatibility with the original behavior of this class. */ + + /** + * Creates a new File instance from a parent pathname string + * and a child pathname string. + * + *

If parent is null then the new + * File instance is created as if by invoking the + * single-argument File constructor on the given + * child pathname string. + * + *

Otherwise the parent pathname string is taken to denote + * a directory, and the child pathname string is taken to + * denote either a directory or a file. If the child pathname + * string is absolute then it is converted into a relative pathname in a + * system-dependent way. If parent is the empty string then + * the new File instance is created by converting + * child into an abstract pathname and resolving the result + * against a system-dependent default directory. Otherwise each pathname + * string is converted into an abstract pathname and the child abstract + * pathname is resolved against the parent. + * + * @param parent The parent pathname string + * @param child The child pathname string + * @throws NullPointerException + * If child is null + */ + public File(String parent, String child) { + if (child == null) { + throw new NullPointerException(); + } + if (parent != null) { + if (parent.equals("")) { + this.path = fs.resolve(fs.getDefaultParent(), + fs.normalize(child)); + } else { + this.path = fs.resolve(fs.normalize(parent), + fs.normalize(child)); + } + } else { + this.path = fs.normalize(child); + } + this.prefixLength = fs.prefixLength(this.path); + } + + /** + * Creates a new File instance from a parent abstract + * pathname and a child pathname string. + * + *

If parent is null then the new + * File instance is created as if by invoking the + * single-argument File constructor on the given + * child pathname string. + * + *

Otherwise the parent abstract pathname is taken to + * denote a directory, and the child pathname string is taken + * to denote either a directory or a file. If the child + * pathname string is absolute then it is converted into a relative + * pathname in a system-dependent way. If parent is the empty + * abstract pathname then the new File instance is created by + * converting child into an abstract pathname and resolving + * the result against a system-dependent default directory. Otherwise each + * pathname string is converted into an abstract pathname and the child + * abstract pathname is resolved against the parent. + * + * @param parent The parent abstract pathname + * @param child The child pathname string + * @throws NullPointerException + * If child is null + */ + public File(File parent, String child) { + if (child == null) { + throw new NullPointerException(); + } + if (parent != null) { + if (parent.path.equals("")) { + this.path = fs.resolve(fs.getDefaultParent(), + fs.normalize(child)); + } else { + this.path = fs.resolve(parent.path, + fs.normalize(child)); + } + } else { + this.path = fs.normalize(child); + } + this.prefixLength = fs.prefixLength(this.path); + } + + /** + * Creates a new File instance by converting the given + * file: URI into an abstract pathname. + * + *

The exact form of a file: URI is system-dependent, hence + * the transformation performed by this constructor is also + * system-dependent. + * + *

For a given abstract pathname f it is guaranteed that + * + *

+ * new File( f.{@link #toURI() toURI}()).equals( f.{@link #getAbsoluteFile() getAbsoluteFile}()) + *
+ * + * so long as the original abstract pathname, the URI, and the new abstract + * pathname are all created in (possibly different invocations of) the same + * Java virtual machine. This relationship typically does not hold, + * however, when a file: URI that is created in a virtual machine + * on one operating system is converted into an abstract pathname in a + * virtual machine on a different operating system. + * + * @param uri + * An absolute, hierarchical URI with a scheme equal to + * "file", a non-empty path component, and undefined + * authority, query, and fragment components + * + * @throws NullPointerException + * If uri is null + * + * @throws IllegalArgumentException + * If the preconditions on the parameter do not hold + * + * @see #toURI() + * @see java.net.URI + * @since 1.4 + */ + public File(URI uri) { + + // Check our many preconditions + if (!uri.isAbsolute()) + throw new IllegalArgumentException("URI is not absolute"); + if (uri.isOpaque()) + throw new IllegalArgumentException("URI is not hierarchical"); + String scheme = uri.getScheme(); + if ((scheme == null) || !scheme.equalsIgnoreCase("file")) + throw new IllegalArgumentException("URI scheme is not \"file\""); + if (uri.getAuthority() != null) + throw new IllegalArgumentException("URI has an authority component"); + if (uri.getFragment() != null) + throw new IllegalArgumentException("URI has a fragment component"); + if (uri.getQuery() != null) + throw new IllegalArgumentException("URI has a query component"); + String p = uri.getPath(); + if (p.equals("")) + throw new IllegalArgumentException("URI path component is empty"); + + // Okay, now initialize + p = fs.fromURIPath(p); + if (File.separatorChar != '/') + p = p.replace('/', File.separatorChar); + this.path = fs.normalize(p); + this.prefixLength = fs.prefixLength(this.path); + } + + + /* -- Path-component accessors -- */ + + /** + * Returns the name of the file or directory denoted by this abstract + * pathname. This is just the last name in the pathname's name + * sequence. If the pathname's name sequence is empty, then the empty + * string is returned. + * + * @return The name of the file or directory denoted by this abstract + * pathname, or the empty string if this pathname's name sequence + * is empty + */ + public String getName() { + int index = path.lastIndexOf(separatorChar); + if (index < prefixLength) return path.substring(prefixLength); + return path.substring(index + 1); + } + + /** + * Returns the pathname string of this abstract pathname's parent, or + * null if this pathname does not name a parent directory. + * + *

The parent of an abstract pathname consists of the + * pathname's prefix, if any, and each name in the pathname's name + * sequence except for the last. If the name sequence is empty then + * the pathname does not name a parent directory. + * + * @return The pathname string of the parent directory named by this + * abstract pathname, or null if this pathname + * does not name a parent + */ + public String getParent() { + int index = path.lastIndexOf(separatorChar); + if (index < prefixLength) { + if ((prefixLength > 0) && (path.length() > prefixLength)) + return path.substring(0, prefixLength); + return null; + } + return path.substring(0, index); + } + + /** + * Returns the abstract pathname of this abstract pathname's parent, + * or null if this pathname does not name a parent + * directory. + * + *

The parent of an abstract pathname consists of the + * pathname's prefix, if any, and each name in the pathname's name + * sequence except for the last. If the name sequence is empty then + * the pathname does not name a parent directory. + * + * @return The abstract pathname of the parent directory named by this + * abstract pathname, or null if this pathname + * does not name a parent + * + * @since 1.2 + */ + public File getParentFile() { + String p = this.getParent(); + if (p == null) return null; + return new File(p, this.prefixLength); + } + + /** + * Converts this abstract pathname into a pathname string. The resulting + * string uses the {@link #separator default name-separator character} to + * separate the names in the name sequence. + * + * @return The string form of this abstract pathname + */ + public String getPath() { + return path; + } + + + /* -- Path operations -- */ + + /** + * Tests whether this abstract pathname is absolute. The definition of + * absolute pathname is system dependent. On UNIX systems, a pathname is + * absolute if its prefix is "/". On Microsoft Windows systems, a + * pathname is absolute if its prefix is a drive specifier followed by + * "\\", or if its prefix is "\\\\". + * + * @return true if this abstract pathname is absolute, + * false otherwise + */ + public boolean isAbsolute() { + return fs.isAbsolute(this); + } + + /** + * Returns the absolute pathname string of this abstract pathname. + * + *

If this abstract pathname is already absolute, then the pathname + * string is simply returned as if by the {@link #getPath} + * method. If this abstract pathname is the empty abstract pathname then + * the pathname string of the current user directory, which is named by the + * system property user.dir, is returned. Otherwise this + * pathname is resolved in a system-dependent way. On UNIX systems, a + * relative pathname is made absolute by resolving it against the current + * user directory. On Microsoft Windows systems, a relative pathname is made absolute + * by resolving it against the current directory of the drive named by the + * pathname, if any; if not, it is resolved against the current user + * directory. + * + * @return The absolute pathname string denoting the same file or + * directory as this abstract pathname + * + * @throws SecurityException + * If a required system property value cannot be accessed. + * + * @see java.io.File#isAbsolute() + */ + public String getAbsolutePath() { + throw new SecurityException(); + } + + /** + * Returns the absolute form of this abstract pathname. Equivalent to + * new File(this.{@link #getAbsolutePath}). + * + * @return The absolute abstract pathname denoting the same file or + * directory as this abstract pathname + * + * @throws SecurityException + * If a required system property value cannot be accessed. + * + * @since 1.2 + */ + public File getAbsoluteFile() { + String absPath = getAbsolutePath(); + return new File(absPath, fs.prefixLength(absPath)); + } + + /** + * Returns the canonical pathname string of this abstract pathname. + * + *

A canonical pathname is both absolute and unique. The precise + * definition of canonical form is system-dependent. This method first + * converts this pathname to absolute form if necessary, as if by invoking the + * {@link #getAbsolutePath} method, and then maps it to its unique form in a + * system-dependent way. This typically involves removing redundant names + * such as "." and ".." from the pathname, resolving + * symbolic links (on UNIX platforms), and converting drive letters to a + * standard case (on Microsoft Windows platforms). + * + *

Every pathname that denotes an existing file or directory has a + * unique canonical form. Every pathname that denotes a nonexistent file + * or directory also has a unique canonical form. The canonical form of + * the pathname of a nonexistent file or directory may be different from + * the canonical form of the same pathname after the file or directory is + * created. Similarly, the canonical form of the pathname of an existing + * file or directory may be different from the canonical form of the same + * pathname after the file or directory is deleted. + * + * @return The canonical pathname string denoting the same file or + * directory as this abstract pathname + * + * @throws IOException + * If an I/O error occurs, which is possible because the + * construction of the canonical pathname may require + * filesystem queries + * + * @throws SecurityException + * If a required system property value cannot be accessed, or + * if a security manager exists and its {@link + * java.lang.SecurityManager#checkRead} method denies + * read access to the file + * + * @since JDK1.1 + * @see Path#toRealPath + */ + public String getCanonicalPath() throws IOException { + throw new SecurityException(); + } + + /** + * Returns the canonical form of this abstract pathname. Equivalent to + * new File(this.{@link #getCanonicalPath}). + * + * @return The canonical pathname string denoting the same file or + * directory as this abstract pathname + * + * @throws IOException + * If an I/O error occurs, which is possible because the + * construction of the canonical pathname may require + * filesystem queries + * + * @throws SecurityException + * If a required system property value cannot be accessed, or + * if a security manager exists and its {@link + * java.lang.SecurityManager#checkRead} method denies + * read access to the file + * + * @since 1.2 + * @see Path#toRealPath + */ + public File getCanonicalFile() throws IOException { + String canonPath = getCanonicalPath(); + return new File(canonPath, fs.prefixLength(canonPath)); + } + + private static String slashify(String path, boolean isDirectory) { + String p = path; + if (File.separatorChar != '/') + p = p.replace(File.separatorChar, '/'); + if (!p.startsWith("/")) + p = "/" + p; + if (!p.endsWith("/") && isDirectory) + p = p + "/"; + return p; + } + + /** + * Converts this abstract pathname into a file: URL. The + * exact form of the URL is system-dependent. If it can be determined that + * the file denoted by this abstract pathname is a directory, then the + * resulting URL will end with a slash. + * + * @return A URL object representing the equivalent file URL + * + * @throws MalformedURLException + * If the path cannot be parsed as a URL + * + * @see #toURI() + * @see java.net.URI + * @see java.net.URI#toURL() + * @see java.net.URL + * @since 1.2 + * + * @deprecated This method does not automatically escape characters that + * are illegal in URLs. It is recommended that new code convert an + * abstract pathname into a URL by first converting it into a URI, via the + * {@link #toURI() toURI} method, and then converting the URI into a URL + * via the {@link java.net.URI#toURL() URI.toURL} method. + */ + @Deprecated + public URL toURL() throws MalformedURLException { + return new URL("file", "", slashify(getAbsolutePath(), isDirectory())); + } + + /** + * Constructs a file: URI that represents this abstract pathname. + * + *

The exact form of the URI is system-dependent. If it can be + * determined that the file denoted by this abstract pathname is a + * directory, then the resulting URI will end with a slash. + * + *

For a given abstract pathname f, it is guaranteed that + * + *

+ * new {@link #File(java.net.URI) File}( f.toURI()).equals( f.{@link #getAbsoluteFile() getAbsoluteFile}()) + *
+ * + * so long as the original abstract pathname, the URI, and the new abstract + * pathname are all created in (possibly different invocations of) the same + * Java virtual machine. Due to the system-dependent nature of abstract + * pathnames, however, this relationship typically does not hold when a + * file: URI that is created in a virtual machine on one operating + * system is converted into an abstract pathname in a virtual machine on a + * different operating system. + * + *

Note that when this abstract pathname represents a UNC pathname then + * all components of the UNC (including the server name component) are encoded + * in the {@code URI} path. The authority component is undefined, meaning + * that it is represented as {@code null}. The {@link Path} class defines the + * {@link Path#toUri toUri} method to encode the server name in the authority + * component of the resulting {@code URI}. The {@link #toPath toPath} method + * may be used to obtain a {@code Path} representing this abstract pathname. + * + * @return An absolute, hierarchical URI with a scheme equal to + * "file", a path representing this abstract pathname, + * and undefined authority, query, and fragment components + * @throws SecurityException If a required system property value cannot + * be accessed. + * + * @see #File(java.net.URI) + * @see java.net.URI + * @see java.net.URI#toURL() + * @since 1.4 + */ + public URI toURI() { + try { + File f = getAbsoluteFile(); + String sp = slashify(f.getPath(), f.isDirectory()); + if (sp.startsWith("//")) + sp = "//" + sp; + return new URI("file", null, sp, null); + } catch (URISyntaxException x) { + throw new Error(x); // Can't happen + } + } + + + /* -- Attribute accessors -- */ + + /** + * Tests whether the application can read the file denoted by this + * abstract pathname. + * + * @return true if and only if the file specified by this + * abstract pathname exists and can be read by the + * application; false otherwise + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkRead(java.lang.String)} + * method denies read access to the file + */ + public boolean canRead() { + throw new SecurityException(); + } + + /** + * Tests whether the application can modify the file denoted by this + * abstract pathname. + * + * @return true if and only if the file system actually + * contains a file denoted by this abstract pathname and + * the application is allowed to write to the file; + * false otherwise. + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method denies write access to the file + */ + public boolean canWrite() { + throw new SecurityException(); + } + + /** + * Tests whether the file or directory denoted by this abstract pathname + * exists. + * + * @return true if and only if the file or directory denoted + * by this abstract pathname exists; false otherwise + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkRead(java.lang.String)} + * method denies read access to the file or directory + */ + public boolean exists() { + throw new SecurityException(); + } + + /** + * Tests whether the file denoted by this abstract pathname is a + * directory. + * + *

Where it is required to distinguish an I/O exception from the case + * that the file is not a directory, or where several attributes of the + * same file are required at the same time, then the {@link + * java.nio.file.Files#readAttributes(Path,Class,LinkOption[]) + * Files.readAttributes} method may be used. + * + * @return true if and only if the file denoted by this + * abstract pathname exists and is a directory; + * false otherwise + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkRead(java.lang.String)} + * method denies read access to the file + */ + public boolean isDirectory() { + throw new SecurityException(); + } + + /** + * Tests whether the file denoted by this abstract pathname is a normal + * file. A file is normal if it is not a directory and, in + * addition, satisfies other system-dependent criteria. Any non-directory + * file created by a Java application is guaranteed to be a normal file. + * + *

Where it is required to distinguish an I/O exception from the case + * that the file is not a normal file, or where several attributes of the + * same file are required at the same time, then the {@link + * java.nio.file.Files#readAttributes(Path,Class,LinkOption[]) + * Files.readAttributes} method may be used. + * + * @return true if and only if the file denoted by this + * abstract pathname exists and is a normal file; + * false otherwise + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkRead(java.lang.String)} + * method denies read access to the file + */ + public boolean isFile() { + throw new SecurityException(); + } + + /** + * Tests whether the file named by this abstract pathname is a hidden + * file. The exact definition of hidden is system-dependent. On + * UNIX systems, a file is considered to be hidden if its name begins with + * a period character ('.'). On Microsoft Windows systems, a file is + * considered to be hidden if it has been marked as such in the filesystem. + * + * @return true if and only if the file denoted by this + * abstract pathname is hidden according to the conventions of the + * underlying platform + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkRead(java.lang.String)} + * method denies read access to the file + * + * @since 1.2 + */ + public boolean isHidden() { + throw new SecurityException(); + } + + /** + * Returns the time that the file denoted by this abstract pathname was + * last modified. + * + *

Where it is required to distinguish an I/O exception from the case + * where {@code 0L} is returned, or where several attributes of the + * same file are required at the same time, or where the time of last + * access or the creation time are required, then the {@link + * java.nio.file.Files#readAttributes(Path,Class,LinkOption[]) + * Files.readAttributes} method may be used. + * + * @return A long value representing the time the file was + * last modified, measured in milliseconds since the epoch + * (00:00:00 GMT, January 1, 1970), or 0L if the + * file does not exist or if an I/O error occurs + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkRead(java.lang.String)} + * method denies read access to the file + */ + public long lastModified() { + throw new SecurityException(); + } + + /** + * Returns the length of the file denoted by this abstract pathname. + * The return value is unspecified if this pathname denotes a directory. + * + *

Where it is required to distinguish an I/O exception from the case + * that {@code 0L} is returned, or where several attributes of the same file + * are required at the same time, then the {@link + * java.nio.file.Files#readAttributes(Path,Class,LinkOption[]) + * Files.readAttributes} method may be used. + * + * @return The length, in bytes, of the file denoted by this abstract + * pathname, or 0L if the file does not exist. Some + * operating systems may return 0L for pathnames + * denoting system-dependent entities such as devices or pipes. + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkRead(java.lang.String)} + * method denies read access to the file + */ + public long length() { + throw new SecurityException(); + } + + + /* -- File operations -- */ + + /** + * Atomically creates a new, empty file named by this abstract pathname if + * and only if a file with this name does not yet exist. The check for the + * existence of the file and the creation of the file if it does not exist + * are a single operation that is atomic with respect to all other + * filesystem activities that might affect the file. + *

+ * Note: this method should not be used for file-locking, as + * the resulting protocol cannot be made to work reliably. The + * {@link java.nio.channels.FileLock FileLock} + * facility should be used instead. + * + * @return true if the named file does not exist and was + * successfully created; false if the named file + * already exists + * + * @throws IOException + * If an I/O error occurred + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method denies write access to the file + * + * @since 1.2 + */ + public boolean createNewFile() throws IOException { + throw new SecurityException(); + } + + /** + * Deletes the file or directory denoted by this abstract pathname. If + * this pathname denotes a directory, then the directory must be empty in + * order to be deleted. + * + *

Note that the {@link java.nio.file.Files} class defines the {@link + * java.nio.file.Files#delete(Path) delete} method to throw an {@link IOException} + * when a file cannot be deleted. This is useful for error reporting and to + * diagnose why a file cannot be deleted. + * + * @return true if and only if the file or directory is + * successfully deleted; false otherwise + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkDelete} method denies + * delete access to the file + */ + public boolean delete() { + throw new SecurityException(); + } + + /** + * Requests that the file or directory denoted by this abstract + * pathname be deleted when the virtual machine terminates. + * Files (or directories) are deleted in the reverse order that + * they are registered. Invoking this method to delete a file or + * directory that is already registered for deletion has no effect. + * Deletion will be attempted only for normal termination of the + * virtual machine, as defined by the Java Language Specification. + * + *

Once deletion has been requested, it is not possible to cancel the + * request. This method should therefore be used with care. + * + *

+ * Note: this method should not be used for file-locking, as + * the resulting protocol cannot be made to work reliably. The + * {@link java.nio.channels.FileLock FileLock} + * facility should be used instead. + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkDelete} method denies + * delete access to the file + * + * @see #delete + * + * @since 1.2 + */ + public void deleteOnExit() { + throw new SecurityException(); + } + + /** + * Returns an array of strings naming the files and directories in the + * directory denoted by this abstract pathname. + * + *

If this abstract pathname does not denote a directory, then this + * method returns {@code null}. Otherwise an array of strings is + * returned, one for each file or directory in the directory. Names + * denoting the directory itself and the directory's parent directory are + * not included in the result. Each string is a file name rather than a + * complete path. + * + *

There is no guarantee that the name strings in the resulting array + * will appear in any specific order; they are not, in particular, + * guaranteed to appear in alphabetical order. + * + *

Note that the {@link java.nio.file.Files} class defines the {@link + * java.nio.file.Files#newDirectoryStream(Path) newDirectoryStream} method to + * open a directory and iterate over the names of the files in the directory. + * This may use less resources when working with very large directories, and + * may be more responsive when working with remote directories. + * + * @return An array of strings naming the files and directories in the + * directory denoted by this abstract pathname. The array will be + * empty if the directory is empty. Returns {@code null} if + * this abstract pathname does not denote a directory, or if an + * I/O error occurs. + * + * @throws SecurityException + * If a security manager exists and its {@link + * SecurityManager#checkRead(String)} method denies read access to + * the directory + */ + public String[] list() { + throw new SecurityException(); + } + + /** + * Returns an array of strings naming the files and directories in the + * directory denoted by this abstract pathname that satisfy the specified + * filter. The behavior of this method is the same as that of the + * {@link #list()} method, except that the strings in the returned array + * must satisfy the filter. If the given {@code filter} is {@code null} + * then all names are accepted. Otherwise, a name satisfies the filter if + * and only if the value {@code true} results when the {@link + * FilenameFilter#accept FilenameFilter.accept(File, String)} method + * of the filter is invoked on this abstract pathname and the name of a + * file or directory in the directory that it denotes. + * + * @param filter + * A filename filter + * + * @return An array of strings naming the files and directories in the + * directory denoted by this abstract pathname that were accepted + * by the given {@code filter}. The array will be empty if the + * directory is empty or if no names were accepted by the filter. + * Returns {@code null} if this abstract pathname does not denote + * a directory, or if an I/O error occurs. + * + * @throws SecurityException + * If a security manager exists and its {@link + * SecurityManager#checkRead(String)} method denies read access to + * the directory + * + * @see java.nio.file.Files#newDirectoryStream(Path,String) + */ + public String[] list(FilenameFilter filter) { + throw new SecurityException(); + } + + /** + * Returns an array of abstract pathnames denoting the files in the + * directory denoted by this abstract pathname. + * + *

If this abstract pathname does not denote a directory, then this + * method returns {@code null}. Otherwise an array of {@code File} objects + * is returned, one for each file or directory in the directory. Pathnames + * denoting the directory itself and the directory's parent directory are + * not included in the result. Each resulting abstract pathname is + * constructed from this abstract pathname using the {@link #File(File, + * String) File(File, String)} constructor. Therefore if this + * pathname is absolute then each resulting pathname is absolute; if this + * pathname is relative then each resulting pathname will be relative to + * the same directory. + * + *

There is no guarantee that the name strings in the resulting array + * will appear in any specific order; they are not, in particular, + * guaranteed to appear in alphabetical order. + * + *

Note that the {@link java.nio.file.Files} class defines the {@link + * java.nio.file.Files#newDirectoryStream(Path) newDirectoryStream} method + * to open a directory and iterate over the names of the files in the + * directory. This may use less resources when working with very large + * directories. + * + * @return An array of abstract pathnames denoting the files and + * directories in the directory denoted by this abstract pathname. + * The array will be empty if the directory is empty. Returns + * {@code null} if this abstract pathname does not denote a + * directory, or if an I/O error occurs. + * + * @throws SecurityException + * If a security manager exists and its {@link + * SecurityManager#checkRead(String)} method denies read access to + * the directory + * + * @since 1.2 + */ + public File[] listFiles() { + throw new SecurityException(); + } + + /** + * Returns an array of abstract pathnames denoting the files and + * directories in the directory denoted by this abstract pathname that + * satisfy the specified filter. The behavior of this method is the same + * as that of the {@link #listFiles()} method, except that the pathnames in + * the returned array must satisfy the filter. If the given {@code filter} + * is {@code null} then all pathnames are accepted. Otherwise, a pathname + * satisfies the filter if and only if the value {@code true} results when + * the {@link FilenameFilter#accept + * FilenameFilter.accept(File, String)} method of the filter is + * invoked on this abstract pathname and the name of a file or directory in + * the directory that it denotes. + * + * @param filter + * A filename filter + * + * @return An array of abstract pathnames denoting the files and + * directories in the directory denoted by this abstract pathname. + * The array will be empty if the directory is empty. Returns + * {@code null} if this abstract pathname does not denote a + * directory, or if an I/O error occurs. + * + * @throws SecurityException + * If a security manager exists and its {@link + * SecurityManager#checkRead(String)} method denies read access to + * the directory + * + * @since 1.2 + * @see java.nio.file.Files#newDirectoryStream(Path,String) + */ + public File[] listFiles(FilenameFilter filter) { + throw new SecurityException(); + } + + /** + * Returns an array of abstract pathnames denoting the files and + * directories in the directory denoted by this abstract pathname that + * satisfy the specified filter. The behavior of this method is the same + * as that of the {@link #listFiles()} method, except that the pathnames in + * the returned array must satisfy the filter. If the given {@code filter} + * is {@code null} then all pathnames are accepted. Otherwise, a pathname + * satisfies the filter if and only if the value {@code true} results when + * the {@link FileFilter#accept FileFilter.accept(File)} method of the + * filter is invoked on the pathname. + * + * @param filter + * A file filter + * + * @return An array of abstract pathnames denoting the files and + * directories in the directory denoted by this abstract pathname. + * The array will be empty if the directory is empty. Returns + * {@code null} if this abstract pathname does not denote a + * directory, or if an I/O error occurs. + * + * @throws SecurityException + * If a security manager exists and its {@link + * SecurityManager#checkRead(String)} method denies read access to + * the directory + * + * @since 1.2 + * @see java.nio.file.Files#newDirectoryStream(Path,java.nio.file.DirectoryStream.Filter) + */ + public File[] listFiles(FileFilter filter) { + throw new SecurityException(); + } + + /** + * Creates the directory named by this abstract pathname. + * + * @return true if and only if the directory was + * created; false otherwise + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method does not permit the named directory to be created + */ + public boolean mkdir() { + throw new SecurityException(); + } + + /** + * Creates the directory named by this abstract pathname, including any + * necessary but nonexistent parent directories. Note that if this + * operation fails it may have succeeded in creating some of the necessary + * parent directories. + * + * @return true if and only if the directory was created, + * along with all necessary parent directories; false + * otherwise + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkRead(java.lang.String)} + * method does not permit verification of the existence of the + * named directory and all necessary parent directories; or if + * the {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method does not permit the named directory and all necessary + * parent directories to be created + */ + public boolean mkdirs() { + throw new SecurityException(); + } + + /** + * Renames the file denoted by this abstract pathname. + * + *

Many aspects of the behavior of this method are inherently + * platform-dependent: The rename operation might not be able to move a + * file from one filesystem to another, it might not be atomic, and it + * might not succeed if a file with the destination abstract pathname + * already exists. The return value should always be checked to make sure + * that the rename operation was successful. + * + *

Note that the {@link java.nio.file.Files} class defines the {@link + * java.nio.file.Files#move move} method to move or rename a file in a + * platform independent manner. + * + * @param dest The new abstract pathname for the named file + * + * @return true if and only if the renaming succeeded; + * false otherwise + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method denies write access to either the old or new pathnames + * + * @throws NullPointerException + * If parameter dest is null + */ + public boolean renameTo(File dest) { + throw new SecurityException(); + } + + /** + * Sets the last-modified time of the file or directory named by this + * abstract pathname. + * + *

All platforms support file-modification times to the nearest second, + * but some provide more precision. The argument will be truncated to fit + * the supported precision. If the operation succeeds and no intervening + * operations on the file take place, then the next invocation of the + * {@link #lastModified} method will return the (possibly + * truncated) time argument that was passed to this method. + * + * @param time The new last-modified time, measured in milliseconds since + * the epoch (00:00:00 GMT, January 1, 1970) + * + * @return true if and only if the operation succeeded; + * false otherwise + * + * @throws IllegalArgumentException If the argument is negative + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method denies write access to the named file + * + * @since 1.2 + */ + public boolean setLastModified(long time) { + throw new SecurityException(); + } + + /** + * Marks the file or directory named by this abstract pathname so that + * only read operations are allowed. After invoking this method the file + * or directory is guaranteed not to change until it is either deleted or + * marked to allow write access. Whether or not a read-only file or + * directory may be deleted depends upon the underlying system. + * + * @return true if and only if the operation succeeded; + * false otherwise + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method denies write access to the named file + * + * @since 1.2 + */ + public boolean setReadOnly() { + throw new SecurityException(); + } + + /** + * Sets the owner's or everybody's write permission for this abstract + * pathname. + * + *

The {@link java.nio.file.Files} class defines methods that operate on + * file attributes including file permissions. This may be used when finer + * manipulation of file permissions is required. + * + * @param writable + * If true, sets the access permission to allow write + * operations; if false to disallow write operations + * + * @param ownerOnly + * If true, the write permission applies only to the + * owner's write permission; otherwise, it applies to everybody. If + * the underlying file system can not distinguish the owner's write + * permission from that of others, then the permission will apply to + * everybody, regardless of this value. + * + * @return true if and only if the operation succeeded. The + * operation will fail if the user does not have permission to change + * the access permissions of this abstract pathname. + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method denies write access to the named file + * + * @since 1.6 + */ + public boolean setWritable(boolean writable, boolean ownerOnly) { + throw new SecurityException(); + } + + /** + * A convenience method to set the owner's write permission for this abstract + * pathname. + * + *

An invocation of this method of the form file.setWritable(arg) + * behaves in exactly the same way as the invocation + * + *

+     *     file.setWritable(arg, true) 
+ * + * @param writable + * If true, sets the access permission to allow write + * operations; if false to disallow write operations + * + * @return true if and only if the operation succeeded. The + * operation will fail if the user does not have permission to + * change the access permissions of this abstract pathname. + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method denies write access to the file + * + * @since 1.6 + */ + public boolean setWritable(boolean writable) { + return setWritable(writable, true); + } + + /** + * Sets the owner's or everybody's read permission for this abstract + * pathname. + * + *

The {@link java.nio.file.Files} class defines methods that operate on + * file attributes including file permissions. This may be used when finer + * manipulation of file permissions is required. + * + * @param readable + * If true, sets the access permission to allow read + * operations; if false to disallow read operations + * + * @param ownerOnly + * If true, the read permission applies only to the + * owner's read permission; otherwise, it applies to everybody. If + * the underlying file system can not distinguish the owner's read + * permission from that of others, then the permission will apply to + * everybody, regardless of this value. + * + * @return true if and only if the operation succeeded. The + * operation will fail if the user does not have permission to + * change the access permissions of this abstract pathname. If + * readable is false and the underlying + * file system does not implement a read permission, then the + * operation will fail. + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method denies write access to the file + * + * @since 1.6 + */ + public boolean setReadable(boolean readable, boolean ownerOnly) { + throw new SecurityException(); + } + + /** + * A convenience method to set the owner's read permission for this abstract + * pathname. + * + *

An invocation of this method of the form file.setReadable(arg) + * behaves in exactly the same way as the invocation + * + *

+     *     file.setReadable(arg, true) 
+ * + * @param readable + * If true, sets the access permission to allow read + * operations; if false to disallow read operations + * + * @return true if and only if the operation succeeded. The + * operation will fail if the user does not have permission to + * change the access permissions of this abstract pathname. If + * readable is false and the underlying + * file system does not implement a read permission, then the + * operation will fail. + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method denies write access to the file + * + * @since 1.6 + */ + public boolean setReadable(boolean readable) { + return setReadable(readable, true); + } + + /** + * Sets the owner's or everybody's execute permission for this abstract + * pathname. + * + *

The {@link java.nio.file.Files} class defines methods that operate on + * file attributes including file permissions. This may be used when finer + * manipulation of file permissions is required. + * + * @param executable + * If true, sets the access permission to allow execute + * operations; if false to disallow execute operations + * + * @param ownerOnly + * If true, the execute permission applies only to the + * owner's execute permission; otherwise, it applies to everybody. + * If the underlying file system can not distinguish the owner's + * execute permission from that of others, then the permission will + * apply to everybody, regardless of this value. + * + * @return true if and only if the operation succeeded. The + * operation will fail if the user does not have permission to + * change the access permissions of this abstract pathname. If + * executable is false and the underlying + * file system does not implement an execute permission, then the + * operation will fail. + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method denies write access to the file + * + * @since 1.6 + */ + public boolean setExecutable(boolean executable, boolean ownerOnly) { + throw new SecurityException(); + } + + /** + * A convenience method to set the owner's execute permission for this abstract + * pathname. + * + *

An invocation of this method of the form file.setExcutable(arg) + * behaves in exactly the same way as the invocation + * + *

+     *     file.setExecutable(arg, true) 
+ * + * @param executable + * If true, sets the access permission to allow execute + * operations; if false to disallow execute operations + * + * @return true if and only if the operation succeeded. The + * operation will fail if the user does not have permission to + * change the access permissions of this abstract pathname. If + * executable is false and the underlying + * file system does not implement an excute permission, then the + * operation will fail. + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method denies write access to the file + * + * @since 1.6 + */ + public boolean setExecutable(boolean executable) { + return setExecutable(executable, true); + } + + /** + * Tests whether the application can execute the file denoted by this + * abstract pathname. + * + * @return true if and only if the abstract pathname exists + * and the application is allowed to execute the file + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkExec(java.lang.String)} + * method denies execute access to the file + * + * @since 1.6 + */ + public boolean canExecute() { + throw new SecurityException(); + } + + + /* -- Filesystem interface -- */ + + /** + * List the available filesystem roots. + * + *

A particular Java platform may support zero or more + * hierarchically-organized file systems. Each file system has a + * {@code root} directory from which all other files in that file system + * can be reached. Windows platforms, for example, have a root directory + * for each active drive; UNIX platforms have a single root directory, + * namely {@code "/"}. The set of available filesystem roots is affected + * by various system-level operations such as the insertion or ejection of + * removable media and the disconnecting or unmounting of physical or + * virtual disk drives. + * + *

This method returns an array of {@code File} objects that denote the + * root directories of the available filesystem roots. It is guaranteed + * that the canonical pathname of any file physically present on the local + * machine will begin with one of the roots returned by this method. + * + *

The canonical pathname of a file that resides on some other machine + * and is accessed via a remote-filesystem protocol such as SMB or NFS may + * or may not begin with one of the roots returned by this method. If the + * pathname of a remote file is syntactically indistinguishable from the + * pathname of a local file then it will begin with one of the roots + * returned by this method. Thus, for example, {@code File} objects + * denoting the root directories of the mapped network drives of a Windows + * platform will be returned by this method, while {@code File} objects + * containing UNC pathnames will not be returned by this method. + * + *

Unlike most methods in this class, this method does not throw + * security exceptions. If a security manager exists and its {@link + * SecurityManager#checkRead(String)} method denies read access to a + * particular root directory, then that directory will not appear in the + * result. + * + * @return An array of {@code File} objects denoting the available + * filesystem roots, or {@code null} if the set of roots could not + * be determined. The array will be empty if there are no + * filesystem roots. + * + * @since 1.2 + * @see java.nio.file.FileStore + */ + public static File[] listRoots() { + throw new SecurityException(); + } + + + /* -- Disk usage -- */ + + /** + * Returns the size of the partition named by this + * abstract pathname. + * + * @return The size, in bytes, of the partition or 0L if this + * abstract pathname does not name a partition + * + * @throws SecurityException + * If a security manager has been installed and it denies + * {@link RuntimePermission}("getFileSystemAttributes") + * or its {@link SecurityManager#checkRead(String)} method denies + * read access to the file named by this abstract pathname + * + * @since 1.6 + */ + public long getTotalSpace() { + throw new SecurityException(); + } + + /** + * Returns the number of unallocated bytes in the partition named by this abstract path name. + * + *

The returned number of unallocated bytes is a hint, but not + * a guarantee, that it is possible to use most or any of these + * bytes. The number of unallocated bytes is most likely to be + * accurate immediately after this call. It is likely to be made + * inaccurate by any external I/O operations including those made + * on the system outside of this virtual machine. This method + * makes no guarantee that write operations to this file system + * will succeed. + * + * @return The number of unallocated bytes on the partition 0L + * if the abstract pathname does not name a partition. This + * value will be less than or equal to the total file system size + * returned by {@link #getTotalSpace}. + * + * @throws SecurityException + * If a security manager has been installed and it denies + * {@link RuntimePermission}("getFileSystemAttributes") + * or its {@link SecurityManager#checkRead(String)} method denies + * read access to the file named by this abstract pathname + * + * @since 1.6 + */ + public long getFreeSpace() { + throw new SecurityException(); + } + + /** + * Returns the number of bytes available to this virtual machine on the + * partition named by this abstract pathname. When + * possible, this method checks for write permissions and other operating + * system restrictions and will therefore usually provide a more accurate + * estimate of how much new data can actually be written than {@link + * #getFreeSpace}. + * + *

The returned number of available bytes is a hint, but not a + * guarantee, that it is possible to use most or any of these bytes. The + * number of unallocated bytes is most likely to be accurate immediately + * after this call. It is likely to be made inaccurate by any external + * I/O operations including those made on the system outside of this + * virtual machine. This method makes no guarantee that write operations + * to this file system will succeed. + * + * @return The number of available bytes on the partition or 0L + * if the abstract pathname does not name a partition. On + * systems where this information is not available, this method + * will be equivalent to a call to {@link #getFreeSpace}. + * + * @throws SecurityException + * If a security manager has been installed and it denies + * {@link RuntimePermission}("getFileSystemAttributes") + * or its {@link SecurityManager#checkRead(String)} method denies + * read access to the file named by this abstract pathname + * + * @since 1.6 + */ + public long getUsableSpace() { + throw new SecurityException(); + } + + /* -- Temporary files -- */ + + + /** + *

Creates a new empty file in the specified directory, using the + * given prefix and suffix strings to generate its name. If this method + * returns successfully then it is guaranteed that: + * + *

    + *
  1. The file denoted by the returned abstract pathname did not exist + * before this method was invoked, and + *
  2. Neither this method nor any of its variants will return the same + * abstract pathname again in the current invocation of the virtual + * machine. + *
+ * + * This method provides only part of a temporary-file facility. To arrange + * for a file created by this method to be deleted automatically, use the + * {@link #deleteOnExit} method. + * + *

The prefix argument must be at least three characters + * long. It is recommended that the prefix be a short, meaningful string + * such as "hjb" or "mail". The + * suffix argument may be null, in which case the + * suffix ".tmp" will be used. + * + *

To create the new file, the prefix and the suffix may first be + * adjusted to fit the limitations of the underlying platform. If the + * prefix is too long then it will be truncated, but its first three + * characters will always be preserved. If the suffix is too long then it + * too will be truncated, but if it begins with a period character + * ('.') then the period and the first three characters + * following it will always be preserved. Once these adjustments have been + * made the name of the new file will be generated by concatenating the + * prefix, five or more internally-generated characters, and the suffix. + * + *

If the directory argument is null then the + * system-dependent default temporary-file directory will be used. The + * default temporary-file directory is specified by the system property + * java.io.tmpdir. On UNIX systems the default value of this + * property is typically "/tmp" or "/var/tmp"; on + * Microsoft Windows systems it is typically "C:\\WINNT\\TEMP". A different + * value may be given to this system property when the Java virtual machine + * is invoked, but programmatic changes to this property are not guaranteed + * to have any effect upon the temporary directory used by this method. + * + * @param prefix The prefix string to be used in generating the file's + * name; must be at least three characters long + * + * @param suffix The suffix string to be used in generating the file's + * name; may be null, in which case the + * suffix ".tmp" will be used + * + * @param directory The directory in which the file is to be created, or + * null if the default temporary-file + * directory is to be used + * + * @return An abstract pathname denoting a newly-created empty file + * + * @throws IllegalArgumentException + * If the prefix argument contains fewer than three + * characters + * + * @throws IOException If a file could not be created + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method does not allow a file to be created + * + * @since 1.2 + */ + public static File createTempFile(String prefix, String suffix, + File directory) + throws IOException + { + throw new SecurityException(); + } + + /** + * Creates an empty file in the default temporary-file directory, using + * the given prefix and suffix to generate its name. Invoking this method + * is equivalent to invoking {@link #createTempFile(java.lang.String, + * java.lang.String, java.io.File) + * createTempFile(prefix, suffix, null)}. + * + *

The {@link + * java.nio.file.Files#createTempFile(String,String,java.nio.file.attribute.FileAttribute[]) + * Files.createTempFile} method provides an alternative method to create an + * empty file in the temporary-file directory. Files created by that method + * may have more restrictive access permissions to files created by this + * method and so may be more suited to security-sensitive applications. + * + * @param prefix The prefix string to be used in generating the file's + * name; must be at least three characters long + * + * @param suffix The suffix string to be used in generating the file's + * name; may be null, in which case the + * suffix ".tmp" will be used + * + * @return An abstract pathname denoting a newly-created empty file + * + * @throws IllegalArgumentException + * If the prefix argument contains fewer than three + * characters + * + * @throws IOException If a file could not be created + * + * @throws SecurityException + * If a security manager exists and its {@link + * java.lang.SecurityManager#checkWrite(java.lang.String)} + * method does not allow a file to be created + * + * @since 1.2 + * @see java.nio.file.Files#createTempDirectory(String,FileAttribute[]) + */ + public static File createTempFile(String prefix, String suffix) + throws IOException + { + return createTempFile(prefix, suffix, null); + } + + /* -- Basic infrastructure -- */ + + /** + * Compares two abstract pathnames lexicographically. The ordering + * defined by this method depends upon the underlying system. On UNIX + * systems, alphabetic case is significant in comparing pathnames; on Microsoft Windows + * systems it is not. + * + * @param pathname The abstract pathname to be compared to this abstract + * pathname + * + * @return Zero if the argument is equal to this abstract pathname, a + * value less than zero if this abstract pathname is + * lexicographically less than the argument, or a value greater + * than zero if this abstract pathname is lexicographically + * greater than the argument + * + * @since 1.2 + */ + public int compareTo(File pathname) { + return fs.compare(this, pathname); + } + + /** + * Tests this abstract pathname for equality with the given object. + * Returns true if and only if the argument is not + * null and is an abstract pathname that denotes the same file + * or directory as this abstract pathname. Whether or not two abstract + * pathnames are equal depends upon the underlying system. On UNIX + * systems, alphabetic case is significant in comparing pathnames; on Microsoft Windows + * systems it is not. + * + * @param obj The object to be compared with this abstract pathname + * + * @return true if and only if the objects are the same; + * false otherwise + */ + public boolean equals(Object obj) { + if ((obj != null) && (obj instanceof File)) { + return compareTo((File)obj) == 0; + } + return false; + } + + /** + * Computes a hash code for this abstract pathname. Because equality of + * abstract pathnames is inherently system-dependent, so is the computation + * of their hash codes. On UNIX systems, the hash code of an abstract + * pathname is equal to the exclusive or of the hash code + * of its pathname string and the decimal value + * 1234321. On Microsoft Windows systems, the hash + * code is equal to the exclusive or of the hash code of + * its pathname string converted to lower case and the decimal + * value 1234321. Locale is not taken into account on + * lowercasing the pathname string. + * + * @return A hash code for this abstract pathname + */ + public int hashCode() { + return fs.hashCode(this); + } + + /** + * Returns the pathname string of this abstract pathname. This is just the + * string returned by the {@link #getPath} method. + * + * @return The string form of this abstract pathname + */ + public String toString() { + return getPath(); + } + + /** + * WriteObject is called to save this filename. + * The separator character is saved also so it can be replaced + * in case the path is reconstituted on a different host type. + *

+ * @serialData Default fields followed by separator character. + */ + private synchronized void writeObject(java.io.ObjectOutputStream s) + throws IOException + { + s.defaultWriteObject(); + s.writeChar(this.separatorChar); // Add the separator character + } + + /** + * readObject is called to restore this filename. + * The original separator character is read. If it is different + * than the separator character on this system, then the old separator + * is replaced by the local separator. + */ + private synchronized void readObject(java.io.ObjectInputStream s) + throws IOException, ClassNotFoundException + { + ObjectInputStream.GetField fields = s.readFields(); + String pathField = (String)fields.get("path", null); + char sep = s.readChar(); // read the previous separator char + if (sep != separatorChar) + pathField = pathField.replace(sep, separatorChar); + this.path = fs.normalize(pathField); + this.prefixLength = fs.prefixLength(this.path); + } + + /** use serialVersionUID from JDK 1.0.2 for interoperability */ + private static final long serialVersionUID = 301077366599181567L; + + // -- Integration with java.nio.file -- +/* + private volatile transient Path filePath; + + /** + * Returns a {@link Path java.nio.file.Path} object constructed from the + * this abstract path. The resulting {@code Path} is associated with the + * {@link java.nio.file.FileSystems#getDefault default-filesystem}. + * + *

The first invocation of this method works as if invoking it were + * equivalent to evaluating the expression: + *

+     * {@link java.nio.file.FileSystems#getDefault FileSystems.getDefault}().{@link
+     * java.nio.file.FileSystem#getPath getPath}(this.{@link #getPath getPath}());
+     * 
+ * Subsequent invocations of this method return the same {@code Path}. + * + *

If this abstract pathname is the empty abstract pathname then this + * method returns a {@code Path} that may be used to access the current + * user directory. + * + * @return a {@code Path} constructed from this abstract path + * + * @throws java.nio.file.InvalidPathException + * if a {@code Path} object cannot be constructed from the abstract + * path (see {@link java.nio.file.FileSystem#getPath FileSystem.getPath}) + * + * @since 1.7 + * @see Path#toFile + */ +// public Path toPath() { +// Path result = filePath; +// if (result == null) { +// synchronized (this) { +// result = filePath; +// if (result == null) { +// result = FileSystems.getDefault().getPath(path); +// filePath = result; +// } +// } +// } +// return result; +// } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/io/FileFilter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FileFilter.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,50 @@ +/* + * Copyright (c) 1998, 2002, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + + +/** + * A filter for abstract pathnames. + * + *

Instances of this interface may be passed to the {@link + * File#listFiles(java.io.FileFilter) listFiles(FileFilter)} method + * of the {@link java.io.File} class. + * + * @since 1.2 + */ +public interface FileFilter { + + /** + * Tests whether or not the specified abstract pathname should be + * included in a pathname list. + * + * @param pathname The abstract pathname to be tested + * @return true if and only if pathname + * should be included + */ + boolean accept(File pathname); + +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/io/FileNotFoundException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FileNotFoundException.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 1994, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + + +/** + * Signals that an attempt to open the file denoted by a specified pathname + * has failed. + * + *

This exception will be thrown by the {@link FileInputStream}, {@link + * FileOutputStream}, and {@link RandomAccessFile} constructors when a file + * with the specified pathname does not exist. It will also be thrown by these + * constructors if the file does exist but for some reason is inaccessible, for + * example when an attempt is made to open a read-only file for writing. + * + * @author unascribed + * @since JDK1.0 + */ + +public class FileNotFoundException extends IOException { + private static final long serialVersionUID = -897856973823710492L; + + /** + * Constructs a FileNotFoundException with + * null as its error detail message. + */ + public FileNotFoundException() { + super(); + } + + /** + * Constructs a FileNotFoundException with the + * specified detail message. The string s can be + * retrieved later by the + * {@link java.lang.Throwable#getMessage} + * method of class java.lang.Throwable. + * + * @param s the detail message. + */ + public FileNotFoundException(String s) { + super(s); + } + + /** + * Constructs a FileNotFoundException with a detail message + * consisting of the given pathname string followed by the given reason + * string. If the reason argument is null then + * it will be omitted. This private constructor is invoked only by native + * I/O methods. + * + * @since 1.2 + */ + private FileNotFoundException(String path, String reason) { + super(path + ((reason == null) + ? "" + : " (" + reason + ")")); + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/io/FilenameFilter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FilenameFilter.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,53 @@ +/* + * Copyright (c) 1994, 1998, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +/** + * Instances of classes that implement this interface are used to + * filter filenames. These instances are used to filter directory + * listings in the list method of class + * File, and by the Abstract Window Toolkit's file + * dialog component. + * + * @author Arthur van Hoff + * @author Jonathan Payne + * @see java.awt.FileDialog#setFilenameFilter(java.io.FilenameFilter) + * @see java.io.File + * @see java.io.File#list(java.io.FilenameFilter) + * @since JDK1.0 + */ +public +interface FilenameFilter { + /** + * Tests if a specified file should be included in a file list. + * + * @param dir the directory in which the file was found. + * @param name the name of the file. + * @return true if and only if the name should be + * included in the file list; false otherwise. + */ + boolean accept(File dir, String name); +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/io/InterruptedIOException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/InterruptedIOException.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,74 @@ +/* + * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +/** + * Signals that an I/O operation has been interrupted. An + * InterruptedIOException is thrown to indicate that an + * input or output transfer has been terminated because the thread + * performing it was interrupted. The field {@link #bytesTransferred} + * indicates how many bytes were successfully transferred before + * the interruption occurred. + * + * @author unascribed + * @see java.io.InputStream + * @see java.io.OutputStream + * @see java.lang.Thread#interrupt() + * @since JDK1.0 + */ +public +class InterruptedIOException extends IOException { + private static final long serialVersionUID = 4020568460727500567L; + + /** + * Constructs an InterruptedIOException with + * null as its error detail message. + */ + public InterruptedIOException() { + super(); + } + + /** + * Constructs an InterruptedIOException with the + * specified detail message. The string s can be + * retrieved later by the + * {@link java.lang.Throwable#getMessage} + * method of class java.lang.Throwable. + * + * @param s the detail message. + */ + public InterruptedIOException(String s) { + super(s); + } + + /** + * Reports how many bytes had been transferred as part of the I/O + * operation before it was interrupted. + * + * @serial + */ + public int bytesTransferred = 0; +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/io/OutputStreamWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/OutputStreamWriter.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,242 @@ +/* + * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +/** + * An OutputStreamWriter is a bridge from character streams to byte streams: + * Characters written to it are encoded into bytes using a specified {@link + * java.nio.charset.Charset charset}. The charset that it uses + * may be specified by name or may be given explicitly, or the platform's + * default charset may be accepted. + * + *

Each invocation of a write() method causes the encoding converter to be + * invoked on the given character(s). The resulting bytes are accumulated in a + * buffer before being written to the underlying output stream. The size of + * this buffer may be specified, but by default it is large enough for most + * purposes. Note that the characters passed to the write() methods are not + * buffered. + * + *

For top efficiency, consider wrapping an OutputStreamWriter within a + * BufferedWriter so as to avoid frequent converter invocations. For example: + * + *

+ * Writer out
+ *   = new BufferedWriter(new OutputStreamWriter(System.out));
+ * 
+ * + *

A surrogate pair is a character represented by a sequence of two + * char values: A high surrogate in the range '\uD800' to + * '\uDBFF' followed by a low surrogate in the range '\uDC00' to + * '\uDFFF'. + * + *

A malformed surrogate element is a high surrogate that is not + * followed by a low surrogate or a low surrogate that is not preceded by a + * high surrogate. + * + *

This class always replaces malformed surrogate elements and unmappable + * character sequences with the charset's default substitution sequence. + * The {@linkplain java.nio.charset.CharsetEncoder} class should be used when more + * control over the encoding process is required. + * + * @see BufferedWriter + * @see OutputStream + * @see java.nio.charset.Charset + * + * @author Mark Reinhold + * @since JDK1.1 + */ + +public class OutputStreamWriter extends Writer { + + /** + * Creates an OutputStreamWriter that uses the named charset. + * + * @param out + * An OutputStream + * + * @param charsetName + * The name of a supported + * {@link java.nio.charset.Charset charset} + * + * @exception UnsupportedEncodingException + * If the named encoding is not supported + */ + public OutputStreamWriter(OutputStream out, String charsetName) + throws UnsupportedEncodingException + { + super(out); + if (charsetName == null) + throw new NullPointerException("charsetName"); + if (!charsetName.toUpperCase().equals("UTF-8")) { + throw new UnsupportedEncodingException(charsetName); + } + } + + /** + * Creates an OutputStreamWriter that uses the default character encoding. + * + * @param out An OutputStream + */ + public OutputStreamWriter(OutputStream out) { + super(out); + } + + /** + * Creates an OutputStreamWriter that uses the given charset.

+ * + * @param out + * An OutputStream + * + * @param cs + * A charset + * + * @since 1.4 + * @spec JSR-51 + */ +// public OutputStreamWriter(OutputStream out, Charset cs) { +// super(out); +// if (cs == null) +// throw new NullPointerException("charset"); +// se = StreamEncoder.forOutputStreamWriter(out, this, cs); +// } + + /** + * Creates an OutputStreamWriter that uses the given charset encoder.

+ * + * @param out + * An OutputStream + * + * @param enc + * A charset encoder + * + * @since 1.4 + * @spec JSR-51 + */ +// public OutputStreamWriter(OutputStream out, CharsetEncoder enc) { +// super(out); +// if (enc == null) +// throw new NullPointerException("charset encoder"); +// se = StreamEncoder.forOutputStreamWriter(out, this, enc); +// } + + /** + * Returns the name of the character encoding being used by this stream. + * + *

If the encoding has an historical name then that name is returned; + * otherwise the encoding's canonical name is returned. + * + *

If this instance was created with the {@link + * #OutputStreamWriter(OutputStream, String)} constructor then the returned + * name, being unique for the encoding, may differ from the name passed to + * the constructor. This method may return null if the stream has + * been closed.

+ * + * @return The historical name of this encoding, or possibly + * null if the stream has been closed + * + * @see java.nio.charset.Charset + * + * @revised 1.4 + * @spec JSR-51 + */ + public String getEncoding() { + return "UTF-8"; + } + + /** + * Flushes the output buffer to the underlying byte stream, without flushing + * the byte stream itself. This method is non-private only so that it may + * be invoked by PrintStream. + */ + void flushBuffer() throws IOException { + out().flush(); + } + + /** + * Writes a single character. + * + * @exception IOException If an I/O error occurs + */ + public void write(int c) throws IOException { + if (c <= 0x7F) { + out().write(c); + } else if (c <= 0x7FF) { + out().write(0xC0 | (c >> 6)); + out().write(0x80 | (c & 0x3F)); + } else { + out().write(0xE0 | (c >> 12)); + out().write(0x80 | ((c >> 6) & 0x3F)); + out().write(0x80 | (c & 0x3F)); + } + } + + /** + * Writes a portion of an array of characters. + * + * @param cbuf Buffer of characters + * @param off Offset from which to start writing characters + * @param len Number of characters to write + * + * @exception IOException If an I/O error occurs + */ + public void write(char cbuf[], int off, int len) throws IOException { + while (len-- > 0) { + write(cbuf[off++]); + } + } + + /** + * Writes a portion of a string. + * + * @param str A String + * @param off Offset from which to start writing characters + * @param len Number of characters to write + * + * @exception IOException If an I/O error occurs + */ + public void write(String str, int off, int len) throws IOException { + while (len-- > 0) { + write(str.charAt(off++)); + } + } + + /** + * Flushes the stream. + * + * @exception IOException If an I/O error occurs + */ + public void flush() throws IOException { + out().flush(); + } + + public void close() throws IOException { + out().close(); + } + + private OutputStream out() { + return (OutputStream) lock; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/io/PrintStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/PrintStream.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,1125 @@ +/* + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +import java.util.Arrays; + + +/** + * A PrintStream adds functionality to another output stream, + * namely the ability to print representations of various data values + * conveniently. Two other features are provided as well. Unlike other output + * streams, a PrintStream never throws an + * IOException; instead, exceptional situations merely set an + * internal flag that can be tested via the checkError method. + * Optionally, a PrintStream can be created so as to flush + * automatically; this means that the flush method is + * automatically invoked after a byte array is written, one of the + * println methods is invoked, or a newline character or byte + * ('\n') is written. + * + *

All characters printed by a PrintStream are converted into + * bytes using the platform's default character encoding. The {@link + * PrintWriter} class should be used in situations that require writing + * characters rather than bytes. + * + * @author Frank Yellin + * @author Mark Reinhold + * @since JDK1.0 + */ + +public class PrintStream extends FilterOutputStream + implements Appendable, Closeable +{ + + private final boolean autoFlush; + private boolean trouble = false; + private Formatter formatter; + + /** + * Track both the text- and character-output streams, so that their buffers + * can be flushed without flushing the entire stream. + */ + private BufferedWriter textOut; + private OutputStreamWriter charOut; + + /** + * requireNonNull is explicitly declared here so as not to create an extra + * dependency on java.util.Objects.requireNonNull. PrintStream is loaded + * early during system initialization. + */ + private static T requireNonNull(T obj, String message) { + if (obj == null) + throw new NullPointerException(message); + return obj; + } + + /* Private constructors */ + private PrintStream(boolean autoFlush, OutputStream out) { + super(out); + this.autoFlush = autoFlush; + this.charOut = new OutputStreamWriter(this); + this.textOut = new BufferedWriter(charOut); + } + + static final class Formatter { + } + + static final class Charset { + } + + static Charset toCharset(String ch) throws UnsupportedEncodingException { + if (!"UTF-8".equals(ch)) { + throw new UnsupportedEncodingException(); + } + return null; + } + + private PrintStream(boolean autoFlush, OutputStream out, Charset charset) { + super(out); + this.autoFlush = autoFlush; + this.charOut = new OutputStreamWriter(this); + this.textOut = new BufferedWriter(charOut); + } + + /* Variant of the private constructor so that the given charset name + * can be verified before evaluating the OutputStream argument. Used + * by constructors creating a FileOutputStream that also take a + * charset name. + */ + private PrintStream(boolean autoFlush, Charset charset, OutputStream out) + throws UnsupportedEncodingException + { + this(autoFlush, out, charset); + } + + /** + * Creates a new print stream. This stream will not flush automatically. + * + * @param out The output stream to which values and objects will be + * printed + * + * @see java.io.PrintWriter#PrintWriter(java.io.OutputStream) + */ + public PrintStream(OutputStream out) { + this(out, false); + } + + /** + * Creates a new print stream. + * + * @param out The output stream to which values and objects will be + * printed + * @param autoFlush A boolean; if true, the output buffer will be flushed + * whenever a byte array is written, one of the + * println methods is invoked, or a newline + * character or byte ('\n') is written + * + * @see java.io.PrintWriter#PrintWriter(java.io.OutputStream, boolean) + */ + public PrintStream(OutputStream out, boolean autoFlush) { + this(autoFlush, requireNonNull(out, "Null output stream")); + } + + /** + * Creates a new print stream. + * + * @param out The output stream to which values and objects will be + * printed + * @param autoFlush A boolean; if true, the output buffer will be flushed + * whenever a byte array is written, one of the + * println methods is invoked, or a newline + * character or byte ('\n') is written + * @param encoding The name of a supported + * + * character encoding + * + * @throws UnsupportedEncodingException + * If the named encoding is not supported + * + * @since 1.4 + */ + public PrintStream(OutputStream out, boolean autoFlush, String encoding) + throws UnsupportedEncodingException + { + this(autoFlush, + requireNonNull(out, "Null output stream"), + toCharset(encoding)); + } + + /** + * Creates a new print stream, without automatic line flushing, with the + * specified file name. This convenience constructor creates + * the necessary intermediate {@link java.io.OutputStreamWriter + * OutputStreamWriter}, which will encode characters using the + * {@linkplain java.nio.charset.Charset#defaultCharset() default charset} + * for this instance of the Java virtual machine. + * + * @param fileName + * The name of the file to use as the destination of this print + * stream. If the file exists, then it will be truncated to + * zero size; otherwise, a new file will be created. The output + * will be written to the file and is buffered. + * + * @throws FileNotFoundException + * If the given file object does not denote an existing, writable + * regular file and a new regular file of that name cannot be + * created, or if some other error occurs while opening or + * creating the file + * + * @throws SecurityException + * If a security manager is present and {@link + * SecurityManager#checkWrite checkWrite(fileName)} denies write + * access to the file + * + * @since 1.5 + */ + public PrintStream(String fileName) throws FileNotFoundException { + super(null); + throw new FileNotFoundException(); + } + + /** + * Creates a new print stream, without automatic line flushing, with the + * specified file name and charset. This convenience constructor creates + * the necessary intermediate {@link java.io.OutputStreamWriter + * OutputStreamWriter}, which will encode characters using the provided + * charset. + * + * @param fileName + * The name of the file to use as the destination of this print + * stream. If the file exists, then it will be truncated to + * zero size; otherwise, a new file will be created. The output + * will be written to the file and is buffered. + * + * @param csn + * The name of a supported {@linkplain java.nio.charset.Charset + * charset} + * + * @throws FileNotFoundException + * If the given file object does not denote an existing, writable + * regular file and a new regular file of that name cannot be + * created, or if some other error occurs while opening or + * creating the file + * + * @throws SecurityException + * If a security manager is present and {@link + * SecurityManager#checkWrite checkWrite(fileName)} denies write + * access to the file + * + * @throws UnsupportedEncodingException + * If the named charset is not supported + * + * @since 1.5 + */ + public PrintStream(String fileName, String csn) + throws FileNotFoundException, UnsupportedEncodingException + { + super(null); + throw new FileNotFoundException(); + } + + /** + * Creates a new print stream, without automatic line flushing, with the + * specified file. This convenience constructor creates the necessary + * intermediate {@link java.io.OutputStreamWriter OutputStreamWriter}, + * which will encode characters using the {@linkplain + * java.nio.charset.Charset#defaultCharset() default charset} for this + * instance of the Java virtual machine. + * + * @param file + * The file to use as the destination of this print stream. If the + * file exists, then it will be truncated to zero size; otherwise, + * a new file will be created. The output will be written to the + * file and is buffered. + * + * @throws FileNotFoundException + * If the given file object does not denote an existing, writable + * regular file and a new regular file of that name cannot be + * created, or if some other error occurs while opening or + * creating the file + * + * @throws SecurityException + * If a security manager is present and {@link + * SecurityManager#checkWrite checkWrite(file.getPath())} + * denies write access to the file + * + * @since 1.5 + */ + public PrintStream(File file) throws FileNotFoundException { + super(null); + throw new FileNotFoundException(); + } + + /** + * Creates a new print stream, without automatic line flushing, with the + * specified file and charset. This convenience constructor creates + * the necessary intermediate {@link java.io.OutputStreamWriter + * OutputStreamWriter}, which will encode characters using the provided + * charset. + * + * @param file + * The file to use as the destination of this print stream. If the + * file exists, then it will be truncated to zero size; otherwise, + * a new file will be created. The output will be written to the + * file and is buffered. + * + * @param csn + * The name of a supported {@linkplain java.nio.charset.Charset + * charset} + * + * @throws FileNotFoundException + * If the given file object does not denote an existing, writable + * regular file and a new regular file of that name cannot be + * created, or if some other error occurs while opening or + * creating the file + * + * @throws SecurityException + * If a security manager is presentand {@link + * SecurityManager#checkWrite checkWrite(file.getPath())} + * denies write access to the file + * + * @throws UnsupportedEncodingException + * If the named charset is not supported + * + * @since 1.5 + */ + public PrintStream(File file, String csn) + throws FileNotFoundException, UnsupportedEncodingException + { + super(null); + throw new FileNotFoundException(); + } + + /** Check to make sure that the stream has not been closed */ + private void ensureOpen() throws IOException { + if (out == null) + throw new IOException("Stream closed"); + } + + /** + * Flushes the stream. This is done by writing any buffered output bytes to + * the underlying output stream and then flushing that stream. + * + * @see java.io.OutputStream#flush() + */ + public void flush() { + synchronized (this) { + try { + ensureOpen(); + out.flush(); + } + catch (IOException x) { + trouble = true; + } + } + } + + private boolean closing = false; /* To avoid recursive closing */ + + /** + * Closes the stream. This is done by flushing the stream and then closing + * the underlying output stream. + * + * @see java.io.OutputStream#close() + */ + public void close() { + synchronized (this) { + if (! closing) { + closing = true; + try { + textOut.close(); + out.close(); + } + catch (IOException x) { + trouble = true; + } + textOut = null; + charOut = null; + out = null; + } + } + } + + /** + * Flushes the stream and checks its error state. The internal error state + * is set to true when the underlying output stream throws an + * IOException other than InterruptedIOException, + * and when the setError method is invoked. If an operation + * on the underlying output stream throws an + * InterruptedIOException, then the PrintStream + * converts the exception back into an interrupt by doing: + *

+     *     Thread.currentThread().interrupt();
+     * 
+ * or the equivalent. + * + * @return true if and only if this stream has encountered an + * IOException other than + * InterruptedIOException, or the + * setError method has been invoked + */ + public boolean checkError() { + if (out != null) + flush(); + if (out instanceof java.io.PrintStream) { + PrintStream ps = (PrintStream) out; + return ps.checkError(); + } + return trouble; + } + + /** + * Sets the error state of the stream to true. + * + *

This method will cause subsequent invocations of {@link + * #checkError()} to return true until {@link + * #clearError()} is invoked. + * + * @since JDK1.1 + */ + protected void setError() { + trouble = true; + } + + /** + * Clears the internal error state of this stream. + * + *

This method will cause subsequent invocations of {@link + * #checkError()} to return false until another write + * operation fails and invokes {@link #setError()}. + * + * @since 1.6 + */ + protected void clearError() { + trouble = false; + } + + /* + * Exception-catching, synchronized output operations, + * which also implement the write() methods of OutputStream + */ + + /** + * Writes the specified byte to this stream. If the byte is a newline and + * automatic flushing is enabled then the flush method will be + * invoked. + * + *

Note that the byte is written as given; to write a character that + * will be translated according to the platform's default character + * encoding, use the print(char) or println(char) + * methods. + * + * @param b The byte to be written + * @see #print(char) + * @see #println(char) + */ + public void write(int b) { + try { + synchronized (this) { + ensureOpen(); + out.write(b); + if ((b == '\n') && autoFlush) + out.flush(); + } + } + catch (InterruptedIOException x) { + Thread.currentThread().interrupt(); + } + catch (IOException x) { + trouble = true; + } + } + + /** + * Writes len bytes from the specified byte array starting at + * offset off to this stream. If automatic flushing is + * enabled then the flush method will be invoked. + * + *

Note that the bytes will be written as given; to write characters + * that will be translated according to the platform's default character + * encoding, use the print(char) or println(char) + * methods. + * + * @param buf A byte array + * @param off Offset from which to start taking bytes + * @param len Number of bytes to write + */ + public void write(byte buf[], int off, int len) { + try { + synchronized (this) { + ensureOpen(); + out.write(buf, off, len); + if (autoFlush) + out.flush(); + } + } + catch (InterruptedIOException x) { + Thread.currentThread().interrupt(); + } + catch (IOException x) { + trouble = true; + } + } + + /* + * The following private methods on the text- and character-output streams + * always flush the stream buffers, so that writes to the underlying byte + * stream occur as promptly as with the original PrintStream. + */ + + private void write(char buf[]) { + try { + synchronized (this) { + ensureOpen(); + textOut.write(buf); + textOut.flushBuffer(); + charOut.flushBuffer(); + if (autoFlush) { + for (int i = 0; i < buf.length; i++) + if (buf[i] == '\n') + out.flush(); + } + } + } + catch (InterruptedIOException x) { + Thread.currentThread().interrupt(); + } + catch (IOException x) { + trouble = true; + } + } + + private void write(String s) { + try { + synchronized (this) { + ensureOpen(); + textOut.write(s); + textOut.flushBuffer(); + charOut.flushBuffer(); + if (autoFlush && (s.indexOf('\n') >= 0)) + out.flush(); + } + } + catch (InterruptedIOException x) { + Thread.currentThread().interrupt(); + } + catch (IOException x) { + trouble = true; + } + } + + private void newLine() { + try { + synchronized (this) { + ensureOpen(); + textOut.newLine(); + textOut.flushBuffer(); + charOut.flushBuffer(); + if (autoFlush) + out.flush(); + } + } + catch (InterruptedIOException x) { + Thread.currentThread().interrupt(); + } + catch (IOException x) { + trouble = true; + } + } + + /* Methods that do not terminate lines */ + + /** + * Prints a boolean value. The string produced by {@link + * java.lang.String#valueOf(boolean)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param b The boolean to be printed + */ + public void print(boolean b) { + write(b ? "true" : "false"); + } + + /** + * Prints a character. The character is translated into one or more bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param c The char to be printed + */ + public void print(char c) { + write(String.valueOf(c)); + } + + /** + * Prints an integer. The string produced by {@link + * java.lang.String#valueOf(int)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param i The int to be printed + * @see java.lang.Integer#toString(int) + */ + public void print(int i) { + write(String.valueOf(i)); + } + + /** + * Prints a long integer. The string produced by {@link + * java.lang.String#valueOf(long)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param l The long to be printed + * @see java.lang.Long#toString(long) + */ + public void print(long l) { + write(String.valueOf(l)); + } + + /** + * Prints a floating-point number. The string produced by {@link + * java.lang.String#valueOf(float)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param f The float to be printed + * @see java.lang.Float#toString(float) + */ + public void print(float f) { + write(String.valueOf(f)); + } + + /** + * Prints a double-precision floating-point number. The string produced by + * {@link java.lang.String#valueOf(double)} is translated into + * bytes according to the platform's default character encoding, and these + * bytes are written in exactly the manner of the {@link + * #write(int)} method. + * + * @param d The double to be printed + * @see java.lang.Double#toString(double) + */ + public void print(double d) { + write(String.valueOf(d)); + } + + /** + * Prints an array of characters. The characters are converted into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param s The array of chars to be printed + * + * @throws NullPointerException If s is null + */ + public void print(char s[]) { + write(s); + } + + /** + * Prints a string. If the argument is null then the string + * "null" is printed. Otherwise, the string's characters are + * converted into bytes according to the platform's default character + * encoding, and these bytes are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param s The String to be printed + */ + public void print(String s) { + if (s == null) { + s = "null"; + } + write(s); + } + + /** + * Prints an object. The string produced by the {@link + * java.lang.String#valueOf(Object)} method is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param obj The Object to be printed + * @see java.lang.Object#toString() + */ + public void print(Object obj) { + write(String.valueOf(obj)); + } + + + /* Methods that do terminate lines */ + + /** + * Terminates the current line by writing the line separator string. The + * line separator string is defined by the system property + * line.separator, and is not necessarily a single newline + * character ('\n'). + */ + public void println() { + newLine(); + } + + /** + * Prints a boolean and then terminate the line. This method behaves as + * though it invokes {@link #print(boolean)} and then + * {@link #println()}. + * + * @param x The boolean to be printed + */ + public void println(boolean x) { + synchronized (this) { + print(x); + newLine(); + } + } + + /** + * Prints a character and then terminate the line. This method behaves as + * though it invokes {@link #print(char)} and then + * {@link #println()}. + * + * @param x The char to be printed. + */ + public void println(char x) { + synchronized (this) { + print(x); + newLine(); + } + } + + /** + * Prints an integer and then terminate the line. This method behaves as + * though it invokes {@link #print(int)} and then + * {@link #println()}. + * + * @param x The int to be printed. + */ + public void println(int x) { + synchronized (this) { + print(x); + newLine(); + } + } + + /** + * Prints a long and then terminate the line. This method behaves as + * though it invokes {@link #print(long)} and then + * {@link #println()}. + * + * @param x a The long to be printed. + */ + public void println(long x) { + synchronized (this) { + print(x); + newLine(); + } + } + + /** + * Prints a float and then terminate the line. This method behaves as + * though it invokes {@link #print(float)} and then + * {@link #println()}. + * + * @param x The float to be printed. + */ + public void println(float x) { + synchronized (this) { + print(x); + newLine(); + } + } + + /** + * Prints a double and then terminate the line. This method behaves as + * though it invokes {@link #print(double)} and then + * {@link #println()}. + * + * @param x The double to be printed. + */ + public void println(double x) { + synchronized (this) { + print(x); + newLine(); + } + } + + /** + * Prints an array of characters and then terminate the line. This method + * behaves as though it invokes {@link #print(char[])} and + * then {@link #println()}. + * + * @param x an array of chars to print. + */ + public void println(char x[]) { + synchronized (this) { + print(x); + newLine(); + } + } + + /** + * Prints a String and then terminate the line. This method behaves as + * though it invokes {@link #print(String)} and then + * {@link #println()}. + * + * @param x The String to be printed. + */ + public void println(String x) { + synchronized (this) { + print(x); + newLine(); + } + } + + /** + * Prints an Object and then terminate the line. This method calls + * at first String.valueOf(x) to get the printed object's string value, + * then behaves as + * though it invokes {@link #print(String)} and then + * {@link #println()}. + * + * @param x The Object to be printed. + */ + public void println(Object x) { + String s = String.valueOf(x); + synchronized (this) { + print(s); + newLine(); + } + } + + + /** + * A convenience method to write a formatted string to this output stream + * using the specified format string and arguments. + * + *

An invocation of this method of the form out.printf(format, + * args) behaves in exactly the same way as the invocation + * + *

+     *     out.format(format, args) 
+ * + * @param format + * A format string as described in Format string syntax + * + * @param args + * Arguments referenced by the format specifiers in the format + * string. If there are more arguments than format specifiers, the + * extra arguments are ignored. The number of arguments is + * variable and may be zero. The maximum number of arguments is + * limited by the maximum dimension of a Java array as defined by + * The Java™ Virtual Machine Specification. + * The behaviour on a + * null argument depends on the conversion. + * + * @throws IllegalFormatException + * If a format string contains an illegal syntax, a format + * specifier that is incompatible with the given arguments, + * insufficient arguments given the format string, or other + * illegal conditions. For specification of all possible + * formatting errors, see the Details section of the + * formatter class specification. + * + * @throws NullPointerException + * If the format is null + * + * @return This output stream + * + * @since 1.5 + */ + public PrintStream printf(String format, Object ... args) { + append(format).append(Arrays.toString(args)); + return this; + } + + /** + * A convenience method to write a formatted string to this output stream + * using the specified format string and arguments. + * + *

An invocation of this method of the form out.printf(l, format, + * args) behaves in exactly the same way as the invocation + * + *

+     *     out.format(l, format, args) 
+ * + * @param l + * The {@linkplain java.util.Locale locale} to apply during + * formatting. If l is null then no localization + * is applied. + * + * @param format + * A format string as described in Format string syntax + * + * @param args + * Arguments referenced by the format specifiers in the format + * string. If there are more arguments than format specifiers, the + * extra arguments are ignored. The number of arguments is + * variable and may be zero. The maximum number of arguments is + * limited by the maximum dimension of a Java array as defined by + * The Java™ Virtual Machine Specification. + * The behaviour on a + * null argument depends on the conversion. + * + * @throws IllegalFormatException + * If a format string contains an illegal syntax, a format + * specifier that is incompatible with the given arguments, + * insufficient arguments given the format string, or other + * illegal conditions. For specification of all possible + * formatting errors, see the Details section of the + * formatter class specification. + * + * @throws NullPointerException + * If the format is null + * + * @return This output stream + * + * @since 1.5 + */ +// public PrintStream printf(Locale l, String format, Object ... args) { +// return format(l, format, args); +// } + + /** + * Writes a formatted string to this output stream using the specified + * format string and arguments. + * + *

The locale always used is the one returned by {@link + * java.util.Locale#getDefault() Locale.getDefault()}, regardless of any + * previous invocations of other formatting methods on this object. + * + * @param format + * A format string as described in Format string syntax + * + * @param args + * Arguments referenced by the format specifiers in the format + * string. If there are more arguments than format specifiers, the + * extra arguments are ignored. The number of arguments is + * variable and may be zero. The maximum number of arguments is + * limited by the maximum dimension of a Java array as defined by + * The Java™ Virtual Machine Specification. + * The behaviour on a + * null argument depends on the conversion. + * + * @throws IllegalFormatException + * If a format string contains an illegal syntax, a format + * specifier that is incompatible with the given arguments, + * insufficient arguments given the format string, or other + * illegal conditions. For specification of all possible + * formatting errors, see the Details section of the + * formatter class specification. + * + * @throws NullPointerException + * If the format is null + * + * @return This output stream + * + * @since 1.5 + */ +// public PrintStream format(String format, Object ... args) { +// try { +// synchronized (this) { +// ensureOpen(); +// if ((formatter == null) +// || (formatter.locale() != Locale.getDefault())) +// formatter = new Formatter((Appendable) this); +// formatter.format(Locale.getDefault(), format, args); +// } +// } catch (InterruptedIOException x) { +// Thread.currentThread().interrupt(); +// } catch (IOException x) { +// trouble = true; +// } +// return this; +// } + + /** + * Writes a formatted string to this output stream using the specified + * format string and arguments. + * + * @param l + * The {@linkplain java.util.Locale locale} to apply during + * formatting. If l is null then no localization + * is applied. + * + * @param format + * A format string as described in Format string syntax + * + * @param args + * Arguments referenced by the format specifiers in the format + * string. If there are more arguments than format specifiers, the + * extra arguments are ignored. The number of arguments is + * variable and may be zero. The maximum number of arguments is + * limited by the maximum dimension of a Java array as defined by + * The Java™ Virtual Machine Specification. + * The behaviour on a + * null argument depends on the conversion. + * + * @throws IllegalFormatException + * If a format string contains an illegal syntax, a format + * specifier that is incompatible with the given arguments, + * insufficient arguments given the format string, or other + * illegal conditions. For specification of all possible + * formatting errors, see the Details section of the + * formatter class specification. + * + * @throws NullPointerException + * If the format is null + * + * @return This output stream + * + * @since 1.5 + */ +//// public PrintStream format(Locale l, String format, Object ... args) { +//// try { +//// synchronized (this) { +//// ensureOpen(); +//// if ((formatter == null) +//// || (formatter.locale() != l)) +//// formatter = new Formatter(this, l); +//// formatter.format(l, format, args); +//// } +//// } catch (InterruptedIOException x) { +//// Thread.currentThread().interrupt(); +//// } catch (IOException x) { +//// trouble = true; +//// } +//// return this; +//// } + + /** + * Appends the specified character sequence to this output stream. + * + *

An invocation of this method of the form out.append(csq) + * behaves in exactly the same way as the invocation + * + *

+     *     out.print(csq.toString()) 
+ * + *

Depending on the specification of toString for the + * character sequence csq, the entire sequence may not be + * appended. For instance, invoking then toString method of a + * character buffer will return a subsequence whose content depends upon + * the buffer's position and limit. + * + * @param csq + * The character sequence to append. If csq is + * null, then the four characters "null" are + * appended to this output stream. + * + * @return This output stream + * + * @since 1.5 + */ + public PrintStream append(CharSequence csq) { + if (csq == null) + print("null"); + else + print(csq.toString()); + return this; + } + + /** + * Appends a subsequence of the specified character sequence to this output + * stream. + * + *

An invocation of this method of the form out.append(csq, start, + * end) when csq is not null, behaves in + * exactly the same way as the invocation + * + *

+     *     out.print(csq.subSequence(start, end).toString()) 
+ * + * @param csq + * The character sequence from which a subsequence will be + * appended. If csq is null, then characters + * will be appended as if csq contained the four + * characters "null". + * + * @param start + * The index of the first character in the subsequence + * + * @param end + * The index of the character following the last character in the + * subsequence + * + * @return This output stream + * + * @throws IndexOutOfBoundsException + * If start or end are negative, start + * is greater than end, or end is greater than + * csq.length() + * + * @since 1.5 + */ + public PrintStream append(CharSequence csq, int start, int end) { + CharSequence cs = (csq == null ? "null" : csq); + write(cs.subSequence(start, end).toString()); + return this; + } + + /** + * Appends the specified character to this output stream. + * + *

An invocation of this method of the form out.append(c) + * behaves in exactly the same way as the invocation + * + *

+     *     out.print(c) 
+ * + * @param c + * The 16-bit character to append + * + * @return This output stream + * + * @since 1.5 + */ + public PrintStream append(char c) { + print(c); + return this; + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/io/PrintWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/PrintWriter.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,1030 @@ +/* + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +import java.io.PrintStream.Charset; +import java.io.PrintStream.Formatter; +import java.util.Arrays; +import java.util.Objects; + +/** + * Prints formatted representations of objects to a text-output stream. This + * class implements all of the print methods found in {@link + * PrintStream}. It does not contain methods for writing raw bytes, for which + * a program should use unencoded byte streams. + * + *

Unlike the {@link PrintStream} class, if automatic flushing is enabled + * it will be done only when one of the println, printf, or + * format methods is invoked, rather than whenever a newline character + * happens to be output. These methods use the platform's own notion of line + * separator rather than the newline character. + * + *

Methods in this class never throw I/O exceptions, although some of its + * constructors may. The client may inquire as to whether any errors have + * occurred by invoking {@link #checkError checkError()}. + * + * @author Frank Yellin + * @author Mark Reinhold + * @since JDK1.1 + */ + +public class PrintWriter extends Writer { + + /** + * The underlying character-output stream of this + * PrintWriter. + * + * @since 1.2 + */ + protected Writer out; + + private final boolean autoFlush; + private boolean trouble = false; + private Formatter formatter; +// private PrintStream psOut = null; + + /** + * Line separator string. This is the value of the line.separator + * property at the moment that the stream was created. + */ + private final String lineSeparator; + + /** + * Returns a charset object for the given charset name. + * @throws NullPointerException is csn is null + * @throws UnsupportedEncodingException if the charset is not supported + */ + private static Charset toCharset(String csn) + throws UnsupportedEncodingException + { + return PrintStream.toCharset(csn); + } + + /** + * Creates a new PrintWriter, without automatic line flushing. + * + * @param out A character-output stream + */ + public PrintWriter (Writer out) { + this(out, false); + } + + /** + * Creates a new PrintWriter. + * + * @param out A character-output stream + * @param autoFlush A boolean; if true, the println, + * printf, or format methods will + * flush the output buffer + */ + public PrintWriter(Writer out, + boolean autoFlush) { + super(out); + this.out = out; + this.autoFlush = autoFlush; + lineSeparator = "\n"; + } + + /** + * Creates a new PrintWriter, without automatic line flushing, from an + * existing OutputStream. This convenience constructor creates the + * necessary intermediate OutputStreamWriter, which will convert characters + * into bytes using the default character encoding. + * + * @param out An output stream + * + * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream) + */ + public PrintWriter(OutputStream out) { + this(out, false); + } + + /** + * Creates a new PrintWriter from an existing OutputStream. This + * convenience constructor creates the necessary intermediate + * OutputStreamWriter, which will convert characters into bytes using the + * default character encoding. + * + * @param out An output stream + * @param autoFlush A boolean; if true, the println, + * printf, or format methods will + * flush the output buffer + * + * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream) + */ + public PrintWriter(OutputStream out, boolean autoFlush) { + this(new BufferedWriter(new OutputStreamWriter(out)), autoFlush); + + // save print stream for error propagation +// if (out instanceof java.io.PrintStream) { +// psOut = (PrintStream) out; +// } + } + + /** + * Creates a new PrintWriter, without automatic line flushing, with the + * specified file name. This convenience constructor creates the necessary + * intermediate {@link java.io.OutputStreamWriter OutputStreamWriter}, + * which will encode characters using the {@linkplain + * java.nio.charset.Charset#defaultCharset() default charset} for this + * instance of the Java virtual machine. + * + * @param fileName + * The name of the file to use as the destination of this writer. + * If the file exists then it will be truncated to zero size; + * otherwise, a new file will be created. The output will be + * written to the file and is buffered. + * + * @throws FileNotFoundException + * If the given string does not denote an existing, writable + * regular file and a new regular file of that name cannot be + * created, or if some other error occurs while opening or + * creating the file + * + * @throws SecurityException + * If a security manager is present and {@link + * SecurityManager#checkWrite checkWrite(fileName)} denies write + * access to the file + * + * @since 1.5 + */ + public PrintWriter(String fileName) throws FileNotFoundException { + super(); + throw new FileNotFoundException(); + } + + /* Private constructor */ + private PrintWriter(Charset charset, File file) + throws FileNotFoundException + { + super(); + throw new FileNotFoundException(); + } + + /** + * Creates a new PrintWriter, without automatic line flushing, with the + * specified file name and charset. This convenience constructor creates + * the necessary intermediate {@link java.io.OutputStreamWriter + * OutputStreamWriter}, which will encode characters using the provided + * charset. + * + * @param fileName + * The name of the file to use as the destination of this writer. + * If the file exists then it will be truncated to zero size; + * otherwise, a new file will be created. The output will be + * written to the file and is buffered. + * + * @param csn + * The name of a supported {@linkplain java.nio.charset.Charset + * charset} + * + * @throws FileNotFoundException + * If the given string does not denote an existing, writable + * regular file and a new regular file of that name cannot be + * created, or if some other error occurs while opening or + * creating the file + * + * @throws SecurityException + * If a security manager is present and {@link + * SecurityManager#checkWrite checkWrite(fileName)} denies write + * access to the file + * + * @throws UnsupportedEncodingException + * If the named charset is not supported + * + * @since 1.5 + */ + public PrintWriter(String fileName, String csn) + throws FileNotFoundException, UnsupportedEncodingException + { + this(toCharset(csn), new File(fileName)); + } + + /** + * Creates a new PrintWriter, without automatic line flushing, with the + * specified file. This convenience constructor creates the necessary + * intermediate {@link java.io.OutputStreamWriter OutputStreamWriter}, + * which will encode characters using the {@linkplain + * java.nio.charset.Charset#defaultCharset() default charset} for this + * instance of the Java virtual machine. + * + * @param file + * The file to use as the destination of this writer. If the file + * exists then it will be truncated to zero size; otherwise, a new + * file will be created. The output will be written to the file + * and is buffered. + * + * @throws FileNotFoundException + * If the given file object does not denote an existing, writable + * regular file and a new regular file of that name cannot be + * created, or if some other error occurs while opening or + * creating the file + * + * @throws SecurityException + * If a security manager is present and {@link + * SecurityManager#checkWrite checkWrite(file.getPath())} + * denies write access to the file + * + * @since 1.5 + */ + public PrintWriter(File file) throws FileNotFoundException { + super(); + throw new FileNotFoundException(); + } + + /** + * Creates a new PrintWriter, without automatic line flushing, with the + * specified file and charset. This convenience constructor creates the + * necessary intermediate {@link java.io.OutputStreamWriter + * OutputStreamWriter}, which will encode characters using the provided + * charset. + * + * @param file + * The file to use as the destination of this writer. If the file + * exists then it will be truncated to zero size; otherwise, a new + * file will be created. The output will be written to the file + * and is buffered. + * + * @param csn + * The name of a supported {@linkplain java.nio.charset.Charset + * charset} + * + * @throws FileNotFoundException + * If the given file object does not denote an existing, writable + * regular file and a new regular file of that name cannot be + * created, or if some other error occurs while opening or + * creating the file + * + * @throws SecurityException + * If a security manager is present and {@link + * SecurityManager#checkWrite checkWrite(file.getPath())} + * denies write access to the file + * + * @throws UnsupportedEncodingException + * If the named charset is not supported + * + * @since 1.5 + */ + public PrintWriter(File file, String csn) + throws FileNotFoundException, UnsupportedEncodingException + { + this(toCharset(csn), file); + } + + /** Checks to make sure that the stream has not been closed */ + private void ensureOpen() throws IOException { + if (out == null) + throw new IOException("Stream closed"); + } + + /** + * Flushes the stream. + * @see #checkError() + */ + public void flush() { + try { + synchronized (lock) { + ensureOpen(); + out.flush(); + } + } + catch (IOException x) { + trouble = true; + } + } + + /** + * Closes the stream and releases any system resources associated + * with it. Closing a previously closed stream has no effect. + * + * @see #checkError() + */ + public void close() { + try { + synchronized (lock) { + if (out == null) + return; + out.close(); + out = null; + } + } + catch (IOException x) { + trouble = true; + } + } + + /** + * Flushes the stream if it's not closed and checks its error state. + * + * @return true if the print stream has encountered an error, + * either on the underlying output stream or during a format + * conversion. + */ + public boolean checkError() { + if (out != null) { + flush(); + } + if (out instanceof java.io.PrintWriter) { + PrintWriter pw = (PrintWriter) out; + return pw.checkError(); + } else +// if (psOut != null) { +// return psOut.checkError(); +// } + return trouble; + } + + /** + * Indicates that an error has occurred. + * + *

This method will cause subsequent invocations of {@link + * #checkError()} to return true until {@link + * #clearError()} is invoked. + */ + protected void setError() { + trouble = true; + } + + /** + * Clears the error state of this stream. + * + *

This method will cause subsequent invocations of {@link + * #checkError()} to return false until another write + * operation fails and invokes {@link #setError()}. + * + * @since 1.6 + */ + protected void clearError() { + trouble = false; + } + + /* + * Exception-catching, synchronized output operations, + * which also implement the write() methods of Writer + */ + + /** + * Writes a single character. + * @param c int specifying a character to be written. + */ + public void write(int c) { + try { + synchronized (lock) { + ensureOpen(); + out.write(c); + } + } + catch (InterruptedIOException x) { + Thread.currentThread().interrupt(); + } + catch (IOException x) { + trouble = true; + } + } + + /** + * Writes A Portion of an array of characters. + * @param buf Array of characters + * @param off Offset from which to start writing characters + * @param len Number of characters to write + */ + public void write(char buf[], int off, int len) { + try { + synchronized (lock) { + ensureOpen(); + out.write(buf, off, len); + } + } + catch (InterruptedIOException x) { + Thread.currentThread().interrupt(); + } + catch (IOException x) { + trouble = true; + } + } + + /** + * Writes an array of characters. This method cannot be inherited from the + * Writer class because it must suppress I/O exceptions. + * @param buf Array of characters to be written + */ + public void write(char buf[]) { + write(buf, 0, buf.length); + } + + /** + * Writes a portion of a string. + * @param s A String + * @param off Offset from which to start writing characters + * @param len Number of characters to write + */ + public void write(String s, int off, int len) { + try { + synchronized (lock) { + ensureOpen(); + out.write(s, off, len); + } + } + catch (InterruptedIOException x) { + Thread.currentThread().interrupt(); + } + catch (IOException x) { + trouble = true; + } + } + + /** + * Writes a string. This method cannot be inherited from the Writer class + * because it must suppress I/O exceptions. + * @param s String to be written + */ + public void write(String s) { + write(s, 0, s.length()); + } + + private void newLine() { + try { + synchronized (lock) { + ensureOpen(); + out.write(lineSeparator); + if (autoFlush) + out.flush(); + } + } + catch (InterruptedIOException x) { + Thread.currentThread().interrupt(); + } + catch (IOException x) { + trouble = true; + } + } + + /* Methods that do not terminate lines */ + + /** + * Prints a boolean value. The string produced by {@link + * java.lang.String#valueOf(boolean)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link + * #write(int)} method. + * + * @param b The boolean to be printed + */ + public void print(boolean b) { + write(b ? "true" : "false"); + } + + /** + * Prints a character. The character is translated into one or more bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link + * #write(int)} method. + * + * @param c The char to be printed + */ + public void print(char c) { + write(c); + } + + /** + * Prints an integer. The string produced by {@link + * java.lang.String#valueOf(int)} is translated into bytes according + * to the platform's default character encoding, and these bytes are + * written in exactly the manner of the {@link #write(int)} + * method. + * + * @param i The int to be printed + * @see java.lang.Integer#toString(int) + */ + public void print(int i) { + write(String.valueOf(i)); + } + + /** + * Prints a long integer. The string produced by {@link + * java.lang.String#valueOf(long)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link #write(int)} + * method. + * + * @param l The long to be printed + * @see java.lang.Long#toString(long) + */ + public void print(long l) { + write(String.valueOf(l)); + } + + /** + * Prints a floating-point number. The string produced by {@link + * java.lang.String#valueOf(float)} is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link #write(int)} + * method. + * + * @param f The float to be printed + * @see java.lang.Float#toString(float) + */ + public void print(float f) { + write(String.valueOf(f)); + } + + /** + * Prints a double-precision floating-point number. The string produced by + * {@link java.lang.String#valueOf(double)} is translated into + * bytes according to the platform's default character encoding, and these + * bytes are written in exactly the manner of the {@link + * #write(int)} method. + * + * @param d The double to be printed + * @see java.lang.Double#toString(double) + */ + public void print(double d) { + write(String.valueOf(d)); + } + + /** + * Prints an array of characters. The characters are converted into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link #write(int)} + * method. + * + * @param s The array of chars to be printed + * + * @throws NullPointerException If s is null + */ + public void print(char s[]) { + write(s); + } + + /** + * Prints a string. If the argument is null then the string + * "null" is printed. Otherwise, the string's characters are + * converted into bytes according to the platform's default character + * encoding, and these bytes are written in exactly the manner of the + * {@link #write(int)} method. + * + * @param s The String to be printed + */ + public void print(String s) { + if (s == null) { + s = "null"; + } + write(s); + } + + /** + * Prints an object. The string produced by the {@link + * java.lang.String#valueOf(Object)} method is translated into bytes + * according to the platform's default character encoding, and these bytes + * are written in exactly the manner of the {@link #write(int)} + * method. + * + * @param obj The Object to be printed + * @see java.lang.Object#toString() + */ + public void print(Object obj) { + write(String.valueOf(obj)); + } + + /* Methods that do terminate lines */ + + /** + * Terminates the current line by writing the line separator string. The + * line separator string is defined by the system property + * line.separator, and is not necessarily a single newline + * character ('\n'). + */ + public void println() { + newLine(); + } + + /** + * Prints a boolean value and then terminates the line. This method behaves + * as though it invokes {@link #print(boolean)} and then + * {@link #println()}. + * + * @param x the boolean value to be printed + */ + public void println(boolean x) { + synchronized (lock) { + print(x); + println(); + } + } + + /** + * Prints a character and then terminates the line. This method behaves as + * though it invokes {@link #print(char)} and then {@link + * #println()}. + * + * @param x the char value to be printed + */ + public void println(char x) { + synchronized (lock) { + print(x); + println(); + } + } + + /** + * Prints an integer and then terminates the line. This method behaves as + * though it invokes {@link #print(int)} and then {@link + * #println()}. + * + * @param x the int value to be printed + */ + public void println(int x) { + synchronized (lock) { + print(x); + println(); + } + } + + /** + * Prints a long integer and then terminates the line. This method behaves + * as though it invokes {@link #print(long)} and then + * {@link #println()}. + * + * @param x the long value to be printed + */ + public void println(long x) { + synchronized (lock) { + print(x); + println(); + } + } + + /** + * Prints a floating-point number and then terminates the line. This method + * behaves as though it invokes {@link #print(float)} and then + * {@link #println()}. + * + * @param x the float value to be printed + */ + public void println(float x) { + synchronized (lock) { + print(x); + println(); + } + } + + /** + * Prints a double-precision floating-point number and then terminates the + * line. This method behaves as though it invokes {@link + * #print(double)} and then {@link #println()}. + * + * @param x the double value to be printed + */ + public void println(double x) { + synchronized (lock) { + print(x); + println(); + } + } + + /** + * Prints an array of characters and then terminates the line. This method + * behaves as though it invokes {@link #print(char[])} and then + * {@link #println()}. + * + * @param x the array of char values to be printed + */ + public void println(char x[]) { + synchronized (lock) { + print(x); + println(); + } + } + + /** + * Prints a String and then terminates the line. This method behaves as + * though it invokes {@link #print(String)} and then + * {@link #println()}. + * + * @param x the String value to be printed + */ + public void println(String x) { + synchronized (lock) { + print(x); + println(); + } + } + + /** + * Prints an Object and then terminates the line. This method calls + * at first String.valueOf(x) to get the printed object's string value, + * then behaves as + * though it invokes {@link #print(String)} and then + * {@link #println()}. + * + * @param x The Object to be printed. + */ + public void println(Object x) { + String s = String.valueOf(x); + synchronized (lock) { + print(s); + println(); + } + } + + /** + * A convenience method to write a formatted string to this writer using + * the specified format string and arguments. If automatic flushing is + * enabled, calls to this method will flush the output buffer. + * + *

An invocation of this method of the form out.printf(format, + * args) behaves in exactly the same way as the invocation + * + *

+     *     out.format(format, args) 
+ * + * @param format + * A format string as described in Format string syntax. + * + * @param args + * Arguments referenced by the format specifiers in the format + * string. If there are more arguments than format specifiers, the + * extra arguments are ignored. The number of arguments is + * variable and may be zero. The maximum number of arguments is + * limited by the maximum dimension of a Java array as defined by + * The Java™ Virtual Machine Specification. + * The behaviour on a + * null argument depends on the conversion. + * + * @throws IllegalFormatException + * If a format string contains an illegal syntax, a format + * specifier that is incompatible with the given arguments, + * insufficient arguments given the format string, or other + * illegal conditions. For specification of all possible + * formatting errors, see the Details section of the + * formatter class specification. + * + * @throws NullPointerException + * If the format is null + * + * @return This writer + * + * @since 1.5 + */ + public PrintWriter printf(String format, Object ... args) { + return format(format, args); + } + + /** + * A convenience method to write a formatted string to this writer using + * the specified format string and arguments. If automatic flushing is + * enabled, calls to this method will flush the output buffer. + * + *

An invocation of this method of the form out.printf(l, format, + * args) behaves in exactly the same way as the invocation + * + *

+     *     out.format(l, format, args) 
+ * + * @param l + * The {@linkplain java.util.Locale locale} to apply during + * formatting. If l is null then no localization + * is applied. + * + * @param format + * A format string as described in Format string syntax. + * + * @param args + * Arguments referenced by the format specifiers in the format + * string. If there are more arguments than format specifiers, the + * extra arguments are ignored. The number of arguments is + * variable and may be zero. The maximum number of arguments is + * limited by the maximum dimension of a Java array as defined by + * The Java™ Virtual Machine Specification. + * The behaviour on a + * null argument depends on the conversion. + * + * @throws IllegalFormatException + * If a format string contains an illegal syntax, a format + * specifier that is incompatible with the given arguments, + * insufficient arguments given the format string, or other + * illegal conditions. For specification of all possible + * formatting errors, see the Details section of the + * formatter class specification. + * + * @throws NullPointerException + * If the format is null + * + * @return This writer + * + * @since 1.5 + */ +// public PrintWriter printf(Locale l, String format, Object ... args) { +// return format(l, format, args); +// } + + /** + * Writes a formatted string to this writer using the specified format + * string and arguments. If automatic flushing is enabled, calls to this + * method will flush the output buffer. + * + *

The locale always used is the one returned by {@link + * java.util.Locale#getDefault() Locale.getDefault()}, regardless of any + * previous invocations of other formatting methods on this object. + * + * @param format + * A format string as described in Format string syntax. + * + * @param args + * Arguments referenced by the format specifiers in the format + * string. If there are more arguments than format specifiers, the + * extra arguments are ignored. The number of arguments is + * variable and may be zero. The maximum number of arguments is + * limited by the maximum dimension of a Java array as defined by + * The Java™ Virtual Machine Specification. + * The behaviour on a + * null argument depends on the conversion. + * + * @throws IllegalFormatException + * If a format string contains an illegal syntax, a format + * specifier that is incompatible with the given arguments, + * insufficient arguments given the format string, or other + * illegal conditions. For specification of all possible + * formatting errors, see the Details section of the + * Formatter class specification. + * + * @throws NullPointerException + * If the format is null + * + * @return This writer + * + * @since 1.5 + */ + public PrintWriter format(String format, Object ... args) { + append(format).append(Arrays.toString(args)); + return this; + } + + /** + * Writes a formatted string to this writer using the specified format + * string and arguments. If automatic flushing is enabled, calls to this + * method will flush the output buffer. + * + * @param l + * The {@linkplain java.util.Locale locale} to apply during + * formatting. If l is null then no localization + * is applied. + * + * @param format + * A format string as described in Format string syntax. + * + * @param args + * Arguments referenced by the format specifiers in the format + * string. If there are more arguments than format specifiers, the + * extra arguments are ignored. The number of arguments is + * variable and may be zero. The maximum number of arguments is + * limited by the maximum dimension of a Java array as defined by + * The Java™ Virtual Machine Specification. + * The behaviour on a + * null argument depends on the conversion. + * + * @throws IllegalFormatException + * If a format string contains an illegal syntax, a format + * specifier that is incompatible with the given arguments, + * insufficient arguments given the format string, or other + * illegal conditions. For specification of all possible + * formatting errors, see the Details section of the + * formatter class specification. + * + * @throws NullPointerException + * If the format is null + * + * @return This writer + * + * @since 1.5 + */ +// public PrintWriter format(Locale l, String format, Object ... args) { +// return format(format, args); +// } + + /** + * Appends the specified character sequence to this writer. + * + *

An invocation of this method of the form out.append(csq) + * behaves in exactly the same way as the invocation + * + *

+     *     out.write(csq.toString()) 
+ * + *

Depending on the specification of toString for the + * character sequence csq, the entire sequence may not be + * appended. For instance, invoking the toString method of a + * character buffer will return a subsequence whose content depends upon + * the buffer's position and limit. + * + * @param csq + * The character sequence to append. If csq is + * null, then the four characters "null" are + * appended to this writer. + * + * @return This writer + * + * @since 1.5 + */ + public PrintWriter append(CharSequence csq) { + if (csq == null) + write("null"); + else + write(csq.toString()); + return this; + } + + /** + * Appends a subsequence of the specified character sequence to this writer. + * + *

An invocation of this method of the form out.append(csq, start, + * end) when csq is not null, behaves in + * exactly the same way as the invocation + * + *

+     *     out.write(csq.subSequence(start, end).toString()) 
+ * + * @param csq + * The character sequence from which a subsequence will be + * appended. If csq is null, then characters + * will be appended as if csq contained the four + * characters "null". + * + * @param start + * The index of the first character in the subsequence + * + * @param end + * The index of the character following the last character in the + * subsequence + * + * @return This writer + * + * @throws IndexOutOfBoundsException + * If start or end are negative, start + * is greater than end, or end is greater than + * csq.length() + * + * @since 1.5 + */ + public PrintWriter append(CharSequence csq, int start, int end) { + CharSequence cs = (csq == null ? "null" : csq); + write(cs.subSequence(start, end).toString()); + return this; + } + + /** + * Appends the specified character to this writer. + * + *

An invocation of this method of the form out.append(c) + * behaves in exactly the same way as the invocation + * + *

+     *     out.write(c) 
+ * + * @param c + * The 16-bit character to append + * + * @return This writer + * + * @since 1.5 + */ + public PrintWriter append(char c) { + write(c); + return this; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/io/Writer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/Writer.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,325 @@ +/* + * Copyright (c) 1996, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + + +/** + * Abstract class for writing to character streams. The only methods that a + * subclass must implement are write(char[], int, int), flush(), and close(). + * Most subclasses, however, will override some of the methods defined here in + * order to provide higher efficiency, additional functionality, or both. + * + * @see Writer + * @see BufferedWriter + * @see CharArrayWriter + * @see FilterWriter + * @see OutputStreamWriter + * @see FileWriter + * @see PipedWriter + * @see PrintWriter + * @see StringWriter + * @see Reader + * + * @author Mark Reinhold + * @since JDK1.1 + */ + +public abstract class Writer implements Appendable, Closeable, Flushable { + + /** + * Temporary buffer used to hold writes of strings and single characters + */ + private char[] writeBuffer; + + /** + * Size of writeBuffer, must be >= 1 + */ + private final int writeBufferSize = 1024; + + /** + * The object used to synchronize operations on this stream. For + * efficiency, a character-stream object may use an object other than + * itself to protect critical sections. A subclass should therefore use + * the object in this field rather than this or a synchronized + * method. + */ + protected Object lock; + + /** + * Creates a new character-stream writer whose critical sections will + * synchronize on the writer itself. + */ + protected Writer() { + this.lock = this; + } + + /** + * Creates a new character-stream writer whose critical sections will + * synchronize on the given object. + * + * @param lock + * Object to synchronize on + */ + protected Writer(Object lock) { + if (lock == null) { + throw new NullPointerException(); + } + this.lock = lock; + } + + /** + * Writes a single character. The character to be written is contained in + * the 16 low-order bits of the given integer value; the 16 high-order bits + * are ignored. + * + *

Subclasses that intend to support efficient single-character output + * should override this method. + * + * @param c + * int specifying a character to be written + * + * @throws IOException + * If an I/O error occurs + */ + public void write(int c) throws IOException { + synchronized (lock) { + if (writeBuffer == null){ + writeBuffer = new char[writeBufferSize]; + } + writeBuffer[0] = (char) c; + write(writeBuffer, 0, 1); + } + } + + /** + * Writes an array of characters. + * + * @param cbuf + * Array of characters to be written + * + * @throws IOException + * If an I/O error occurs + */ + public void write(char cbuf[]) throws IOException { + write(cbuf, 0, cbuf.length); + } + + /** + * Writes a portion of an array of characters. + * + * @param cbuf + * Array of characters + * + * @param off + * Offset from which to start writing characters + * + * @param len + * Number of characters to write + * + * @throws IOException + * If an I/O error occurs + */ + abstract public void write(char cbuf[], int off, int len) throws IOException; + + /** + * Writes a string. + * + * @param str + * String to be written + * + * @throws IOException + * If an I/O error occurs + */ + public void write(String str) throws IOException { + write(str, 0, str.length()); + } + + /** + * Writes a portion of a string. + * + * @param str + * A String + * + * @param off + * Offset from which to start writing characters + * + * @param len + * Number of characters to write + * + * @throws IndexOutOfBoundsException + * If off is negative, or len is negative, + * or off+len is negative or greater than the length + * of the given string + * + * @throws IOException + * If an I/O error occurs + */ + public void write(String str, int off, int len) throws IOException { + synchronized (lock) { + char cbuf[]; + if (len <= writeBufferSize) { + if (writeBuffer == null) { + writeBuffer = new char[writeBufferSize]; + } + cbuf = writeBuffer; + } else { // Don't permanently allocate very large buffers. + cbuf = new char[len]; + } + str.getChars(off, (off + len), cbuf, 0); + write(cbuf, 0, len); + } + } + + /** + * Appends the specified character sequence to this writer. + * + *

An invocation of this method of the form out.append(csq) + * behaves in exactly the same way as the invocation + * + *

+     *     out.write(csq.toString()) 
+ * + *

Depending on the specification of toString for the + * character sequence csq, the entire sequence may not be + * appended. For instance, invoking the toString method of a + * character buffer will return a subsequence whose content depends upon + * the buffer's position and limit. + * + * @param csq + * The character sequence to append. If csq is + * null, then the four characters "null" are + * appended to this writer. + * + * @return This writer + * + * @throws IOException + * If an I/O error occurs + * + * @since 1.5 + */ + public Writer append(CharSequence csq) throws IOException { + if (csq == null) + write("null"); + else + write(csq.toString()); + return this; + } + + /** + * Appends a subsequence of the specified character sequence to this writer. + * Appendable. + * + *

An invocation of this method of the form out.append(csq, start, + * end) when csq is not null behaves in exactly the + * same way as the invocation + * + *

+     *     out.write(csq.subSequence(start, end).toString()) 
+ * + * @param csq + * The character sequence from which a subsequence will be + * appended. If csq is null, then characters + * will be appended as if csq contained the four + * characters "null". + * + * @param start + * The index of the first character in the subsequence + * + * @param end + * The index of the character following the last character in the + * subsequence + * + * @return This writer + * + * @throws IndexOutOfBoundsException + * If start or end are negative, start + * is greater than end, or end is greater than + * csq.length() + * + * @throws IOException + * If an I/O error occurs + * + * @since 1.5 + */ + public Writer append(CharSequence csq, int start, int end) throws IOException { + CharSequence cs = (csq == null ? "null" : csq); + write(cs.subSequence(start, end).toString()); + return this; + } + + /** + * Appends the specified character to this writer. + * + *

An invocation of this method of the form out.append(c) + * behaves in exactly the same way as the invocation + * + *

+     *     out.write(c) 
+ * + * @param c + * The 16-bit character to append + * + * @return This writer + * + * @throws IOException + * If an I/O error occurs + * + * @since 1.5 + */ + public Writer append(char c) throws IOException { + write(c); + return this; + } + + /** + * Flushes the stream. If the stream has saved any characters from the + * various write() methods in a buffer, write them immediately to their + * intended destination. Then, if that destination is another character or + * byte stream, flush it. Thus one flush() invocation will flush all the + * buffers in a chain of Writers and OutputStreams. + * + *

If the intended destination of this stream is an abstraction provided + * by the underlying operating system, for example a file, then flushing the + * stream guarantees only that bytes previously written to the stream are + * passed to the operating system for writing; it does not guarantee that + * they are actually written to a physical device such as a disk drive. + * + * @throws IOException + * If an I/O error occurs + */ + abstract public void flush() throws IOException; + + /** + * Closes the stream, flushing it first. Once the stream has been closed, + * further write() or flush() invocations will cause an IOException to be + * thrown. Closing a previously closed stream has no effect. + * + * @throws IOException + * If an I/O error occurs + */ + abstract public void close() throws IOException; + +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/lang/StackOverflowError.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/StackOverflowError.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,55 @@ +/* + * Copyright (c) 1994, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang; + +/** + * Thrown when a stack overflow occurs because an application + * recurses too deeply. + * + * @author unascribed + * @since JDK1.0 + */ +public +class StackOverflowError extends VirtualMachineError { + private static final long serialVersionUID = 8609175038441759607L; + + /** + * Constructs a StackOverflowError with no detail message. + */ + public StackOverflowError() { + super(); + } + + /** + * Constructs a StackOverflowError with the specified + * detail message. + * + * @param s the detail message. + */ + public StackOverflowError(String s) { + super(s); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/lang/System.java --- a/rt/emul/compact/src/main/java/java/lang/System.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/compact/src/main/java/java/lang/System.java Mon Oct 07 14:20:58 2013 +0200 @@ -17,6 +17,8 @@ */ package java.lang; +import org.apidesign.bck2brwsr.core.JavaScriptBody; + /** Poor man's re-implementation of most important System methods. * * @author Jaroslav Tulach @@ -33,4 +35,31 @@ return org.apidesign.bck2brwsr.emul.lang.System.currentTimeMillis(); } + public static int identityHashCode(Object obj) { + return obj.defaultHashCode(); + } + + public static String getProperty(String name) { + return null; + } + + public static String getProperty(String key, String def) { + return def; + } + + /** + * Returns the system-dependent line separator string. It always + * returns the same value - the initial value of the {@linkplain + * #getProperty(String) system property} {@code line.separator}. + * + *

On UNIX systems, it returns {@code "\n"}; on Microsoft + * Windows systems it returns {@code "\r\n"}. + */ + public static String lineSeparator() { + return "\n"; + } + + @JavaScriptBody(args = { "exitCode" }, body = "window.close();") + public static void exit(int exitCode) { + } } diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/lang/Thread.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/Thread.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,1544 @@ +/* + * Copyright (c) 1994, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang; + +import java.util.Map; + + +/** + * A thread is a thread of execution in a program. The Java + * Virtual Machine allows an application to have multiple threads of + * execution running concurrently. + *

+ * Every thread has a priority. Threads with higher priority are + * executed in preference to threads with lower priority. Each thread + * may or may not also be marked as a daemon. When code running in + * some thread creates a new Thread object, the new + * thread has its priority initially set equal to the priority of the + * creating thread, and is a daemon thread if and only if the + * creating thread is a daemon. + *

+ * When a Java Virtual Machine starts up, there is usually a single + * non-daemon thread (which typically calls the method named + * main of some designated class). The Java Virtual + * Machine continues to execute threads until either of the following + * occurs: + *

    + *
  • The exit method of class Runtime has been + * called and the security manager has permitted the exit operation + * to take place. + *
  • All threads that are not daemon threads have died, either by + * returning from the call to the run method or by + * throwing an exception that propagates beyond the run + * method. + *
+ *

+ * There are two ways to create a new thread of execution. One is to + * declare a class to be a subclass of Thread. This + * subclass should override the run method of class + * Thread. An instance of the subclass can then be + * allocated and started. For example, a thread that computes primes + * larger than a stated value could be written as follows: + *


+ *     class PrimeThread extends Thread {
+ *         long minPrime;
+ *         PrimeThread(long minPrime) {
+ *             this.minPrime = minPrime;
+ *         }
+ *
+ *         public void run() {
+ *             // compute primes larger than minPrime
+ *              . . .
+ *         }
+ *     }
+ * 

+ *

+ * The following code would then create a thread and start it running: + *

+ *     PrimeThread p = new PrimeThread(143);
+ *     p.start();
+ * 
+ *

+ * The other way to create a thread is to declare a class that + * implements the Runnable interface. That class then + * implements the run method. An instance of the class can + * then be allocated, passed as an argument when creating + * Thread, and started. The same example in this other + * style looks like the following: + *


+ *     class PrimeRun implements Runnable {
+ *         long minPrime;
+ *         PrimeRun(long minPrime) {
+ *             this.minPrime = minPrime;
+ *         }
+ *
+ *         public void run() {
+ *             // compute primes larger than minPrime
+ *              . . .
+ *         }
+ *     }
+ * 

+ *

+ * The following code would then create a thread and start it running: + *

+ *     PrimeRun p = new PrimeRun(143);
+ *     new Thread(p).start();
+ * 
+ *

+ * Every thread has a name for identification purposes. More than + * one thread may have the same name. If a name is not specified when + * a thread is created, a new name is generated for it. + *

+ * Unless otherwise noted, passing a {@code null} argument to a constructor + * or method in this class will cause a {@link NullPointerException} to be + * thrown. + * + * @author unascribed + * @see Runnable + * @see Runtime#exit(int) + * @see #run() + * @see #stop() + * @since JDK1.0 + */ +public +class Thread implements Runnable { + + /** + * The minimum priority that a thread can have. + */ + public final static int MIN_PRIORITY = 1; + + /** + * The default priority that is assigned to a thread. + */ + public final static int NORM_PRIORITY = 5; + + /** + * The maximum priority that a thread can have. + */ + public final static int MAX_PRIORITY = 10; + + private static final Thread ONE = new Thread("main"); + /** + * Returns a reference to the currently executing thread object. + * + * @return the currently executing thread. + */ + public static Thread currentThread() { + return ONE; + } + + /** + * A hint to the scheduler that the current thread is willing to yield + * its current use of a processor. The scheduler is free to ignore this + * hint. + * + *

Yield is a heuristic attempt to improve relative progression + * between threads that would otherwise over-utilise a CPU. Its use + * should be combined with detailed profiling and benchmarking to + * ensure that it actually has the desired effect. + * + *

It is rarely appropriate to use this method. It may be useful + * for debugging or testing purposes, where it may help to reproduce + * bugs due to race conditions. It may also be useful when designing + * concurrency control constructs such as the ones in the + * {@link java.util.concurrent.locks} package. + */ + public static void yield() { + } + + /** + * Causes the currently executing thread to sleep (temporarily cease + * execution) for the specified number of milliseconds, subject to + * the precision and accuracy of system timers and schedulers. The thread + * does not lose ownership of any monitors. + * + * @param millis + * the length of time to sleep in milliseconds + * + * @throws IllegalArgumentException + * if the value of {@code millis} is negative + * + * @throws InterruptedException + * if any thread has interrupted the current thread. The + * interrupted status of the current thread is + * cleared when this exception is thrown. + */ + public static native void sleep(long millis) throws InterruptedException; + + /** + * Causes the currently executing thread to sleep (temporarily cease + * execution) for the specified number of milliseconds plus the specified + * number of nanoseconds, subject to the precision and accuracy of system + * timers and schedulers. The thread does not lose ownership of any + * monitors. + * + * @param millis + * the length of time to sleep in milliseconds + * + * @param nanos + * {@code 0-999999} additional nanoseconds to sleep + * + * @throws IllegalArgumentException + * if the value of {@code millis} is negative, or the value of + * {@code nanos} is not in the range {@code 0-999999} + * + * @throws InterruptedException + * if any thread has interrupted the current thread. The + * interrupted status of the current thread is + * cleared when this exception is thrown. + */ + public static void sleep(long millis, int nanos) + throws InterruptedException { + if (millis < 0) { + throw new IllegalArgumentException("timeout value is negative"); + } + + if (nanos < 0 || nanos > 999999) { + throw new IllegalArgumentException( + "nanosecond timeout value out of range"); + } + + if (nanos >= 500000 || (nanos != 0 && millis == 0)) { + millis++; + } + + sleep(millis); + } + private Runnable target; + private String name; + + /** + * Throws CloneNotSupportedException as a Thread can not be meaningfully + * cloned. Construct a new Thread instead. + * + * @throws CloneNotSupportedException + * always + */ + @Override + protected Object clone() throws CloneNotSupportedException { + throw new CloneNotSupportedException(); + } + + /** + * Allocates a new {@code Thread} object. This constructor has the same + * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} + * {@code (null, null, gname)}, where {@code gname} is a newly generated + * name. Automatically generated names are of the form + * {@code "Thread-"+}n, where n is an integer. + */ + public Thread() { + init(null, null, "Thread-" + nextThreadNum(), 0); + } + + private static int nextThreadNum() { + return -1; + } + + /** + * Allocates a new {@code Thread} object. This constructor has the same + * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} + * {@code (null, target, gname)}, where {@code gname} is a newly generated + * name. Automatically generated names are of the form + * {@code "Thread-"+}n, where n is an integer. + * + * @param target + * the object whose {@code run} method is invoked when this thread + * is started. If {@code null}, this classes {@code run} method does + * nothing. + */ + public Thread(Runnable target) { + init(null, target, "Thread-" + nextThreadNum(), 0); + } + + /** + * Allocates a new {@code Thread} object. This constructor has the same + * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} + * {@code (group, target, gname)} ,where {@code gname} is a newly generated + * name. Automatically generated names are of the form + * {@code "Thread-"+}n, where n is an integer. + * + * @param group + * the thread group. If {@code null} and there is a security + * manager, the group is determined by {@linkplain + * SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}. + * If there is not a security manager or {@code + * SecurityManager.getThreadGroup()} returns {@code null}, the group + * is set to the current thread's thread group. + * + * @param target + * the object whose {@code run} method is invoked when this thread + * is started. If {@code null}, this thread's run method is invoked. + * + * @throws SecurityException + * if the current thread cannot create a thread in the specified + * thread group + */ +// public Thread(ThreadGroup group, Runnable target) { +// init(group, target, "Thread-" + nextThreadNum(), 0); +// } + + /** + * Allocates a new {@code Thread} object. This constructor has the same + * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} + * {@code (null, null, name)}. + * + * @param name + * the name of the new thread + */ + public Thread(String name) { + init(null, null, name, 0); + } + + private void init(Object o1, Runnable trgt, String nm, int i4) { + this.target = trgt; + this.name = nm; + } + + /** + * Allocates a new {@code Thread} object. This constructor has the same + * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} + * {@code (group, null, name)}. + * + * @param group + * the thread group. If {@code null} and there is a security + * manager, the group is determined by {@linkplain + * SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}. + * If there is not a security manager or {@code + * SecurityManager.getThreadGroup()} returns {@code null}, the group + * is set to the current thread's thread group. + * + * @param name + * the name of the new thread + * + * @throws SecurityException + * if the current thread cannot create a thread in the specified + * thread group + */ +// public Thread(ThreadGroup group, String name) { +// init(group, null, name, 0); +// } + + /** + * Allocates a new {@code Thread} object. This constructor has the same + * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} + * {@code (null, target, name)}. + * + * @param target + * the object whose {@code run} method is invoked when this thread + * is started. If {@code null}, this thread's run method is invoked. + * + * @param name + * the name of the new thread + */ + public Thread(Runnable target, String name) { + init(null, target, name, 0); + } + + /** + * Allocates a new {@code Thread} object so that it has {@code target} + * as its run object, has the specified {@code name} as its name, + * and belongs to the thread group referred to by {@code group}. + * + *

If there is a security manager, its + * {@link SecurityManager#checkAccess(ThreadGroup) checkAccess} + * method is invoked with the ThreadGroup as its argument. + * + *

In addition, its {@code checkPermission} method is invoked with + * the {@code RuntimePermission("enableContextClassLoaderOverride")} + * permission when invoked directly or indirectly by the constructor + * of a subclass which overrides the {@code getContextClassLoader} + * or {@code setContextClassLoader} methods. + * + *

The priority of the newly created thread is set equal to the + * priority of the thread creating it, that is, the currently running + * thread. The method {@linkplain #setPriority setPriority} may be + * used to change the priority to a new value. + * + *

The newly created thread is initially marked as being a daemon + * thread if and only if the thread creating it is currently marked + * as a daemon thread. The method {@linkplain #setDaemon setDaemon} + * may be used to change whether or not a thread is a daemon. + * + * @param group + * the thread group. If {@code null} and there is a security + * manager, the group is determined by {@linkplain + * SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}. + * If there is not a security manager or {@code + * SecurityManager.getThreadGroup()} returns {@code null}, the group + * is set to the current thread's thread group. + * + * @param target + * the object whose {@code run} method is invoked when this thread + * is started. If {@code null}, this thread's run method is invoked. + * + * @param name + * the name of the new thread + * + * @throws SecurityException + * if the current thread cannot create a thread in the specified + * thread group or cannot override the context class loader methods. + */ +// public Thread(ThreadGroup group, Runnable target, String name) { +// init(group, target, name, 0); +// } + + /** + * Allocates a new {@code Thread} object so that it has {@code target} + * as its run object, has the specified {@code name} as its name, + * and belongs to the thread group referred to by {@code group}, and has + * the specified stack size. + * + *

This constructor is identical to {@link + * #Thread(ThreadGroup,Runnable,String)} with the exception of the fact + * that it allows the thread stack size to be specified. The stack size + * is the approximate number of bytes of address space that the virtual + * machine is to allocate for this thread's stack. The effect of the + * {@code stackSize} parameter, if any, is highly platform dependent. + * + *

On some platforms, specifying a higher value for the + * {@code stackSize} parameter may allow a thread to achieve greater + * recursion depth before throwing a {@link StackOverflowError}. + * Similarly, specifying a lower value may allow a greater number of + * threads to exist concurrently without throwing an {@link + * OutOfMemoryError} (or other internal error). The details of + * the relationship between the value of the stackSize parameter + * and the maximum recursion depth and concurrency level are + * platform-dependent. On some platforms, the value of the + * {@code stackSize} parameter may have no effect whatsoever. + * + *

The virtual machine is free to treat the {@code stackSize} + * parameter as a suggestion. If the specified value is unreasonably low + * for the platform, the virtual machine may instead use some + * platform-specific minimum value; if the specified value is unreasonably + * high, the virtual machine may instead use some platform-specific + * maximum. Likewise, the virtual machine is free to round the specified + * value up or down as it sees fit (or to ignore it completely). + * + *

Specifying a value of zero for the {@code stackSize} parameter will + * cause this constructor to behave exactly like the + * {@code Thread(ThreadGroup, Runnable, String)} constructor. + * + *

Due to the platform-dependent nature of the behavior of this + * constructor, extreme care should be exercised in its use. + * The thread stack size necessary to perform a given computation will + * likely vary from one JRE implementation to another. In light of this + * variation, careful tuning of the stack size parameter may be required, + * and the tuning may need to be repeated for each JRE implementation on + * which an application is to run. + * + *

Implementation note: Java platform implementers are encouraged to + * document their implementation's behavior with respect to the + * {@code stackSize} parameter. + * + * + * @param group + * the thread group. If {@code null} and there is a security + * manager, the group is determined by {@linkplain + * SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}. + * If there is not a security manager or {@code + * SecurityManager.getThreadGroup()} returns {@code null}, the group + * is set to the current thread's thread group. + * + * @param target + * the object whose {@code run} method is invoked when this thread + * is started. If {@code null}, this thread's run method is invoked. + * + * @param name + * the name of the new thread + * + * @param stackSize + * the desired stack size for the new thread, or zero to indicate + * that this parameter is to be ignored. + * + * @throws SecurityException + * if the current thread cannot create a thread in the specified + * thread group + * + * @since 1.4 + */ +// public Thread(ThreadGroup group, Runnable target, String name, +// long stackSize) { +// init(group, target, name, stackSize); +// } + + /** + * Causes this thread to begin execution; the Java Virtual Machine + * calls the run method of this thread. + *

+ * The result is that two threads are running concurrently: the + * current thread (which returns from the call to the + * start method) and the other thread (which executes its + * run method). + *

+ * It is never legal to start a thread more than once. + * In particular, a thread may not be restarted once it has completed + * execution. + * + * @exception IllegalThreadStateException if the thread was already + * started. + * @see #run() + * @see #stop() + */ + public void start() { + throw new SecurityException(); + } + + /** + * If this thread was constructed using a separate + * Runnable run object, then that + * Runnable object's run method is called; + * otherwise, this method does nothing and returns. + *

+ * Subclasses of Thread should override this method. + * + * @see #start() + * @see #stop() + * @see #Thread(ThreadGroup, Runnable, String) + */ + @Override + public void run() { + if (target != null) { + target.run(); + } + } + + /** + * Forces the thread to stop executing. + *

+ * If there is a security manager installed, its checkAccess + * method is called with this + * as its argument. This may result in a + * SecurityException being raised (in the current thread). + *

+ * If this thread is different from the current thread (that is, the current + * thread is trying to stop a thread other than itself), the + * security manager's checkPermission method (with a + * RuntimePermission("stopThread") argument) is called in + * addition. + * Again, this may result in throwing a + * SecurityException (in the current thread). + *

+ * The thread represented by this thread is forced to stop whatever + * it is doing abnormally and to throw a newly created + * ThreadDeath object as an exception. + *

+ * It is permitted to stop a thread that has not yet been started. + * If the thread is eventually started, it immediately terminates. + *

+ * An application should not normally try to catch + * ThreadDeath unless it must do some extraordinary + * cleanup operation (note that the throwing of + * ThreadDeath causes finally clauses of + * try statements to be executed before the thread + * officially dies). If a catch clause catches a + * ThreadDeath object, it is important to rethrow the + * object so that the thread actually dies. + *

+ * The top-level error handler that reacts to otherwise uncaught + * exceptions does not print out a message or otherwise notify the + * application if the uncaught exception is an instance of + * ThreadDeath. + * + * @exception SecurityException if the current thread cannot + * modify this thread. + * @see #interrupt() + * @see #checkAccess() + * @see #run() + * @see #start() + * @see ThreadDeath + * @see ThreadGroup#uncaughtException(Thread,Throwable) + * @see SecurityManager#checkAccess(Thread) + * @see SecurityManager#checkPermission + * @deprecated This method is inherently unsafe. Stopping a thread with + * Thread.stop causes it to unlock all of the monitors that it + * has locked (as a natural consequence of the unchecked + * ThreadDeath exception propagating up the stack). If + * any of the objects previously protected by these monitors were in + * an inconsistent state, the damaged objects become visible to + * other threads, potentially resulting in arbitrary behavior. Many + * uses of stop should be replaced by code that simply + * modifies some variable to indicate that the target thread should + * stop running. The target thread should check this variable + * regularly, and return from its run method in an orderly fashion + * if the variable indicates that it is to stop running. If the + * target thread waits for long periods (on a condition variable, + * for example), the interrupt method should be used to + * interrupt the wait. + * For more information, see + * Why + * are Thread.stop, Thread.suspend and Thread.resume Deprecated?. + */ + @Deprecated + public final void stop() { + stop(null); + } + + /** + * Forces the thread to stop executing. + *

+ * If there is a security manager installed, the checkAccess + * method of this thread is called, which may result in a + * SecurityException being raised (in the current thread). + *

+ * If this thread is different from the current thread (that is, the current + * thread is trying to stop a thread other than itself) or + * obj is not an instance of ThreadDeath, the + * security manager's checkPermission method (with the + * RuntimePermission("stopThread") argument) is called in + * addition. + * Again, this may result in throwing a + * SecurityException (in the current thread). + *

+ * If the argument obj is null, a + * NullPointerException is thrown (in the current thread). + *

+ * The thread represented by this thread is forced to stop + * whatever it is doing abnormally and to throw the + * Throwable object obj as an exception. This + * is an unusual action to take; normally, the stop method + * that takes no arguments should be used. + *

+ * It is permitted to stop a thread that has not yet been started. + * If the thread is eventually started, it immediately terminates. + * + * @param obj the Throwable object to be thrown. + * @exception SecurityException if the current thread cannot modify + * this thread. + * @throws NullPointerException if obj is null. + * @see #interrupt() + * @see #checkAccess() + * @see #run() + * @see #start() + * @see #stop() + * @see SecurityManager#checkAccess(Thread) + * @see SecurityManager#checkPermission + * @deprecated This method is inherently unsafe. See {@link #stop()} + * for details. An additional danger of this + * method is that it may be used to generate exceptions that the + * target thread is unprepared to handle (including checked + * exceptions that the thread could not possibly throw, were it + * not for this method). + * For more information, see + * Why + * are Thread.stop, Thread.suspend and Thread.resume Deprecated?. + */ + @Deprecated + public final synchronized void stop(Throwable obj) { + throw new SecurityException(); + } + + /** + * Interrupts this thread. + * + *

Unless the current thread is interrupting itself, which is + * always permitted, the {@link #checkAccess() checkAccess} method + * of this thread is invoked, which may cause a {@link + * SecurityException} to be thrown. + * + *

If this thread is blocked in an invocation of the {@link + * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link + * Object#wait(long, int) wait(long, int)} methods of the {@link Object} + * class, or of the {@link #join()}, {@link #join(long)}, {@link + * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, + * methods of this class, then its interrupt status will be cleared and it + * will receive an {@link InterruptedException}. + * + *

If this thread is blocked in an I/O operation upon an {@link + * java.nio.channels.InterruptibleChannel interruptible + * channel} then the channel will be closed, the thread's interrupt + * status will be set, and the thread will receive a {@link + * java.nio.channels.ClosedByInterruptException}. + * + *

If this thread is blocked in a {@link java.nio.channels.Selector} + * then the thread's interrupt status will be set and it will return + * immediately from the selection operation, possibly with a non-zero + * value, just as if the selector's {@link + * java.nio.channels.Selector#wakeup wakeup} method were invoked. + * + *

If none of the previous conditions hold then this thread's interrupt + * status will be set.

+ * + *

Interrupting a thread that is not alive need not have any effect. + * + * @throws SecurityException + * if the current thread cannot modify this thread + * + * @revised 6.0 + * @spec JSR-51 + */ + public void interrupt() { + throw new SecurityException(); + } + + /** + * Tests whether the current thread has been interrupted. The + * interrupted status of the thread is cleared by this method. In + * other words, if this method were to be called twice in succession, the + * second call would return false (unless the current thread were + * interrupted again, after the first call had cleared its interrupted + * status and before the second call had examined it). + * + *

A thread interruption ignored because a thread was not alive + * at the time of the interrupt will be reflected by this method + * returning false. + * + * @return true if the current thread has been interrupted; + * false otherwise. + * @see #isInterrupted() + * @revised 6.0 + */ + public static boolean interrupted() { + return currentThread().isInterrupted(); + } + + /** + * Tests whether this thread has been interrupted. The interrupted + * status of the thread is unaffected by this method. + * + *

A thread interruption ignored because a thread was not alive + * at the time of the interrupt will be reflected by this method + * returning false. + * + * @return true if this thread has been interrupted; + * false otherwise. + * @see #interrupted() + * @revised 6.0 + */ + public boolean isInterrupted() { + return false; + } + + /** + * Throws {@link NoSuchMethodError}. + * + * @deprecated This method was originally designed to destroy this + * thread without any cleanup. Any monitors it held would have + * remained locked. However, the method was never implemented. + * If if were to be implemented, it would be deadlock-prone in + * much the manner of {@link #suspend}. If the target thread held + * a lock protecting a critical system resource when it was + * destroyed, no thread could ever access this resource again. + * If another thread ever attempted to lock this resource, deadlock + * would result. Such deadlocks typically manifest themselves as + * "frozen" processes. For more information, see + * + * Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?. + * @throws NoSuchMethodError always + */ + @Deprecated + public void destroy() { + throw new SecurityException(); + } + + /** + * Tests if this thread is alive. A thread is alive if it has + * been started and has not yet died. + * + * @return true if this thread is alive; + * false otherwise. + */ + public final boolean isAlive() { + return true; + } + + /** + * Suspends this thread. + *

+ * First, the checkAccess method of this thread is called + * with no arguments. This may result in throwing a + * SecurityException (in the current thread). + *

+ * If the thread is alive, it is suspended and makes no further + * progress unless and until it is resumed. + * + * @exception SecurityException if the current thread cannot modify + * this thread. + * @see #checkAccess + * @deprecated This method has been deprecated, as it is + * inherently deadlock-prone. If the target thread holds a lock on the + * monitor protecting a critical system resource when it is suspended, no + * thread can access this resource until the target thread is resumed. If + * the thread that would resume the target thread attempts to lock this + * monitor prior to calling resume, deadlock results. Such + * deadlocks typically manifest themselves as "frozen" processes. + * For more information, see + * Why + * are Thread.stop, Thread.suspend and Thread.resume Deprecated?. + */ + @Deprecated + public final void suspend() { + checkAccess(); + } + + /** + * Resumes a suspended thread. + *

+ * First, the checkAccess method of this thread is called + * with no arguments. This may result in throwing a + * SecurityException (in the current thread). + *

+ * If the thread is alive but suspended, it is resumed and is + * permitted to make progress in its execution. + * + * @exception SecurityException if the current thread cannot modify this + * thread. + * @see #checkAccess + * @see #suspend() + * @deprecated This method exists solely for use with {@link #suspend}, + * which has been deprecated because it is deadlock-prone. + * For more information, see + * Why + * are Thread.stop, Thread.suspend and Thread.resume Deprecated?. + */ + @Deprecated + public final void resume() { + checkAccess(); + } + + /** + * Changes the priority of this thread. + *

+ * First the checkAccess method of this thread is called + * with no arguments. This may result in throwing a + * SecurityException. + *

+ * Otherwise, the priority of this thread is set to the smaller of + * the specified newPriority and the maximum permitted + * priority of the thread's thread group. + * + * @param newPriority priority to set this thread to + * @exception IllegalArgumentException If the priority is not in the + * range MIN_PRIORITY to + * MAX_PRIORITY. + * @exception SecurityException if the current thread cannot modify + * this thread. + * @see #getPriority + * @see #checkAccess() + * @see #getThreadGroup() + * @see #MAX_PRIORITY + * @see #MIN_PRIORITY + * @see ThreadGroup#getMaxPriority() + */ + public final void setPriority(int newPriority) { + throw new SecurityException(); + } + + /** + * Returns this thread's priority. + * + * @return this thread's priority. + * @see #setPriority + */ + public final int getPriority() { + return Thread.NORM_PRIORITY; + } + + /** + * Changes the name of this thread to be equal to the argument + * name. + *

+ * First the checkAccess method of this thread is called + * with no arguments. This may result in throwing a + * SecurityException. + * + * @param name the new name for this thread. + * @exception SecurityException if the current thread cannot modify this + * thread. + * @see #getName + * @see #checkAccess() + */ + public final void setName(String name) { + throw new SecurityException(); + } + + /** + * Returns this thread's name. + * + * @return this thread's name. + * @see #setName(String) + */ + public final String getName() { + return String.valueOf(name); + } + + /** + * Returns the thread group to which this thread belongs. + * This method returns null if this thread has died + * (been stopped). + * + * @return this thread's thread group. + */ +// public final ThreadGroup getThreadGroup() { +// return group; +// } + + /** + * Returns an estimate of the number of active threads in the current + * thread's {@linkplain java.lang.ThreadGroup thread group} and its + * subgroups. Recursively iterates over all subgroups in the current + * thread's thread group. + * + *

The value returned is only an estimate because the number of + * threads may change dynamically while this method traverses internal + * data structures, and might be affected by the presence of certain + * system threads. This method is intended primarily for debugging + * and monitoring purposes. + * + * @return an estimate of the number of active threads in the current + * thread's thread group and in any other thread group that + * has the current thread's thread group as an ancestor + */ + public static int activeCount() { + return 1; + } + + /** + * Copies into the specified array every active thread in the current + * thread's thread group and its subgroups. This method simply + * invokes the {@link java.lang.ThreadGroup#enumerate(Thread[])} + * method of the current thread's thread group. + * + *

An application might use the {@linkplain #activeCount activeCount} + * method to get an estimate of how big the array should be, however + * if the array is too short to hold all the threads, the extra threads + * are silently ignored. If it is critical to obtain every active + * thread in the current thread's thread group and its subgroups, the + * invoker should verify that the returned int value is strictly less + * than the length of {@code tarray}. + * + *

Due to the inherent race condition in this method, it is recommended + * that the method only be used for debugging and monitoring purposes. + * + * @param tarray + * an array into which to put the list of threads + * + * @return the number of threads put into the array + * + * @throws SecurityException + * if {@link java.lang.ThreadGroup#checkAccess} determines that + * the current thread cannot access its thread group + */ + public static int enumerate(Thread tarray[]) { + throw new SecurityException(); + } + + /** + * Counts the number of stack frames in this thread. The thread must + * be suspended. + * + * @return the number of stack frames in this thread. + * @exception IllegalThreadStateException if this thread is not + * suspended. + * @deprecated The definition of this call depends on {@link #suspend}, + * which is deprecated. Further, the results of this call + * were never well-defined. + */ + @Deprecated + public native int countStackFrames(); + + /** + * Waits at most {@code millis} milliseconds for this thread to + * die. A timeout of {@code 0} means to wait forever. + * + *

This implementation uses a loop of {@code this.wait} calls + * conditioned on {@code this.isAlive}. As a thread terminates the + * {@code this.notifyAll} method is invoked. It is recommended that + * applications not use {@code wait}, {@code notify}, or + * {@code notifyAll} on {@code Thread} instances. + * + * @param millis + * the time to wait in milliseconds + * + * @throws IllegalArgumentException + * if the value of {@code millis} is negative + * + * @throws InterruptedException + * if any thread has interrupted the current thread. The + * interrupted status of the current thread is + * cleared when this exception is thrown. + */ + public final synchronized void join(long millis) + throws InterruptedException { + long base = System.currentTimeMillis(); + long now = 0; + + if (millis < 0) { + throw new IllegalArgumentException("timeout value is negative"); + } + + if (millis == 0) { + while (isAlive()) { + wait(0); + } + } else { + while (isAlive()) { + long delay = millis - now; + if (delay <= 0) { + break; + } + wait(delay); + now = System.currentTimeMillis() - base; + } + } + } + + /** + * Waits at most {@code millis} milliseconds plus + * {@code nanos} nanoseconds for this thread to die. + * + *

This implementation uses a loop of {@code this.wait} calls + * conditioned on {@code this.isAlive}. As a thread terminates the + * {@code this.notifyAll} method is invoked. It is recommended that + * applications not use {@code wait}, {@code notify}, or + * {@code notifyAll} on {@code Thread} instances. + * + * @param millis + * the time to wait in milliseconds + * + * @param nanos + * {@code 0-999999} additional nanoseconds to wait + * + * @throws IllegalArgumentException + * if the value of {@code millis} is negative, or the value + * of {@code nanos} is not in the range {@code 0-999999} + * + * @throws InterruptedException + * if any thread has interrupted the current thread. The + * interrupted status of the current thread is + * cleared when this exception is thrown. + */ + public final synchronized void join(long millis, int nanos) + throws InterruptedException { + + if (millis < 0) { + throw new IllegalArgumentException("timeout value is negative"); + } + + if (nanos < 0 || nanos > 999999) { + throw new IllegalArgumentException( + "nanosecond timeout value out of range"); + } + + if (nanos >= 500000 || (nanos != 0 && millis == 0)) { + millis++; + } + + join(millis); + } + + /** + * Waits for this thread to die. + * + *

An invocation of this method behaves in exactly the same + * way as the invocation + * + *

+ * {@linkplain #join(long) join}{@code (0)} + *
+ * + * @throws InterruptedException + * if any thread has interrupted the current thread. The + * interrupted status of the current thread is + * cleared when this exception is thrown. + */ + public final void join() throws InterruptedException { + join(0); + } + + /** + * Prints a stack trace of the current thread to the standard error stream. + * This method is used only for debugging. + * + * @see Throwable#printStackTrace() + */ + public static void dumpStack() { + new Exception("Stack trace").printStackTrace(); + } + + /** + * Marks this thread as either a {@linkplain #isDaemon daemon} thread + * or a user thread. The Java Virtual Machine exits when the only + * threads running are all daemon threads. + * + *

This method must be invoked before the thread is started. + * + * @param on + * if {@code true}, marks this thread as a daemon thread + * + * @throws IllegalThreadStateException + * if this thread is {@linkplain #isAlive alive} + * + * @throws SecurityException + * if {@link #checkAccess} determines that the current + * thread cannot modify this thread + */ + public final void setDaemon(boolean on) { + throw new SecurityException(); + } + + /** + * Tests if this thread is a daemon thread. + * + * @return true if this thread is a daemon thread; + * false otherwise. + * @see #setDaemon(boolean) + */ + public final boolean isDaemon() { + return false; + } + + /** + * Determines if the currently running thread has permission to + * modify this thread. + *

+ * If there is a security manager, its checkAccess method + * is called with this thread as its argument. This may result in + * throwing a SecurityException. + * + * @exception SecurityException if the current thread is not allowed to + * access this thread. + * @see SecurityManager#checkAccess(Thread) + */ + public final void checkAccess() { + throw new SecurityException(); + } + + /** + * Returns a string representation of this thread, including the + * thread's name, priority, and thread group. + * + * @return a string representation of this thread. + */ + public String toString() { + return "Thread[" + getName() + "," + getPriority() + "," + + "" + "]"; + } + + /** + * Returns the context ClassLoader for this Thread. The context + * ClassLoader is provided by the creator of the thread for use + * by code running in this thread when loading classes and resources. + * If not {@linkplain #setContextClassLoader set}, the default is the + * ClassLoader context of the parent Thread. The context ClassLoader of the + * primordial thread is typically set to the class loader used to load the + * application. + * + *

If a security manager is present, and the invoker's class loader is not + * {@code null} and is not the same as or an ancestor of the context class + * loader, then this method invokes the security manager's {@link + * SecurityManager#checkPermission(java.security.Permission) checkPermission} + * method with a {@link RuntimePermission RuntimePermission}{@code + * ("getClassLoader")} permission to verify that retrieval of the context + * class loader is permitted. + * + * @return the context ClassLoader for this Thread, or {@code null} + * indicating the system class loader (or, failing that, the + * bootstrap class loader) + * + * @throws SecurityException + * if the current thread cannot get the context ClassLoader + * + * @since 1.2 + */ + public ClassLoader getContextClassLoader() { + return null; + } + + /** + * Sets the context ClassLoader for this Thread. The context + * ClassLoader can be set when a thread is created, and allows + * the creator of the thread to provide the appropriate class loader, + * through {@code getContextClassLoader}, to code running in the thread + * when loading classes and resources. + * + *

If a security manager is present, its {@link + * SecurityManager#checkPermission(java.security.Permission) checkPermission} + * method is invoked with a {@link RuntimePermission RuntimePermission}{@code + * ("setContextClassLoader")} permission to see if setting the context + * ClassLoader is permitted. + * + * @param cl + * the context ClassLoader for this Thread, or null indicating the + * system class loader (or, failing that, the bootstrap class loader) + * + * @throws SecurityException + * if the current thread cannot set the context ClassLoader + * + * @since 1.2 + */ + public void setContextClassLoader(ClassLoader cl) { + throw new SecurityException(); + } + + /** + * Returns true if and only if the current thread holds the + * monitor lock on the specified object. + * + *

This method is designed to allow a program to assert that + * the current thread already holds a specified lock: + *

+     *     assert Thread.holdsLock(obj);
+     * 
+ * + * @param obj the object on which to test lock ownership + * @throws NullPointerException if obj is null + * @return true if the current thread holds the monitor lock on + * the specified object. + * @since 1.4 + */ + public static boolean holdsLock(Object obj) { + return true; + } + + /** + * Returns an array of stack trace elements representing the stack dump + * of this thread. This method will return a zero-length array if + * this thread has not started, has started but has not yet been + * scheduled to run by the system, or has terminated. + * If the returned array is of non-zero length then the first element of + * the array represents the top of the stack, which is the most recent + * method invocation in the sequence. The last element of the array + * represents the bottom of the stack, which is the least recent method + * invocation in the sequence. + * + *

If there is a security manager, and this thread is not + * the current thread, then the security manager's + * checkPermission method is called with a + * RuntimePermission("getStackTrace") permission + * to see if it's ok to get the stack trace. + * + *

Some virtual machines may, under some circumstances, omit one + * or more stack frames from the stack trace. In the extreme case, + * a virtual machine that has no stack trace information concerning + * this thread is permitted to return a zero-length array from this + * method. + * + * @return an array of StackTraceElement, + * each represents one stack frame. + * + * @throws SecurityException + * if a security manager exists and its + * checkPermission method doesn't allow + * getting the stack trace of thread. + * @see SecurityManager#checkPermission + * @see RuntimePermission + * @see Throwable#getStackTrace + * + * @since 1.5 + */ + public StackTraceElement[] getStackTrace() { + throw new SecurityException(); + } + + /** + * Returns a map of stack traces for all live threads. + * The map keys are threads and each map value is an array of + * StackTraceElement that represents the stack dump + * of the corresponding Thread. + * The returned stack traces are in the format specified for + * the {@link #getStackTrace getStackTrace} method. + * + *

The threads may be executing while this method is called. + * The stack trace of each thread only represents a snapshot and + * each stack trace may be obtained at different time. A zero-length + * array will be returned in the map value if the virtual machine has + * no stack trace information about a thread. + * + *

If there is a security manager, then the security manager's + * checkPermission method is called with a + * RuntimePermission("getStackTrace") permission as well as + * RuntimePermission("modifyThreadGroup") permission + * to see if it is ok to get the stack trace of all threads. + * + * @return a Map from Thread to an array of + * StackTraceElement that represents the stack trace of + * the corresponding thread. + * + * @throws SecurityException + * if a security manager exists and its + * checkPermission method doesn't allow + * getting the stack trace of thread. + * @see #getStackTrace + * @see SecurityManager#checkPermission + * @see RuntimePermission + * @see Throwable#getStackTrace + * + * @since 1.5 + */ + public static Map getAllStackTraces() { + throw new SecurityException(); + } + + /** + * Returns the identifier of this Thread. The thread ID is a positive + * long number generated when this thread was created. + * The thread ID is unique and remains unchanged during its lifetime. + * When a thread is terminated, this thread ID may be reused. + * + * @return this thread's ID. + * @since 1.5 + */ + public long getId() { + return 0; + } + + /** + * A thread state. A thread can be in one of the following states: + *

    + *
  • {@link #NEW}
    + * A thread that has not yet started is in this state. + *
  • + *
  • {@link #RUNNABLE}
    + * A thread executing in the Java virtual machine is in this state. + *
  • + *
  • {@link #BLOCKED}
    + * A thread that is blocked waiting for a monitor lock + * is in this state. + *
  • + *
  • {@link #WAITING}
    + * A thread that is waiting indefinitely for another thread to + * perform a particular action is in this state. + *
  • + *
  • {@link #TIMED_WAITING}
    + * A thread that is waiting for another thread to perform an action + * for up to a specified waiting time is in this state. + *
  • + *
  • {@link #TERMINATED}
    + * A thread that has exited is in this state. + *
  • + *
+ * + *

+ * A thread can be in only one state at a given point in time. + * These states are virtual machine states which do not reflect + * any operating system thread states. + * + * @since 1.5 + * @see #getState + */ + public enum State { + /** + * Thread state for a thread which has not yet started. + */ + NEW, + + /** + * Thread state for a runnable thread. A thread in the runnable + * state is executing in the Java virtual machine but it may + * be waiting for other resources from the operating system + * such as processor. + */ + RUNNABLE, + + /** + * Thread state for a thread blocked waiting for a monitor lock. + * A thread in the blocked state is waiting for a monitor lock + * to enter a synchronized block/method or + * reenter a synchronized block/method after calling + * {@link Object#wait() Object.wait}. + */ + BLOCKED, + + /** + * Thread state for a waiting thread. + * A thread is in the waiting state due to calling one of the + * following methods: + *

    + *
  • {@link Object#wait() Object.wait} with no timeout
  • + *
  • {@link #join() Thread.join} with no timeout
  • + *
  • {@link LockSupport#park() LockSupport.park}
  • + *
+ * + *

A thread in the waiting state is waiting for another thread to + * perform a particular action. + * + * For example, a thread that has called Object.wait() + * on an object is waiting for another thread to call + * Object.notify() or Object.notifyAll() on + * that object. A thread that has called Thread.join() + * is waiting for a specified thread to terminate. + */ + WAITING, + + /** + * Thread state for a waiting thread with a specified waiting time. + * A thread is in the timed waiting state due to calling one of + * the following methods with a specified positive waiting time: + *

    + *
  • {@link #sleep Thread.sleep}
  • + *
  • {@link Object#wait(long) Object.wait} with timeout
  • + *
  • {@link #join(long) Thread.join} with timeout
  • + *
  • {@link LockSupport#parkNanos LockSupport.parkNanos}
  • + *
  • {@link LockSupport#parkUntil LockSupport.parkUntil}
  • + *
+ */ + TIMED_WAITING, + + /** + * Thread state for a terminated thread. + * The thread has completed execution. + */ + TERMINATED; + } + + /** + * Returns the state of this thread. + * This method is designed for use in monitoring of the system state, + * not for synchronization control. + * + * @return this thread's state. + * @since 1.5 + */ + public State getState() { + // get current thread state + return State.RUNNABLE; + } + + // Added in JSR-166 + + /** + * Interface for handlers invoked when a Thread abruptly + * terminates due to an uncaught exception. + *

When a thread is about to terminate due to an uncaught exception + * the Java Virtual Machine will query the thread for its + * UncaughtExceptionHandler using + * {@link #getUncaughtExceptionHandler} and will invoke the handler's + * uncaughtException method, passing the thread and the + * exception as arguments. + * If a thread has not had its UncaughtExceptionHandler + * explicitly set, then its ThreadGroup object acts as its + * UncaughtExceptionHandler. If the ThreadGroup object + * has no + * special requirements for dealing with the exception, it can forward + * the invocation to the {@linkplain #getDefaultUncaughtExceptionHandler + * default uncaught exception handler}. + * + * @see #setDefaultUncaughtExceptionHandler + * @see #setUncaughtExceptionHandler + * @see ThreadGroup#uncaughtException + * @since 1.5 + */ + public interface UncaughtExceptionHandler { + /** + * Method invoked when the given thread terminates due to the + * given uncaught exception. + *

Any exception thrown by this method will be ignored by the + * Java Virtual Machine. + * @param t the thread + * @param e the exception + */ + void uncaughtException(Thread t, Throwable e); + } + + // null unless explicitly set + private volatile UncaughtExceptionHandler uncaughtExceptionHandler; + + // null unless explicitly set + private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler; + + /** + * Set the default handler invoked when a thread abruptly terminates + * due to an uncaught exception, and no other handler has been defined + * for that thread. + * + *

Uncaught exception handling is controlled first by the thread, then + * by the thread's {@link ThreadGroup} object and finally by the default + * uncaught exception handler. If the thread does not have an explicit + * uncaught exception handler set, and the thread's thread group + * (including parent thread groups) does not specialize its + * uncaughtException method, then the default handler's + * uncaughtException method will be invoked. + *

By setting the default uncaught exception handler, an application + * can change the way in which uncaught exceptions are handled (such as + * logging to a specific device, or file) for those threads that would + * already accept whatever "default" behavior the system + * provided. + * + *

Note that the default uncaught exception handler should not usually + * defer to the thread's ThreadGroup object, as that could cause + * infinite recursion. + * + * @param eh the object to use as the default uncaught exception handler. + * If null then there is no default handler. + * + * @throws SecurityException if a security manager is present and it + * denies {@link RuntimePermission} + * ("setDefaultUncaughtExceptionHandler") + * + * @see #setUncaughtExceptionHandler + * @see #getUncaughtExceptionHandler + * @see ThreadGroup#uncaughtException + * @since 1.5 + */ + public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) { + throw new SecurityException(); + } + + /** + * Returns the default handler invoked when a thread abruptly terminates + * due to an uncaught exception. If the returned value is null, + * there is no default. + * @since 1.5 + * @see #setDefaultUncaughtExceptionHandler + */ + public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){ + return defaultUncaughtExceptionHandler; + } + + /** + * Returns the handler invoked when this thread abruptly terminates + * due to an uncaught exception. If this thread has not had an + * uncaught exception handler explicitly set then this thread's + * ThreadGroup object is returned, unless this thread + * has terminated, in which case null is returned. + * @since 1.5 + */ + public UncaughtExceptionHandler getUncaughtExceptionHandler() { + return uncaughtExceptionHandler != null ? + uncaughtExceptionHandler : null; + } + + /** + * Set the handler invoked when this thread abruptly terminates + * due to an uncaught exception. + *

A thread can take full control of how it responds to uncaught + * exceptions by having its uncaught exception handler explicitly set. + * If no such handler is set then the thread's ThreadGroup + * object acts as its handler. + * @param eh the object to use as this thread's uncaught exception + * handler. If null then this thread has no explicit handler. + * @throws SecurityException if the current thread is not allowed to + * modify this thread. + * @see #setDefaultUncaughtExceptionHandler + * @see ThreadGroup#uncaughtException + * @since 1.5 + */ + public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) { + checkAccess(); + uncaughtExceptionHandler = eh; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/lang/ThreadLocal.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/ThreadLocal.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,157 @@ +/* + * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang; + +/** + * This class provides thread-local variables. These variables differ from + * their normal counterparts in that each thread that accesses one (via its + * get or set method) has its own, independently initialized + * copy of the variable. ThreadLocal instances are typically private + * static fields in classes that wish to associate state with a thread (e.g., + * a user ID or Transaction ID). + * + *

For example, the class below generates unique identifiers local to each + * thread. + * A thread's id is assigned the first time it invokes ThreadId.get() + * and remains unchanged on subsequent calls. + *

+ * import java.util.concurrent.atomic.AtomicInteger;
+ *
+ * public class ThreadId {
+ *     // Atomic integer containing the next thread ID to be assigned
+ *     private static final AtomicInteger nextId = new AtomicInteger(0);
+ *
+ *     // Thread local variable containing each thread's ID
+ *     private static final ThreadLocal<Integer> threadId =
+ *         new ThreadLocal<Integer>() {
+ *             @Override protected Integer initialValue() {
+ *                 return nextId.getAndIncrement();
+ *         }
+ *     };
+ *
+ *     // Returns the current thread's unique ID, assigning it if necessary
+ *     public static int get() {
+ *         return threadId.get();
+ *     }
+ * }
+ * 
+ *

Each thread holds an implicit reference to its copy of a thread-local + * variable as long as the thread is alive and the ThreadLocal + * instance is accessible; after a thread goes away, all of its copies of + * thread-local instances are subject to garbage collection (unless other + * references to these copies exist). + * + * @author Josh Bloch and Doug Lea + * @since 1.2 + */ +public class ThreadLocal { + private static final Object NONE = new Object(); + private Object value = NONE; + + /** + * Returns the current thread's "initial value" for this + * thread-local variable. This method will be invoked the first + * time a thread accesses the variable with the {@link #get} + * method, unless the thread previously invoked the {@link #set} + * method, in which case the initialValue method will not + * be invoked for the thread. Normally, this method is invoked at + * most once per thread, but it may be invoked again in case of + * subsequent invocations of {@link #remove} followed by {@link #get}. + * + *

This implementation simply returns null; if the + * programmer desires thread-local variables to have an initial + * value other than null, ThreadLocal must be + * subclassed, and this method overridden. Typically, an + * anonymous inner class will be used. + * + * @return the initial value for this thread-local + */ + protected T initialValue() { + return null; + } + + /** + * Creates a thread local variable. + */ + public ThreadLocal() { + } + + /** + * Returns the value in the current thread's copy of this + * thread-local variable. If the variable has no value for the + * current thread, it is first initialized to the value returned + * by an invocation of the {@link #initialValue} method. + * + * @return the current thread's value of this thread-local + */ + public T get() { + if (value == NONE) { + return setInitialValue(); + } else { + return (T)value; + } + } + + /** + * Variant of set() to establish initialValue. Used instead + * of set() in case user has overridden the set() method. + * + * @return the initial value + */ + private T setInitialValue() { + T v = initialValue(); + this.value = v; + return v; + } + + /** + * Sets the current thread's copy of this thread-local variable + * to the specified value. Most subclasses will have no need to + * override this method, relying solely on the {@link #initialValue} + * method to set the values of thread-locals. + * + * @param value the value to be stored in the current thread's copy of + * this thread-local. + */ + public void set(T value) { + this.value = value; + } + + /** + * Removes the current thread's value for this thread-local + * variable. If this thread-local variable is subsequently + * {@linkplain #get read} by the current thread, its value will be + * reinitialized by invoking its {@link #initialValue} method, + * unless its value is {@linkplain #set set} by the current thread + * in the interim. This may result in multiple invocations of the + * initialValue method in the current thread. + * + * @since 1.5 + */ + public void remove() { + this.value = NONE; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/math/BigDecimal.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/math/BigDecimal.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,3842 @@ +/* + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * Portions Copyright IBM Corporation, 2001. All Rights Reserved. + */ + +package java.math; + +import java.util.Arrays; +import static java.math.BigInteger.LONG_MASK; + +/** + * Immutable, arbitrary-precision signed decimal numbers. A + * {@code BigDecimal} consists of an arbitrary precision integer + * unscaled value and a 32-bit integer scale. If zero + * or positive, the scale is the number of digits to the right of the + * decimal point. If negative, the unscaled value of the number is + * multiplied by ten to the power of the negation of the scale. The + * value of the number represented by the {@code BigDecimal} is + * therefore (unscaledValue × 10-scale). + * + *

The {@code BigDecimal} class provides operations for + * arithmetic, scale manipulation, rounding, comparison, hashing, and + * format conversion. The {@link #toString} method provides a + * canonical representation of a {@code BigDecimal}. + * + *

The {@code BigDecimal} class gives its user complete control + * over rounding behavior. If no rounding mode is specified and the + * exact result cannot be represented, an exception is thrown; + * otherwise, calculations can be carried out to a chosen precision + * and rounding mode by supplying an appropriate {@link MathContext} + * object to the operation. In either case, eight rounding + * modes are provided for the control of rounding. Using the + * integer fields in this class (such as {@link #ROUND_HALF_UP}) to + * represent rounding mode is largely obsolete; the enumeration values + * of the {@code RoundingMode} {@code enum}, (such as {@link + * RoundingMode#HALF_UP}) should be used instead. + * + *

When a {@code MathContext} object is supplied with a precision + * setting of 0 (for example, {@link MathContext#UNLIMITED}), + * arithmetic operations are exact, as are the arithmetic methods + * which take no {@code MathContext} object. (This is the only + * behavior that was supported in releases prior to 5.) As a + * corollary of computing the exact result, the rounding mode setting + * of a {@code MathContext} object with a precision setting of 0 is + * not used and thus irrelevant. In the case of divide, the exact + * quotient could have an infinitely long decimal expansion; for + * example, 1 divided by 3. If the quotient has a nonterminating + * decimal expansion and the operation is specified to return an exact + * result, an {@code ArithmeticException} is thrown. Otherwise, the + * exact result of the division is returned, as done for other + * operations. + * + *

When the precision setting is not 0, the rules of + * {@code BigDecimal} arithmetic are broadly compatible with selected + * modes of operation of the arithmetic defined in ANSI X3.274-1996 + * and ANSI X3.274-1996/AM 1-2000 (section 7.4). Unlike those + * standards, {@code BigDecimal} includes many rounding modes, which + * were mandatory for division in {@code BigDecimal} releases prior + * to 5. Any conflicts between these ANSI standards and the + * {@code BigDecimal} specification are resolved in favor of + * {@code BigDecimal}. + * + *

Since the same numerical value can have different + * representations (with different scales), the rules of arithmetic + * and rounding must specify both the numerical result and the scale + * used in the result's representation. + * + * + *

In general the rounding modes and precision setting determine + * how operations return results with a limited number of digits when + * the exact result has more digits (perhaps infinitely many in the + * case of division) than the number of digits returned. + * + * First, the + * total number of digits to return is specified by the + * {@code MathContext}'s {@code precision} setting; this determines + * the result's precision. The digit count starts from the + * leftmost nonzero digit of the exact result. The rounding mode + * determines how any discarded trailing digits affect the returned + * result. + * + *

For all arithmetic operators , the operation is carried out as + * though an exact intermediate result were first calculated and then + * rounded to the number of digits specified by the precision setting + * (if necessary), using the selected rounding mode. If the exact + * result is not returned, some digit positions of the exact result + * are discarded. When rounding increases the magnitude of the + * returned result, it is possible for a new digit position to be + * created by a carry propagating to a leading {@literal "9"} digit. + * For example, rounding the value 999.9 to three digits rounding up + * would be numerically equal to one thousand, represented as + * 100×101. In such cases, the new {@literal "1"} is + * the leading digit position of the returned result. + * + *

Besides a logical exact result, each arithmetic operation has a + * preferred scale for representing a result. The preferred + * scale for each operation is listed in the table below. + * + * + * + * + * + * + * + * + *
Preferred Scales for Results of Arithmetic Operations + *
OperationPreferred Scale of Result
Addmax(addend.scale(), augend.scale())
Subtractmax(minuend.scale(), subtrahend.scale())
Multiplymultiplier.scale() + multiplicand.scale()
Dividedividend.scale() - divisor.scale()
+ * + * These scales are the ones used by the methods which return exact + * arithmetic results; except that an exact divide may have to use a + * larger scale since the exact result may have more digits. For + * example, {@code 1/32} is {@code 0.03125}. + * + *

Before rounding, the scale of the logical exact intermediate + * result is the preferred scale for that operation. If the exact + * numerical result cannot be represented in {@code precision} + * digits, rounding selects the set of digits to return and the scale + * of the result is reduced from the scale of the intermediate result + * to the least scale which can represent the {@code precision} + * digits actually returned. If the exact result can be represented + * with at most {@code precision} digits, the representation + * of the result with the scale closest to the preferred scale is + * returned. In particular, an exactly representable quotient may be + * represented in fewer than {@code precision} digits by removing + * trailing zeros and decreasing the scale. For example, rounding to + * three digits using the {@linkplain RoundingMode#FLOOR floor} + * rounding mode,
+ * + * {@code 19/100 = 0.19 // integer=19, scale=2}
+ * + * but
+ * + * {@code 21/110 = 0.190 // integer=190, scale=3}
+ * + *

Note that for add, subtract, and multiply, the reduction in + * scale will equal the number of digit positions of the exact result + * which are discarded. If the rounding causes a carry propagation to + * create a new high-order digit position, an additional digit of the + * result is discarded than when no new digit position is created. + * + *

Other methods may have slightly different rounding semantics. + * For example, the result of the {@code pow} method using the + * {@linkplain #pow(int, MathContext) specified algorithm} can + * occasionally differ from the rounded mathematical result by more + * than one unit in the last place, one {@linkplain #ulp() ulp}. + * + *

Two types of operations are provided for manipulating the scale + * of a {@code BigDecimal}: scaling/rounding operations and decimal + * point motion operations. Scaling/rounding operations ({@link + * #setScale setScale} and {@link #round round}) return a + * {@code BigDecimal} whose value is approximately (or exactly) equal + * to that of the operand, but whose scale or precision is the + * specified value; that is, they increase or decrease the precision + * of the stored number with minimal effect on its value. Decimal + * point motion operations ({@link #movePointLeft movePointLeft} and + * {@link #movePointRight movePointRight}) return a + * {@code BigDecimal} created from the operand by moving the decimal + * point a specified distance in the specified direction. + * + *

For the sake of brevity and clarity, pseudo-code is used + * throughout the descriptions of {@code BigDecimal} methods. The + * pseudo-code expression {@code (i + j)} is shorthand for "a + * {@code BigDecimal} whose value is that of the {@code BigDecimal} + * {@code i} added to that of the {@code BigDecimal} + * {@code j}." The pseudo-code expression {@code (i == j)} is + * shorthand for "{@code true} if and only if the + * {@code BigDecimal} {@code i} represents the same value as the + * {@code BigDecimal} {@code j}." Other pseudo-code expressions + * are interpreted similarly. Square brackets are used to represent + * the particular {@code BigInteger} and scale pair defining a + * {@code BigDecimal} value; for example [19, 2] is the + * {@code BigDecimal} numerically equal to 0.19 having a scale of 2. + * + *

Note: care should be exercised if {@code BigDecimal} objects + * are used as keys in a {@link java.util.SortedMap SortedMap} or + * elements in a {@link java.util.SortedSet SortedSet} since + * {@code BigDecimal}'s natural ordering is inconsistent + * with equals. See {@link Comparable}, {@link + * java.util.SortedMap} or {@link java.util.SortedSet} for more + * information. + * + *

All methods and constructors for this class throw + * {@code NullPointerException} when passed a {@code null} object + * reference for any input parameter. + * + * @see BigInteger + * @see MathContext + * @see RoundingMode + * @see java.util.SortedMap + * @see java.util.SortedSet + * @author Josh Bloch + * @author Mike Cowlishaw + * @author Joseph D. Darcy + */ +public class BigDecimal extends Number implements Comparable { + /** + * The unscaled value of this BigDecimal, as returned by {@link + * #unscaledValue}. + * + * @serial + * @see #unscaledValue + */ + private volatile BigInteger intVal; + + /** + * The scale of this BigDecimal, as returned by {@link #scale}. + * + * @serial + * @see #scale + */ + private int scale; // Note: this may have any value, so + // calculations must be done in longs + /** + * The number of decimal digits in this BigDecimal, or 0 if the + * number of digits are not known (lookaside information). If + * nonzero, the value is guaranteed correct. Use the precision() + * method to obtain and set the value if it might be 0. This + * field is mutable until set nonzero. + * + * @since 1.5 + */ + private transient int precision; + + /** + * Used to store the canonical string representation, if computed. + */ + private transient String stringCache; + + /** + * Sentinel value for {@link #intCompact} indicating the + * significand information is only available from {@code intVal}. + */ + static final long INFLATED = Long.MIN_VALUE; + + /** + * If the absolute value of the significand of this BigDecimal is + * less than or equal to {@code Long.MAX_VALUE}, the value can be + * compactly stored in this field and used in computations. + */ + private transient long intCompact; + + // All 18-digit base ten strings fit into a long; not all 19-digit + // strings will + private static final int MAX_COMPACT_DIGITS = 18; + + private static final int MAX_BIGINT_BITS = 62; + + /* Appease the serialization gods */ + private static final long serialVersionUID = 6108874887143696463L; + + // Cache of common small BigDecimal values. + private static final BigDecimal zeroThroughTen[] = { + new BigDecimal(BigInteger.ZERO, 0, 0, 1), + new BigDecimal(BigInteger.ONE, 1, 0, 1), + new BigDecimal(BigInteger.valueOf(2), 2, 0, 1), + new BigDecimal(BigInteger.valueOf(3), 3, 0, 1), + new BigDecimal(BigInteger.valueOf(4), 4, 0, 1), + new BigDecimal(BigInteger.valueOf(5), 5, 0, 1), + new BigDecimal(BigInteger.valueOf(6), 6, 0, 1), + new BigDecimal(BigInteger.valueOf(7), 7, 0, 1), + new BigDecimal(BigInteger.valueOf(8), 8, 0, 1), + new BigDecimal(BigInteger.valueOf(9), 9, 0, 1), + new BigDecimal(BigInteger.TEN, 10, 0, 2), + }; + + // Cache of zero scaled by 0 - 15 + private static final BigDecimal[] ZERO_SCALED_BY = { + zeroThroughTen[0], + new BigDecimal(BigInteger.ZERO, 0, 1, 1), + new BigDecimal(BigInteger.ZERO, 0, 2, 1), + new BigDecimal(BigInteger.ZERO, 0, 3, 1), + new BigDecimal(BigInteger.ZERO, 0, 4, 1), + new BigDecimal(BigInteger.ZERO, 0, 5, 1), + new BigDecimal(BigInteger.ZERO, 0, 6, 1), + new BigDecimal(BigInteger.ZERO, 0, 7, 1), + new BigDecimal(BigInteger.ZERO, 0, 8, 1), + new BigDecimal(BigInteger.ZERO, 0, 9, 1), + new BigDecimal(BigInteger.ZERO, 0, 10, 1), + new BigDecimal(BigInteger.ZERO, 0, 11, 1), + new BigDecimal(BigInteger.ZERO, 0, 12, 1), + new BigDecimal(BigInteger.ZERO, 0, 13, 1), + new BigDecimal(BigInteger.ZERO, 0, 14, 1), + new BigDecimal(BigInteger.ZERO, 0, 15, 1), + }; + + // Half of Long.MIN_VALUE & Long.MAX_VALUE. + private static final long HALF_LONG_MAX_VALUE = Long.MAX_VALUE / 2; + private static final long HALF_LONG_MIN_VALUE = Long.MIN_VALUE / 2; + + // Constants + /** + * The value 0, with a scale of 0. + * + * @since 1.5 + */ + public static final BigDecimal ZERO = + zeroThroughTen[0]; + + /** + * The value 1, with a scale of 0. + * + * @since 1.5 + */ + public static final BigDecimal ONE = + zeroThroughTen[1]; + + /** + * The value 10, with a scale of 0. + * + * @since 1.5 + */ + public static final BigDecimal TEN = + zeroThroughTen[10]; + + // Constructors + + /** + * Trusted package private constructor. + * Trusted simply means if val is INFLATED, intVal could not be null and + * if intVal is null, val could not be INFLATED. + */ + BigDecimal(BigInteger intVal, long val, int scale, int prec) { + this.scale = scale; + this.precision = prec; + this.intCompact = val; + this.intVal = intVal; + } + + /** + * Translates a character array representation of a + * {@code BigDecimal} into a {@code BigDecimal}, accepting the + * same sequence of characters as the {@link #BigDecimal(String)} + * constructor, while allowing a sub-array to be specified. + * + *

Note that if the sequence of characters is already available + * within a character array, using this constructor is faster than + * converting the {@code char} array to string and using the + * {@code BigDecimal(String)} constructor . + * + * @param in {@code char} array that is the source of characters. + * @param offset first character in the array to inspect. + * @param len number of characters to consider. + * @throws NumberFormatException if {@code in} is not a valid + * representation of a {@code BigDecimal} or the defined subarray + * is not wholly within {@code in}. + * @since 1.5 + */ + public BigDecimal(char[] in, int offset, int len) { + // protect against huge length. + if (offset+len > in.length || offset < 0) + throw new NumberFormatException(); + // This is the primary string to BigDecimal constructor; all + // incoming strings end up here; it uses explicit (inline) + // parsing for speed and generates at most one intermediate + // (temporary) object (a char[] array) for non-compact case. + + // Use locals for all fields values until completion + int prec = 0; // record precision value + int scl = 0; // record scale value + long rs = 0; // the compact value in long + BigInteger rb = null; // the inflated value in BigInteger + + // use array bounds checking to handle too-long, len == 0, + // bad offset, etc. + try { + // handle the sign + boolean isneg = false; // assume positive + if (in[offset] == '-') { + isneg = true; // leading minus means negative + offset++; + len--; + } else if (in[offset] == '+') { // leading + allowed + offset++; + len--; + } + + // should now be at numeric part of the significand + boolean dot = false; // true when there is a '.' + int cfirst = offset; // record start of integer + long exp = 0; // exponent + char c; // current character + + boolean isCompact = (len <= MAX_COMPACT_DIGITS); + // integer significand array & idx is the index to it. The array + // is ONLY used when we can't use a compact representation. + char coeff[] = isCompact ? null : new char[len]; + int idx = 0; + + for (; len > 0; offset++, len--) { + c = in[offset]; + // have digit + if ((c >= '0' && c <= '9') || Character.isDigit(c)) { + // First compact case, we need not to preserve the character + // and we can just compute the value in place. + if (isCompact) { + int digit = Character.digit(c, 10); + if (digit == 0) { + if (prec == 0) + prec = 1; + else if (rs != 0) { + rs *= 10; + ++prec; + } // else digit is a redundant leading zero + } else { + if (prec != 1 || rs != 0) + ++prec; // prec unchanged if preceded by 0s + rs = rs * 10 + digit; + } + } else { // the unscaled value is likely a BigInteger object. + if (c == '0' || Character.digit(c, 10) == 0) { + if (prec == 0) { + coeff[idx] = c; + prec = 1; + } else if (idx != 0) { + coeff[idx++] = c; + ++prec; + } // else c must be a redundant leading zero + } else { + if (prec != 1 || idx != 0) + ++prec; // prec unchanged if preceded by 0s + coeff[idx++] = c; + } + } + if (dot) + ++scl; + continue; + } + // have dot + if (c == '.') { + // have dot + if (dot) // two dots + throw new NumberFormatException(); + dot = true; + continue; + } + // exponent expected + if ((c != 'e') && (c != 'E')) + throw new NumberFormatException(); + offset++; + c = in[offset]; + len--; + boolean negexp = (c == '-'); + // optional sign + if (negexp || c == '+') { + offset++; + c = in[offset]; + len--; + } + if (len <= 0) // no exponent digits + throw new NumberFormatException(); + // skip leading zeros in the exponent + while (len > 10 && Character.digit(c, 10) == 0) { + offset++; + c = in[offset]; + len--; + } + if (len > 10) // too many nonzero exponent digits + throw new NumberFormatException(); + // c now holds first digit of exponent + for (;; len--) { + int v; + if (c >= '0' && c <= '9') { + v = c - '0'; + } else { + v = Character.digit(c, 10); + if (v < 0) // not a digit + throw new NumberFormatException(); + } + exp = exp * 10 + v; + if (len == 1) + break; // that was final character + offset++; + c = in[offset]; + } + if (negexp) // apply sign + exp = -exp; + // Next test is required for backwards compatibility + if ((int)exp != exp) // overflow + throw new NumberFormatException(); + break; // [saves a test] + } + // here when no characters left + if (prec == 0) // no digits found + throw new NumberFormatException(); + + // Adjust scale if exp is not zero. + if (exp != 0) { // had significant exponent + // Can't call checkScale which relies on proper fields value + long adjustedScale = scl - exp; + if (adjustedScale > Integer.MAX_VALUE || + adjustedScale < Integer.MIN_VALUE) + throw new NumberFormatException("Scale out of range."); + scl = (int)adjustedScale; + } + + // Remove leading zeros from precision (digits count) + if (isCompact) { + rs = isneg ? -rs : rs; + } else { + char quick[]; + if (!isneg) { + quick = (coeff.length != prec) ? + Arrays.copyOf(coeff, prec) : coeff; + } else { + quick = new char[prec + 1]; + quick[0] = '-'; + System.arraycopy(coeff, 0, quick, 1, prec); + } + rb = new BigInteger(quick); + rs = compactValFor(rb); + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new NumberFormatException(); + } catch (NegativeArraySizeException e) { + throw new NumberFormatException(); + } + this.scale = scl; + this.precision = prec; + this.intCompact = rs; + this.intVal = (rs != INFLATED) ? null : rb; + } + + /** + * Translates a character array representation of a + * {@code BigDecimal} into a {@code BigDecimal}, accepting the + * same sequence of characters as the {@link #BigDecimal(String)} + * constructor, while allowing a sub-array to be specified and + * with rounding according to the context settings. + * + *

Note that if the sequence of characters is already available + * within a character array, using this constructor is faster than + * converting the {@code char} array to string and using the + * {@code BigDecimal(String)} constructor . + * + * @param in {@code char} array that is the source of characters. + * @param offset first character in the array to inspect. + * @param len number of characters to consider.. + * @param mc the context to use. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + * @throws NumberFormatException if {@code in} is not a valid + * representation of a {@code BigDecimal} or the defined subarray + * is not wholly within {@code in}. + * @since 1.5 + */ + public BigDecimal(char[] in, int offset, int len, MathContext mc) { + this(in, offset, len); + if (mc.precision > 0) + roundThis(mc); + } + + /** + * Translates a character array representation of a + * {@code BigDecimal} into a {@code BigDecimal}, accepting the + * same sequence of characters as the {@link #BigDecimal(String)} + * constructor. + * + *

Note that if the sequence of characters is already available + * as a character array, using this constructor is faster than + * converting the {@code char} array to string and using the + * {@code BigDecimal(String)} constructor . + * + * @param in {@code char} array that is the source of characters. + * @throws NumberFormatException if {@code in} is not a valid + * representation of a {@code BigDecimal}. + * @since 1.5 + */ + public BigDecimal(char[] in) { + this(in, 0, in.length); + } + + /** + * Translates a character array representation of a + * {@code BigDecimal} into a {@code BigDecimal}, accepting the + * same sequence of characters as the {@link #BigDecimal(String)} + * constructor and with rounding according to the context + * settings. + * + *

Note that if the sequence of characters is already available + * as a character array, using this constructor is faster than + * converting the {@code char} array to string and using the + * {@code BigDecimal(String)} constructor . + * + * @param in {@code char} array that is the source of characters. + * @param mc the context to use. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + * @throws NumberFormatException if {@code in} is not a valid + * representation of a {@code BigDecimal}. + * @since 1.5 + */ + public BigDecimal(char[] in, MathContext mc) { + this(in, 0, in.length, mc); + } + + /** + * Translates the string representation of a {@code BigDecimal} + * into a {@code BigDecimal}. The string representation consists + * of an optional sign, {@code '+'} ( '\u002B') or + * {@code '-'} ('\u002D'), followed by a sequence of + * zero or more decimal digits ("the integer"), optionally + * followed by a fraction, optionally followed by an exponent. + * + *

The fraction consists of a decimal point followed by zero + * or more decimal digits. The string must contain at least one + * digit in either the integer or the fraction. The number formed + * by the sign, the integer and the fraction is referred to as the + * significand. + * + *

The exponent consists of the character {@code 'e'} + * ('\u0065') or {@code 'E'} ('\u0045') + * followed by one or more decimal digits. The value of the + * exponent must lie between -{@link Integer#MAX_VALUE} ({@link + * Integer#MIN_VALUE}+1) and {@link Integer#MAX_VALUE}, inclusive. + * + *

More formally, the strings this constructor accepts are + * described by the following grammar: + *

+ *
+ *
BigDecimalString: + *
Signopt Significand Exponentopt + *

+ *

Sign: + *
{@code +} + *
{@code -} + *

+ *

Significand: + *
IntegerPart {@code .} FractionPartopt + *
{@code .} FractionPart + *
IntegerPart + *

+ *

IntegerPart: + *
Digits + *

+ *

FractionPart: + *
Digits + *

+ *

Exponent: + *
ExponentIndicator SignedInteger + *

+ *

ExponentIndicator: + *
{@code e} + *
{@code E} + *

+ *

SignedInteger: + *
Signopt Digits + *

+ *

Digits: + *
Digit + *
Digits Digit + *

+ *

Digit: + *
any character for which {@link Character#isDigit} + * returns {@code true}, including 0, 1, 2 ... + *
+ *
+ * + *

The scale of the returned {@code BigDecimal} will be the + * number of digits in the fraction, or zero if the string + * contains no decimal point, subject to adjustment for any + * exponent; if the string contains an exponent, the exponent is + * subtracted from the scale. The value of the resulting scale + * must lie between {@code Integer.MIN_VALUE} and + * {@code Integer.MAX_VALUE}, inclusive. + * + *

The character-to-digit mapping is provided by {@link + * java.lang.Character#digit} set to convert to radix 10. The + * String may not contain any extraneous characters (whitespace, + * for example). + * + *

Examples:
+ * The value of the returned {@code BigDecimal} is equal to + * significand × 10 exponent. + * For each string on the left, the resulting representation + * [{@code BigInteger}, {@code scale}] is shown on the right. + *

+     * "0"            [0,0]
+     * "0.00"         [0,2]
+     * "123"          [123,0]
+     * "-123"         [-123,0]
+     * "1.23E3"       [123,-1]
+     * "1.23E+3"      [123,-1]
+     * "12.3E+7"      [123,-6]
+     * "12.0"         [120,1]
+     * "12.3"         [123,1]
+     * "0.00123"      [123,5]
+     * "-1.23E-12"    [-123,14]
+     * "1234.5E-4"    [12345,5]
+     * "0E+7"         [0,-7]
+     * "-0"           [0,0]
+     * 
+ * + *

Note: For values other than {@code float} and + * {@code double} NaN and ±Infinity, this constructor is + * compatible with the values returned by {@link Float#toString} + * and {@link Double#toString}. This is generally the preferred + * way to convert a {@code float} or {@code double} into a + * BigDecimal, as it doesn't suffer from the unpredictability of + * the {@link #BigDecimal(double)} constructor. + * + * @param val String representation of {@code BigDecimal}. + * + * @throws NumberFormatException if {@code val} is not a valid + * representation of a {@code BigDecimal}. + */ + public BigDecimal(String val) { + this(val.toCharArray(), 0, val.length()); + } + + /** + * Translates the string representation of a {@code BigDecimal} + * into a {@code BigDecimal}, accepting the same strings as the + * {@link #BigDecimal(String)} constructor, with rounding + * according to the context settings. + * + * @param val string representation of a {@code BigDecimal}. + * @param mc the context to use. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + * @throws NumberFormatException if {@code val} is not a valid + * representation of a BigDecimal. + * @since 1.5 + */ + public BigDecimal(String val, MathContext mc) { + this(val.toCharArray(), 0, val.length()); + if (mc.precision > 0) + roundThis(mc); + } + + /** + * Translates a {@code double} into a {@code BigDecimal} which + * is the exact decimal representation of the {@code double}'s + * binary floating-point value. The scale of the returned + * {@code BigDecimal} is the smallest value such that + * (10scale × val) is an integer. + *

+ * Notes: + *

    + *
  1. + * The results of this constructor can be somewhat unpredictable. + * One might assume that writing {@code new BigDecimal(0.1)} in + * Java creates a {@code BigDecimal} which is exactly equal to + * 0.1 (an unscaled value of 1, with a scale of 1), but it is + * actually equal to + * 0.1000000000000000055511151231257827021181583404541015625. + * This is because 0.1 cannot be represented exactly as a + * {@code double} (or, for that matter, as a binary fraction of + * any finite length). Thus, the value that is being passed + * in to the constructor is not exactly equal to 0.1, + * appearances notwithstanding. + * + *
  2. + * The {@code String} constructor, on the other hand, is + * perfectly predictable: writing {@code new BigDecimal("0.1")} + * creates a {@code BigDecimal} which is exactly equal to + * 0.1, as one would expect. Therefore, it is generally + * recommended that the {@linkplain #BigDecimal(String) + * String constructor} be used in preference to this one. + * + *
  3. + * When a {@code double} must be used as a source for a + * {@code BigDecimal}, note that this constructor provides an + * exact conversion; it does not give the same result as + * converting the {@code double} to a {@code String} using the + * {@link Double#toString(double)} method and then using the + * {@link #BigDecimal(String)} constructor. To get that result, + * use the {@code static} {@link #valueOf(double)} method. + *
+ * + * @param val {@code double} value to be converted to + * {@code BigDecimal}. + * @throws NumberFormatException if {@code val} is infinite or NaN. + */ + public BigDecimal(double val) { + if (Double.isInfinite(val) || Double.isNaN(val)) + throw new NumberFormatException("Infinite or NaN"); + + // Translate the double into sign, exponent and significand, according + // to the formulae in JLS, Section 20.10.22. + long valBits = Double.doubleToLongBits(val); + int sign = ((valBits >> 63)==0 ? 1 : -1); + int exponent = (int) ((valBits >> 52) & 0x7ffL); + long significand = (exponent==0 ? (valBits & ((1L<<52) - 1)) << 1 + : (valBits & ((1L<<52) - 1)) | (1L<<52)); + exponent -= 1075; + // At this point, val == sign * significand * 2**exponent. + + /* + * Special case zero to supress nonterminating normalization + * and bogus scale calculation. + */ + if (significand == 0) { + intVal = BigInteger.ZERO; + intCompact = 0; + precision = 1; + return; + } + + // Normalize + while((significand & 1) == 0) { // i.e., significand is even + significand >>= 1; + exponent++; + } + + // Calculate intVal and scale + long s = sign * significand; + BigInteger b; + if (exponent < 0) { + b = BigInteger.valueOf(5).pow(-exponent).multiply(s); + scale = -exponent; + } else if (exponent > 0) { + b = BigInteger.valueOf(2).pow(exponent).multiply(s); + } else { + b = BigInteger.valueOf(s); + } + intCompact = compactValFor(b); + intVal = (intCompact != INFLATED) ? null : b; + } + + /** + * Translates a {@code double} into a {@code BigDecimal}, with + * rounding according to the context settings. The scale of the + * {@code BigDecimal} is the smallest value such that + * (10scale × val) is an integer. + * + *

The results of this constructor can be somewhat unpredictable + * and its use is generally not recommended; see the notes under + * the {@link #BigDecimal(double)} constructor. + * + * @param val {@code double} value to be converted to + * {@code BigDecimal}. + * @param mc the context to use. + * @throws ArithmeticException if the result is inexact but the + * RoundingMode is UNNECESSARY. + * @throws NumberFormatException if {@code val} is infinite or NaN. + * @since 1.5 + */ + public BigDecimal(double val, MathContext mc) { + this(val); + if (mc.precision > 0) + roundThis(mc); + } + + /** + * Translates a {@code BigInteger} into a {@code BigDecimal}. + * The scale of the {@code BigDecimal} is zero. + * + * @param val {@code BigInteger} value to be converted to + * {@code BigDecimal}. + */ + public BigDecimal(BigInteger val) { + intCompact = compactValFor(val); + intVal = (intCompact != INFLATED) ? null : val; + } + + /** + * Translates a {@code BigInteger} into a {@code BigDecimal} + * rounding according to the context settings. The scale of the + * {@code BigDecimal} is zero. + * + * @param val {@code BigInteger} value to be converted to + * {@code BigDecimal}. + * @param mc the context to use. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + * @since 1.5 + */ + public BigDecimal(BigInteger val, MathContext mc) { + this(val); + if (mc.precision > 0) + roundThis(mc); + } + + /** + * Translates a {@code BigInteger} unscaled value and an + * {@code int} scale into a {@code BigDecimal}. The value of + * the {@code BigDecimal} is + * (unscaledVal × 10-scale). + * + * @param unscaledVal unscaled value of the {@code BigDecimal}. + * @param scale scale of the {@code BigDecimal}. + */ + public BigDecimal(BigInteger unscaledVal, int scale) { + // Negative scales are now allowed + this(unscaledVal); + this.scale = scale; + } + + /** + * Translates a {@code BigInteger} unscaled value and an + * {@code int} scale into a {@code BigDecimal}, with rounding + * according to the context settings. The value of the + * {@code BigDecimal} is (unscaledVal × + * 10-scale), rounded according to the + * {@code precision} and rounding mode settings. + * + * @param unscaledVal unscaled value of the {@code BigDecimal}. + * @param scale scale of the {@code BigDecimal}. + * @param mc the context to use. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + * @since 1.5 + */ + public BigDecimal(BigInteger unscaledVal, int scale, MathContext mc) { + this(unscaledVal); + this.scale = scale; + if (mc.precision > 0) + roundThis(mc); + } + + /** + * Translates an {@code int} into a {@code BigDecimal}. The + * scale of the {@code BigDecimal} is zero. + * + * @param val {@code int} value to be converted to + * {@code BigDecimal}. + * @since 1.5 + */ + public BigDecimal(int val) { + intCompact = val; + } + + /** + * Translates an {@code int} into a {@code BigDecimal}, with + * rounding according to the context settings. The scale of the + * {@code BigDecimal}, before any rounding, is zero. + * + * @param val {@code int} value to be converted to {@code BigDecimal}. + * @param mc the context to use. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + * @since 1.5 + */ + public BigDecimal(int val, MathContext mc) { + intCompact = val; + if (mc.precision > 0) + roundThis(mc); + } + + /** + * Translates a {@code long} into a {@code BigDecimal}. The + * scale of the {@code BigDecimal} is zero. + * + * @param val {@code long} value to be converted to {@code BigDecimal}. + * @since 1.5 + */ + public BigDecimal(long val) { + this.intCompact = val; + this.intVal = (val == INFLATED) ? BigInteger.valueOf(val) : null; + } + + /** + * Translates a {@code long} into a {@code BigDecimal}, with + * rounding according to the context settings. The scale of the + * {@code BigDecimal}, before any rounding, is zero. + * + * @param val {@code long} value to be converted to {@code BigDecimal}. + * @param mc the context to use. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + * @since 1.5 + */ + public BigDecimal(long val, MathContext mc) { + this(val); + if (mc.precision > 0) + roundThis(mc); + } + + // Static Factory Methods + + /** + * Translates a {@code long} unscaled value and an + * {@code int} scale into a {@code BigDecimal}. This + * {@literal "static factory method"} is provided in preference to + * a ({@code long}, {@code int}) constructor because it + * allows for reuse of frequently used {@code BigDecimal} values.. + * + * @param unscaledVal unscaled value of the {@code BigDecimal}. + * @param scale scale of the {@code BigDecimal}. + * @return a {@code BigDecimal} whose value is + * (unscaledVal × 10-scale). + */ + public static BigDecimal valueOf(long unscaledVal, int scale) { + if (scale == 0) + return valueOf(unscaledVal); + else if (unscaledVal == 0) { + if (scale > 0 && scale < ZERO_SCALED_BY.length) + return ZERO_SCALED_BY[scale]; + else + return new BigDecimal(BigInteger.ZERO, 0, scale, 1); + } + return new BigDecimal(unscaledVal == INFLATED ? + BigInteger.valueOf(unscaledVal) : null, + unscaledVal, scale, 0); + } + + /** + * Translates a {@code long} value into a {@code BigDecimal} + * with a scale of zero. This {@literal "static factory method"} + * is provided in preference to a ({@code long}) constructor + * because it allows for reuse of frequently used + * {@code BigDecimal} values. + * + * @param val value of the {@code BigDecimal}. + * @return a {@code BigDecimal} whose value is {@code val}. + */ + public static BigDecimal valueOf(long val) { + if (val >= 0 && val < zeroThroughTen.length) + return zeroThroughTen[(int)val]; + else if (val != INFLATED) + return new BigDecimal(null, val, 0, 0); + return new BigDecimal(BigInteger.valueOf(val), val, 0, 0); + } + + /** + * Translates a {@code double} into a {@code BigDecimal}, using + * the {@code double}'s canonical string representation provided + * by the {@link Double#toString(double)} method. + * + *

Note: This is generally the preferred way to convert + * a {@code double} (or {@code float}) into a + * {@code BigDecimal}, as the value returned is equal to that + * resulting from constructing a {@code BigDecimal} from the + * result of using {@link Double#toString(double)}. + * + * @param val {@code double} to convert to a {@code BigDecimal}. + * @return a {@code BigDecimal} whose value is equal to or approximately + * equal to the value of {@code val}. + * @throws NumberFormatException if {@code val} is infinite or NaN. + * @since 1.5 + */ + public static BigDecimal valueOf(double val) { + // Reminder: a zero double returns '0.0', so we cannot fastpath + // to use the constant ZERO. This might be important enough to + // justify a factory approach, a cache, or a few private + // constants, later. + return new BigDecimal(Double.toString(val)); + } + + // Arithmetic Operations + /** + * Returns a {@code BigDecimal} whose value is {@code (this + + * augend)}, and whose scale is {@code max(this.scale(), + * augend.scale())}. + * + * @param augend value to be added to this {@code BigDecimal}. + * @return {@code this + augend} + */ + public BigDecimal add(BigDecimal augend) { + long xs = this.intCompact; + long ys = augend.intCompact; + BigInteger fst = (xs != INFLATED) ? null : this.intVal; + BigInteger snd = (ys != INFLATED) ? null : augend.intVal; + int rscale = this.scale; + + long sdiff = (long)rscale - augend.scale; + if (sdiff != 0) { + if (sdiff < 0) { + int raise = checkScale(-sdiff); + rscale = augend.scale; + if (xs == INFLATED || + (xs = longMultiplyPowerTen(xs, raise)) == INFLATED) + fst = bigMultiplyPowerTen(raise); + } else { + int raise = augend.checkScale(sdiff); + if (ys == INFLATED || + (ys = longMultiplyPowerTen(ys, raise)) == INFLATED) + snd = augend.bigMultiplyPowerTen(raise); + } + } + if (xs != INFLATED && ys != INFLATED) { + long sum = xs + ys; + // See "Hacker's Delight" section 2-12 for explanation of + // the overflow test. + if ( (((sum ^ xs) & (sum ^ ys))) >= 0L) // not overflowed + return BigDecimal.valueOf(sum, rscale); + } + if (fst == null) + fst = BigInteger.valueOf(xs); + if (snd == null) + snd = BigInteger.valueOf(ys); + BigInteger sum = fst.add(snd); + return (fst.signum == snd.signum) ? + new BigDecimal(sum, INFLATED, rscale, 0) : + new BigDecimal(sum, rscale); + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (this + augend)}, + * with rounding according to the context settings. + * + * If either number is zero and the precision setting is nonzero then + * the other number, rounded if necessary, is used as the result. + * + * @param augend value to be added to this {@code BigDecimal}. + * @param mc the context to use. + * @return {@code this + augend}, rounded as necessary. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + * @since 1.5 + */ + public BigDecimal add(BigDecimal augend, MathContext mc) { + if (mc.precision == 0) + return add(augend); + BigDecimal lhs = this; + + // Could optimize if values are compact + this.inflate(); + augend.inflate(); + + // If either number is zero then the other number, rounded and + // scaled if necessary, is used as the result. + { + boolean lhsIsZero = lhs.signum() == 0; + boolean augendIsZero = augend.signum() == 0; + + if (lhsIsZero || augendIsZero) { + int preferredScale = Math.max(lhs.scale(), augend.scale()); + BigDecimal result; + + // Could use a factory for zero instead of a new object + if (lhsIsZero && augendIsZero) + return new BigDecimal(BigInteger.ZERO, 0, preferredScale, 0); + + result = lhsIsZero ? doRound(augend, mc) : doRound(lhs, mc); + + if (result.scale() == preferredScale) + return result; + else if (result.scale() > preferredScale) { + BigDecimal scaledResult = + new BigDecimal(result.intVal, result.intCompact, + result.scale, 0); + scaledResult.stripZerosToMatchScale(preferredScale); + return scaledResult; + } else { // result.scale < preferredScale + int precisionDiff = mc.precision - result.precision(); + int scaleDiff = preferredScale - result.scale(); + + if (precisionDiff >= scaleDiff) + return result.setScale(preferredScale); // can achieve target scale + else + return result.setScale(result.scale() + precisionDiff); + } + } + } + + long padding = (long)lhs.scale - augend.scale; + if (padding != 0) { // scales differ; alignment needed + BigDecimal arg[] = preAlign(lhs, augend, padding, mc); + matchScale(arg); + lhs = arg[0]; + augend = arg[1]; + } + + BigDecimal d = new BigDecimal(lhs.inflate().add(augend.inflate()), + lhs.scale); + return doRound(d, mc); + } + + /** + * Returns an array of length two, the sum of whose entries is + * equal to the rounded sum of the {@code BigDecimal} arguments. + * + *

If the digit positions of the arguments have a sufficient + * gap between them, the value smaller in magnitude can be + * condensed into a {@literal "sticky bit"} and the end result will + * round the same way if the precision of the final + * result does not include the high order digit of the small + * magnitude operand. + * + *

Note that while strictly speaking this is an optimization, + * it makes a much wider range of additions practical. + * + *

This corresponds to a pre-shift operation in a fixed + * precision floating-point adder; this method is complicated by + * variable precision of the result as determined by the + * MathContext. A more nuanced operation could implement a + * {@literal "right shift"} on the smaller magnitude operand so + * that the number of digits of the smaller operand could be + * reduced even though the significands partially overlapped. + */ + private BigDecimal[] preAlign(BigDecimal lhs, BigDecimal augend, + long padding, MathContext mc) { + assert padding != 0; + BigDecimal big; + BigDecimal small; + + if (padding < 0) { // lhs is big; augend is small + big = lhs; + small = augend; + } else { // lhs is small; augend is big + big = augend; + small = lhs; + } + + /* + * This is the estimated scale of an ulp of the result; it + * assumes that the result doesn't have a carry-out on a true + * add (e.g. 999 + 1 => 1000) or any subtractive cancellation + * on borrowing (e.g. 100 - 1.2 => 98.8) + */ + long estResultUlpScale = (long)big.scale - big.precision() + mc.precision; + + /* + * The low-order digit position of big is big.scale(). This + * is true regardless of whether big has a positive or + * negative scale. The high-order digit position of small is + * small.scale - (small.precision() - 1). To do the full + * condensation, the digit positions of big and small must be + * disjoint *and* the digit positions of small should not be + * directly visible in the result. + */ + long smallHighDigitPos = (long)small.scale - small.precision() + 1; + if (smallHighDigitPos > big.scale + 2 && // big and small disjoint + smallHighDigitPos > estResultUlpScale + 2) { // small digits not visible + small = BigDecimal.valueOf(small.signum(), + this.checkScale(Math.max(big.scale, estResultUlpScale) + 3)); + } + + // Since addition is symmetric, preserving input order in + // returned operands doesn't matter + BigDecimal[] result = {big, small}; + return result; + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (this - + * subtrahend)}, and whose scale is {@code max(this.scale(), + * subtrahend.scale())}. + * + * @param subtrahend value to be subtracted from this {@code BigDecimal}. + * @return {@code this - subtrahend} + */ + public BigDecimal subtract(BigDecimal subtrahend) { + return add(subtrahend.negate()); + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (this - subtrahend)}, + * with rounding according to the context settings. + * + * If {@code subtrahend} is zero then this, rounded if necessary, is used as the + * result. If this is zero then the result is {@code subtrahend.negate(mc)}. + * + * @param subtrahend value to be subtracted from this {@code BigDecimal}. + * @param mc the context to use. + * @return {@code this - subtrahend}, rounded as necessary. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + * @since 1.5 + */ + public BigDecimal subtract(BigDecimal subtrahend, MathContext mc) { + BigDecimal nsubtrahend = subtrahend.negate(); + if (mc.precision == 0) + return add(nsubtrahend); + // share the special rounding code in add() + return add(nsubtrahend, mc); + } + + /** + * Returns a {@code BigDecimal} whose value is (this × + * multiplicand), and whose scale is {@code (this.scale() + + * multiplicand.scale())}. + * + * @param multiplicand value to be multiplied by this {@code BigDecimal}. + * @return {@code this * multiplicand} + */ + public BigDecimal multiply(BigDecimal multiplicand) { + long x = this.intCompact; + long y = multiplicand.intCompact; + int productScale = checkScale((long)scale + multiplicand.scale); + + // Might be able to do a more clever check incorporating the + // inflated check into the overflow computation. + if (x != INFLATED && y != INFLATED) { + /* + * If the product is not an overflowed value, continue + * to use the compact representation. if either of x or y + * is INFLATED, the product should also be regarded as + * an overflow. Before using the overflow test suggested in + * "Hacker's Delight" section 2-12, we perform quick checks + * using the precision information to see whether the overflow + * would occur since division is expensive on most CPUs. + */ + long product = x * y; + long prec = this.precision() + multiplicand.precision(); + if (prec < 19 || (prec < 21 && (y == 0 || product / y == x))) + return BigDecimal.valueOf(product, productScale); + return new BigDecimal(BigInteger.valueOf(x).multiply(y), INFLATED, + productScale, 0); + } + BigInteger rb; + if (x == INFLATED && y == INFLATED) + rb = this.intVal.multiply(multiplicand.intVal); + else if (x != INFLATED) + rb = multiplicand.intVal.multiply(x); + else + rb = this.intVal.multiply(y); + return new BigDecimal(rb, INFLATED, productScale, 0); + } + + /** + * Returns a {@code BigDecimal} whose value is (this × + * multiplicand), with rounding according to the context settings. + * + * @param multiplicand value to be multiplied by this {@code BigDecimal}. + * @param mc the context to use. + * @return {@code this * multiplicand}, rounded as necessary. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + * @since 1.5 + */ + public BigDecimal multiply(BigDecimal multiplicand, MathContext mc) { + if (mc.precision == 0) + return multiply(multiplicand); + return doRound(this.multiply(multiplicand), mc); + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (this / + * divisor)}, and whose scale is as specified. If rounding must + * be performed to generate a result with the specified scale, the + * specified rounding mode is applied. + * + *

The new {@link #divide(BigDecimal, int, RoundingMode)} method + * should be used in preference to this legacy method. + * + * @param divisor value by which this {@code BigDecimal} is to be divided. + * @param scale scale of the {@code BigDecimal} quotient to be returned. + * @param roundingMode rounding mode to apply. + * @return {@code this / divisor} + * @throws ArithmeticException if {@code divisor} is zero, + * {@code roundingMode==ROUND_UNNECESSARY} and + * the specified scale is insufficient to represent the result + * of the division exactly. + * @throws IllegalArgumentException if {@code roundingMode} does not + * represent a valid rounding mode. + * @see #ROUND_UP + * @see #ROUND_DOWN + * @see #ROUND_CEILING + * @see #ROUND_FLOOR + * @see #ROUND_HALF_UP + * @see #ROUND_HALF_DOWN + * @see #ROUND_HALF_EVEN + * @see #ROUND_UNNECESSARY + */ + public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) { + /* + * IMPLEMENTATION NOTE: This method *must* return a new object + * since divideAndRound uses divide to generate a value whose + * scale is then modified. + */ + if (roundingMode < ROUND_UP || roundingMode > ROUND_UNNECESSARY) + throw new IllegalArgumentException("Invalid rounding mode"); + /* + * Rescale dividend or divisor (whichever can be "upscaled" to + * produce correctly scaled quotient). + * Take care to detect out-of-range scales + */ + BigDecimal dividend = this; + if (checkScale((long)scale + divisor.scale) > this.scale) + dividend = this.setScale(scale + divisor.scale, ROUND_UNNECESSARY); + else + divisor = divisor.setScale(checkScale((long)this.scale - scale), + ROUND_UNNECESSARY); + return divideAndRound(dividend.intCompact, dividend.intVal, + divisor.intCompact, divisor.intVal, + scale, roundingMode, scale); + } + + /** + * Internally used for division operation. The dividend and divisor are + * passed both in {@code long} format and {@code BigInteger} format. The + * returned {@code BigDecimal} object is the quotient whose scale is set to + * the passed in scale. If the remainder is not zero, it will be rounded + * based on the passed in roundingMode. Also, if the remainder is zero and + * the last parameter, i.e. preferredScale is NOT equal to scale, the + * trailing zeros of the result is stripped to match the preferredScale. + */ + private static BigDecimal divideAndRound(long ldividend, BigInteger bdividend, + long ldivisor, BigInteger bdivisor, + int scale, int roundingMode, + int preferredScale) { + boolean isRemainderZero; // record remainder is zero or not + int qsign; // quotient sign + long q = 0, r = 0; // store quotient & remainder in long + MutableBigInteger mq = null; // store quotient + MutableBigInteger mr = null; // store remainder + MutableBigInteger mdivisor = null; + boolean isLongDivision = (ldividend != INFLATED && ldivisor != INFLATED); + if (isLongDivision) { + q = ldividend / ldivisor; + if (roundingMode == ROUND_DOWN && scale == preferredScale) + return new BigDecimal(null, q, scale, 0); + r = ldividend % ldivisor; + isRemainderZero = (r == 0); + qsign = ((ldividend < 0) == (ldivisor < 0)) ? 1 : -1; + } else { + if (bdividend == null) + bdividend = BigInteger.valueOf(ldividend); + // Descend into mutables for faster remainder checks + MutableBigInteger mdividend = new MutableBigInteger(bdividend.mag); + mq = new MutableBigInteger(); + if (ldivisor != INFLATED) { + r = mdividend.divide(ldivisor, mq); + isRemainderZero = (r == 0); + qsign = (ldivisor < 0) ? -bdividend.signum : bdividend.signum; + } else { + mdivisor = new MutableBigInteger(bdivisor.mag); + mr = mdividend.divide(mdivisor, mq); + isRemainderZero = mr.isZero(); + qsign = (bdividend.signum != bdivisor.signum) ? -1 : 1; + } + } + boolean increment = false; + if (!isRemainderZero) { + int cmpFracHalf; + /* Round as appropriate */ + if (roundingMode == ROUND_UNNECESSARY) { // Rounding prohibited + throw new ArithmeticException("Rounding necessary"); + } else if (roundingMode == ROUND_UP) { // Away from zero + increment = true; + } else if (roundingMode == ROUND_DOWN) { // Towards zero + increment = false; + } else if (roundingMode == ROUND_CEILING) { // Towards +infinity + increment = (qsign > 0); + } else if (roundingMode == ROUND_FLOOR) { // Towards -infinity + increment = (qsign < 0); + } else { + if (isLongDivision || ldivisor != INFLATED) { + if (r <= HALF_LONG_MIN_VALUE || r > HALF_LONG_MAX_VALUE) { + cmpFracHalf = 1; // 2 * r can't fit into long + } else { + cmpFracHalf = longCompareMagnitude(2 * r, ldivisor); + } + } else { + cmpFracHalf = mr.compareHalf(mdivisor); + } + if (cmpFracHalf < 0) + increment = false; // We're closer to higher digit + else if (cmpFracHalf > 0) // We're closer to lower digit + increment = true; + else if (roundingMode == ROUND_HALF_UP) + increment = true; + else if (roundingMode == ROUND_HALF_DOWN) + increment = false; + else // roundingMode == ROUND_HALF_EVEN, true iff quotient is odd + increment = isLongDivision ? (q & 1L) != 0L : mq.isOdd(); + } + } + BigDecimal res; + if (isLongDivision) + res = new BigDecimal(null, (increment ? q + qsign : q), scale, 0); + else { + if (increment) + mq.add(MutableBigInteger.ONE); + res = mq.toBigDecimal(qsign, scale); + } + if (isRemainderZero && preferredScale != scale) + res.stripZerosToMatchScale(preferredScale); + return res; + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (this / + * divisor)}, and whose scale is as specified. If rounding must + * be performed to generate a result with the specified scale, the + * specified rounding mode is applied. + * + * @param divisor value by which this {@code BigDecimal} is to be divided. + * @param scale scale of the {@code BigDecimal} quotient to be returned. + * @param roundingMode rounding mode to apply. + * @return {@code this / divisor} + * @throws ArithmeticException if {@code divisor} is zero, + * {@code roundingMode==RoundingMode.UNNECESSARY} and + * the specified scale is insufficient to represent the result + * of the division exactly. + * @since 1.5 + */ + public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) { + return divide(divisor, scale, roundingMode.oldMode); + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (this / + * divisor)}, and whose scale is {@code this.scale()}. If + * rounding must be performed to generate a result with the given + * scale, the specified rounding mode is applied. + * + *

The new {@link #divide(BigDecimal, RoundingMode)} method + * should be used in preference to this legacy method. + * + * @param divisor value by which this {@code BigDecimal} is to be divided. + * @param roundingMode rounding mode to apply. + * @return {@code this / divisor} + * @throws ArithmeticException if {@code divisor==0}, or + * {@code roundingMode==ROUND_UNNECESSARY} and + * {@code this.scale()} is insufficient to represent the result + * of the division exactly. + * @throws IllegalArgumentException if {@code roundingMode} does not + * represent a valid rounding mode. + * @see #ROUND_UP + * @see #ROUND_DOWN + * @see #ROUND_CEILING + * @see #ROUND_FLOOR + * @see #ROUND_HALF_UP + * @see #ROUND_HALF_DOWN + * @see #ROUND_HALF_EVEN + * @see #ROUND_UNNECESSARY + */ + public BigDecimal divide(BigDecimal divisor, int roundingMode) { + return this.divide(divisor, scale, roundingMode); + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (this / + * divisor)}, and whose scale is {@code this.scale()}. If + * rounding must be performed to generate a result with the given + * scale, the specified rounding mode is applied. + * + * @param divisor value by which this {@code BigDecimal} is to be divided. + * @param roundingMode rounding mode to apply. + * @return {@code this / divisor} + * @throws ArithmeticException if {@code divisor==0}, or + * {@code roundingMode==RoundingMode.UNNECESSARY} and + * {@code this.scale()} is insufficient to represent the result + * of the division exactly. + * @since 1.5 + */ + public BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode) { + return this.divide(divisor, scale, roundingMode.oldMode); + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (this / + * divisor)}, and whose preferred scale is {@code (this.scale() - + * divisor.scale())}; if the exact quotient cannot be + * represented (because it has a non-terminating decimal + * expansion) an {@code ArithmeticException} is thrown. + * + * @param divisor value by which this {@code BigDecimal} is to be divided. + * @throws ArithmeticException if the exact quotient does not have a + * terminating decimal expansion + * @return {@code this / divisor} + * @since 1.5 + * @author Joseph D. Darcy + */ + public BigDecimal divide(BigDecimal divisor) { + /* + * Handle zero cases first. + */ + if (divisor.signum() == 0) { // x/0 + if (this.signum() == 0) // 0/0 + throw new ArithmeticException("Division undefined"); // NaN + throw new ArithmeticException("Division by zero"); + } + + // Calculate preferred scale + int preferredScale = saturateLong((long)this.scale - divisor.scale); + if (this.signum() == 0) // 0/y + return (preferredScale >= 0 && + preferredScale < ZERO_SCALED_BY.length) ? + ZERO_SCALED_BY[preferredScale] : + BigDecimal.valueOf(0, preferredScale); + else { + this.inflate(); + divisor.inflate(); + /* + * If the quotient this/divisor has a terminating decimal + * expansion, the expansion can have no more than + * (a.precision() + ceil(10*b.precision)/3) digits. + * Therefore, create a MathContext object with this + * precision and do a divide with the UNNECESSARY rounding + * mode. + */ + MathContext mc = new MathContext( (int)Math.min(this.precision() + + (long)Math.ceil(10.0*divisor.precision()/3.0), + Integer.MAX_VALUE), + RoundingMode.UNNECESSARY); + BigDecimal quotient; + try { + quotient = this.divide(divisor, mc); + } catch (ArithmeticException e) { + throw new ArithmeticException("Non-terminating decimal expansion; " + + "no exact representable decimal result."); + } + + int quotientScale = quotient.scale(); + + // divide(BigDecimal, mc) tries to adjust the quotient to + // the desired one by removing trailing zeros; since the + // exact divide method does not have an explicit digit + // limit, we can add zeros too. + + if (preferredScale > quotientScale) + return quotient.setScale(preferredScale, ROUND_UNNECESSARY); + + return quotient; + } + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (this / + * divisor)}, with rounding according to the context settings. + * + * @param divisor value by which this {@code BigDecimal} is to be divided. + * @param mc the context to use. + * @return {@code this / divisor}, rounded as necessary. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY} or + * {@code mc.precision == 0} and the quotient has a + * non-terminating decimal expansion. + * @since 1.5 + */ + public BigDecimal divide(BigDecimal divisor, MathContext mc) { + int mcp = mc.precision; + if (mcp == 0) + return divide(divisor); + + BigDecimal dividend = this; + long preferredScale = (long)dividend.scale - divisor.scale; + // Now calculate the answer. We use the existing + // divide-and-round method, but as this rounds to scale we have + // to normalize the values here to achieve the desired result. + // For x/y we first handle y=0 and x=0, and then normalize x and + // y to give x' and y' with the following constraints: + // (a) 0.1 <= x' < 1 + // (b) x' <= y' < 10*x' + // Dividing x'/y' with the required scale set to mc.precision then + // will give a result in the range 0.1 to 1 rounded to exactly + // the right number of digits (except in the case of a result of + // 1.000... which can arise when x=y, or when rounding overflows + // The 1.000... case will reduce properly to 1. + if (divisor.signum() == 0) { // x/0 + if (dividend.signum() == 0) // 0/0 + throw new ArithmeticException("Division undefined"); // NaN + throw new ArithmeticException("Division by zero"); + } + if (dividend.signum() == 0) // 0/y + return new BigDecimal(BigInteger.ZERO, 0, + saturateLong(preferredScale), 1); + + // Normalize dividend & divisor so that both fall into [0.1, 0.999...] + int xscale = dividend.precision(); + int yscale = divisor.precision(); + dividend = new BigDecimal(dividend.intVal, dividend.intCompact, + xscale, xscale); + divisor = new BigDecimal(divisor.intVal, divisor.intCompact, + yscale, yscale); + if (dividend.compareMagnitude(divisor) > 0) // satisfy constraint (b) + yscale = divisor.scale -= 1; // [that is, divisor *= 10] + + // In order to find out whether the divide generates the exact result, + // we avoid calling the above divide method. 'quotient' holds the + // return BigDecimal object whose scale will be set to 'scl'. + BigDecimal quotient; + int scl = checkScale(preferredScale + yscale - xscale + mcp); + if (checkScale((long)mcp + yscale) > xscale) + dividend = dividend.setScale(mcp + yscale, ROUND_UNNECESSARY); + else + divisor = divisor.setScale(checkScale((long)xscale - mcp), + ROUND_UNNECESSARY); + quotient = divideAndRound(dividend.intCompact, dividend.intVal, + divisor.intCompact, divisor.intVal, + scl, mc.roundingMode.oldMode, + checkScale(preferredScale)); + // doRound, here, only affects 1000000000 case. + quotient = doRound(quotient, mc); + + return quotient; + } + + /** + * Returns a {@code BigDecimal} whose value is the integer part + * of the quotient {@code (this / divisor)} rounded down. The + * preferred scale of the result is {@code (this.scale() - + * divisor.scale())}. + * + * @param divisor value by which this {@code BigDecimal} is to be divided. + * @return The integer part of {@code this / divisor}. + * @throws ArithmeticException if {@code divisor==0} + * @since 1.5 + */ + public BigDecimal divideToIntegralValue(BigDecimal divisor) { + // Calculate preferred scale + int preferredScale = saturateLong((long)this.scale - divisor.scale); + if (this.compareMagnitude(divisor) < 0) { + // much faster when this << divisor + return BigDecimal.valueOf(0, preferredScale); + } + + if(this.signum() == 0 && divisor.signum() != 0) + return this.setScale(preferredScale, ROUND_UNNECESSARY); + + // Perform a divide with enough digits to round to a correct + // integer value; then remove any fractional digits + + int maxDigits = (int)Math.min(this.precision() + + (long)Math.ceil(10.0*divisor.precision()/3.0) + + Math.abs((long)this.scale() - divisor.scale()) + 2, + Integer.MAX_VALUE); + BigDecimal quotient = this.divide(divisor, new MathContext(maxDigits, + RoundingMode.DOWN)); + if (quotient.scale > 0) { + quotient = quotient.setScale(0, RoundingMode.DOWN); + quotient.stripZerosToMatchScale(preferredScale); + } + + if (quotient.scale < preferredScale) { + // pad with zeros if necessary + quotient = quotient.setScale(preferredScale, ROUND_UNNECESSARY); + } + return quotient; + } + + /** + * Returns a {@code BigDecimal} whose value is the integer part + * of {@code (this / divisor)}. Since the integer part of the + * exact quotient does not depend on the rounding mode, the + * rounding mode does not affect the values returned by this + * method. The preferred scale of the result is + * {@code (this.scale() - divisor.scale())}. An + * {@code ArithmeticException} is thrown if the integer part of + * the exact quotient needs more than {@code mc.precision} + * digits. + * + * @param divisor value by which this {@code BigDecimal} is to be divided. + * @param mc the context to use. + * @return The integer part of {@code this / divisor}. + * @throws ArithmeticException if {@code divisor==0} + * @throws ArithmeticException if {@code mc.precision} {@literal >} 0 and the result + * requires a precision of more than {@code mc.precision} digits. + * @since 1.5 + * @author Joseph D. Darcy + */ + public BigDecimal divideToIntegralValue(BigDecimal divisor, MathContext mc) { + if (mc.precision == 0 || // exact result + (this.compareMagnitude(divisor) < 0) ) // zero result + return divideToIntegralValue(divisor); + + // Calculate preferred scale + int preferredScale = saturateLong((long)this.scale - divisor.scale); + + /* + * Perform a normal divide to mc.precision digits. If the + * remainder has absolute value less than the divisor, the + * integer portion of the quotient fits into mc.precision + * digits. Next, remove any fractional digits from the + * quotient and adjust the scale to the preferred value. + */ + BigDecimal result = this. + divide(divisor, new MathContext(mc.precision, RoundingMode.DOWN)); + + if (result.scale() < 0) { + /* + * Result is an integer. See if quotient represents the + * full integer portion of the exact quotient; if it does, + * the computed remainder will be less than the divisor. + */ + BigDecimal product = result.multiply(divisor); + // If the quotient is the full integer value, + // |dividend-product| < |divisor|. + if (this.subtract(product).compareMagnitude(divisor) >= 0) { + throw new ArithmeticException("Division impossible"); + } + } else if (result.scale() > 0) { + /* + * Integer portion of quotient will fit into precision + * digits; recompute quotient to scale 0 to avoid double + * rounding and then try to adjust, if necessary. + */ + result = result.setScale(0, RoundingMode.DOWN); + } + // else result.scale() == 0; + + int precisionDiff; + if ((preferredScale > result.scale()) && + (precisionDiff = mc.precision - result.precision()) > 0) { + return result.setScale(result.scale() + + Math.min(precisionDiff, preferredScale - result.scale) ); + } else { + result.stripZerosToMatchScale(preferredScale); + return result; + } + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (this % divisor)}. + * + *

The remainder is given by + * {@code this.subtract(this.divideToIntegralValue(divisor).multiply(divisor))}. + * Note that this is not the modulo operation (the result can be + * negative). + * + * @param divisor value by which this {@code BigDecimal} is to be divided. + * @return {@code this % divisor}. + * @throws ArithmeticException if {@code divisor==0} + * @since 1.5 + */ + public BigDecimal remainder(BigDecimal divisor) { + BigDecimal divrem[] = this.divideAndRemainder(divisor); + return divrem[1]; + } + + + /** + * Returns a {@code BigDecimal} whose value is {@code (this % + * divisor)}, with rounding according to the context settings. + * The {@code MathContext} settings affect the implicit divide + * used to compute the remainder. The remainder computation + * itself is by definition exact. Therefore, the remainder may + * contain more than {@code mc.getPrecision()} digits. + * + *

The remainder is given by + * {@code this.subtract(this.divideToIntegralValue(divisor, + * mc).multiply(divisor))}. Note that this is not the modulo + * operation (the result can be negative). + * + * @param divisor value by which this {@code BigDecimal} is to be divided. + * @param mc the context to use. + * @return {@code this % divisor}, rounded as necessary. + * @throws ArithmeticException if {@code divisor==0} + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}, or {@code mc.precision} + * {@literal >} 0 and the result of {@code this.divideToIntgralValue(divisor)} would + * require a precision of more than {@code mc.precision} digits. + * @see #divideToIntegralValue(java.math.BigDecimal, java.math.MathContext) + * @since 1.5 + */ + public BigDecimal remainder(BigDecimal divisor, MathContext mc) { + BigDecimal divrem[] = this.divideAndRemainder(divisor, mc); + return divrem[1]; + } + + /** + * Returns a two-element {@code BigDecimal} array containing the + * result of {@code divideToIntegralValue} followed by the result of + * {@code remainder} on the two operands. + * + *

Note that if both the integer quotient and remainder are + * needed, this method is faster than using the + * {@code divideToIntegralValue} and {@code remainder} methods + * separately because the division need only be carried out once. + * + * @param divisor value by which this {@code BigDecimal} is to be divided, + * and the remainder computed. + * @return a two element {@code BigDecimal} array: the quotient + * (the result of {@code divideToIntegralValue}) is the initial element + * and the remainder is the final element. + * @throws ArithmeticException if {@code divisor==0} + * @see #divideToIntegralValue(java.math.BigDecimal, java.math.MathContext) + * @see #remainder(java.math.BigDecimal, java.math.MathContext) + * @since 1.5 + */ + public BigDecimal[] divideAndRemainder(BigDecimal divisor) { + // we use the identity x = i * y + r to determine r + BigDecimal[] result = new BigDecimal[2]; + + result[0] = this.divideToIntegralValue(divisor); + result[1] = this.subtract(result[0].multiply(divisor)); + return result; + } + + /** + * Returns a two-element {@code BigDecimal} array containing the + * result of {@code divideToIntegralValue} followed by the result of + * {@code remainder} on the two operands calculated with rounding + * according to the context settings. + * + *

Note that if both the integer quotient and remainder are + * needed, this method is faster than using the + * {@code divideToIntegralValue} and {@code remainder} methods + * separately because the division need only be carried out once. + * + * @param divisor value by which this {@code BigDecimal} is to be divided, + * and the remainder computed. + * @param mc the context to use. + * @return a two element {@code BigDecimal} array: the quotient + * (the result of {@code divideToIntegralValue}) is the + * initial element and the remainder is the final element. + * @throws ArithmeticException if {@code divisor==0} + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}, or {@code mc.precision} + * {@literal >} 0 and the result of {@code this.divideToIntgralValue(divisor)} would + * require a precision of more than {@code mc.precision} digits. + * @see #divideToIntegralValue(java.math.BigDecimal, java.math.MathContext) + * @see #remainder(java.math.BigDecimal, java.math.MathContext) + * @since 1.5 + */ + public BigDecimal[] divideAndRemainder(BigDecimal divisor, MathContext mc) { + if (mc.precision == 0) + return divideAndRemainder(divisor); + + BigDecimal[] result = new BigDecimal[2]; + BigDecimal lhs = this; + + result[0] = lhs.divideToIntegralValue(divisor, mc); + result[1] = lhs.subtract(result[0].multiply(divisor)); + return result; + } + + /** + * Returns a {@code BigDecimal} whose value is + * (thisn), The power is computed exactly, to + * unlimited precision. + * + *

The parameter {@code n} must be in the range 0 through + * 999999999, inclusive. {@code ZERO.pow(0)} returns {@link + * #ONE}. + * + * Note that future releases may expand the allowable exponent + * range of this method. + * + * @param n power to raise this {@code BigDecimal} to. + * @return thisn + * @throws ArithmeticException if {@code n} is out of range. + * @since 1.5 + */ + public BigDecimal pow(int n) { + if (n < 0 || n > 999999999) + throw new ArithmeticException("Invalid operation"); + // No need to calculate pow(n) if result will over/underflow. + // Don't attempt to support "supernormal" numbers. + int newScale = checkScale((long)scale * n); + this.inflate(); + return new BigDecimal(intVal.pow(n), newScale); + } + + + /** + * Returns a {@code BigDecimal} whose value is + * (thisn). The current implementation uses + * the core algorithm defined in ANSI standard X3.274-1996 with + * rounding according to the context settings. In general, the + * returned numerical value is within two ulps of the exact + * numerical value for the chosen precision. Note that future + * releases may use a different algorithm with a decreased + * allowable error bound and increased allowable exponent range. + * + *

The X3.274-1996 algorithm is: + * + *

    + *
  • An {@code ArithmeticException} exception is thrown if + *
      + *
    • {@code abs(n) > 999999999} + *
    • {@code mc.precision == 0} and {@code n < 0} + *
    • {@code mc.precision > 0} and {@code n} has more than + * {@code mc.precision} decimal digits + *
    + * + *
  • if {@code n} is zero, {@link #ONE} is returned even if + * {@code this} is zero, otherwise + *
      + *
    • if {@code n} is positive, the result is calculated via + * the repeated squaring technique into a single accumulator. + * The individual multiplications with the accumulator use the + * same math context settings as in {@code mc} except for a + * precision increased to {@code mc.precision + elength + 1} + * where {@code elength} is the number of decimal digits in + * {@code n}. + * + *
    • if {@code n} is negative, the result is calculated as if + * {@code n} were positive; this value is then divided into one + * using the working precision specified above. + * + *
    • The final value from either the positive or negative case + * is then rounded to the destination precision. + *
    + *
+ * + * @param n power to raise this {@code BigDecimal} to. + * @param mc the context to use. + * @return thisn using the ANSI standard X3.274-1996 + * algorithm + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}, or {@code n} is out + * of range. + * @since 1.5 + */ + public BigDecimal pow(int n, MathContext mc) { + if (mc.precision == 0) + return pow(n); + if (n < -999999999 || n > 999999999) + throw new ArithmeticException("Invalid operation"); + if (n == 0) + return ONE; // x**0 == 1 in X3.274 + this.inflate(); + BigDecimal lhs = this; + MathContext workmc = mc; // working settings + int mag = Math.abs(n); // magnitude of n + if (mc.precision > 0) { + + int elength = longDigitLength(mag); // length of n in digits + if (elength > mc.precision) // X3.274 rule + throw new ArithmeticException("Invalid operation"); + workmc = new MathContext(mc.precision + elength + 1, + mc.roundingMode); + } + // ready to carry out power calculation... + BigDecimal acc = ONE; // accumulator + boolean seenbit = false; // set once we've seen a 1-bit + for (int i=1;;i++) { // for each bit [top bit ignored] + mag += mag; // shift left 1 bit + if (mag < 0) { // top bit is set + seenbit = true; // OK, we're off + acc = acc.multiply(lhs, workmc); // acc=acc*x + } + if (i == 31) + break; // that was the last bit + if (seenbit) + acc=acc.multiply(acc, workmc); // acc=acc*acc [square] + // else (!seenbit) no point in squaring ONE + } + // if negative n, calculate the reciprocal using working precision + if (n<0) // [hence mc.precision>0] + acc=ONE.divide(acc, workmc); + // round to final precision and strip zeros + return doRound(acc, mc); + } + + /** + * Returns a {@code BigDecimal} whose value is the absolute value + * of this {@code BigDecimal}, and whose scale is + * {@code this.scale()}. + * + * @return {@code abs(this)} + */ + public BigDecimal abs() { + return (signum() < 0 ? negate() : this); + } + + /** + * Returns a {@code BigDecimal} whose value is the absolute value + * of this {@code BigDecimal}, with rounding according to the + * context settings. + * + * @param mc the context to use. + * @return {@code abs(this)}, rounded as necessary. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + * @since 1.5 + */ + public BigDecimal abs(MathContext mc) { + return (signum() < 0 ? negate(mc) : plus(mc)); + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (-this)}, + * and whose scale is {@code this.scale()}. + * + * @return {@code -this}. + */ + public BigDecimal negate() { + BigDecimal result; + if (intCompact != INFLATED) + result = BigDecimal.valueOf(-intCompact, scale); + else { + result = new BigDecimal(intVal.negate(), scale); + result.precision = precision; + } + return result; + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (-this)}, + * with rounding according to the context settings. + * + * @param mc the context to use. + * @return {@code -this}, rounded as necessary. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + * @since 1.5 + */ + public BigDecimal negate(MathContext mc) { + return negate().plus(mc); + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (+this)}, and whose + * scale is {@code this.scale()}. + * + *

This method, which simply returns this {@code BigDecimal} + * is included for symmetry with the unary minus method {@link + * #negate()}. + * + * @return {@code this}. + * @see #negate() + * @since 1.5 + */ + public BigDecimal plus() { + return this; + } + + /** + * Returns a {@code BigDecimal} whose value is {@code (+this)}, + * with rounding according to the context settings. + * + *

The effect of this method is identical to that of the {@link + * #round(MathContext)} method. + * + * @param mc the context to use. + * @return {@code this}, rounded as necessary. A zero result will + * have a scale of 0. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + * @see #round(MathContext) + * @since 1.5 + */ + public BigDecimal plus(MathContext mc) { + if (mc.precision == 0) // no rounding please + return this; + return doRound(this, mc); + } + + /** + * Returns the signum function of this {@code BigDecimal}. + * + * @return -1, 0, or 1 as the value of this {@code BigDecimal} + * is negative, zero, or positive. + */ + public int signum() { + return (intCompact != INFLATED)? + Long.signum(intCompact): + intVal.signum(); + } + + /** + * Returns the scale of this {@code BigDecimal}. If zero + * or positive, the scale is the number of digits to the right of + * the decimal point. If negative, the unscaled value of the + * number is multiplied by ten to the power of the negation of the + * scale. For example, a scale of {@code -3} means the unscaled + * value is multiplied by 1000. + * + * @return the scale of this {@code BigDecimal}. + */ + public int scale() { + return scale; + } + + /** + * Returns the precision of this {@code BigDecimal}. (The + * precision is the number of digits in the unscaled value.) + * + *

The precision of a zero value is 1. + * + * @return the precision of this {@code BigDecimal}. + * @since 1.5 + */ + public int precision() { + int result = precision; + if (result == 0) { + long s = intCompact; + if (s != INFLATED) + result = longDigitLength(s); + else + result = bigDigitLength(inflate()); + precision = result; + } + return result; + } + + + /** + * Returns a {@code BigInteger} whose value is the unscaled + * value of this {@code BigDecimal}. (Computes (this * + * 10this.scale()).) + * + * @return the unscaled value of this {@code BigDecimal}. + * @since 1.2 + */ + public BigInteger unscaledValue() { + return this.inflate(); + } + + // Rounding Modes + + /** + * Rounding mode to round away from zero. Always increments the + * digit prior to a nonzero discarded fraction. Note that this rounding + * mode never decreases the magnitude of the calculated value. + */ + public final static int ROUND_UP = 0; + + /** + * Rounding mode to round towards zero. Never increments the digit + * prior to a discarded fraction (i.e., truncates). Note that this + * rounding mode never increases the magnitude of the calculated value. + */ + public final static int ROUND_DOWN = 1; + + /** + * Rounding mode to round towards positive infinity. If the + * {@code BigDecimal} is positive, behaves as for + * {@code ROUND_UP}; if negative, behaves as for + * {@code ROUND_DOWN}. Note that this rounding mode never + * decreases the calculated value. + */ + public final static int ROUND_CEILING = 2; + + /** + * Rounding mode to round towards negative infinity. If the + * {@code BigDecimal} is positive, behave as for + * {@code ROUND_DOWN}; if negative, behave as for + * {@code ROUND_UP}. Note that this rounding mode never + * increases the calculated value. + */ + public final static int ROUND_FLOOR = 3; + + /** + * Rounding mode to round towards {@literal "nearest neighbor"} + * unless both neighbors are equidistant, in which case round up. + * Behaves as for {@code ROUND_UP} if the discarded fraction is + * ≥ 0.5; otherwise, behaves as for {@code ROUND_DOWN}. Note + * that this is the rounding mode that most of us were taught in + * grade school. + */ + public final static int ROUND_HALF_UP = 4; + + /** + * Rounding mode to round towards {@literal "nearest neighbor"} + * unless both neighbors are equidistant, in which case round + * down. Behaves as for {@code ROUND_UP} if the discarded + * fraction is {@literal >} 0.5; otherwise, behaves as for + * {@code ROUND_DOWN}. + */ + public final static int ROUND_HALF_DOWN = 5; + + /** + * Rounding mode to round towards the {@literal "nearest neighbor"} + * unless both neighbors are equidistant, in which case, round + * towards the even neighbor. Behaves as for + * {@code ROUND_HALF_UP} if the digit to the left of the + * discarded fraction is odd; behaves as for + * {@code ROUND_HALF_DOWN} if it's even. Note that this is the + * rounding mode that minimizes cumulative error when applied + * repeatedly over a sequence of calculations. + */ + public final static int ROUND_HALF_EVEN = 6; + + /** + * Rounding mode to assert that the requested operation has an exact + * result, hence no rounding is necessary. If this rounding mode is + * specified on an operation that yields an inexact result, an + * {@code ArithmeticException} is thrown. + */ + public final static int ROUND_UNNECESSARY = 7; + + + // Scaling/Rounding Operations + + /** + * Returns a {@code BigDecimal} rounded according to the + * {@code MathContext} settings. If the precision setting is 0 then + * no rounding takes place. + * + *

The effect of this method is identical to that of the + * {@link #plus(MathContext)} method. + * + * @param mc the context to use. + * @return a {@code BigDecimal} rounded according to the + * {@code MathContext} settings. + * @throws ArithmeticException if the rounding mode is + * {@code UNNECESSARY} and the + * {@code BigDecimal} operation would require rounding. + * @see #plus(MathContext) + * @since 1.5 + */ + public BigDecimal round(MathContext mc) { + return plus(mc); + } + + /** + * Returns a {@code BigDecimal} whose scale is the specified + * value, and whose unscaled value is determined by multiplying or + * dividing this {@code BigDecimal}'s unscaled value by the + * appropriate power of ten to maintain its overall value. If the + * scale is reduced by the operation, the unscaled value must be + * divided (rather than multiplied), and the value may be changed; + * in this case, the specified rounding mode is applied to the + * division. + * + *

Note that since BigDecimal objects are immutable, calls of + * this method do not result in the original object being + * modified, contrary to the usual convention of having methods + * named setX mutate field {@code X}. + * Instead, {@code setScale} returns an object with the proper + * scale; the returned object may or may not be newly allocated. + * + * @param newScale scale of the {@code BigDecimal} value to be returned. + * @param roundingMode The rounding mode to apply. + * @return a {@code BigDecimal} whose scale is the specified value, + * and whose unscaled value is determined by multiplying or + * dividing this {@code BigDecimal}'s unscaled value by the + * appropriate power of ten to maintain its overall value. + * @throws ArithmeticException if {@code roundingMode==UNNECESSARY} + * and the specified scaling operation would require + * rounding. + * @see RoundingMode + * @since 1.5 + */ + public BigDecimal setScale(int newScale, RoundingMode roundingMode) { + return setScale(newScale, roundingMode.oldMode); + } + + /** + * Returns a {@code BigDecimal} whose scale is the specified + * value, and whose unscaled value is determined by multiplying or + * dividing this {@code BigDecimal}'s unscaled value by the + * appropriate power of ten to maintain its overall value. If the + * scale is reduced by the operation, the unscaled value must be + * divided (rather than multiplied), and the value may be changed; + * in this case, the specified rounding mode is applied to the + * division. + * + *

Note that since BigDecimal objects are immutable, calls of + * this method do not result in the original object being + * modified, contrary to the usual convention of having methods + * named setX mutate field {@code X}. + * Instead, {@code setScale} returns an object with the proper + * scale; the returned object may or may not be newly allocated. + * + *

The new {@link #setScale(int, RoundingMode)} method should + * be used in preference to this legacy method. + * + * @param newScale scale of the {@code BigDecimal} value to be returned. + * @param roundingMode The rounding mode to apply. + * @return a {@code BigDecimal} whose scale is the specified value, + * and whose unscaled value is determined by multiplying or + * dividing this {@code BigDecimal}'s unscaled value by the + * appropriate power of ten to maintain its overall value. + * @throws ArithmeticException if {@code roundingMode==ROUND_UNNECESSARY} + * and the specified scaling operation would require + * rounding. + * @throws IllegalArgumentException if {@code roundingMode} does not + * represent a valid rounding mode. + * @see #ROUND_UP + * @see #ROUND_DOWN + * @see #ROUND_CEILING + * @see #ROUND_FLOOR + * @see #ROUND_HALF_UP + * @see #ROUND_HALF_DOWN + * @see #ROUND_HALF_EVEN + * @see #ROUND_UNNECESSARY + */ + public BigDecimal setScale(int newScale, int roundingMode) { + if (roundingMode < ROUND_UP || roundingMode > ROUND_UNNECESSARY) + throw new IllegalArgumentException("Invalid rounding mode"); + + int oldScale = this.scale; + if (newScale == oldScale) // easy case + return this; + if (this.signum() == 0) // zero can have any scale + return BigDecimal.valueOf(0, newScale); + + long rs = this.intCompact; + if (newScale > oldScale) { + int raise = checkScale((long)newScale - oldScale); + BigInteger rb = null; + if (rs == INFLATED || + (rs = longMultiplyPowerTen(rs, raise)) == INFLATED) + rb = bigMultiplyPowerTen(raise); + return new BigDecimal(rb, rs, newScale, + (precision > 0) ? precision + raise : 0); + } else { + // newScale < oldScale -- drop some digits + // Can't predict the precision due to the effect of rounding. + int drop = checkScale((long)oldScale - newScale); + if (drop < LONG_TEN_POWERS_TABLE.length) + return divideAndRound(rs, this.intVal, + LONG_TEN_POWERS_TABLE[drop], null, + newScale, roundingMode, newScale); + else + return divideAndRound(rs, this.intVal, + INFLATED, bigTenToThe(drop), + newScale, roundingMode, newScale); + } + } + + /** + * Returns a {@code BigDecimal} whose scale is the specified + * value, and whose value is numerically equal to this + * {@code BigDecimal}'s. Throws an {@code ArithmeticException} + * if this is not possible. + * + *

This call is typically used to increase the scale, in which + * case it is guaranteed that there exists a {@code BigDecimal} + * of the specified scale and the correct value. The call can + * also be used to reduce the scale if the caller knows that the + * {@code BigDecimal} has sufficiently many zeros at the end of + * its fractional part (i.e., factors of ten in its integer value) + * to allow for the rescaling without changing its value. + * + *

This method returns the same result as the two-argument + * versions of {@code setScale}, but saves the caller the trouble + * of specifying a rounding mode in cases where it is irrelevant. + * + *

Note that since {@code BigDecimal} objects are immutable, + * calls of this method do not result in the original + * object being modified, contrary to the usual convention of + * having methods named setX mutate field + * {@code X}. Instead, {@code setScale} returns an + * object with the proper scale; the returned object may or may + * not be newly allocated. + * + * @param newScale scale of the {@code BigDecimal} value to be returned. + * @return a {@code BigDecimal} whose scale is the specified value, and + * whose unscaled value is determined by multiplying or dividing + * this {@code BigDecimal}'s unscaled value by the appropriate + * power of ten to maintain its overall value. + * @throws ArithmeticException if the specified scaling operation would + * require rounding. + * @see #setScale(int, int) + * @see #setScale(int, RoundingMode) + */ + public BigDecimal setScale(int newScale) { + return setScale(newScale, ROUND_UNNECESSARY); + } + + // Decimal Point Motion Operations + + /** + * Returns a {@code BigDecimal} which is equivalent to this one + * with the decimal point moved {@code n} places to the left. If + * {@code n} is non-negative, the call merely adds {@code n} to + * the scale. If {@code n} is negative, the call is equivalent + * to {@code movePointRight(-n)}. The {@code BigDecimal} + * returned by this call has value (this × + * 10-n) and scale {@code max(this.scale()+n, + * 0)}. + * + * @param n number of places to move the decimal point to the left. + * @return a {@code BigDecimal} which is equivalent to this one with the + * decimal point moved {@code n} places to the left. + * @throws ArithmeticException if scale overflows. + */ + public BigDecimal movePointLeft(int n) { + // Cannot use movePointRight(-n) in case of n==Integer.MIN_VALUE + int newScale = checkScale((long)scale + n); + BigDecimal num = new BigDecimal(intVal, intCompact, newScale, 0); + return num.scale < 0 ? num.setScale(0, ROUND_UNNECESSARY) : num; + } + + /** + * Returns a {@code BigDecimal} which is equivalent to this one + * with the decimal point moved {@code n} places to the right. + * If {@code n} is non-negative, the call merely subtracts + * {@code n} from the scale. If {@code n} is negative, the call + * is equivalent to {@code movePointLeft(-n)}. The + * {@code BigDecimal} returned by this call has value (this + * × 10n) and scale {@code max(this.scale()-n, + * 0)}. + * + * @param n number of places to move the decimal point to the right. + * @return a {@code BigDecimal} which is equivalent to this one + * with the decimal point moved {@code n} places to the right. + * @throws ArithmeticException if scale overflows. + */ + public BigDecimal movePointRight(int n) { + // Cannot use movePointLeft(-n) in case of n==Integer.MIN_VALUE + int newScale = checkScale((long)scale - n); + BigDecimal num = new BigDecimal(intVal, intCompact, newScale, 0); + return num.scale < 0 ? num.setScale(0, ROUND_UNNECESSARY) : num; + } + + /** + * Returns a BigDecimal whose numerical value is equal to + * ({@code this} * 10n). The scale of + * the result is {@code (this.scale() - n)}. + * + * @throws ArithmeticException if the scale would be + * outside the range of a 32-bit integer. + * + * @since 1.5 + */ + public BigDecimal scaleByPowerOfTen(int n) { + return new BigDecimal(intVal, intCompact, + checkScale((long)scale - n), precision); + } + + /** + * Returns a {@code BigDecimal} which is numerically equal to + * this one but with any trailing zeros removed from the + * representation. For example, stripping the trailing zeros from + * the {@code BigDecimal} value {@code 600.0}, which has + * [{@code BigInteger}, {@code scale}] components equals to + * [6000, 1], yields {@code 6E2} with [{@code BigInteger}, + * {@code scale}] components equals to [6, -2] + * + * @return a numerically equal {@code BigDecimal} with any + * trailing zeros removed. + * @since 1.5 + */ + public BigDecimal stripTrailingZeros() { + this.inflate(); + BigDecimal result = new BigDecimal(intVal, scale); + result.stripZerosToMatchScale(Long.MIN_VALUE); + return result; + } + + // Comparison Operations + + /** + * Compares this {@code BigDecimal} with the specified + * {@code BigDecimal}. Two {@code BigDecimal} objects that are + * equal in value but have a different scale (like 2.0 and 2.00) + * are considered equal by this method. This method is provided + * in preference to individual methods for each of the six boolean + * comparison operators ({@literal <}, ==, + * {@literal >}, {@literal >=}, !=, {@literal <=}). The + * suggested idiom for performing these comparisons is: + * {@code (x.compareTo(y)} <op> {@code 0)}, where + * <op> is one of the six comparison operators. + * + * @param val {@code BigDecimal} to which this {@code BigDecimal} is + * to be compared. + * @return -1, 0, or 1 as this {@code BigDecimal} is numerically + * less than, equal to, or greater than {@code val}. + */ + public int compareTo(BigDecimal val) { + // Quick path for equal scale and non-inflated case. + if (scale == val.scale) { + long xs = intCompact; + long ys = val.intCompact; + if (xs != INFLATED && ys != INFLATED) + return xs != ys ? ((xs > ys) ? 1 : -1) : 0; + } + int xsign = this.signum(); + int ysign = val.signum(); + if (xsign != ysign) + return (xsign > ysign) ? 1 : -1; + if (xsign == 0) + return 0; + int cmp = compareMagnitude(val); + return (xsign > 0) ? cmp : -cmp; + } + + /** + * Version of compareTo that ignores sign. + */ + private int compareMagnitude(BigDecimal val) { + // Match scales, avoid unnecessary inflation + long ys = val.intCompact; + long xs = this.intCompact; + if (xs == 0) + return (ys == 0) ? 0 : -1; + if (ys == 0) + return 1; + + int sdiff = this.scale - val.scale; + if (sdiff != 0) { + // Avoid matching scales if the (adjusted) exponents differ + int xae = this.precision() - this.scale; // [-1] + int yae = val.precision() - val.scale; // [-1] + if (xae < yae) + return -1; + if (xae > yae) + return 1; + BigInteger rb = null; + if (sdiff < 0) { + if ( (xs == INFLATED || + (xs = longMultiplyPowerTen(xs, -sdiff)) == INFLATED) && + ys == INFLATED) { + rb = bigMultiplyPowerTen(-sdiff); + return rb.compareMagnitude(val.intVal); + } + } else { // sdiff > 0 + if ( (ys == INFLATED || + (ys = longMultiplyPowerTen(ys, sdiff)) == INFLATED) && + xs == INFLATED) { + rb = val.bigMultiplyPowerTen(sdiff); + return this.intVal.compareMagnitude(rb); + } + } + } + if (xs != INFLATED) + return (ys != INFLATED) ? longCompareMagnitude(xs, ys) : -1; + else if (ys != INFLATED) + return 1; + else + return this.intVal.compareMagnitude(val.intVal); + } + + /** + * Compares this {@code BigDecimal} with the specified + * {@code Object} for equality. Unlike {@link + * #compareTo(BigDecimal) compareTo}, this method considers two + * {@code BigDecimal} objects equal only if they are equal in + * value and scale (thus 2.0 is not equal to 2.00 when compared by + * this method). + * + * @param x {@code Object} to which this {@code BigDecimal} is + * to be compared. + * @return {@code true} if and only if the specified {@code Object} is a + * {@code BigDecimal} whose value and scale are equal to this + * {@code BigDecimal}'s. + * @see #compareTo(java.math.BigDecimal) + * @see #hashCode + */ + @Override + public boolean equals(Object x) { + if (!(x instanceof BigDecimal)) + return false; + BigDecimal xDec = (BigDecimal) x; + if (x == this) + return true; + if (scale != xDec.scale) + return false; + long s = this.intCompact; + long xs = xDec.intCompact; + if (s != INFLATED) { + if (xs == INFLATED) + xs = compactValFor(xDec.intVal); + return xs == s; + } else if (xs != INFLATED) + return xs == compactValFor(this.intVal); + + return this.inflate().equals(xDec.inflate()); + } + + /** + * Returns the minimum of this {@code BigDecimal} and + * {@code val}. + * + * @param val value with which the minimum is to be computed. + * @return the {@code BigDecimal} whose value is the lesser of this + * {@code BigDecimal} and {@code val}. If they are equal, + * as defined by the {@link #compareTo(BigDecimal) compareTo} + * method, {@code this} is returned. + * @see #compareTo(java.math.BigDecimal) + */ + public BigDecimal min(BigDecimal val) { + return (compareTo(val) <= 0 ? this : val); + } + + /** + * Returns the maximum of this {@code BigDecimal} and {@code val}. + * + * @param val value with which the maximum is to be computed. + * @return the {@code BigDecimal} whose value is the greater of this + * {@code BigDecimal} and {@code val}. If they are equal, + * as defined by the {@link #compareTo(BigDecimal) compareTo} + * method, {@code this} is returned. + * @see #compareTo(java.math.BigDecimal) + */ + public BigDecimal max(BigDecimal val) { + return (compareTo(val) >= 0 ? this : val); + } + + // Hash Function + + /** + * Returns the hash code for this {@code BigDecimal}. Note that + * two {@code BigDecimal} objects that are numerically equal but + * differ in scale (like 2.0 and 2.00) will generally not + * have the same hash code. + * + * @return hash code for this {@code BigDecimal}. + * @see #equals(Object) + */ + @Override + public int hashCode() { + if (intCompact != INFLATED) { + long val2 = (intCompact < 0)? -intCompact : intCompact; + int temp = (int)( ((int)(val2 >>> 32)) * 31 + + (val2 & LONG_MASK)); + return 31*((intCompact < 0) ?-temp:temp) + scale; + } else + return 31*intVal.hashCode() + scale; + } + + // Format Converters + + /** + * Returns the string representation of this {@code BigDecimal}, + * using scientific notation if an exponent is needed. + * + *

A standard canonical string form of the {@code BigDecimal} + * is created as though by the following steps: first, the + * absolute value of the unscaled value of the {@code BigDecimal} + * is converted to a string in base ten using the characters + * {@code '0'} through {@code '9'} with no leading zeros (except + * if its value is zero, in which case a single {@code '0'} + * character is used). + * + *

Next, an adjusted exponent is calculated; this is the + * negated scale, plus the number of characters in the converted + * unscaled value, less one. That is, + * {@code -scale+(ulength-1)}, where {@code ulength} is the + * length of the absolute value of the unscaled value in decimal + * digits (its precision). + * + *

If the scale is greater than or equal to zero and the + * adjusted exponent is greater than or equal to {@code -6}, the + * number will be converted to a character form without using + * exponential notation. In this case, if the scale is zero then + * no decimal point is added and if the scale is positive a + * decimal point will be inserted with the scale specifying the + * number of characters to the right of the decimal point. + * {@code '0'} characters are added to the left of the converted + * unscaled value as necessary. If no character precedes the + * decimal point after this insertion then a conventional + * {@code '0'} character is prefixed. + * + *

Otherwise (that is, if the scale is negative, or the + * adjusted exponent is less than {@code -6}), the number will be + * converted to a character form using exponential notation. In + * this case, if the converted {@code BigInteger} has more than + * one digit a decimal point is inserted after the first digit. + * An exponent in character form is then suffixed to the converted + * unscaled value (perhaps with inserted decimal point); this + * comprises the letter {@code 'E'} followed immediately by the + * adjusted exponent converted to a character form. The latter is + * in base ten, using the characters {@code '0'} through + * {@code '9'} with no leading zeros, and is always prefixed by a + * sign character {@code '-'} ('\u002D') if the + * adjusted exponent is negative, {@code '+'} + * ('\u002B') otherwise). + * + *

Finally, the entire string is prefixed by a minus sign + * character {@code '-'} ('\u002D') if the unscaled + * value is less than zero. No sign character is prefixed if the + * unscaled value is zero or positive. + * + *

Examples: + *

For each representation [unscaled value, scale] + * on the left, the resulting string is shown on the right. + *

+     * [123,0]      "123"
+     * [-123,0]     "-123"
+     * [123,-1]     "1.23E+3"
+     * [123,-3]     "1.23E+5"
+     * [123,1]      "12.3"
+     * [123,5]      "0.00123"
+     * [123,10]     "1.23E-8"
+     * [-123,12]    "-1.23E-10"
+     * 
+ * + * Notes: + *
    + * + *
  1. There is a one-to-one mapping between the distinguishable + * {@code BigDecimal} values and the result of this conversion. + * That is, every distinguishable {@code BigDecimal} value + * (unscaled value and scale) has a unique string representation + * as a result of using {@code toString}. If that string + * representation is converted back to a {@code BigDecimal} using + * the {@link #BigDecimal(String)} constructor, then the original + * value will be recovered. + * + *
  2. The string produced for a given number is always the same; + * it is not affected by locale. This means that it can be used + * as a canonical string representation for exchanging decimal + * data, or as a key for a Hashtable, etc. Locale-sensitive + * number formatting and parsing is handled by the {@link + * java.text.NumberFormat} class and its subclasses. + * + *
  3. The {@link #toEngineeringString} method may be used for + * presenting numbers with exponents in engineering notation, and the + * {@link #setScale(int,RoundingMode) setScale} method may be used for + * rounding a {@code BigDecimal} so it has a known number of digits after + * the decimal point. + * + *
  4. The digit-to-character mapping provided by + * {@code Character.forDigit} is used. + * + *
+ * + * @return string representation of this {@code BigDecimal}. + * @see Character#forDigit + * @see #BigDecimal(java.lang.String) + */ + @Override + public String toString() { + String sc = stringCache; + if (sc == null) + stringCache = sc = layoutChars(true); + return sc; + } + + /** + * Returns a string representation of this {@code BigDecimal}, + * using engineering notation if an exponent is needed. + * + *

Returns a string that represents the {@code BigDecimal} as + * described in the {@link #toString()} method, except that if + * exponential notation is used, the power of ten is adjusted to + * be a multiple of three (engineering notation) such that the + * integer part of nonzero values will be in the range 1 through + * 999. If exponential notation is used for zero values, a + * decimal point and one or two fractional zero digits are used so + * that the scale of the zero value is preserved. Note that + * unlike the output of {@link #toString()}, the output of this + * method is not guaranteed to recover the same [integer, + * scale] pair of this {@code BigDecimal} if the output string is + * converting back to a {@code BigDecimal} using the {@linkplain + * #BigDecimal(String) string constructor}. The result of this method meets + * the weaker constraint of always producing a numerically equal + * result from applying the string constructor to the method's output. + * + * @return string representation of this {@code BigDecimal}, using + * engineering notation if an exponent is needed. + * @since 1.5 + */ + public String toEngineeringString() { + return layoutChars(false); + } + + /** + * Returns a string representation of this {@code BigDecimal} + * without an exponent field. For values with a positive scale, + * the number of digits to the right of the decimal point is used + * to indicate scale. For values with a zero or negative scale, + * the resulting string is generated as if the value were + * converted to a numerically equal value with zero scale and as + * if all the trailing zeros of the zero scale value were present + * in the result. + * + * The entire string is prefixed by a minus sign character '-' + * ('\u002D') if the unscaled value is less than + * zero. No sign character is prefixed if the unscaled value is + * zero or positive. + * + * Note that if the result of this method is passed to the + * {@linkplain #BigDecimal(String) string constructor}, only the + * numerical value of this {@code BigDecimal} will necessarily be + * recovered; the representation of the new {@code BigDecimal} + * may have a different scale. In particular, if this + * {@code BigDecimal} has a negative scale, the string resulting + * from this method will have a scale of zero when processed by + * the string constructor. + * + * (This method behaves analogously to the {@code toString} + * method in 1.4 and earlier releases.) + * + * @return a string representation of this {@code BigDecimal} + * without an exponent field. + * @since 1.5 + * @see #toString() + * @see #toEngineeringString() + */ + public String toPlainString() { + BigDecimal bd = this; + if (bd.scale < 0) + bd = bd.setScale(0); + bd.inflate(); + if (bd.scale == 0) // No decimal point + return bd.intVal.toString(); + return bd.getValueString(bd.signum(), bd.intVal.abs().toString(), bd.scale); + } + + /* Returns a digit.digit string */ + private String getValueString(int signum, String intString, int scale) { + /* Insert decimal point */ + StringBuilder buf; + int insertionPoint = intString.length() - scale; + if (insertionPoint == 0) { /* Point goes right before intVal */ + return (signum<0 ? "-0." : "0.") + intString; + } else if (insertionPoint > 0) { /* Point goes inside intVal */ + buf = new StringBuilder(intString); + buf.insert(insertionPoint, '.'); + if (signum < 0) + buf.insert(0, '-'); + } else { /* We must insert zeros between point and intVal */ + buf = new StringBuilder(3-insertionPoint + intString.length()); + buf.append(signum<0 ? "-0." : "0."); + for (int i=0; i<-insertionPoint; i++) + buf.append('0'); + buf.append(intString); + } + return buf.toString(); + } + + /** + * Converts this {@code BigDecimal} to a {@code BigInteger}. + * This conversion is analogous to the + * narrowing primitive conversion from {@code double} to + * {@code long} as defined in section 5.1.3 of + * The Java™ Language Specification: + * any fractional part of this + * {@code BigDecimal} will be discarded. Note that this + * conversion can lose information about the precision of the + * {@code BigDecimal} value. + *

+ * To have an exception thrown if the conversion is inexact (in + * other words if a nonzero fractional part is discarded), use the + * {@link #toBigIntegerExact()} method. + * + * @return this {@code BigDecimal} converted to a {@code BigInteger}. + */ + public BigInteger toBigInteger() { + // force to an integer, quietly + return this.setScale(0, ROUND_DOWN).inflate(); + } + + /** + * Converts this {@code BigDecimal} to a {@code BigInteger}, + * checking for lost information. An exception is thrown if this + * {@code BigDecimal} has a nonzero fractional part. + * + * @return this {@code BigDecimal} converted to a {@code BigInteger}. + * @throws ArithmeticException if {@code this} has a nonzero + * fractional part. + * @since 1.5 + */ + public BigInteger toBigIntegerExact() { + // round to an integer, with Exception if decimal part non-0 + return this.setScale(0, ROUND_UNNECESSARY).inflate(); + } + + /** + * Converts this {@code BigDecimal} to a {@code long}. + * This conversion is analogous to the + * narrowing primitive conversion from {@code double} to + * {@code short} as defined in section 5.1.3 of + * The Java™ Language Specification: + * any fractional part of this + * {@code BigDecimal} will be discarded, and if the resulting + * "{@code BigInteger}" is too big to fit in a + * {@code long}, only the low-order 64 bits are returned. + * Note that this conversion can lose information about the + * overall magnitude and precision of this {@code BigDecimal} value as well + * as return a result with the opposite sign. + * + * @return this {@code BigDecimal} converted to a {@code long}. + */ + public long longValue(){ + return (intCompact != INFLATED && scale == 0) ? + intCompact: + toBigInteger().longValue(); + } + + /** + * Converts this {@code BigDecimal} to a {@code long}, checking + * for lost information. If this {@code BigDecimal} has a + * nonzero fractional part or is out of the possible range for a + * {@code long} result then an {@code ArithmeticException} is + * thrown. + * + * @return this {@code BigDecimal} converted to a {@code long}. + * @throws ArithmeticException if {@code this} has a nonzero + * fractional part, or will not fit in a {@code long}. + * @since 1.5 + */ + public long longValueExact() { + if (intCompact != INFLATED && scale == 0) + return intCompact; + // If more than 19 digits in integer part it cannot possibly fit + if ((precision() - scale) > 19) // [OK for negative scale too] + throw new java.lang.ArithmeticException("Overflow"); + // Fastpath zero and < 1.0 numbers (the latter can be very slow + // to round if very small) + if (this.signum() == 0) + return 0; + if ((this.precision() - this.scale) <= 0) + throw new ArithmeticException("Rounding necessary"); + // round to an integer, with Exception if decimal part non-0 + BigDecimal num = this.setScale(0, ROUND_UNNECESSARY); + if (num.precision() >= 19) // need to check carefully + LongOverflow.check(num); + return num.inflate().longValue(); + } + + private static class LongOverflow { + /** BigInteger equal to Long.MIN_VALUE. */ + private static final BigInteger LONGMIN = BigInteger.valueOf(Long.MIN_VALUE); + + /** BigInteger equal to Long.MAX_VALUE. */ + private static final BigInteger LONGMAX = BigInteger.valueOf(Long.MAX_VALUE); + + public static void check(BigDecimal num) { + num.inflate(); + if ((num.intVal.compareTo(LONGMIN) < 0) || + (num.intVal.compareTo(LONGMAX) > 0)) + throw new java.lang.ArithmeticException("Overflow"); + } + } + + /** + * Converts this {@code BigDecimal} to an {@code int}. + * This conversion is analogous to the + * narrowing primitive conversion from {@code double} to + * {@code short} as defined in section 5.1.3 of + * The Java™ Language Specification: + * any fractional part of this + * {@code BigDecimal} will be discarded, and if the resulting + * "{@code BigInteger}" is too big to fit in an + * {@code int}, only the low-order 32 bits are returned. + * Note that this conversion can lose information about the + * overall magnitude and precision of this {@code BigDecimal} + * value as well as return a result with the opposite sign. + * + * @return this {@code BigDecimal} converted to an {@code int}. + */ + public int intValue() { + return (intCompact != INFLATED && scale == 0) ? + (int)intCompact : + toBigInteger().intValue(); + } + + /** + * Converts this {@code BigDecimal} to an {@code int}, checking + * for lost information. If this {@code BigDecimal} has a + * nonzero fractional part or is out of the possible range for an + * {@code int} result then an {@code ArithmeticException} is + * thrown. + * + * @return this {@code BigDecimal} converted to an {@code int}. + * @throws ArithmeticException if {@code this} has a nonzero + * fractional part, or will not fit in an {@code int}. + * @since 1.5 + */ + public int intValueExact() { + long num; + num = this.longValueExact(); // will check decimal part + if ((int)num != num) + throw new java.lang.ArithmeticException("Overflow"); + return (int)num; + } + + /** + * Converts this {@code BigDecimal} to a {@code short}, checking + * for lost information. If this {@code BigDecimal} has a + * nonzero fractional part or is out of the possible range for a + * {@code short} result then an {@code ArithmeticException} is + * thrown. + * + * @return this {@code BigDecimal} converted to a {@code short}. + * @throws ArithmeticException if {@code this} has a nonzero + * fractional part, or will not fit in a {@code short}. + * @since 1.5 + */ + public short shortValueExact() { + long num; + num = this.longValueExact(); // will check decimal part + if ((short)num != num) + throw new java.lang.ArithmeticException("Overflow"); + return (short)num; + } + + /** + * Converts this {@code BigDecimal} to a {@code byte}, checking + * for lost information. If this {@code BigDecimal} has a + * nonzero fractional part or is out of the possible range for a + * {@code byte} result then an {@code ArithmeticException} is + * thrown. + * + * @return this {@code BigDecimal} converted to a {@code byte}. + * @throws ArithmeticException if {@code this} has a nonzero + * fractional part, or will not fit in a {@code byte}. + * @since 1.5 + */ + public byte byteValueExact() { + long num; + num = this.longValueExact(); // will check decimal part + if ((byte)num != num) + throw new java.lang.ArithmeticException("Overflow"); + return (byte)num; + } + + /** + * Converts this {@code BigDecimal} to a {@code float}. + * This conversion is similar to the + * narrowing primitive conversion from {@code double} to + * {@code float} as defined in section 5.1.3 of + * The Java™ Language Specification: + * if this {@code BigDecimal} has too great a + * magnitude to represent as a {@code float}, it will be + * converted to {@link Float#NEGATIVE_INFINITY} or {@link + * Float#POSITIVE_INFINITY} as appropriate. Note that even when + * the return value is finite, this conversion can lose + * information about the precision of the {@code BigDecimal} + * value. + * + * @return this {@code BigDecimal} converted to a {@code float}. + */ + public float floatValue(){ + if (scale == 0 && intCompact != INFLATED) + return (float)intCompact; + // Somewhat inefficient, but guaranteed to work. + return Float.parseFloat(this.toString()); + } + + /** + * Converts this {@code BigDecimal} to a {@code double}. + * This conversion is similar to the + * narrowing primitive conversion from {@code double} to + * {@code float} as defined in section 5.1.3 of + * The Java™ Language Specification: + * if this {@code BigDecimal} has too great a + * magnitude represent as a {@code double}, it will be + * converted to {@link Double#NEGATIVE_INFINITY} or {@link + * Double#POSITIVE_INFINITY} as appropriate. Note that even when + * the return value is finite, this conversion can lose + * information about the precision of the {@code BigDecimal} + * value. + * + * @return this {@code BigDecimal} converted to a {@code double}. + */ + public double doubleValue(){ + if (scale == 0 && intCompact != INFLATED) + return (double)intCompact; + // Somewhat inefficient, but guaranteed to work. + return Double.parseDouble(this.toString()); + } + + /** + * Returns the size of an ulp, a unit in the last place, of this + * {@code BigDecimal}. An ulp of a nonzero {@code BigDecimal} + * value is the positive distance between this value and the + * {@code BigDecimal} value next larger in magnitude with the + * same number of digits. An ulp of a zero value is numerically + * equal to 1 with the scale of {@code this}. The result is + * stored with the same scale as {@code this} so the result + * for zero and nonzero values is equal to {@code [1, + * this.scale()]}. + * + * @return the size of an ulp of {@code this} + * @since 1.5 + */ + public BigDecimal ulp() { + return BigDecimal.valueOf(1, this.scale()); + } + + + // Private class to build a string representation for BigDecimal object. + // "StringBuilderHelper" is constructed as a thread local variable so it is + // thread safe. The StringBuilder field acts as a buffer to hold the temporary + // representation of BigDecimal. The cmpCharArray holds all the characters for + // the compact representation of BigDecimal (except for '-' sign' if it is + // negative) if its intCompact field is not INFLATED. It is shared by all + // calls to toString() and its variants in that particular thread. + static class StringBuilderHelper { + private static StringBuilderHelper INSTANCE = new StringBuilderHelper(); + final StringBuilder sb; // Placeholder for BigDecimal string + final char[] cmpCharArray; // character array to place the intCompact + + StringBuilderHelper() { + sb = new StringBuilder(); + // All non negative longs can be made to fit into 19 character array. + cmpCharArray = new char[19]; + } + + // Accessors. + StringBuilder getStringBuilder() { + sb.setLength(0); + return sb; + } + + char[] getCompactCharArray() { + return cmpCharArray; + } + + /** + * Places characters representing the intCompact in {@code long} into + * cmpCharArray and returns the offset to the array where the + * representation starts. + * + * @param intCompact the number to put into the cmpCharArray. + * @return offset to the array where the representation starts. + * Note: intCompact must be greater or equal to zero. + */ + int putIntCompact(long intCompact) { + assert intCompact >= 0; + + long q; + int r; + // since we start from the least significant digit, charPos points to + // the last character in cmpCharArray. + int charPos = cmpCharArray.length; + + // Get 2 digits/iteration using longs until quotient fits into an int + while (intCompact > Integer.MAX_VALUE) { + q = intCompact / 100; + r = (int)(intCompact - q * 100); + intCompact = q; + cmpCharArray[--charPos] = DIGIT_ONES[r]; + cmpCharArray[--charPos] = DIGIT_TENS[r]; + } + + // Get 2 digits/iteration using ints when i2 >= 100 + int q2; + int i2 = (int)intCompact; + while (i2 >= 100) { + q2 = i2 / 100; + r = i2 - q2 * 100; + i2 = q2; + cmpCharArray[--charPos] = DIGIT_ONES[r]; + cmpCharArray[--charPos] = DIGIT_TENS[r]; + } + + cmpCharArray[--charPos] = DIGIT_ONES[i2]; + if (i2 >= 10) + cmpCharArray[--charPos] = DIGIT_TENS[i2]; + + return charPos; + } + + final static char[] DIGIT_TENS = { + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', + '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', + '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', + '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', + '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', + '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', + '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', + '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', + '9', '9', '9', '9', '9', '9', '9', '9', '9', '9', + }; + + final static char[] DIGIT_ONES = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + }; + } + + /** + * Lay out this {@code BigDecimal} into a {@code char[]} array. + * The Java 1.2 equivalent to this was called {@code getValueString}. + * + * @param sci {@code true} for Scientific exponential notation; + * {@code false} for Engineering + * @return string with canonical string representation of this + * {@code BigDecimal} + */ + private String layoutChars(boolean sci) { + if (scale == 0) // zero scale is trivial + return (intCompact != INFLATED) ? + Long.toString(intCompact): + intVal.toString(); + + StringBuilderHelper sbHelper = StringBuilderHelper.INSTANCE; + char[] coeff; + int offset; // offset is the starting index for coeff array + // Get the significand as an absolute value + if (intCompact != INFLATED) { + offset = sbHelper.putIntCompact(Math.abs(intCompact)); + coeff = sbHelper.getCompactCharArray(); + } else { + offset = 0; + coeff = intVal.abs().toString().toCharArray(); + } + + // Construct a buffer, with sufficient capacity for all cases. + // If E-notation is needed, length will be: +1 if negative, +1 + // if '.' needed, +2 for "E+", + up to 10 for adjusted exponent. + // Otherwise it could have +1 if negative, plus leading "0.00000" + StringBuilder buf = sbHelper.getStringBuilder(); + if (signum() < 0) // prefix '-' if negative + buf.append('-'); + int coeffLen = coeff.length - offset; + long adjusted = -(long)scale + (coeffLen -1); + if ((scale >= 0) && (adjusted >= -6)) { // plain number + int pad = scale - coeffLen; // count of padding zeros + if (pad >= 0) { // 0.xxx form + buf.append('0'); + buf.append('.'); + for (; pad>0; pad--) { + buf.append('0'); + } + buf.append(coeff, offset, coeffLen); + } else { // xx.xx form + buf.append(coeff, offset, -pad); + buf.append('.'); + buf.append(coeff, -pad + offset, scale); + } + } else { // E-notation is needed + if (sci) { // Scientific notation + buf.append(coeff[offset]); // first character + if (coeffLen > 1) { // more to come + buf.append('.'); + buf.append(coeff, offset + 1, coeffLen - 1); + } + } else { // Engineering notation + int sig = (int)(adjusted % 3); + if (sig < 0) + sig += 3; // [adjusted was negative] + adjusted -= sig; // now a multiple of 3 + sig++; + if (signum() == 0) { + switch (sig) { + case 1: + buf.append('0'); // exponent is a multiple of three + break; + case 2: + buf.append("0.00"); + adjusted += 3; + break; + case 3: + buf.append("0.0"); + adjusted += 3; + break; + default: + throw new AssertionError("Unexpected sig value " + sig); + } + } else if (sig >= coeffLen) { // significand all in integer + buf.append(coeff, offset, coeffLen); + // may need some zeros, too + for (int i = sig - coeffLen; i > 0; i--) + buf.append('0'); + } else { // xx.xxE form + buf.append(coeff, offset, sig); + buf.append('.'); + buf.append(coeff, offset + sig, coeffLen - sig); + } + } + if (adjusted != 0) { // [!sci could have made 0] + buf.append('E'); + if (adjusted > 0) // force sign for positive + buf.append('+'); + buf.append(adjusted); + } + } + return buf.toString(); + } + + /** + * Return 10 to the power n, as a {@code BigInteger}. + * + * @param n the power of ten to be returned (>=0) + * @return a {@code BigInteger} with the value (10n) + */ + private static BigInteger bigTenToThe(int n) { + if (n < 0) + return BigInteger.ZERO; + + if (n < BIG_TEN_POWERS_TABLE_MAX) { + BigInteger[] pows = BIG_TEN_POWERS_TABLE; + if (n < pows.length) + return pows[n]; + else + return expandBigIntegerTenPowers(n); + } + // BigInteger.pow is slow, so make 10**n by constructing a + // BigInteger from a character string (still not very fast) + char tenpow[] = new char[n + 1]; + tenpow[0] = '1'; + for (int i = 1; i <= n; i++) + tenpow[i] = '0'; + return new BigInteger(tenpow); + } + + /** + * Expand the BIG_TEN_POWERS_TABLE array to contain at least 10**n. + * + * @param n the power of ten to be returned (>=0) + * @return a {@code BigDecimal} with the value (10n) and + * in the meantime, the BIG_TEN_POWERS_TABLE array gets + * expanded to the size greater than n. + */ + private static BigInteger expandBigIntegerTenPowers(int n) { + synchronized(BigDecimal.class) { + BigInteger[] pows = BIG_TEN_POWERS_TABLE; + int curLen = pows.length; + // The following comparison and the above synchronized statement is + // to prevent multiple threads from expanding the same array. + if (curLen <= n) { + int newLen = curLen << 1; + while (newLen <= n) + newLen <<= 1; + pows = Arrays.copyOf(pows, newLen); + for (int i = curLen; i < newLen; i++) + pows[i] = pows[i - 1].multiply(BigInteger.TEN); + // Based on the following facts: + // 1. pows is a private local varible; + // 2. the following store is a volatile store. + // the newly created array elements can be safely published. + BIG_TEN_POWERS_TABLE = pows; + } + return pows[n]; + } + } + + private static final long[] LONG_TEN_POWERS_TABLE = { + 1, // 0 / 10^0 + 10, // 1 / 10^1 + 100, // 2 / 10^2 + 1000, // 3 / 10^3 + 10000, // 4 / 10^4 + 100000, // 5 / 10^5 + 1000000, // 6 / 10^6 + 10000000, // 7 / 10^7 + 100000000, // 8 / 10^8 + 1000000000, // 9 / 10^9 + 10000000000L, // 10 / 10^10 + 100000000000L, // 11 / 10^11 + 1000000000000L, // 12 / 10^12 + 10000000000000L, // 13 / 10^13 + 100000000000000L, // 14 / 10^14 + 1000000000000000L, // 15 / 10^15 + 10000000000000000L, // 16 / 10^16 + 100000000000000000L, // 17 / 10^17 + 1000000000000000000L // 18 / 10^18 + }; + + private static volatile BigInteger BIG_TEN_POWERS_TABLE[] = {BigInteger.ONE, + BigInteger.valueOf(10), BigInteger.valueOf(100), + BigInteger.valueOf(1000), BigInteger.valueOf(10000), + BigInteger.valueOf(100000), BigInteger.valueOf(1000000), + BigInteger.valueOf(10000000), BigInteger.valueOf(100000000), + BigInteger.valueOf(1000000000), + BigInteger.valueOf(10000000000L), + BigInteger.valueOf(100000000000L), + BigInteger.valueOf(1000000000000L), + BigInteger.valueOf(10000000000000L), + BigInteger.valueOf(100000000000000L), + BigInteger.valueOf(1000000000000000L), + BigInteger.valueOf(10000000000000000L), + BigInteger.valueOf(100000000000000000L), + BigInteger.valueOf(1000000000000000000L) + }; + + private static final int BIG_TEN_POWERS_TABLE_INITLEN = + BIG_TEN_POWERS_TABLE.length; + private static final int BIG_TEN_POWERS_TABLE_MAX = + 16 * BIG_TEN_POWERS_TABLE_INITLEN; + + private static final long THRESHOLDS_TABLE[] = { + Long.MAX_VALUE, // 0 + Long.MAX_VALUE/10L, // 1 + Long.MAX_VALUE/100L, // 2 + Long.MAX_VALUE/1000L, // 3 + Long.MAX_VALUE/10000L, // 4 + Long.MAX_VALUE/100000L, // 5 + Long.MAX_VALUE/1000000L, // 6 + Long.MAX_VALUE/10000000L, // 7 + Long.MAX_VALUE/100000000L, // 8 + Long.MAX_VALUE/1000000000L, // 9 + Long.MAX_VALUE/10000000000L, // 10 + Long.MAX_VALUE/100000000000L, // 11 + Long.MAX_VALUE/1000000000000L, // 12 + Long.MAX_VALUE/10000000000000L, // 13 + Long.MAX_VALUE/100000000000000L, // 14 + Long.MAX_VALUE/1000000000000000L, // 15 + Long.MAX_VALUE/10000000000000000L, // 16 + Long.MAX_VALUE/100000000000000000L, // 17 + Long.MAX_VALUE/1000000000000000000L // 18 + }; + + /** + * Compute val * 10 ^ n; return this product if it is + * representable as a long, INFLATED otherwise. + */ + private static long longMultiplyPowerTen(long val, int n) { + if (val == 0 || n <= 0) + return val; + long[] tab = LONG_TEN_POWERS_TABLE; + long[] bounds = THRESHOLDS_TABLE; + if (n < tab.length && n < bounds.length) { + long tenpower = tab[n]; + if (val == 1) + return tenpower; + if (Math.abs(val) <= bounds[n]) + return val * tenpower; + } + return INFLATED; + } + + /** + * Compute this * 10 ^ n. + * Needed mainly to allow special casing to trap zero value + */ + private BigInteger bigMultiplyPowerTen(int n) { + if (n <= 0) + return this.inflate(); + + if (intCompact != INFLATED) + return bigTenToThe(n).multiply(intCompact); + else + return intVal.multiply(bigTenToThe(n)); + } + + /** + * Assign appropriate BigInteger to intVal field if intVal is + * null, i.e. the compact representation is in use. + */ + private BigInteger inflate() { + if (intVal == null) + intVal = BigInteger.valueOf(intCompact); + return intVal; + } + + /** + * Match the scales of two {@code BigDecimal}s to align their + * least significant digits. + * + *

If the scales of val[0] and val[1] differ, rescale + * (non-destructively) the lower-scaled {@code BigDecimal} so + * they match. That is, the lower-scaled reference will be + * replaced by a reference to a new object with the same scale as + * the other {@code BigDecimal}. + * + * @param val array of two elements referring to the two + * {@code BigDecimal}s to be aligned. + */ + private static void matchScale(BigDecimal[] val) { + if (val[0].scale == val[1].scale) { + return; + } else if (val[0].scale < val[1].scale) { + val[0] = val[0].setScale(val[1].scale, ROUND_UNNECESSARY); + } else if (val[1].scale < val[0].scale) { + val[1] = val[1].setScale(val[0].scale, ROUND_UNNECESSARY); + } + } + + /** + * Reconstitute the {@code BigDecimal} instance from a stream (that is, + * deserialize it). + * + * @param s the stream being read. + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + // Read in all fields + s.defaultReadObject(); + // validate possibly bad fields + if (intVal == null) { + String message = "BigDecimal: null intVal in stream"; + throw new java.io.StreamCorruptedException(message); + // [all values of scale are now allowed] + } + intCompact = compactValFor(intVal); + } + + /** + * Serialize this {@code BigDecimal} to the stream in question + * + * @param s the stream to serialize to. + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + // Must inflate to maintain compatible serial form. + this.inflate(); + + // Write proper fields + s.defaultWriteObject(); + } + + + /** + * Returns the length of the absolute value of a {@code long}, in decimal + * digits. + * + * @param x the {@code long} + * @return the length of the unscaled value, in deciaml digits. + */ + private static int longDigitLength(long x) { + /* + * As described in "Bit Twiddling Hacks" by Sean Anderson, + * (http://graphics.stanford.edu/~seander/bithacks.html) + * integer log 10 of x is within 1 of + * (1233/4096)* (1 + integer log 2 of x). + * The fraction 1233/4096 approximates log10(2). So we first + * do a version of log2 (a variant of Long class with + * pre-checks and opposite directionality) and then scale and + * check against powers table. This is a little simpler in + * present context than the version in Hacker's Delight sec + * 11-4. Adding one to bit length allows comparing downward + * from the LONG_TEN_POWERS_TABLE that we need anyway. + */ + assert x != INFLATED; + if (x < 0) + x = -x; + if (x < 10) // must screen for 0, might as well 10 + return 1; + int n = 64; // not 63, to avoid needing to add 1 later + int y = (int)(x >>> 32); + if (y == 0) { n -= 32; y = (int)x; } + if (y >>> 16 == 0) { n -= 16; y <<= 16; } + if (y >>> 24 == 0) { n -= 8; y <<= 8; } + if (y >>> 28 == 0) { n -= 4; y <<= 4; } + if (y >>> 30 == 0) { n -= 2; y <<= 2; } + int r = (((y >>> 31) + n) * 1233) >>> 12; + long[] tab = LONG_TEN_POWERS_TABLE; + // if r >= length, must have max possible digits for long + return (r >= tab.length || x < tab[r])? r : r+1; + } + + /** + * Returns the length of the absolute value of a BigInteger, in + * decimal digits. + * + * @param b the BigInteger + * @return the length of the unscaled value, in decimal digits + */ + private static int bigDigitLength(BigInteger b) { + /* + * Same idea as the long version, but we need a better + * approximation of log10(2). Using 646456993/2^31 + * is accurate up to max possible reported bitLength. + */ + if (b.signum == 0) + return 1; + int r = (int)((((long)b.bitLength() + 1) * 646456993) >>> 31); + return b.compareMagnitude(bigTenToThe(r)) < 0? r : r+1; + } + + + /** + * Remove insignificant trailing zeros from this + * {@code BigDecimal} until the preferred scale is reached or no + * more zeros can be removed. If the preferred scale is less than + * Integer.MIN_VALUE, all the trailing zeros will be removed. + * + * {@code BigInteger} assistance could help, here? + * + *

WARNING: This method should only be called on new objects as + * it mutates the value fields. + * + * @return this {@code BigDecimal} with a scale possibly reduced + * to be closed to the preferred scale. + */ + private BigDecimal stripZerosToMatchScale(long preferredScale) { + this.inflate(); + BigInteger qr[]; // quotient-remainder pair + while ( intVal.compareMagnitude(BigInteger.TEN) >= 0 && + scale > preferredScale) { + if (intVal.testBit(0)) + break; // odd number cannot end in 0 + qr = intVal.divideAndRemainder(BigInteger.TEN); + if (qr[1].signum() != 0) + break; // non-0 remainder + intVal=qr[0]; + scale = checkScale((long)scale-1); // could Overflow + if (precision > 0) // adjust precision if known + precision--; + } + if (intVal != null) + intCompact = compactValFor(intVal); + return this; + } + + /** + * Check a scale for Underflow or Overflow. If this BigDecimal is + * nonzero, throw an exception if the scale is outof range. If this + * is zero, saturate the scale to the extreme value of the right + * sign if the scale is out of range. + * + * @param val The new scale. + * @throws ArithmeticException (overflow or underflow) if the new + * scale is out of range. + * @return validated scale as an int. + */ + private int checkScale(long val) { + int asInt = (int)val; + if (asInt != val) { + asInt = val>Integer.MAX_VALUE ? Integer.MAX_VALUE : Integer.MIN_VALUE; + BigInteger b; + if (intCompact != 0 && + ((b = intVal) == null || b.signum() != 0)) + throw new ArithmeticException(asInt>0 ? "Underflow":"Overflow"); + } + return asInt; + } + + /** + * Round an operand; used only if digits > 0. Does not change + * {@code this}; if rounding is needed a new {@code BigDecimal} + * is created and returned. + * + * @param mc the context to use. + * @throws ArithmeticException if the result is inexact but the + * rounding mode is {@code UNNECESSARY}. + */ + private BigDecimal roundOp(MathContext mc) { + BigDecimal rounded = doRound(this, mc); + return rounded; + } + + /** Round this BigDecimal according to the MathContext settings; + * used only if precision {@literal >} 0. + * + *

WARNING: This method should only be called on new objects as + * it mutates the value fields. + * + * @param mc the context to use. + * @throws ArithmeticException if the rounding mode is + * {@code RoundingMode.UNNECESSARY} and the + * {@code BigDecimal} operation would require rounding. + */ + private void roundThis(MathContext mc) { + BigDecimal rounded = doRound(this, mc); + if (rounded == this) // wasn't rounded + return; + this.intVal = rounded.intVal; + this.intCompact = rounded.intCompact; + this.scale = rounded.scale; + this.precision = rounded.precision; + } + + /** + * Returns a {@code BigDecimal} rounded according to the + * MathContext settings; used only if {@code mc.precision > 0}. + * Does not change {@code this}; if rounding is needed a new + * {@code BigDecimal} is created and returned. + * + * @param mc the context to use. + * @return a {@code BigDecimal} rounded according to the MathContext + * settings. May return this, if no rounding needed. + * @throws ArithmeticException if the rounding mode is + * {@code RoundingMode.UNNECESSARY} and the + * result is inexact. + */ + private static BigDecimal doRound(BigDecimal d, MathContext mc) { + int mcp = mc.precision; + int drop; + // This might (rarely) iterate to cover the 999=>1000 case + while ((drop = d.precision() - mcp) > 0) { + int newScale = d.checkScale((long)d.scale - drop); + int mode = mc.roundingMode.oldMode; + if (drop < LONG_TEN_POWERS_TABLE.length) + d = divideAndRound(d.intCompact, d.intVal, + LONG_TEN_POWERS_TABLE[drop], null, + newScale, mode, newScale); + else + d = divideAndRound(d.intCompact, d.intVal, + INFLATED, bigTenToThe(drop), + newScale, mode, newScale); + } + return d; + } + + /** + * Returns the compact value for given {@code BigInteger}, or + * INFLATED if too big. Relies on internal representation of + * {@code BigInteger}. + */ + private static long compactValFor(BigInteger b) { + int[] m = b.mag; + int len = m.length; + if (len == 0) + return 0; + int d = m[0]; + if (len > 2 || (len == 2 && d < 0)) + return INFLATED; + + long u = (len == 2)? + (((long) m[1] & LONG_MASK) + (((long)d) << 32)) : + (((long)d) & LONG_MASK); + return (b.signum < 0)? -u : u; + } + + private static int longCompareMagnitude(long x, long y) { + if (x < 0) + x = -x; + if (y < 0) + y = -y; + return (x < y) ? -1 : ((x == y) ? 0 : 1); + } + + private static int saturateLong(long s) { + int i = (int)s; + return (s == i) ? i : (s < 0 ? Integer.MIN_VALUE : Integer.MAX_VALUE); + } + + /* + * Internal printing routine + */ + private static void print(String name, BigDecimal bd) { + } + + /** + * Check internal invariants of this BigDecimal. These invariants + * include: + * + *

    + * + *
  • The object must be initialized; either intCompact must not be + * INFLATED or intVal is non-null. Both of these conditions may + * be true. + * + *
  • If both intCompact and intVal and set, their values must be + * consistent. + * + *
  • If precision is nonzero, it must have the right value. + *
+ * + * Note: Since this is an audit method, we are not supposed to change the + * state of this BigDecimal object. + */ + private BigDecimal audit() { + if (intCompact == INFLATED) { + if (intVal == null) { + print("audit", this); + throw new AssertionError("null intVal"); + } + // Check precision + if (precision > 0 && precision != bigDigitLength(intVal)) { + print("audit", this); + throw new AssertionError("precision mismatch"); + } + } else { + if (intVal != null) { + long val = intVal.longValue(); + if (val != intCompact) { + print("audit", this); + throw new AssertionError("Inconsistent state, intCompact=" + + intCompact + "\t intVal=" + val); + } + } + // Check precision + if (precision > 0 && precision != longDigitLength(intCompact)) { + print("audit", this); + throw new AssertionError("precision mismatch"); + } + } + return this; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/math/BigInteger.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/math/BigInteger.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,3122 @@ +/* + * Copyright (c) 1996, 2007, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * Portions Copyright (c) 1995 Colin Plumb. All rights reserved. + */ + +package java.math; + +import java.util.Random; +import java.io.*; + +/** + * Immutable arbitrary-precision integers. All operations behave as if + * BigIntegers were represented in two's-complement notation (like Java's + * primitive integer types). BigInteger provides analogues to all of Java's + * primitive integer operators, and all relevant methods from java.lang.Math. + * Additionally, BigInteger provides operations for modular arithmetic, GCD + * calculation, primality testing, prime generation, bit manipulation, + * and a few other miscellaneous operations. + * + *

Semantics of arithmetic operations exactly mimic those of Java's integer + * arithmetic operators, as defined in The Java Language Specification. + * For example, division by zero throws an {@code ArithmeticException}, and + * division of a negative by a positive yields a negative (or zero) remainder. + * All of the details in the Spec concerning overflow are ignored, as + * BigIntegers are made as large as necessary to accommodate the results of an + * operation. + * + *

Semantics of shift operations extend those of Java's shift operators + * to allow for negative shift distances. A right-shift with a negative + * shift distance results in a left shift, and vice-versa. The unsigned + * right shift operator ({@code >>>}) is omitted, as this operation makes + * little sense in combination with the "infinite word size" abstraction + * provided by this class. + * + *

Semantics of bitwise logical operations exactly mimic those of Java's + * bitwise integer operators. The binary operators ({@code and}, + * {@code or}, {@code xor}) implicitly perform sign extension on the shorter + * of the two operands prior to performing the operation. + * + *

Comparison operations perform signed integer comparisons, analogous to + * those performed by Java's relational and equality operators. + * + *

Modular arithmetic operations are provided to compute residues, perform + * exponentiation, and compute multiplicative inverses. These methods always + * return a non-negative result, between {@code 0} and {@code (modulus - 1)}, + * inclusive. + * + *

Bit operations operate on a single bit of the two's-complement + * representation of their operand. If necessary, the operand is sign- + * extended so that it contains the designated bit. None of the single-bit + * operations can produce a BigInteger with a different sign from the + * BigInteger being operated on, as they affect only a single bit, and the + * "infinite word size" abstraction provided by this class ensures that there + * are infinitely many "virtual sign bits" preceding each BigInteger. + * + *

For the sake of brevity and clarity, pseudo-code is used throughout the + * descriptions of BigInteger methods. The pseudo-code expression + * {@code (i + j)} is shorthand for "a BigInteger whose value is + * that of the BigInteger {@code i} plus that of the BigInteger {@code j}." + * The pseudo-code expression {@code (i == j)} is shorthand for + * "{@code true} if and only if the BigInteger {@code i} represents the same + * value as the BigInteger {@code j}." Other pseudo-code expressions are + * interpreted similarly. + * + *

All methods and constructors in this class throw + * {@code NullPointerException} when passed + * a null object reference for any input parameter. + * + * @see BigDecimal + * @author Josh Bloch + * @author Michael McCloskey + * @since JDK1.1 + */ + +public class BigInteger extends Number implements Comparable { + /** + * The signum of this BigInteger: -1 for negative, 0 for zero, or + * 1 for positive. Note that the BigInteger zero must have + * a signum of 0. This is necessary to ensures that there is exactly one + * representation for each BigInteger value. + * + * @serial + */ + final int signum; + + /** + * The magnitude of this BigInteger, in big-endian order: the + * zeroth element of this array is the most-significant int of the + * magnitude. The magnitude must be "minimal" in that the most-significant + * int ({@code mag[0]}) must be non-zero. This is necessary to + * ensure that there is exactly one representation for each BigInteger + * value. Note that this implies that the BigInteger zero has a + * zero-length mag array. + */ + final int[] mag; + + // These "redundant fields" are initialized with recognizable nonsense + // values, and cached the first time they are needed (or never, if they + // aren't needed). + + /** + * One plus the bitCount of this BigInteger. Zeros means unitialized. + * + * @serial + * @see #bitCount + * @deprecated Deprecated since logical value is offset from stored + * value and correction factor is applied in accessor method. + */ + @Deprecated + private int bitCount; + + /** + * One plus the bitLength of this BigInteger. Zeros means unitialized. + * (either value is acceptable). + * + * @serial + * @see #bitLength() + * @deprecated Deprecated since logical value is offset from stored + * value and correction factor is applied in accessor method. + */ + @Deprecated + private int bitLength; + + /** + * Two plus the lowest set bit of this BigInteger, as returned by + * getLowestSetBit(). + * + * @serial + * @see #getLowestSetBit + * @deprecated Deprecated since logical value is offset from stored + * value and correction factor is applied in accessor method. + */ + @Deprecated + private int lowestSetBit; + + /** + * Two plus the index of the lowest-order int in the magnitude of this + * BigInteger that contains a nonzero int, or -2 (either value is acceptable). + * The least significant int has int-number 0, the next int in order of + * increasing significance has int-number 1, and so forth. + * @deprecated Deprecated since logical value is offset from stored + * value and correction factor is applied in accessor method. + */ + @Deprecated + private int firstNonzeroIntNum; + + /** + * This mask is used to obtain the value of an int as if it were unsigned. + */ + final static long LONG_MASK = 0xffffffffL; + + //Constructors + + /** + * Translates a byte array containing the two's-complement binary + * representation of a BigInteger into a BigInteger. The input array is + * assumed to be in big-endian byte-order: the most significant + * byte is in the zeroth element. + * + * @param val big-endian two's-complement binary representation of + * BigInteger. + * @throws NumberFormatException {@code val} is zero bytes long. + */ + public BigInteger(byte[] val) { + if (val.length == 0) + throw new NumberFormatException("Zero length BigInteger"); + + if (val[0] < 0) { + mag = makePositive(val); + signum = -1; + } else { + mag = stripLeadingZeroBytes(val); + signum = (mag.length == 0 ? 0 : 1); + } + } + + /** + * This private constructor translates an int array containing the + * two's-complement binary representation of a BigInteger into a + * BigInteger. The input array is assumed to be in big-endian + * int-order: the most significant int is in the zeroth element. + */ + private BigInteger(int[] val) { + if (val.length == 0) + throw new NumberFormatException("Zero length BigInteger"); + + if (val[0] < 0) { + mag = makePositive(val); + signum = -1; + } else { + mag = trustedStripLeadingZeroInts(val); + signum = (mag.length == 0 ? 0 : 1); + } + } + + /** + * Translates the sign-magnitude representation of a BigInteger into a + * BigInteger. The sign is represented as an integer signum value: -1 for + * negative, 0 for zero, or 1 for positive. The magnitude is a byte array + * in big-endian byte-order: the most significant byte is in the + * zeroth element. A zero-length magnitude array is permissible, and will + * result in a BigInteger value of 0, whether signum is -1, 0 or 1. + * + * @param signum signum of the number (-1 for negative, 0 for zero, 1 + * for positive). + * @param magnitude big-endian binary representation of the magnitude of + * the number. + * @throws NumberFormatException {@code signum} is not one of the three + * legal values (-1, 0, and 1), or {@code signum} is 0 and + * {@code magnitude} contains one or more non-zero bytes. + */ + public BigInteger(int signum, byte[] magnitude) { + this.mag = stripLeadingZeroBytes(magnitude); + + if (signum < -1 || signum > 1) + throw(new NumberFormatException("Invalid signum value")); + + if (this.mag.length==0) { + this.signum = 0; + } else { + if (signum == 0) + throw(new NumberFormatException("signum-magnitude mismatch")); + this.signum = signum; + } + } + + /** + * A constructor for internal use that translates the sign-magnitude + * representation of a BigInteger into a BigInteger. It checks the + * arguments and copies the magnitude so this constructor would be + * safe for external use. + */ + private BigInteger(int signum, int[] magnitude) { + this.mag = stripLeadingZeroInts(magnitude); + + if (signum < -1 || signum > 1) + throw(new NumberFormatException("Invalid signum value")); + + if (this.mag.length==0) { + this.signum = 0; + } else { + if (signum == 0) + throw(new NumberFormatException("signum-magnitude mismatch")); + this.signum = signum; + } + } + + /** + * Translates the String representation of a BigInteger in the + * specified radix into a BigInteger. The String representation + * consists of an optional minus or plus sign followed by a + * sequence of one or more digits in the specified radix. The + * character-to-digit mapping is provided by {@code + * Character.digit}. The String may not contain any extraneous + * characters (whitespace, for example). + * + * @param val String representation of BigInteger. + * @param radix radix to be used in interpreting {@code val}. + * @throws NumberFormatException {@code val} is not a valid representation + * of a BigInteger in the specified radix, or {@code radix} is + * outside the range from {@link Character#MIN_RADIX} to + * {@link Character#MAX_RADIX}, inclusive. + * @see Character#digit + */ + public BigInteger(String val, int radix) { + int cursor = 0, numDigits; + final int len = val.length(); + + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) + throw new NumberFormatException("Radix out of range"); + if (len == 0) + throw new NumberFormatException("Zero length BigInteger"); + + // Check for at most one leading sign + int sign = 1; + int index1 = val.lastIndexOf('-'); + int index2 = val.lastIndexOf('+'); + if ((index1 + index2) <= -1) { + // No leading sign character or at most one leading sign character + if (index1 == 0 || index2 == 0) { + cursor = 1; + if (len == 1) + throw new NumberFormatException("Zero length BigInteger"); + } + if (index1 == 0) + sign = -1; + } else + throw new NumberFormatException("Illegal embedded sign character"); + + // Skip leading zeros and compute number of digits in magnitude + while (cursor < len && + Character.digit(val.charAt(cursor), radix) == 0) + cursor++; + if (cursor == len) { + signum = 0; + mag = ZERO.mag; + return; + } + + numDigits = len - cursor; + signum = sign; + + // Pre-allocate array of expected size. May be too large but can + // never be too small. Typically exact. + int numBits = (int)(((numDigits * bitsPerDigit[radix]) >>> 10) + 1); + int numWords = (numBits + 31) >>> 5; + int[] magnitude = new int[numWords]; + + // Process first (potentially short) digit group + int firstGroupLen = numDigits % digitsPerInt[radix]; + if (firstGroupLen == 0) + firstGroupLen = digitsPerInt[radix]; + String group = val.substring(cursor, cursor += firstGroupLen); + magnitude[numWords - 1] = Integer.parseInt(group, radix); + if (magnitude[numWords - 1] < 0) + throw new NumberFormatException("Illegal digit"); + + // Process remaining digit groups + int superRadix = intRadix[radix]; + int groupVal = 0; + while (cursor < len) { + group = val.substring(cursor, cursor += digitsPerInt[radix]); + groupVal = Integer.parseInt(group, radix); + if (groupVal < 0) + throw new NumberFormatException("Illegal digit"); + destructiveMulAdd(magnitude, superRadix, groupVal); + } + // Required for cases where the array was overallocated. + mag = trustedStripLeadingZeroInts(magnitude); + } + + // Constructs a new BigInteger using a char array with radix=10 + BigInteger(char[] val) { + int cursor = 0, numDigits; + int len = val.length; + + // Check for leading minus sign + int sign = 1; + if (val[0] == '-') { + if (len == 1) + throw new NumberFormatException("Zero length BigInteger"); + sign = -1; + cursor = 1; + } else if (val[0] == '+') { + if (len == 1) + throw new NumberFormatException("Zero length BigInteger"); + cursor = 1; + } + + // Skip leading zeros and compute number of digits in magnitude + while (cursor < len && Character.digit(val[cursor], 10) == 0) + cursor++; + if (cursor == len) { + signum = 0; + mag = ZERO.mag; + return; + } + + numDigits = len - cursor; + signum = sign; + + // Pre-allocate array of expected size + int numWords; + if (len < 10) { + numWords = 1; + } else { + int numBits = (int)(((numDigits * bitsPerDigit[10]) >>> 10) + 1); + numWords = (numBits + 31) >>> 5; + } + int[] magnitude = new int[numWords]; + + // Process first (potentially short) digit group + int firstGroupLen = numDigits % digitsPerInt[10]; + if (firstGroupLen == 0) + firstGroupLen = digitsPerInt[10]; + magnitude[numWords - 1] = parseInt(val, cursor, cursor += firstGroupLen); + + // Process remaining digit groups + while (cursor < len) { + int groupVal = parseInt(val, cursor, cursor += digitsPerInt[10]); + destructiveMulAdd(magnitude, intRadix[10], groupVal); + } + mag = trustedStripLeadingZeroInts(magnitude); + } + + // Create an integer with the digits between the two indexes + // Assumes start < end. The result may be negative, but it + // is to be treated as an unsigned value. + private int parseInt(char[] source, int start, int end) { + int result = Character.digit(source[start++], 10); + if (result == -1) + throw new NumberFormatException(new String(source)); + + for (int index = start; index= 0; i--) { + product = ylong * (x[i] & LONG_MASK) + carry; + x[i] = (int)product; + carry = product >>> 32; + } + + // Perform the addition + long sum = (x[len-1] & LONG_MASK) + zlong; + x[len-1] = (int)sum; + carry = sum >>> 32; + for (int i = len-2; i >= 0; i--) { + sum = (x[i] & LONG_MASK) + carry; + x[i] = (int)sum; + carry = sum >>> 32; + } + } + + /** + * Translates the decimal String representation of a BigInteger into a + * BigInteger. The String representation consists of an optional minus + * sign followed by a sequence of one or more decimal digits. The + * character-to-digit mapping is provided by {@code Character.digit}. + * The String may not contain any extraneous characters (whitespace, for + * example). + * + * @param val decimal String representation of BigInteger. + * @throws NumberFormatException {@code val} is not a valid representation + * of a BigInteger. + * @see Character#digit + */ + public BigInteger(String val) { + this(val, 10); + } + + /** + * Constructs a randomly generated BigInteger, uniformly distributed over + * the range 0 to (2{@code numBits} - 1), inclusive. + * The uniformity of the distribution assumes that a fair source of random + * bits is provided in {@code rnd}. Note that this constructor always + * constructs a non-negative BigInteger. + * + * @param numBits maximum bitLength of the new BigInteger. + * @param rnd source of randomness to be used in computing the new + * BigInteger. + * @throws IllegalArgumentException {@code numBits} is negative. + * @see #bitLength() + */ + public BigInteger(int numBits, Random rnd) { + this(1, randomBits(numBits, rnd)); + } + + private static byte[] randomBits(int numBits, Random rnd) { + if (numBits < 0) + throw new IllegalArgumentException("numBits must be non-negative"); + int numBytes = (int)(((long)numBits+7)/8); // avoid overflow + byte[] randomBits = new byte[numBytes]; + + // Generate random bytes and mask out any excess bits + if (numBytes > 0) { + rnd.nextBytes(randomBits); + int excessBits = 8*numBytes - numBits; + randomBits[0] &= (1 << (8-excessBits)) - 1; + } + return randomBits; + } + + /** + * Constructs a randomly generated positive BigInteger that is probably + * prime, with the specified bitLength. + * + *

It is recommended that the {@link #probablePrime probablePrime} + * method be used in preference to this constructor unless there + * is a compelling need to specify a certainty. + * + * @param bitLength bitLength of the returned BigInteger. + * @param certainty a measure of the uncertainty that the caller is + * willing to tolerate. The probability that the new BigInteger + * represents a prime number will exceed + * (1 - 1/2{@code certainty}). The execution time of + * this constructor is proportional to the value of this parameter. + * @param rnd source of random bits used to select candidates to be + * tested for primality. + * @throws ArithmeticException {@code bitLength < 2}. + * @see #bitLength() + */ + public BigInteger(int bitLength, int certainty, Random rnd) { + BigInteger prime; + + if (bitLength < 2) + throw new ArithmeticException("bitLength < 2"); + // The cutoff of 95 was chosen empirically for best performance + prime = (bitLength < 95 ? smallPrime(bitLength, certainty, rnd) + : largePrime(bitLength, certainty, rnd)); + signum = 1; + mag = prime.mag; + } + + // Minimum size in bits that the requested prime number has + // before we use the large prime number generating algorithms + private static final int SMALL_PRIME_THRESHOLD = 95; + + // Certainty required to meet the spec of probablePrime + private static final int DEFAULT_PRIME_CERTAINTY = 100; + + /** + * Returns a positive BigInteger that is probably prime, with the + * specified bitLength. The probability that a BigInteger returned + * by this method is composite does not exceed 2-100. + * + * @param bitLength bitLength of the returned BigInteger. + * @param rnd source of random bits used to select candidates to be + * tested for primality. + * @return a BigInteger of {@code bitLength} bits that is probably prime + * @throws ArithmeticException {@code bitLength < 2}. + * @see #bitLength() + * @since 1.4 + */ + public static BigInteger probablePrime(int bitLength, Random rnd) { + if (bitLength < 2) + throw new ArithmeticException("bitLength < 2"); + + // The cutoff of 95 was chosen empirically for best performance + return (bitLength < SMALL_PRIME_THRESHOLD ? + smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) : + largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd)); + } + + /** + * Find a random number of the specified bitLength that is probably prime. + * This method is used for smaller primes, its performance degrades on + * larger bitlengths. + * + * This method assumes bitLength > 1. + */ + private static BigInteger smallPrime(int bitLength, int certainty, Random rnd) { + int magLen = (bitLength + 31) >>> 5; + int temp[] = new int[magLen]; + int highBit = 1 << ((bitLength+31) & 0x1f); // High bit of high int + int highMask = (highBit << 1) - 1; // Bits to keep in high int + + while(true) { + // Construct a candidate + for (int i=0; i 2) + temp[magLen-1] |= 1; // Make odd if bitlen > 2 + + BigInteger p = new BigInteger(temp, 1); + + // Do cheap "pre-test" if applicable + if (bitLength > 6) { + long r = p.remainder(SMALL_PRIME_PRODUCT).longValue(); + if ((r%3==0) || (r%5==0) || (r%7==0) || (r%11==0) || + (r%13==0) || (r%17==0) || (r%19==0) || (r%23==0) || + (r%29==0) || (r%31==0) || (r%37==0) || (r%41==0)) + continue; // Candidate is composite; try another + } + + // All candidates of bitLength 2 and 3 are prime by this point + if (bitLength < 4) + return p; + + // Do expensive test if we survive pre-test (or it's inapplicable) + if (p.primeToCertainty(certainty, rnd)) + return p; + } + } + + private static final BigInteger SMALL_PRIME_PRODUCT + = valueOf(3L*5*7*11*13*17*19*23*29*31*37*41); + + /** + * Find a random number of the specified bitLength that is probably prime. + * This method is more appropriate for larger bitlengths since it uses + * a sieve to eliminate most composites before using a more expensive + * test. + */ + private static BigInteger largePrime(int bitLength, int certainty, Random rnd) { + BigInteger p; + p = new BigInteger(bitLength, rnd).setBit(bitLength-1); + p.mag[p.mag.length-1] &= 0xfffffffe; + + // Use a sieve length likely to contain the next prime number + int searchLen = (bitLength / 20) * 64; + BitSieve searchSieve = new BitSieve(p, searchLen); + BigInteger candidate = searchSieve.retrieve(p, certainty, rnd); + + while ((candidate == null) || (candidate.bitLength() != bitLength)) { + p = p.add(BigInteger.valueOf(2*searchLen)); + if (p.bitLength() != bitLength) + p = new BigInteger(bitLength, rnd).setBit(bitLength-1); + p.mag[p.mag.length-1] &= 0xfffffffe; + searchSieve = new BitSieve(p, searchLen); + candidate = searchSieve.retrieve(p, certainty, rnd); + } + return candidate; + } + + /** + * Returns the first integer greater than this {@code BigInteger} that + * is probably prime. The probability that the number returned by this + * method is composite does not exceed 2-100. This method will + * never skip over a prime when searching: if it returns {@code p}, there + * is no prime {@code q} such that {@code this < q < p}. + * + * @return the first integer greater than this {@code BigInteger} that + * is probably prime. + * @throws ArithmeticException {@code this < 0}. + * @since 1.5 + */ + public BigInteger nextProbablePrime() { + if (this.signum < 0) + throw new ArithmeticException("start < 0: " + this); + + // Handle trivial cases + if ((this.signum == 0) || this.equals(ONE)) + return TWO; + + BigInteger result = this.add(ONE); + + // Fastpath for small numbers + if (result.bitLength() < SMALL_PRIME_THRESHOLD) { + + // Ensure an odd number + if (!result.testBit(0)) + result = result.add(ONE); + + while(true) { + // Do cheap "pre-test" if applicable + if (result.bitLength() > 6) { + long r = result.remainder(SMALL_PRIME_PRODUCT).longValue(); + if ((r%3==0) || (r%5==0) || (r%7==0) || (r%11==0) || + (r%13==0) || (r%17==0) || (r%19==0) || (r%23==0) || + (r%29==0) || (r%31==0) || (r%37==0) || (r%41==0)) { + result = result.add(TWO); + continue; // Candidate is composite; try another + } + } + + // All candidates of bitLength 2 and 3 are prime by this point + if (result.bitLength() < 4) + return result; + + // The expensive test + if (result.primeToCertainty(DEFAULT_PRIME_CERTAINTY, null)) + return result; + + result = result.add(TWO); + } + } + + // Start at previous even number + if (result.testBit(0)) + result = result.subtract(ONE); + + // Looking for the next large prime + int searchLen = (result.bitLength() / 20) * 64; + + while(true) { + BitSieve searchSieve = new BitSieve(result, searchLen); + BigInteger candidate = searchSieve.retrieve(result, + DEFAULT_PRIME_CERTAINTY, null); + if (candidate != null) + return candidate; + result = result.add(BigInteger.valueOf(2 * searchLen)); + } + } + + /** + * Returns {@code true} if this BigInteger is probably prime, + * {@code false} if it's definitely composite. + * + * This method assumes bitLength > 2. + * + * @param certainty a measure of the uncertainty that the caller is + * willing to tolerate: if the call returns {@code true} + * the probability that this BigInteger is prime exceeds + * {@code (1 - 1/2certainty)}. The execution time of + * this method is proportional to the value of this parameter. + * @return {@code true} if this BigInteger is probably prime, + * {@code false} if it's definitely composite. + */ + boolean primeToCertainty(int certainty, Random random) { + int rounds = 0; + int n = (Math.min(certainty, Integer.MAX_VALUE-1)+1)/2; + + // The relationship between the certainty and the number of rounds + // we perform is given in the draft standard ANSI X9.80, "PRIME + // NUMBER GENERATION, PRIMALITY TESTING, AND PRIMALITY CERTIFICATES". + int sizeInBits = this.bitLength(); + if (sizeInBits < 100) { + rounds = 50; + rounds = n < rounds ? n : rounds; + return passesMillerRabin(rounds, random); + } + + if (sizeInBits < 256) { + rounds = 27; + } else if (sizeInBits < 512) { + rounds = 15; + } else if (sizeInBits < 768) { + rounds = 8; + } else if (sizeInBits < 1024) { + rounds = 4; + } else { + rounds = 2; + } + rounds = n < rounds ? n : rounds; + + return passesMillerRabin(rounds, random) && passesLucasLehmer(); + } + + /** + * Returns true iff this BigInteger is a Lucas-Lehmer probable prime. + * + * The following assumptions are made: + * This BigInteger is a positive, odd number. + */ + private boolean passesLucasLehmer() { + BigInteger thisPlusOne = this.add(ONE); + + // Step 1 + int d = 5; + while (jacobiSymbol(d, this) != -1) { + // 5, -7, 9, -11, ... + d = (d<0) ? Math.abs(d)+2 : -(d+2); + } + + // Step 2 + BigInteger u = lucasLehmerSequence(d, thisPlusOne, this); + + // Step 3 + return u.mod(this).equals(ZERO); + } + + /** + * Computes Jacobi(p,n). + * Assumes n positive, odd, n>=3. + */ + private static int jacobiSymbol(int p, BigInteger n) { + if (p == 0) + return 0; + + // Algorithm and comments adapted from Colin Plumb's C library. + int j = 1; + int u = n.mag[n.mag.length-1]; + + // Make p positive + if (p < 0) { + p = -p; + int n8 = u & 7; + if ((n8 == 3) || (n8 == 7)) + j = -j; // 3 (011) or 7 (111) mod 8 + } + + // Get rid of factors of 2 in p + while ((p & 3) == 0) + p >>= 2; + if ((p & 1) == 0) { + p >>= 1; + if (((u ^ (u>>1)) & 2) != 0) + j = -j; // 3 (011) or 5 (101) mod 8 + } + if (p == 1) + return j; + // Then, apply quadratic reciprocity + if ((p & u & 2) != 0) // p = u = 3 (mod 4)? + j = -j; + // And reduce u mod p + u = n.mod(BigInteger.valueOf(p)).intValue(); + + // Now compute Jacobi(u,p), u < p + while (u != 0) { + while ((u & 3) == 0) + u >>= 2; + if ((u & 1) == 0) { + u >>= 1; + if (((p ^ (p>>1)) & 2) != 0) + j = -j; // 3 (011) or 5 (101) mod 8 + } + if (u == 1) + return j; + // Now both u and p are odd, so use quadratic reciprocity + assert (u < p); + int t = u; u = p; p = t; + if ((u & p & 2) != 0) // u = p = 3 (mod 4)? + j = -j; + // Now u >= p, so it can be reduced + u %= p; + } + return 0; + } + + private static BigInteger lucasLehmerSequence(int z, BigInteger k, BigInteger n) { + BigInteger d = BigInteger.valueOf(z); + BigInteger u = ONE; BigInteger u2; + BigInteger v = ONE; BigInteger v2; + + for (int i=k.bitLength()-2; i>=0; i--) { + u2 = u.multiply(v).mod(n); + + v2 = v.square().add(d.multiply(u.square())).mod(n); + if (v2.testBit(0)) + v2 = v2.subtract(n); + + v2 = v2.shiftRight(1); + + u = u2; v = v2; + if (k.testBit(i)) { + u2 = u.add(v).mod(n); + if (u2.testBit(0)) + u2 = u2.subtract(n); + + u2 = u2.shiftRight(1); + v2 = v.add(d.multiply(u)).mod(n); + if (v2.testBit(0)) + v2 = v2.subtract(n); + v2 = v2.shiftRight(1); + + u = u2; v = v2; + } + } + return u; + } + + private static volatile Random staticRandom; + + private static Random getSecureRandom() { + if (staticRandom == null) { + staticRandom = new Random(); + } + return staticRandom; + } + + /** + * Returns true iff this BigInteger passes the specified number of + * Miller-Rabin tests. This test is taken from the DSA spec (NIST FIPS + * 186-2). + * + * The following assumptions are made: + * This BigInteger is a positive, odd number greater than 2. + * iterations<=50. + */ + private boolean passesMillerRabin(int iterations, Random rnd) { + // Find a and m such that m is odd and this == 1 + 2**a * m + BigInteger thisMinusOne = this.subtract(ONE); + BigInteger m = thisMinusOne; + int a = m.getLowestSetBit(); + m = m.shiftRight(a); + + // Do the tests + if (rnd == null) { + rnd = getSecureRandom(); + } + for (int i=0; i= 0); + + int j = 0; + BigInteger z = b.modPow(m, this); + while(!((j==0 && z.equals(ONE)) || z.equals(thisMinusOne))) { + if (j>0 && z.equals(ONE) || ++j==a) + return false; + z = z.modPow(TWO, this); + } + } + return true; + } + + /** + * This internal constructor differs from its public cousin + * with the arguments reversed in two ways: it assumes that its + * arguments are correct, and it doesn't copy the magnitude array. + */ + BigInteger(int[] magnitude, int signum) { + this.signum = (magnitude.length==0 ? 0 : signum); + this.mag = magnitude; + } + + /** + * This private constructor is for internal use and assumes that its + * arguments are correct. + */ + private BigInteger(byte[] magnitude, int signum) { + this.signum = (magnitude.length==0 ? 0 : signum); + this.mag = stripLeadingZeroBytes(magnitude); + } + + //Static Factory Methods + + /** + * Returns a BigInteger whose value is equal to that of the + * specified {@code long}. This "static factory method" is + * provided in preference to a ({@code long}) constructor + * because it allows for reuse of frequently used BigIntegers. + * + * @param val value of the BigInteger to return. + * @return a BigInteger with the specified value. + */ + public static BigInteger valueOf(long val) { + // If -MAX_CONSTANT < val < MAX_CONSTANT, return stashed constant + if (val == 0) + return ZERO; + if (val > 0 && val <= MAX_CONSTANT) + return posConst[(int) val]; + else if (val < 0 && val >= -MAX_CONSTANT) + return negConst[(int) -val]; + + return new BigInteger(val); + } + + /** + * Constructs a BigInteger with the specified value, which may not be zero. + */ + private BigInteger(long val) { + if (val < 0) { + val = -val; + signum = -1; + } else { + signum = 1; + } + + int highWord = (int)(val >>> 32); + if (highWord==0) { + mag = new int[1]; + mag[0] = (int)val; + } else { + mag = new int[2]; + mag[0] = highWord; + mag[1] = (int)val; + } + } + + /** + * Returns a BigInteger with the given two's complement representation. + * Assumes that the input array will not be modified (the returned + * BigInteger will reference the input array if feasible). + */ + private static BigInteger valueOf(int val[]) { + return (val[0]>0 ? new BigInteger(val, 1) : new BigInteger(val)); + } + + // Constants + + /** + * Initialize static constant array when class is loaded. + */ + private final static int MAX_CONSTANT = 16; + private static BigInteger posConst[] = new BigInteger[MAX_CONSTANT+1]; + private static BigInteger negConst[] = new BigInteger[MAX_CONSTANT+1]; + static { + for (int i = 1; i <= MAX_CONSTANT; i++) { + int[] magnitude = new int[1]; + magnitude[0] = i; + posConst[i] = new BigInteger(magnitude, 1); + negConst[i] = new BigInteger(magnitude, -1); + } + } + + /** + * The BigInteger constant zero. + * + * @since 1.2 + */ + public static final BigInteger ZERO = new BigInteger(new int[0], 0); + + /** + * The BigInteger constant one. + * + * @since 1.2 + */ + public static final BigInteger ONE = valueOf(1); + + /** + * The BigInteger constant two. (Not exported.) + */ + private static final BigInteger TWO = valueOf(2); + + /** + * The BigInteger constant ten. + * + * @since 1.5 + */ + public static final BigInteger TEN = valueOf(10); + + // Arithmetic Operations + + /** + * Returns a BigInteger whose value is {@code (this + val)}. + * + * @param val value to be added to this BigInteger. + * @return {@code this + val} + */ + public BigInteger add(BigInteger val) { + if (val.signum == 0) + return this; + if (signum == 0) + return val; + if (val.signum == signum) + return new BigInteger(add(mag, val.mag), signum); + + int cmp = compareMagnitude(val); + if (cmp == 0) + return ZERO; + int[] resultMag = (cmp > 0 ? subtract(mag, val.mag) + : subtract(val.mag, mag)); + resultMag = trustedStripLeadingZeroInts(resultMag); + + return new BigInteger(resultMag, cmp == signum ? 1 : -1); + } + + /** + * Adds the contents of the int arrays x and y. This method allocates + * a new int array to hold the answer and returns a reference to that + * array. + */ + private static int[] add(int[] x, int[] y) { + // If x is shorter, swap the two arrays + if (x.length < y.length) { + int[] tmp = x; + x = y; + y = tmp; + } + + int xIndex = x.length; + int yIndex = y.length; + int result[] = new int[xIndex]; + long sum = 0; + + // Add common parts of both numbers + while(yIndex > 0) { + sum = (x[--xIndex] & LONG_MASK) + + (y[--yIndex] & LONG_MASK) + (sum >>> 32); + result[xIndex] = (int)sum; + } + + // Copy remainder of longer number while carry propagation is required + boolean carry = (sum >>> 32 != 0); + while (xIndex > 0 && carry) + carry = ((result[--xIndex] = x[xIndex] + 1) == 0); + + // Copy remainder of longer number + while (xIndex > 0) + result[--xIndex] = x[xIndex]; + + // Grow result if necessary + if (carry) { + int bigger[] = new int[result.length + 1]; + System.arraycopy(result, 0, bigger, 1, result.length); + bigger[0] = 0x01; + return bigger; + } + return result; + } + + /** + * Returns a BigInteger whose value is {@code (this - val)}. + * + * @param val value to be subtracted from this BigInteger. + * @return {@code this - val} + */ + public BigInteger subtract(BigInteger val) { + if (val.signum == 0) + return this; + if (signum == 0) + return val.negate(); + if (val.signum != signum) + return new BigInteger(add(mag, val.mag), signum); + + int cmp = compareMagnitude(val); + if (cmp == 0) + return ZERO; + int[] resultMag = (cmp > 0 ? subtract(mag, val.mag) + : subtract(val.mag, mag)); + resultMag = trustedStripLeadingZeroInts(resultMag); + return new BigInteger(resultMag, cmp == signum ? 1 : -1); + } + + /** + * Subtracts the contents of the second int arrays (little) from the + * first (big). The first int array (big) must represent a larger number + * than the second. This method allocates the space necessary to hold the + * answer. + */ + private static int[] subtract(int[] big, int[] little) { + int bigIndex = big.length; + int result[] = new int[bigIndex]; + int littleIndex = little.length; + long difference = 0; + + // Subtract common parts of both numbers + while(littleIndex > 0) { + difference = (big[--bigIndex] & LONG_MASK) - + (little[--littleIndex] & LONG_MASK) + + (difference >> 32); + result[bigIndex] = (int)difference; + } + + // Subtract remainder of longer number while borrow propagates + boolean borrow = (difference >> 32 != 0); + while (bigIndex > 0 && borrow) + borrow = ((result[--bigIndex] = big[bigIndex] - 1) == -1); + + // Copy remainder of longer number + while (bigIndex > 0) + result[--bigIndex] = big[bigIndex]; + + return result; + } + + /** + * Returns a BigInteger whose value is {@code (this * val)}. + * + * @param val value to be multiplied by this BigInteger. + * @return {@code this * val} + */ + public BigInteger multiply(BigInteger val) { + if (val.signum == 0 || signum == 0) + return ZERO; + + int[] result = multiplyToLen(mag, mag.length, + val.mag, val.mag.length, null); + result = trustedStripLeadingZeroInts(result); + return new BigInteger(result, signum == val.signum ? 1 : -1); + } + + /** + * Package private methods used by BigDecimal code to multiply a BigInteger + * with a long. Assumes v is not equal to INFLATED. + */ + BigInteger multiply(long v) { + if (v == 0 || signum == 0) + return ZERO; + if (v == BigDecimal.INFLATED) + return multiply(BigInteger.valueOf(v)); + int rsign = (v > 0 ? signum : -signum); + if (v < 0) + v = -v; + long dh = v >>> 32; // higher order bits + long dl = v & LONG_MASK; // lower order bits + + int xlen = mag.length; + int[] value = mag; + int[] rmag = (dh == 0L) ? (new int[xlen + 1]) : (new int[xlen + 2]); + long carry = 0; + int rstart = rmag.length - 1; + for (int i = xlen - 1; i >= 0; i--) { + long product = (value[i] & LONG_MASK) * dl + carry; + rmag[rstart--] = (int)product; + carry = product >>> 32; + } + rmag[rstart] = (int)carry; + if (dh != 0L) { + carry = 0; + rstart = rmag.length - 2; + for (int i = xlen - 1; i >= 0; i--) { + long product = (value[i] & LONG_MASK) * dh + + (rmag[rstart] & LONG_MASK) + carry; + rmag[rstart--] = (int)product; + carry = product >>> 32; + } + rmag[0] = (int)carry; + } + if (carry == 0L) + rmag = java.util.Arrays.copyOfRange(rmag, 1, rmag.length); + return new BigInteger(rmag, rsign); + } + + /** + * Multiplies int arrays x and y to the specified lengths and places + * the result into z. There will be no leading zeros in the resultant array. + */ + private int[] multiplyToLen(int[] x, int xlen, int[] y, int ylen, int[] z) { + int xstart = xlen - 1; + int ystart = ylen - 1; + + if (z == null || z.length < (xlen+ ylen)) + z = new int[xlen+ylen]; + + long carry = 0; + for (int j=ystart, k=ystart+1+xstart; j>=0; j--, k--) { + long product = (y[j] & LONG_MASK) * + (x[xstart] & LONG_MASK) + carry; + z[k] = (int)product; + carry = product >>> 32; + } + z[xstart] = (int)carry; + + for (int i = xstart-1; i >= 0; i--) { + carry = 0; + for (int j=ystart, k=ystart+1+i; j>=0; j--, k--) { + long product = (y[j] & LONG_MASK) * + (x[i] & LONG_MASK) + + (z[k] & LONG_MASK) + carry; + z[k] = (int)product; + carry = product >>> 32; + } + z[i] = (int)carry; + } + return z; + } + + /** + * Returns a BigInteger whose value is {@code (this2)}. + * + * @return {@code this2} + */ + private BigInteger square() { + if (signum == 0) + return ZERO; + int[] z = squareToLen(mag, mag.length, null); + return new BigInteger(trustedStripLeadingZeroInts(z), 1); + } + + /** + * Squares the contents of the int array x. The result is placed into the + * int array z. The contents of x are not changed. + */ + private static final int[] squareToLen(int[] x, int len, int[] z) { + /* + * The algorithm used here is adapted from Colin Plumb's C library. + * Technique: Consider the partial products in the multiplication + * of "abcde" by itself: + * + * a b c d e + * * a b c d e + * ================== + * ae be ce de ee + * ad bd cd dd de + * ac bc cc cd ce + * ab bb bc bd be + * aa ab ac ad ae + * + * Note that everything above the main diagonal: + * ae be ce de = (abcd) * e + * ad bd cd = (abc) * d + * ac bc = (ab) * c + * ab = (a) * b + * + * is a copy of everything below the main diagonal: + * de + * cd ce + * bc bd be + * ab ac ad ae + * + * Thus, the sum is 2 * (off the diagonal) + diagonal. + * + * This is accumulated beginning with the diagonal (which + * consist of the squares of the digits of the input), which is then + * divided by two, the off-diagonal added, and multiplied by two + * again. The low bit is simply a copy of the low bit of the + * input, so it doesn't need special care. + */ + int zlen = len << 1; + if (z == null || z.length < zlen) + z = new int[zlen]; + + // Store the squares, right shifted one bit (i.e., divided by 2) + int lastProductLowWord = 0; + for (int j=0, i=0; j>> 33); + z[i++] = (int)(product >>> 1); + lastProductLowWord = (int)product; + } + + // Add in off-diagonal sums + for (int i=len, offset=1; i>0; i--, offset+=2) { + int t = x[i-1]; + t = mulAdd(z, x, offset, i-1, t); + addOne(z, offset-1, i, t); + } + + // Shift back up and set low bit + primitiveLeftShift(z, zlen, 1); + z[zlen-1] |= x[len-1] & 1; + + return z; + } + + /** + * Returns a BigInteger whose value is {@code (this / val)}. + * + * @param val value by which this BigInteger is to be divided. + * @return {@code this / val} + * @throws ArithmeticException if {@code val} is zero. + */ + public BigInteger divide(BigInteger val) { + MutableBigInteger q = new MutableBigInteger(), + a = new MutableBigInteger(this.mag), + b = new MutableBigInteger(val.mag); + + a.divide(b, q); + return q.toBigInteger(this.signum == val.signum ? 1 : -1); + } + + /** + * Returns an array of two BigIntegers containing {@code (this / val)} + * followed by {@code (this % val)}. + * + * @param val value by which this BigInteger is to be divided, and the + * remainder computed. + * @return an array of two BigIntegers: the quotient {@code (this / val)} + * is the initial element, and the remainder {@code (this % val)} + * is the final element. + * @throws ArithmeticException if {@code val} is zero. + */ + public BigInteger[] divideAndRemainder(BigInteger val) { + BigInteger[] result = new BigInteger[2]; + MutableBigInteger q = new MutableBigInteger(), + a = new MutableBigInteger(this.mag), + b = new MutableBigInteger(val.mag); + MutableBigInteger r = a.divide(b, q); + result[0] = q.toBigInteger(this.signum == val.signum ? 1 : -1); + result[1] = r.toBigInteger(this.signum); + return result; + } + + /** + * Returns a BigInteger whose value is {@code (this % val)}. + * + * @param val value by which this BigInteger is to be divided, and the + * remainder computed. + * @return {@code this % val} + * @throws ArithmeticException if {@code val} is zero. + */ + public BigInteger remainder(BigInteger val) { + MutableBigInteger q = new MutableBigInteger(), + a = new MutableBigInteger(this.mag), + b = new MutableBigInteger(val.mag); + + return a.divide(b, q).toBigInteger(this.signum); + } + + /** + * Returns a BigInteger whose value is (thisexponent). + * Note that {@code exponent} is an integer rather than a BigInteger. + * + * @param exponent exponent to which this BigInteger is to be raised. + * @return thisexponent + * @throws ArithmeticException {@code exponent} is negative. (This would + * cause the operation to yield a non-integer value.) + */ + public BigInteger pow(int exponent) { + if (exponent < 0) + throw new ArithmeticException("Negative exponent"); + if (signum==0) + return (exponent==0 ? ONE : this); + + // Perform exponentiation using repeated squaring trick + int newSign = (signum<0 && (exponent&1)==1 ? -1 : 1); + int[] baseToPow2 = this.mag; + int[] result = {1}; + + while (exponent != 0) { + if ((exponent & 1)==1) { + result = multiplyToLen(result, result.length, + baseToPow2, baseToPow2.length, null); + result = trustedStripLeadingZeroInts(result); + } + if ((exponent >>>= 1) != 0) { + baseToPow2 = squareToLen(baseToPow2, baseToPow2.length, null); + baseToPow2 = trustedStripLeadingZeroInts(baseToPow2); + } + } + return new BigInteger(result, newSign); + } + + /** + * Returns a BigInteger whose value is the greatest common divisor of + * {@code abs(this)} and {@code abs(val)}. Returns 0 if + * {@code this==0 && val==0}. + * + * @param val value with which the GCD is to be computed. + * @return {@code GCD(abs(this), abs(val))} + */ + public BigInteger gcd(BigInteger val) { + if (val.signum == 0) + return this.abs(); + else if (this.signum == 0) + return val.abs(); + + MutableBigInteger a = new MutableBigInteger(this); + MutableBigInteger b = new MutableBigInteger(val); + + MutableBigInteger result = a.hybridGCD(b); + + return result.toBigInteger(1); + } + + /** + * Package private method to return bit length for an integer. + */ + static int bitLengthForInt(int n) { + return 32 - Integer.numberOfLeadingZeros(n); + } + + /** + * Left shift int array a up to len by n bits. Returns the array that + * results from the shift since space may have to be reallocated. + */ + private static int[] leftShift(int[] a, int len, int n) { + int nInts = n >>> 5; + int nBits = n&0x1F; + int bitsInHighWord = bitLengthForInt(a[0]); + + // If shift can be done without recopy, do so + if (n <= (32-bitsInHighWord)) { + primitiveLeftShift(a, len, nBits); + return a; + } else { // Array must be resized + if (nBits <= (32-bitsInHighWord)) { + int result[] = new int[nInts+len]; + for (int i=0; i0; i--) { + int b = c; + c = a[i-1]; + a[i] = (c << n2) | (b >>> n); + } + a[0] >>>= n; + } + + // shifts a up to len left n bits assumes no leading zeros, 0<=n<32 + static void primitiveLeftShift(int[] a, int len, int n) { + if (len == 0 || n == 0) + return; + + int n2 = 32 - n; + for (int i=0, c=a[i], m=i+len-1; i>> n2); + } + a[len-1] <<= n; + } + + /** + * Calculate bitlength of contents of the first len elements an int array, + * assuming there are no leading zero ints. + */ + private static int bitLength(int[] val, int len) { + if (len == 0) + return 0; + return ((len - 1) << 5) + bitLengthForInt(val[0]); + } + + /** + * Returns a BigInteger whose value is the absolute value of this + * BigInteger. + * + * @return {@code abs(this)} + */ + public BigInteger abs() { + return (signum >= 0 ? this : this.negate()); + } + + /** + * Returns a BigInteger whose value is {@code (-this)}. + * + * @return {@code -this} + */ + public BigInteger negate() { + return new BigInteger(this.mag, -this.signum); + } + + /** + * Returns the signum function of this BigInteger. + * + * @return -1, 0 or 1 as the value of this BigInteger is negative, zero or + * positive. + */ + public int signum() { + return this.signum; + } + + // Modular Arithmetic Operations + + /** + * Returns a BigInteger whose value is {@code (this mod m}). This method + * differs from {@code remainder} in that it always returns a + * non-negative BigInteger. + * + * @param m the modulus. + * @return {@code this mod m} + * @throws ArithmeticException {@code m} ≤ 0 + * @see #remainder + */ + public BigInteger mod(BigInteger m) { + if (m.signum <= 0) + throw new ArithmeticException("BigInteger: modulus not positive"); + + BigInteger result = this.remainder(m); + return (result.signum >= 0 ? result : result.add(m)); + } + + /** + * Returns a BigInteger whose value is + * (thisexponent mod m). (Unlike {@code pow}, this + * method permits negative exponents.) + * + * @param exponent the exponent. + * @param m the modulus. + * @return thisexponent mod m + * @throws ArithmeticException {@code m} ≤ 0 or the exponent is + * negative and this BigInteger is not relatively + * prime to {@code m}. + * @see #modInverse + */ + public BigInteger modPow(BigInteger exponent, BigInteger m) { + if (m.signum <= 0) + throw new ArithmeticException("BigInteger: modulus not positive"); + + // Trivial cases + if (exponent.signum == 0) + return (m.equals(ONE) ? ZERO : ONE); + + if (this.equals(ONE)) + return (m.equals(ONE) ? ZERO : ONE); + + if (this.equals(ZERO) && exponent.signum >= 0) + return ZERO; + + if (this.equals(negConst[1]) && (!exponent.testBit(0))) + return (m.equals(ONE) ? ZERO : ONE); + + boolean invertResult; + if ((invertResult = (exponent.signum < 0))) + exponent = exponent.negate(); + + BigInteger base = (this.signum < 0 || this.compareTo(m) >= 0 + ? this.mod(m) : this); + BigInteger result; + if (m.testBit(0)) { // odd modulus + result = base.oddModPow(exponent, m); + } else { + /* + * Even modulus. Tear it into an "odd part" (m1) and power of two + * (m2), exponentiate mod m1, manually exponentiate mod m2, and + * use Chinese Remainder Theorem to combine results. + */ + + // Tear m apart into odd part (m1) and power of 2 (m2) + int p = m.getLowestSetBit(); // Max pow of 2 that divides m + + BigInteger m1 = m.shiftRight(p); // m/2**p + BigInteger m2 = ONE.shiftLeft(p); // 2**p + + // Calculate new base from m1 + BigInteger base2 = (this.signum < 0 || this.compareTo(m1) >= 0 + ? this.mod(m1) : this); + + // Caculate (base ** exponent) mod m1. + BigInteger a1 = (m1.equals(ONE) ? ZERO : + base2.oddModPow(exponent, m1)); + + // Calculate (this ** exponent) mod m2 + BigInteger a2 = base.modPow2(exponent, p); + + // Combine results using Chinese Remainder Theorem + BigInteger y1 = m2.modInverse(m1); + BigInteger y2 = m1.modInverse(m2); + + result = a1.multiply(m2).multiply(y1).add + (a2.multiply(m1).multiply(y2)).mod(m); + } + + return (invertResult ? result.modInverse(m) : result); + } + + static int[] bnExpModThreshTable = {7, 25, 81, 241, 673, 1793, + Integer.MAX_VALUE}; // Sentinel + + /** + * Returns a BigInteger whose value is x to the power of y mod z. + * Assumes: z is odd && x < z. + */ + private BigInteger oddModPow(BigInteger y, BigInteger z) { + /* + * The algorithm is adapted from Colin Plumb's C library. + * + * The window algorithm: + * The idea is to keep a running product of b1 = n^(high-order bits of exp) + * and then keep appending exponent bits to it. The following patterns + * apply to a 3-bit window (k = 3): + * To append 0: square + * To append 1: square, multiply by n^1 + * To append 10: square, multiply by n^1, square + * To append 11: square, square, multiply by n^3 + * To append 100: square, multiply by n^1, square, square + * To append 101: square, square, square, multiply by n^5 + * To append 110: square, square, multiply by n^3, square + * To append 111: square, square, square, multiply by n^7 + * + * Since each pattern involves only one multiply, the longer the pattern + * the better, except that a 0 (no multiplies) can be appended directly. + * We precompute a table of odd powers of n, up to 2^k, and can then + * multiply k bits of exponent at a time. Actually, assuming random + * exponents, there is on average one zero bit between needs to + * multiply (1/2 of the time there's none, 1/4 of the time there's 1, + * 1/8 of the time, there's 2, 1/32 of the time, there's 3, etc.), so + * you have to do one multiply per k+1 bits of exponent. + * + * The loop walks down the exponent, squaring the result buffer as + * it goes. There is a wbits+1 bit lookahead buffer, buf, that is + * filled with the upcoming exponent bits. (What is read after the + * end of the exponent is unimportant, but it is filled with zero here.) + * When the most-significant bit of this buffer becomes set, i.e. + * (buf & tblmask) != 0, we have to decide what pattern to multiply + * by, and when to do it. We decide, remember to do it in future + * after a suitable number of squarings have passed (e.g. a pattern + * of "100" in the buffer requires that we multiply by n^1 immediately; + * a pattern of "110" calls for multiplying by n^3 after one more + * squaring), clear the buffer, and continue. + * + * When we start, there is one more optimization: the result buffer + * is implcitly one, so squaring it or multiplying by it can be + * optimized away. Further, if we start with a pattern like "100" + * in the lookahead window, rather than placing n into the buffer + * and then starting to square it, we have already computed n^2 + * to compute the odd-powers table, so we can place that into + * the buffer and save a squaring. + * + * This means that if you have a k-bit window, to compute n^z, + * where z is the high k bits of the exponent, 1/2 of the time + * it requires no squarings. 1/4 of the time, it requires 1 + * squaring, ... 1/2^(k-1) of the time, it reqires k-2 squarings. + * And the remaining 1/2^(k-1) of the time, the top k bits are a + * 1 followed by k-1 0 bits, so it again only requires k-2 + * squarings, not k-1. The average of these is 1. Add that + * to the one squaring we have to do to compute the table, + * and you'll see that a k-bit window saves k-2 squarings + * as well as reducing the multiplies. (It actually doesn't + * hurt in the case k = 1, either.) + */ + // Special case for exponent of one + if (y.equals(ONE)) + return this; + + // Special case for base of zero + if (signum==0) + return ZERO; + + int[] base = mag.clone(); + int[] exp = y.mag; + int[] mod = z.mag; + int modLen = mod.length; + + // Select an appropriate window size + int wbits = 0; + int ebits = bitLength(exp, exp.length); + // if exponent is 65537 (0x10001), use minimum window size + if ((ebits != 17) || (exp[0] != 65537)) { + while (ebits > bnExpModThreshTable[wbits]) { + wbits++; + } + } + + // Calculate appropriate table size + int tblmask = 1 << wbits; + + // Allocate table for precomputed odd powers of base in Montgomery form + int[][] table = new int[tblmask][]; + for (int i=0; i>>= 1; + if (bitpos == 0) { + eIndex++; + bitpos = 1 << (32-1); + elen--; + } + } + + int multpos = ebits; + + // The first iteration, which is hoisted out of the main loop + ebits--; + boolean isone = true; + + multpos = ebits - wbits; + while ((buf & 1) == 0) { + buf >>>= 1; + multpos++; + } + + int[] mult = table[buf >>> 1]; + + buf = 0; + if (multpos == ebits) + isone = false; + + // The main loop + while(true) { + ebits--; + // Advance the window + buf <<= 1; + + if (elen != 0) { + buf |= ((exp[eIndex] & bitpos) != 0) ? 1 : 0; + bitpos >>>= 1; + if (bitpos == 0) { + eIndex++; + bitpos = 1 << (32-1); + elen--; + } + } + + // Examine the window for pending multiplies + if ((buf & tblmask) != 0) { + multpos = ebits - wbits; + while ((buf & 1) == 0) { + buf >>>= 1; + multpos++; + } + mult = table[buf >>> 1]; + buf = 0; + } + + // Perform multiply + if (ebits == multpos) { + if (isone) { + b = mult.clone(); + isone = false; + } else { + t = b; + a = multiplyToLen(t, modLen, mult, modLen, a); + a = montReduce(a, mod, modLen, inv); + t = a; a = b; b = t; + } + } + + // Check if done + if (ebits == 0) + break; + + // Square the input + if (!isone) { + t = b; + a = squareToLen(t, modLen, a); + a = montReduce(a, mod, modLen, inv); + t = a; a = b; b = t; + } + } + + // Convert result out of Montgomery form and return + int[] t2 = new int[2*modLen]; + for(int i=0; i 0); + + while(c>0) + c += subN(n, mod, mlen); + + while (intArrayCmpToLen(n, mod, mlen) >= 0) + subN(n, mod, mlen); + + return n; + } + + + /* + * Returns -1, 0 or +1 as big-endian unsigned int array arg1 is less than, + * equal to, or greater than arg2 up to length len. + */ + private static int intArrayCmpToLen(int[] arg1, int[] arg2, int len) { + for (int i=0; i b2) + return 1; + } + return 0; + } + + /** + * Subtracts two numbers of same length, returning borrow. + */ + private static int subN(int[] a, int[] b, int len) { + long sum = 0; + + while(--len >= 0) { + sum = (a[len] & LONG_MASK) - + (b[len] & LONG_MASK) + (sum >> 32); + a[len] = (int)sum; + } + + return (int)(sum >> 32); + } + + /** + * Multiply an array by one word k and add to result, return the carry + */ + static int mulAdd(int[] out, int[] in, int offset, int len, int k) { + long kLong = k & LONG_MASK; + long carry = 0; + + offset = out.length-offset - 1; + for (int j=len-1; j >= 0; j--) { + long product = (in[j] & LONG_MASK) * kLong + + (out[offset] & LONG_MASK) + carry; + out[offset--] = (int)product; + carry = product >>> 32; + } + return (int)carry; + } + + /** + * Add one word to the number a mlen words into a. Return the resulting + * carry. + */ + static int addOne(int[] a, int offset, int mlen, int carry) { + offset = a.length-1-mlen-offset; + long t = (a[offset] & LONG_MASK) + (carry & LONG_MASK); + + a[offset] = (int)t; + if ((t >>> 32) == 0) + return 0; + while (--mlen >= 0) { + if (--offset < 0) { // Carry out of number + return 1; + } else { + a[offset]++; + if (a[offset] != 0) + return 0; + } + } + return 1; + } + + /** + * Returns a BigInteger whose value is (this ** exponent) mod (2**p) + */ + private BigInteger modPow2(BigInteger exponent, int p) { + /* + * Perform exponentiation using repeated squaring trick, chopping off + * high order bits as indicated by modulus. + */ + BigInteger result = valueOf(1); + BigInteger baseToPow2 = this.mod2(p); + int expOffset = 0; + + int limit = exponent.bitLength(); + + if (this.testBit(0)) + limit = (p-1) < limit ? (p-1) : limit; + + while (expOffset < limit) { + if (exponent.testBit(expOffset)) + result = result.multiply(baseToPow2).mod2(p); + expOffset++; + if (expOffset < limit) + baseToPow2 = baseToPow2.square().mod2(p); + } + + return result; + } + + /** + * Returns a BigInteger whose value is this mod(2**p). + * Assumes that this {@code BigInteger >= 0} and {@code p > 0}. + */ + private BigInteger mod2(int p) { + if (bitLength() <= p) + return this; + + // Copy remaining ints of mag + int numInts = (p + 31) >>> 5; + int[] mag = new int[numInts]; + for (int i=0; i-1 {@code mod m)}. + * + * @param m the modulus. + * @return {@code this}-1 {@code mod m}. + * @throws ArithmeticException {@code m} ≤ 0, or this BigInteger + * has no multiplicative inverse mod m (that is, this BigInteger + * is not relatively prime to m). + */ + public BigInteger modInverse(BigInteger m) { + if (m.signum != 1) + throw new ArithmeticException("BigInteger: modulus not positive"); + + if (m.equals(ONE)) + return ZERO; + + // Calculate (this mod m) + BigInteger modVal = this; + if (signum < 0 || (this.compareMagnitude(m) >= 0)) + modVal = this.mod(m); + + if (modVal.equals(ONE)) + return ONE; + + MutableBigInteger a = new MutableBigInteger(modVal); + MutableBigInteger b = new MutableBigInteger(m); + + MutableBigInteger result = a.mutableModInverse(b); + return result.toBigInteger(1); + } + + // Shift Operations + + /** + * Returns a BigInteger whose value is {@code (this << n)}. + * The shift distance, {@code n}, may be negative, in which case + * this method performs a right shift. + * (Computes floor(this * 2n).) + * + * @param n shift distance, in bits. + * @return {@code this << n} + * @throws ArithmeticException if the shift distance is {@code + * Integer.MIN_VALUE}. + * @see #shiftRight + */ + public BigInteger shiftLeft(int n) { + if (signum == 0) + return ZERO; + if (n==0) + return this; + if (n<0) { + if (n == Integer.MIN_VALUE) { + throw new ArithmeticException("Shift distance of Integer.MIN_VALUE not supported."); + } else { + return shiftRight(-n); + } + } + + int nInts = n >>> 5; + int nBits = n & 0x1f; + int magLen = mag.length; + int newMag[] = null; + + if (nBits == 0) { + newMag = new int[magLen + nInts]; + for (int i=0; i>> nBits2; + if (highBits != 0) { + newMag = new int[magLen + nInts + 1]; + newMag[i++] = highBits; + } else { + newMag = new int[magLen + nInts]; + } + int j=0; + while (j < magLen-1) + newMag[i++] = mag[j++] << nBits | mag[j] >>> nBits2; + newMag[i] = mag[j] << nBits; + } + + return new BigInteger(newMag, signum); + } + + /** + * Returns a BigInteger whose value is {@code (this >> n)}. Sign + * extension is performed. The shift distance, {@code n}, may be + * negative, in which case this method performs a left shift. + * (Computes floor(this / 2n).) + * + * @param n shift distance, in bits. + * @return {@code this >> n} + * @throws ArithmeticException if the shift distance is {@code + * Integer.MIN_VALUE}. + * @see #shiftLeft + */ + public BigInteger shiftRight(int n) { + if (n==0) + return this; + if (n<0) { + if (n == Integer.MIN_VALUE) { + throw new ArithmeticException("Shift distance of Integer.MIN_VALUE not supported."); + } else { + return shiftLeft(-n); + } + } + + int nInts = n >>> 5; + int nBits = n & 0x1f; + int magLen = mag.length; + int newMag[] = null; + + // Special case: entire contents shifted off the end + if (nInts >= magLen) + return (signum >= 0 ? ZERO : negConst[1]); + + if (nBits == 0) { + int newMagLen = magLen - nInts; + newMag = new int[newMagLen]; + for (int i=0; i>> nBits; + if (highBits != 0) { + newMag = new int[magLen - nInts]; + newMag[i++] = highBits; + } else { + newMag = new int[magLen - nInts -1]; + } + + int nBits2 = 32 - nBits; + int j=0; + while (j < magLen - nInts - 1) + newMag[i++] = (mag[j++] << nBits2) | (mag[j] >>> nBits); + } + + if (signum < 0) { + // Find out whether any one-bits were shifted off the end. + boolean onesLost = false; + for (int i=magLen-1, j=magLen-nInts; i>=j && !onesLost; i--) + onesLost = (mag[i] != 0); + if (!onesLost && nBits != 0) + onesLost = (mag[magLen - nInts - 1] << (32 - nBits) != 0); + + if (onesLost) + newMag = javaIncrement(newMag); + } + + return new BigInteger(newMag, signum); + } + + int[] javaIncrement(int[] val) { + int lastSum = 0; + for (int i=val.length-1; i >= 0 && lastSum == 0; i--) + lastSum = (val[i] += 1); + if (lastSum == 0) { + val = new int[val.length+1]; + val[0] = 1; + } + return val; + } + + // Bitwise Operations + + /** + * Returns a BigInteger whose value is {@code (this & val)}. (This + * method returns a negative BigInteger if and only if this and val are + * both negative.) + * + * @param val value to be AND'ed with this BigInteger. + * @return {@code this & val} + */ + public BigInteger and(BigInteger val) { + int[] result = new int[Math.max(intLength(), val.intLength())]; + for (int i=0; i>> 5) & (1 << (n & 31))) != 0; + } + + /** + * Returns a BigInteger whose value is equivalent to this BigInteger + * with the designated bit set. (Computes {@code (this | (1<>> 5; + int[] result = new int[Math.max(intLength(), intNum+2)]; + + for (int i=0; i>> 5; + int[] result = new int[Math.max(intLength(), ((n + 1) >>> 5) + 1)]; + + for (int i=0; i>> 5; + int[] result = new int[Math.max(intLength(), intNum+2)]; + + for (int i=0; iexcluding a sign bit. + * For positive BigIntegers, this is equivalent to the number of bits in + * the ordinary binary representation. (Computes + * {@code (ceil(log2(this < 0 ? -this : this+1)))}.) + * + * @return number of bits in the minimal two's-complement + * representation of this BigInteger, excluding a sign bit. + */ + public int bitLength() { + @SuppressWarnings("deprecation") int n = bitLength - 1; + if (n == -1) { // bitLength not initialized yet + int[] m = mag; + int len = m.length; + if (len == 0) { + n = 0; // offset by one to initialize + } else { + // Calculate the bit length of the magnitude + int magBitLength = ((len - 1) << 5) + bitLengthForInt(mag[0]); + if (signum < 0) { + // Check if magnitude is a power of two + boolean pow2 = (Integer.bitCount(mag[0]) == 1); + for(int i=1; i< len && pow2; i++) + pow2 = (mag[i] == 0); + + n = (pow2 ? magBitLength -1 : magBitLength); + } else { + n = magBitLength; + } + } + bitLength = n + 1; + } + return n; + } + + /** + * Returns the number of bits in the two's complement representation + * of this BigInteger that differ from its sign bit. This method is + * useful when implementing bit-vector style sets atop BigIntegers. + * + * @return number of bits in the two's complement representation + * of this BigInteger that differ from its sign bit. + */ + public int bitCount() { + @SuppressWarnings("deprecation") int bc = bitCount - 1; + if (bc == -1) { // bitCount not initialized yet + bc = 0; // offset by one to initialize + // Count the bits in the magnitude + for (int i=0; i{@code certainty}). The execution time of + * this method is proportional to the value of this parameter. + * @return {@code true} if this BigInteger is probably prime, + * {@code false} if it's definitely composite. + */ + public boolean isProbablePrime(int certainty) { + if (certainty <= 0) + return true; + BigInteger w = this.abs(); + if (w.equals(TWO)) + return true; + if (!w.testBit(0) || w.equals(ONE)) + return false; + + return w.primeToCertainty(certainty, null); + } + + // Comparison Operations + + /** + * Compares this BigInteger with the specified BigInteger. This + * method is provided in preference to individual methods for each + * of the six boolean comparison operators ({@literal <}, ==, + * {@literal >}, {@literal >=}, !=, {@literal <=}). The suggested + * idiom for performing these comparisons is: {@code + * (x.compareTo(y)} <op> {@code 0)}, where + * <op> is one of the six comparison operators. + * + * @param val BigInteger to which this BigInteger is to be compared. + * @return -1, 0 or 1 as this BigInteger is numerically less than, equal + * to, or greater than {@code val}. + */ + public int compareTo(BigInteger val) { + if (signum == val.signum) { + switch (signum) { + case 1: + return compareMagnitude(val); + case -1: + return val.compareMagnitude(this); + default: + return 0; + } + } + return signum > val.signum ? 1 : -1; + } + + /** + * Compares the magnitude array of this BigInteger with the specified + * BigInteger's. This is the version of compareTo ignoring sign. + * + * @param val BigInteger whose magnitude array to be compared. + * @return -1, 0 or 1 as this magnitude array is less than, equal to or + * greater than the magnitude aray for the specified BigInteger's. + */ + final int compareMagnitude(BigInteger val) { + int[] m1 = mag; + int len1 = m1.length; + int[] m2 = val.mag; + int len2 = m2.length; + if (len1 < len2) + return -1; + if (len1 > len2) + return 1; + for (int i = 0; i < len1; i++) { + int a = m1[i]; + int b = m2[i]; + if (a != b) + return ((a & LONG_MASK) < (b & LONG_MASK)) ? -1 : 1; + } + return 0; + } + + /** + * Compares this BigInteger with the specified Object for equality. + * + * @param x Object to which this BigInteger is to be compared. + * @return {@code true} if and only if the specified Object is a + * BigInteger whose value is numerically equal to this BigInteger. + */ + public boolean equals(Object x) { + // This test is just an optimization, which may or may not help + if (x == this) + return true; + + if (!(x instanceof BigInteger)) + return false; + + BigInteger xInt = (BigInteger) x; + if (xInt.signum != signum) + return false; + + int[] m = mag; + int len = m.length; + int[] xm = xInt.mag; + if (len != xm.length) + return false; + + for (int i = 0; i < len; i++) + if (xm[i] != m[i]) + return false; + + return true; + } + + /** + * Returns the minimum of this BigInteger and {@code val}. + * + * @param val value with which the minimum is to be computed. + * @return the BigInteger whose value is the lesser of this BigInteger and + * {@code val}. If they are equal, either may be returned. + */ + public BigInteger min(BigInteger val) { + return (compareTo(val)<0 ? this : val); + } + + /** + * Returns the maximum of this BigInteger and {@code val}. + * + * @param val value with which the maximum is to be computed. + * @return the BigInteger whose value is the greater of this and + * {@code val}. If they are equal, either may be returned. + */ + public BigInteger max(BigInteger val) { + return (compareTo(val)>0 ? this : val); + } + + + // Hash Function + + /** + * Returns the hash code for this BigInteger. + * + * @return hash code for this BigInteger. + */ + public int hashCode() { + int hashCode = 0; + + for (int i=0; i Character.MAX_RADIX) + radix = 10; + + // Compute upper bound on number of digit groups and allocate space + int maxNumDigitGroups = (4*mag.length + 6)/7; + String digitGroup[] = new String[maxNumDigitGroups]; + + // Translate number to string, a digit group at a time + BigInteger tmp = this.abs(); + int numGroups = 0; + while (tmp.signum != 0) { + BigInteger d = longRadix[radix]; + + MutableBigInteger q = new MutableBigInteger(), + a = new MutableBigInteger(tmp.mag), + b = new MutableBigInteger(d.mag); + MutableBigInteger r = a.divide(b, q); + BigInteger q2 = q.toBigInteger(tmp.signum * d.signum); + BigInteger r2 = r.toBigInteger(tmp.signum * d.signum); + + digitGroup[numGroups++] = Long.toString(r2.longValue(), radix); + tmp = q2; + } + + // Put sign (if any) and first digit group into result buffer + StringBuilder buf = new StringBuilder(numGroups*digitsPerLong[radix]+1); + if (signum<0) + buf.append('-'); + buf.append(digitGroup[numGroups-1]); + + // Append remaining digit groups padded with leading zeros + for (int i=numGroups-2; i>=0; i--) { + // Prepend (any) leading zeros for this digit group + int numLeadingZeros = digitsPerLong[radix]-digitGroup[i].length(); + if (numLeadingZeros != 0) + buf.append(zeros[numLeadingZeros]); + buf.append(digitGroup[i]); + } + return buf.toString(); + } + + /* zero[i] is a string of i consecutive zeros. */ + private static String zeros[] = new String[64]; + static { + zeros[63] = + "000000000000000000000000000000000000000000000000000000000000000"; + for (int i=0; i<63; i++) + zeros[i] = zeros[63].substring(0, i); + } + + /** + * Returns the decimal String representation of this BigInteger. + * The digit-to-character mapping provided by + * {@code Character.forDigit} is used, and a minus sign is + * prepended if appropriate. (This representation is compatible + * with the {@link #BigInteger(String) (String)} constructor, and + * allows for String concatenation with Java's + operator.) + * + * @return decimal String representation of this BigInteger. + * @see Character#forDigit + * @see #BigInteger(java.lang.String) + */ + public String toString() { + return toString(10); + } + + /** + * Returns a byte array containing the two's-complement + * representation of this BigInteger. The byte array will be in + * big-endian byte-order: the most significant byte is in + * the zeroth element. The array will contain the minimum number + * of bytes required to represent this BigInteger, including at + * least one sign bit, which is {@code (ceil((this.bitLength() + + * 1)/8))}. (This representation is compatible with the + * {@link #BigInteger(byte[]) (byte[])} constructor.) + * + * @return a byte array containing the two's-complement representation of + * this BigInteger. + * @see #BigInteger(byte[]) + */ + public byte[] toByteArray() { + int byteLen = bitLength()/8 + 1; + byte[] byteArray = new byte[byteLen]; + + for (int i=byteLen-1, bytesCopied=4, nextInt=0, intIndex=0; i>=0; i--) { + if (bytesCopied == 4) { + nextInt = getInt(intIndex++); + bytesCopied = 1; + } else { + nextInt >>>= 8; + bytesCopied++; + } + byteArray[i] = (byte)nextInt; + } + return byteArray; + } + + /** + * Converts this BigInteger to an {@code int}. This + * conversion is analogous to a + * narrowing primitive conversion from {@code long} to + * {@code int} as defined in section 5.1.3 of + * The Java™ Language Specification: + * if this BigInteger is too big to fit in an + * {@code int}, only the low-order 32 bits are returned. + * Note that this conversion can lose information about the + * overall magnitude of the BigInteger value as well as return a + * result with the opposite sign. + * + * @return this BigInteger converted to an {@code int}. + */ + public int intValue() { + int result = 0; + result = getInt(0); + return result; + } + + /** + * Converts this BigInteger to a {@code long}. This + * conversion is analogous to a + * narrowing primitive conversion from {@code long} to + * {@code int} as defined in section 5.1.3 of + * The Java™ Language Specification: + * if this BigInteger is too big to fit in a + * {@code long}, only the low-order 64 bits are returned. + * Note that this conversion can lose information about the + * overall magnitude of the BigInteger value as well as return a + * result with the opposite sign. + * + * @return this BigInteger converted to a {@code long}. + */ + public long longValue() { + long result = 0; + + for (int i=1; i>=0; i--) + result = (result << 32) + (getInt(i) & LONG_MASK); + return result; + } + + /** + * Converts this BigInteger to a {@code float}. This + * conversion is similar to the + * narrowing primitive conversion from {@code double} to + * {@code float} as defined in section 5.1.3 of + * The Java™ Language Specification: + * if this BigInteger has too great a magnitude + * to represent as a {@code float}, it will be converted to + * {@link Float#NEGATIVE_INFINITY} or {@link + * Float#POSITIVE_INFINITY} as appropriate. Note that even when + * the return value is finite, this conversion can lose + * information about the precision of the BigInteger value. + * + * @return this BigInteger converted to a {@code float}. + */ + public float floatValue() { + // Somewhat inefficient, but guaranteed to work. + return Float.parseFloat(this.toString()); + } + + /** + * Converts this BigInteger to a {@code double}. This + * conversion is similar to the + * narrowing primitive conversion from {@code double} to + * {@code float} as defined in section 5.1.3 of + * The Java™ Language Specification: + * if this BigInteger has too great a magnitude + * to represent as a {@code double}, it will be converted to + * {@link Double#NEGATIVE_INFINITY} or {@link + * Double#POSITIVE_INFINITY} as appropriate. Note that even when + * the return value is finite, this conversion can lose + * information about the precision of the BigInteger value. + * + * @return this BigInteger converted to a {@code double}. + */ + public double doubleValue() { + // Somewhat inefficient, but guaranteed to work. + return Double.parseDouble(this.toString()); + } + + /** + * Returns a copy of the input array stripped of any leading zero bytes. + */ + private static int[] stripLeadingZeroInts(int val[]) { + int vlen = val.length; + int keep; + + // Find first nonzero byte + for (keep = 0; keep < vlen && val[keep] == 0; keep++) + ; + return java.util.Arrays.copyOfRange(val, keep, vlen); + } + + /** + * Returns the input array stripped of any leading zero bytes. + * Since the source is trusted the copying may be skipped. + */ + private static int[] trustedStripLeadingZeroInts(int val[]) { + int vlen = val.length; + int keep; + + // Find first nonzero byte + for (keep = 0; keep < vlen && val[keep] == 0; keep++) + ; + return keep == 0 ? val : java.util.Arrays.copyOfRange(val, keep, vlen); + } + + /** + * Returns a copy of the input array stripped of any leading zero bytes. + */ + private static int[] stripLeadingZeroBytes(byte a[]) { + int byteLength = a.length; + int keep; + + // Find first nonzero byte + for (keep = 0; keep < byteLength && a[keep]==0; keep++) + ; + + // Allocate new array and copy relevant part of input array + int intLength = ((byteLength - keep) + 3) >>> 2; + int[] result = new int[intLength]; + int b = byteLength - 1; + for (int i = intLength-1; i >= 0; i--) { + result[i] = a[b--] & 0xff; + int bytesRemaining = b - keep + 1; + int bytesToTransfer = Math.min(3, bytesRemaining); + for (int j=8; j <= (bytesToTransfer << 3); j += 8) + result[i] |= ((a[b--] & 0xff) << j); + } + return result; + } + + /** + * Takes an array a representing a negative 2's-complement number and + * returns the minimal (no leading zero bytes) unsigned whose value is -a. + */ + private static int[] makePositive(byte a[]) { + int keep, k; + int byteLength = a.length; + + // Find first non-sign (0xff) byte of input + for (keep=0; keep= 0; i--) { + result[i] = a[b--] & 0xff; + int numBytesToTransfer = Math.min(3, b-keep+1); + if (numBytesToTransfer < 0) + numBytesToTransfer = 0; + for (int j=8; j <= 8*numBytesToTransfer; j += 8) + result[i] |= ((a[b--] & 0xff) << j); + + // Mask indicates which bits must be complemented + int mask = -1 >>> (8*(3-numBytesToTransfer)); + result[i] = ~result[i] & mask; + } + + // Add one to one's complement to generate two's complement + for (int i=result.length-1; i>=0; i--) { + result[i] = (int)((result[i] & LONG_MASK) + 1); + if (result[i] != 0) + break; + } + + return result; + } + + /** + * Takes an array a representing a negative 2's-complement number and + * returns the minimal (no leading zero ints) unsigned whose value is -a. + */ + private static int[] makePositive(int a[]) { + int keep, j; + + // Find first non-sign (0xffffffff) int of input + for (keep=0; keep>> 5) + 1; + } + + /* Returns sign bit */ + private int signBit() { + return signum < 0 ? 1 : 0; + } + + /* Returns an int of sign bits */ + private int signInt() { + return signum < 0 ? -1 : 0; + } + + /** + * Returns the specified int of the little-endian two's complement + * representation (int 0 is the least significant). The int number can + * be arbitrarily high (values are logically preceded by infinitely many + * sign ints). + */ + private int getInt(int n) { + if (n < 0) + return 0; + if (n >= mag.length) + return signInt(); + + int magInt = mag[mag.length-n-1]; + + return (signum >= 0 ? magInt : + (n <= firstNonzeroIntNum() ? -magInt : ~magInt)); + } + + /** + * Returns the index of the int that contains the first nonzero int in the + * little-endian binary representation of the magnitude (int 0 is the + * least significant). If the magnitude is zero, return value is undefined. + */ + private int firstNonzeroIntNum() { + int fn = firstNonzeroIntNum - 2; + if (fn == -2) { // firstNonzeroIntNum not initialized yet + fn = 0; + + // Search for the first nonzero int + int i; + int mlen = mag.length; + for (i = mlen - 1; i >= 0 && mag[i] == 0; i--) + ; + fn = mlen - i - 1; + firstNonzeroIntNum = fn + 2; // offset by two to initialize + } + return fn; + } + + /** use serialVersionUID from JDK 1.1. for interoperability */ + private static final long serialVersionUID = -8287574255936472291L; + + /** + * Serializable fields for BigInteger. + * + * @serialField signum int + * signum of this BigInteger. + * @serialField magnitude int[] + * magnitude array of this BigInteger. + * @serialField bitCount int + * number of bits in this BigInteger + * @serialField bitLength int + * the number of bits in the minimal two's-complement + * representation of this BigInteger + * @serialField lowestSetBit int + * lowest set bit in the twos complement representation + */ + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("signum", Integer.TYPE), + new ObjectStreamField("magnitude", byte[].class), + new ObjectStreamField("bitCount", Integer.TYPE), + new ObjectStreamField("bitLength", Integer.TYPE), + new ObjectStreamField("firstNonzeroByteNum", Integer.TYPE), + new ObjectStreamField("lowestSetBit", Integer.TYPE) + }; + + + + /** + * Save the {@code BigInteger} instance to a stream. + * The magnitude of a BigInteger is serialized as a byte array for + * historical reasons. + * + * @serialData two necessary fields are written as well as obsolete + * fields for compatibility with older versions. + */ + private void writeObject(ObjectOutputStream s) throws IOException { + // set the values of the Serializable fields + ObjectOutputStream.PutField fields = s.putFields(); + fields.put("signum", signum); + fields.put("magnitude", magSerializedForm()); + // The values written for cached fields are compatible with older + // versions, but are ignored in readObject so don't otherwise matter. + fields.put("bitCount", -1); + fields.put("bitLength", -1); + fields.put("lowestSetBit", -2); + fields.put("firstNonzeroByteNum", -2); + + // save them + s.writeFields(); +} + + /** + * Returns the mag array as an array of bytes. + */ + private byte[] magSerializedForm() { + int len = mag.length; + + int bitLen = (len == 0 ? 0 : ((len - 1) << 5) + bitLengthForInt(mag[0])); + int byteLen = (bitLen + 7) >>> 3; + byte[] result = new byte[byteLen]; + + for (int i = byteLen - 1, bytesCopied = 4, intIndex = len - 1, nextInt = 0; + i>=0; i--) { + if (bytesCopied == 4) { + nextInt = mag[intIndex--]; + bytesCopied = 1; + } else { + nextInt >>>= 8; + bytesCopied++; + } + result[i] = (byte)nextInt; + } + return result; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/math/BitSieve.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/math/BitSieve.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,212 @@ +/* + * Copyright (c) 1999, 2007, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.math; + +/** + * A simple bit sieve used for finding prime number candidates. Allows setting + * and clearing of bits in a storage array. The size of the sieve is assumed to + * be constant to reduce overhead. All the bits of a new bitSieve are zero, and + * bits are removed from it by setting them. + * + * To reduce storage space and increase efficiency, no even numbers are + * represented in the sieve (each bit in the sieve represents an odd number). + * The relationship between the index of a bit and the number it represents is + * given by + * N = offset + (2*index + 1); + * Where N is the integer represented by a bit in the sieve, offset is some + * even integer offset indicating where the sieve begins, and index is the + * index of a bit in the sieve array. + * + * @see BigInteger + * @author Michael McCloskey + * @since 1.3 + */ +class BitSieve { + /** + * Stores the bits in this bitSieve. + */ + private long bits[]; + + /** + * Length is how many bits this sieve holds. + */ + private int length; + + /** + * A small sieve used to filter out multiples of small primes in a search + * sieve. + */ + private static BitSieve smallSieve = new BitSieve(); + + /** + * Construct a "small sieve" with a base of 0. This constructor is + * used internally to generate the set of "small primes" whose multiples + * are excluded from sieves generated by the main (package private) + * constructor, BitSieve(BigInteger base, int searchLen). The length + * of the sieve generated by this constructor was chosen for performance; + * it controls a tradeoff between how much time is spent constructing + * other sieves, and how much time is wasted testing composite candidates + * for primality. The length was chosen experimentally to yield good + * performance. + */ + private BitSieve() { + length = 150 * 64; + bits = new long[(unitIndex(length - 1) + 1)]; + + // Mark 1 as composite + set(0); + int nextIndex = 1; + int nextPrime = 3; + + // Find primes and remove their multiples from sieve + do { + sieveSingle(length, nextIndex + nextPrime, nextPrime); + nextIndex = sieveSearch(length, nextIndex + 1); + nextPrime = 2*nextIndex + 1; + } while((nextIndex > 0) && (nextPrime < length)); + } + + /** + * Construct a bit sieve of searchLen bits used for finding prime number + * candidates. The new sieve begins at the specified base, which must + * be even. + */ + BitSieve(BigInteger base, int searchLen) { + /* + * Candidates are indicated by clear bits in the sieve. As a candidates + * nonprimality is calculated, a bit is set in the sieve to eliminate + * it. To reduce storage space and increase efficiency, no even numbers + * are represented in the sieve (each bit in the sieve represents an + * odd number). + */ + bits = new long[(unitIndex(searchLen-1) + 1)]; + length = searchLen; + int start = 0; + + int step = smallSieve.sieveSearch(smallSieve.length, start); + int convertedStep = (step *2) + 1; + + // Construct the large sieve at an even offset specified by base + MutableBigInteger b = new MutableBigInteger(base); + MutableBigInteger q = new MutableBigInteger(); + do { + // Calculate base mod convertedStep + start = b.divideOneWord(convertedStep, q); + + // Take each multiple of step out of sieve + start = convertedStep - start; + if (start%2 == 0) + start += convertedStep; + sieveSingle(searchLen, (start-1)/2, convertedStep); + + // Find next prime from small sieve + step = smallSieve.sieveSearch(smallSieve.length, step+1); + convertedStep = (step *2) + 1; + } while (step > 0); + } + + /** + * Given a bit index return unit index containing it. + */ + private static int unitIndex(int bitIndex) { + return bitIndex >>> 6; + } + + /** + * Return a unit that masks the specified bit in its unit. + */ + private static long bit(int bitIndex) { + return 1L << (bitIndex & ((1<<6) - 1)); + } + + /** + * Get the value of the bit at the specified index. + */ + private boolean get(int bitIndex) { + int unitIndex = unitIndex(bitIndex); + return ((bits[unitIndex] & bit(bitIndex)) != 0); + } + + /** + * Set the bit at the specified index. + */ + private void set(int bitIndex) { + int unitIndex = unitIndex(bitIndex); + bits[unitIndex] |= bit(bitIndex); + } + + /** + * This method returns the index of the first clear bit in the search + * array that occurs at or after start. It will not search past the + * specified limit. It returns -1 if there is no such clear bit. + */ + private int sieveSearch(int limit, int start) { + if (start >= limit) + return -1; + + int index = start; + do { + if (!get(index)) + return index; + index++; + } while(index < limit-1); + return -1; + } + + /** + * Sieve a single set of multiples out of the sieve. Begin to remove + * multiples of the specified step starting at the specified start index, + * up to the specified limit. + */ + private void sieveSingle(int limit, int start, int step) { + while(start < limit) { + set(start); + start += step; + } + } + + /** + * Test probable primes in the sieve and return successful candidates. + */ + BigInteger retrieve(BigInteger initValue, int certainty, java.util.Random random) { + // Examine the sieve one long at a time to find possible primes + int offset = 1; + for (int i=0; i>>= 1; + offset+=2; + } + } + return null; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/math/MathContext.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/math/MathContext.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2003, 2007, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * Portions Copyright IBM Corporation, 1997, 2001. All Rights Reserved. + */ + +package java.math; +import java.io.*; + +/** + * Immutable objects which encapsulate the context settings which + * describe certain rules for numerical operators, such as those + * implemented by the {@link BigDecimal} class. + * + *

The base-independent settings are: + *

    + *
  1. {@code precision}: + * the number of digits to be used for an operation; results are + * rounded to this precision + * + *
  2. {@code roundingMode}: + * a {@link RoundingMode} object which specifies the algorithm to be + * used for rounding. + *
+ * + * @see BigDecimal + * @see RoundingMode + * @author Mike Cowlishaw + * @author Joseph D. Darcy + * @since 1.5 + */ + +public final class MathContext implements Serializable { + + /* ----- Constants ----- */ + + // defaults for constructors + private static final int DEFAULT_DIGITS = 9; + private static final RoundingMode DEFAULT_ROUNDINGMODE = RoundingMode.HALF_UP; + // Smallest values for digits (Maximum is Integer.MAX_VALUE) + private static final int MIN_DIGITS = 0; + + // Serialization version + private static final long serialVersionUID = 5579720004786848255L; + + /* ----- Public Properties ----- */ + /** + * A {@code MathContext} object whose settings have the values + * required for unlimited precision arithmetic. + * The values of the settings are: + * + * precision=0 roundingMode=HALF_UP + * + */ + public static final MathContext UNLIMITED = + new MathContext(0, RoundingMode.HALF_UP); + + /** + * A {@code MathContext} object with a precision setting + * matching the IEEE 754R Decimal32 format, 7 digits, and a + * rounding mode of {@link RoundingMode#HALF_EVEN HALF_EVEN}, the + * IEEE 754R default. + */ + public static final MathContext DECIMAL32 = + new MathContext(7, RoundingMode.HALF_EVEN); + + /** + * A {@code MathContext} object with a precision setting + * matching the IEEE 754R Decimal64 format, 16 digits, and a + * rounding mode of {@link RoundingMode#HALF_EVEN HALF_EVEN}, the + * IEEE 754R default. + */ + public static final MathContext DECIMAL64 = + new MathContext(16, RoundingMode.HALF_EVEN); + + /** + * A {@code MathContext} object with a precision setting + * matching the IEEE 754R Decimal128 format, 34 digits, and a + * rounding mode of {@link RoundingMode#HALF_EVEN HALF_EVEN}, the + * IEEE 754R default. + */ + public static final MathContext DECIMAL128 = + new MathContext(34, RoundingMode.HALF_EVEN); + + /* ----- Shared Properties ----- */ + /** + * The number of digits to be used for an operation. A value of 0 + * indicates that unlimited precision (as many digits as are + * required) will be used. Note that leading zeros (in the + * coefficient of a number) are never significant. + * + *

{@code precision} will always be non-negative. + * + * @serial + */ + final int precision; + + /** + * The rounding algorithm to be used for an operation. + * + * @see RoundingMode + * @serial + */ + final RoundingMode roundingMode; + + /* ----- Constructors ----- */ + + /** + * Constructs a new {@code MathContext} with the specified + * precision and the {@link RoundingMode#HALF_UP HALF_UP} rounding + * mode. + * + * @param setPrecision The non-negative {@code int} precision setting. + * @throws IllegalArgumentException if the {@code setPrecision} parameter is less + * than zero. + */ + public MathContext(int setPrecision) { + this(setPrecision, DEFAULT_ROUNDINGMODE); + return; + } + + /** + * Constructs a new {@code MathContext} with a specified + * precision and rounding mode. + * + * @param setPrecision The non-negative {@code int} precision setting. + * @param setRoundingMode The rounding mode to use. + * @throws IllegalArgumentException if the {@code setPrecision} parameter is less + * than zero. + * @throws NullPointerException if the rounding mode argument is {@code null} + */ + public MathContext(int setPrecision, + RoundingMode setRoundingMode) { + if (setPrecision < MIN_DIGITS) + throw new IllegalArgumentException("Digits < 0"); + if (setRoundingMode == null) + throw new NullPointerException("null RoundingMode"); + + precision = setPrecision; + roundingMode = setRoundingMode; + return; + } + + /** + * Constructs a new {@code MathContext} from a string. + * + * The string must be in the same format as that produced by the + * {@link #toString} method. + * + *

An {@code IllegalArgumentException} is thrown if the precision + * section of the string is out of range ({@code < 0}) or the string is + * not in the format created by the {@link #toString} method. + * + * @param val The string to be parsed + * @throws IllegalArgumentException if the precision section is out of range + * or of incorrect format + * @throws NullPointerException if the argument is {@code null} + */ + public MathContext(String val) { + boolean bad = false; + int setPrecision; + if (val == null) + throw new NullPointerException("null String"); + try { // any error here is a string format problem + if (!val.startsWith("precision=")) throw new RuntimeException(); + int fence = val.indexOf(' '); // could be -1 + int off = 10; // where value starts + setPrecision = Integer.parseInt(val.substring(10, fence)); + + if (!val.startsWith("roundingMode=", fence+1)) + throw new RuntimeException(); + off = fence + 1 + 13; + String str = val.substring(off, val.length()); + roundingMode = RoundingMode.valueOf(str); + } catch (RuntimeException re) { + throw new IllegalArgumentException("bad string format"); + } + + if (setPrecision < MIN_DIGITS) + throw new IllegalArgumentException("Digits < 0"); + // the other parameters cannot be invalid if we got here + precision = setPrecision; + } + + /** + * Returns the {@code precision} setting. + * This value is always non-negative. + * + * @return an {@code int} which is the value of the {@code precision} + * setting + */ + public int getPrecision() { + return precision; + } + + /** + * Returns the roundingMode setting. + * This will be one of + * {@link RoundingMode#CEILING}, + * {@link RoundingMode#DOWN}, + * {@link RoundingMode#FLOOR}, + * {@link RoundingMode#HALF_DOWN}, + * {@link RoundingMode#HALF_EVEN}, + * {@link RoundingMode#HALF_UP}, + * {@link RoundingMode#UNNECESSARY}, or + * {@link RoundingMode#UP}. + * + * @return a {@code RoundingMode} object which is the value of the + * {@code roundingMode} setting + */ + + public RoundingMode getRoundingMode() { + return roundingMode; + } + + /** + * Compares this {@code MathContext} with the specified + * {@code Object} for equality. + * + * @param x {@code Object} to which this {@code MathContext} is to + * be compared. + * @return {@code true} if and only if the specified {@code Object} is + * a {@code MathContext} object which has exactly the same + * settings as this object + */ + public boolean equals(Object x){ + MathContext mc; + if (!(x instanceof MathContext)) + return false; + mc = (MathContext) x; + return mc.precision == this.precision + && mc.roundingMode == this.roundingMode; // no need for .equals() + } + + /** + * Returns the hash code for this {@code MathContext}. + * + * @return hash code for this {@code MathContext} + */ + public int hashCode() { + return this.precision + roundingMode.hashCode() * 59; + } + + /** + * Returns the string representation of this {@code MathContext}. + * The {@code String} returned represents the settings of the + * {@code MathContext} object as two space-delimited words + * (separated by a single space character, '\u0020', + * and with no leading or trailing white space), as follows: + *

    + *
  1. + * The string {@code "precision="}, immediately followed + * by the value of the precision setting as a numeric string as if + * generated by the {@link Integer#toString(int) Integer.toString} + * method. + * + *
  2. + * The string {@code "roundingMode="}, immediately + * followed by the value of the {@code roundingMode} setting as a + * word. This word will be the same as the name of the + * corresponding public constant in the {@link RoundingMode} + * enum. + *
+ *

+ * For example: + *

+     * precision=9 roundingMode=HALF_UP
+     * 
+ * + * Additional words may be appended to the result of + * {@code toString} in the future if more properties are added to + * this class. + * + * @return a {@code String} representing the context settings + */ + public java.lang.String toString() { + return "precision=" + precision + " " + + "roundingMode=" + roundingMode.toString(); + } + + // Private methods + + /** + * Reconstitute the {@code MathContext} instance from a stream (that is, + * deserialize it). + * + * @param s the stream being read. + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); // read in all fields + // validate possibly bad fields + if (precision < MIN_DIGITS) { + String message = "MathContext: invalid digits in stream"; + throw new java.io.StreamCorruptedException(message); + } + if (roundingMode == null) { + String message = "MathContext: null roundingMode in stream"; + throw new java.io.StreamCorruptedException(message); + } + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/math/MutableBigInteger.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/math/MutableBigInteger.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,1477 @@ +/* + * Copyright (c) 1999, 2007, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.math; + +/** + * A class used to represent multiprecision integers that makes efficient + * use of allocated space by allowing a number to occupy only part of + * an array so that the arrays do not have to be reallocated as often. + * When performing an operation with many iterations the array used to + * hold a number is only reallocated when necessary and does not have to + * be the same size as the number it represents. A mutable number allows + * calculations to occur on the same number without having to create + * a new number for every step of the calculation as occurs with + * BigIntegers. + * + * @see BigInteger + * @author Michael McCloskey + * @since 1.3 + */ + +import java.util.Arrays; + +import static java.math.BigInteger.LONG_MASK; +import static java.math.BigDecimal.INFLATED; + +class MutableBigInteger { + /** + * Holds the magnitude of this MutableBigInteger in big endian order. + * The magnitude may start at an offset into the value array, and it may + * end before the length of the value array. + */ + int[] value; + + /** + * The number of ints of the value array that are currently used + * to hold the magnitude of this MutableBigInteger. The magnitude starts + * at an offset and offset + intLen may be less than value.length. + */ + int intLen; + + /** + * The offset into the value array where the magnitude of this + * MutableBigInteger begins. + */ + int offset = 0; + + // Constants + /** + * MutableBigInteger with one element value array with the value 1. Used by + * BigDecimal divideAndRound to increment the quotient. Use this constant + * only when the method is not going to modify this object. + */ + static final MutableBigInteger ONE = new MutableBigInteger(1); + + // Constructors + + /** + * The default constructor. An empty MutableBigInteger is created with + * a one word capacity. + */ + MutableBigInteger() { + value = new int[1]; + intLen = 0; + } + + /** + * Construct a new MutableBigInteger with a magnitude specified by + * the int val. + */ + MutableBigInteger(int val) { + value = new int[1]; + intLen = 1; + value[0] = val; + } + + /** + * Construct a new MutableBigInteger with the specified value array + * up to the length of the array supplied. + */ + MutableBigInteger(int[] val) { + value = val; + intLen = val.length; + } + + /** + * Construct a new MutableBigInteger with a magnitude equal to the + * specified BigInteger. + */ + MutableBigInteger(BigInteger b) { + intLen = b.mag.length; + value = Arrays.copyOf(b.mag, intLen); + } + + /** + * Construct a new MutableBigInteger with a magnitude equal to the + * specified MutableBigInteger. + */ + MutableBigInteger(MutableBigInteger val) { + intLen = val.intLen; + value = Arrays.copyOfRange(val.value, val.offset, val.offset + intLen); + } + + /** + * Internal helper method to return the magnitude array. The caller is not + * supposed to modify the returned array. + */ + private int[] getMagnitudeArray() { + if (offset > 0 || value.length != intLen) + return Arrays.copyOfRange(value, offset, offset + intLen); + return value; + } + + /** + * Convert this MutableBigInteger to a long value. The caller has to make + * sure this MutableBigInteger can be fit into long. + */ + private long toLong() { + assert (intLen <= 2) : "this MutableBigInteger exceeds the range of long"; + if (intLen == 0) + return 0; + long d = value[offset] & LONG_MASK; + return (intLen == 2) ? d << 32 | (value[offset + 1] & LONG_MASK) : d; + } + + /** + * Convert this MutableBigInteger to a BigInteger object. + */ + BigInteger toBigInteger(int sign) { + if (intLen == 0 || sign == 0) + return BigInteger.ZERO; + return new BigInteger(getMagnitudeArray(), sign); + } + + /** + * Convert this MutableBigInteger to BigDecimal object with the specified sign + * and scale. + */ + BigDecimal toBigDecimal(int sign, int scale) { + if (intLen == 0 || sign == 0) + return BigDecimal.valueOf(0, scale); + int[] mag = getMagnitudeArray(); + int len = mag.length; + int d = mag[0]; + // If this MutableBigInteger can't be fit into long, we need to + // make a BigInteger object for the resultant BigDecimal object. + if (len > 2 || (d < 0 && len == 2)) + return new BigDecimal(new BigInteger(mag, sign), INFLATED, scale, 0); + long v = (len == 2) ? + ((mag[1] & LONG_MASK) | (d & LONG_MASK) << 32) : + d & LONG_MASK; + return new BigDecimal(null, sign == -1 ? -v : v, scale, 0); + } + + /** + * Clear out a MutableBigInteger for reuse. + */ + void clear() { + offset = intLen = 0; + for (int index=0, n=value.length; index < n; index++) + value[index] = 0; + } + + /** + * Set a MutableBigInteger to zero, removing its offset. + */ + void reset() { + offset = intLen = 0; + } + + /** + * Compare the magnitude of two MutableBigIntegers. Returns -1, 0 or 1 + * as this MutableBigInteger is numerically less than, equal to, or + * greater than b. + */ + final int compare(MutableBigInteger b) { + int blen = b.intLen; + if (intLen < blen) + return -1; + if (intLen > blen) + return 1; + + // Add Integer.MIN_VALUE to make the comparison act as unsigned integer + // comparison. + int[] bval = b.value; + for (int i = offset, j = b.offset; i < intLen + offset; i++, j++) { + int b1 = value[i] + 0x80000000; + int b2 = bval[j] + 0x80000000; + if (b1 < b2) + return -1; + if (b1 > b2) + return 1; + } + return 0; + } + + /** + * Compare this against half of a MutableBigInteger object (Needed for + * remainder tests). + * Assumes no leading unnecessary zeros, which holds for results + * from divide(). + */ + final int compareHalf(MutableBigInteger b) { + int blen = b.intLen; + int len = intLen; + if (len <= 0) + return blen <=0 ? 0 : -1; + if (len > blen) + return 1; + if (len < blen - 1) + return -1; + int[] bval = b.value; + int bstart = 0; + int carry = 0; + // Only 2 cases left:len == blen or len == blen - 1 + if (len != blen) { // len == blen - 1 + if (bval[bstart] == 1) { + ++bstart; + carry = 0x80000000; + } else + return -1; + } + // compare values with right-shifted values of b, + // carrying shifted-out bits across words + int[] val = value; + for (int i = offset, j = bstart; i < len + offset;) { + int bv = bval[j++]; + long hb = ((bv >>> 1) + carry) & LONG_MASK; + long v = val[i++] & LONG_MASK; + if (v != hb) + return v < hb ? -1 : 1; + carry = (bv & 1) << 31; // carray will be either 0x80000000 or 0 + } + return carry == 0? 0 : -1; + } + + /** + * Return the index of the lowest set bit in this MutableBigInteger. If the + * magnitude of this MutableBigInteger is zero, -1 is returned. + */ + private final int getLowestSetBit() { + if (intLen == 0) + return -1; + int j, b; + for (j=intLen-1; (j>0) && (value[j+offset]==0); j--) + ; + b = value[j+offset]; + if (b==0) + return -1; + return ((intLen-1-j)<<5) + Integer.numberOfTrailingZeros(b); + } + + /** + * Return the int in use in this MutableBigInteger at the specified + * index. This method is not used because it is not inlined on all + * platforms. + */ + private final int getInt(int index) { + return value[offset+index]; + } + + /** + * Return a long which is equal to the unsigned value of the int in + * use in this MutableBigInteger at the specified index. This method is + * not used because it is not inlined on all platforms. + */ + private final long getLong(int index) { + return value[offset+index] & LONG_MASK; + } + + /** + * Ensure that the MutableBigInteger is in normal form, specifically + * making sure that there are no leading zeros, and that if the + * magnitude is zero, then intLen is zero. + */ + final void normalize() { + if (intLen == 0) { + offset = 0; + return; + } + + int index = offset; + if (value[index] != 0) + return; + + int indexBound = index+intLen; + do { + index++; + } while(index < indexBound && value[index]==0); + + int numZeros = index - offset; + intLen -= numZeros; + offset = (intLen==0 ? 0 : offset+numZeros); + } + + /** + * If this MutableBigInteger cannot hold len words, increase the size + * of the value array to len words. + */ + private final void ensureCapacity(int len) { + if (value.length < len) { + value = new int[len]; + offset = 0; + intLen = len; + } + } + + /** + * Convert this MutableBigInteger into an int array with no leading + * zeros, of a length that is equal to this MutableBigInteger's intLen. + */ + int[] toIntArray() { + int[] result = new int[intLen]; + for(int i=0; i value.length) + return false; + if (intLen ==0) + return true; + return (value[offset] != 0); + } + + /** + * Returns a String representation of this MutableBigInteger in radix 10. + */ + public String toString() { + BigInteger b = toBigInteger(1); + return b.toString(); + } + + /** + * Right shift this MutableBigInteger n bits. The MutableBigInteger is left + * in normal form. + */ + void rightShift(int n) { + if (intLen == 0) + return; + int nInts = n >>> 5; + int nBits = n & 0x1F; + this.intLen -= nInts; + if (nBits == 0) + return; + int bitsInHighWord = BigInteger.bitLengthForInt(value[offset]); + if (nBits >= bitsInHighWord) { + this.primitiveLeftShift(32 - nBits); + this.intLen--; + } else { + primitiveRightShift(nBits); + } + } + + /** + * Left shift this MutableBigInteger n bits. + */ + void leftShift(int n) { + /* + * If there is enough storage space in this MutableBigInteger already + * the available space will be used. Space to the right of the used + * ints in the value array is faster to utilize, so the extra space + * will be taken from the right if possible. + */ + if (intLen == 0) + return; + int nInts = n >>> 5; + int nBits = n&0x1F; + int bitsInHighWord = BigInteger.bitLengthForInt(value[offset]); + + // If shift can be done without moving words, do so + if (n <= (32-bitsInHighWord)) { + primitiveLeftShift(nBits); + return; + } + + int newLen = intLen + nInts +1; + if (nBits <= (32-bitsInHighWord)) + newLen--; + if (value.length < newLen) { + // The array must grow + int[] result = new int[newLen]; + for (int i=0; i= newLen) { + // Use space on right + for(int i=0; i= 0; j--) { + long sum = (a[j] & LONG_MASK) + + (result[j+offset] & LONG_MASK) + carry; + result[j+offset] = (int)sum; + carry = sum >>> 32; + } + return (int)carry; + } + + /** + * This method is used for division. It multiplies an n word input a by one + * word input x, and subtracts the n word product from q. This is needed + * when subtracting qhat*divisor from dividend. + */ + private int mulsub(int[] q, int[] a, int x, int len, int offset) { + long xLong = x & LONG_MASK; + long carry = 0; + offset += len; + + for (int j=len-1; j >= 0; j--) { + long product = (a[j] & LONG_MASK) * xLong + carry; + long difference = q[offset] - product; + q[offset--] = (int)difference; + carry = (product >>> 32) + + (((difference & LONG_MASK) > + (((~(int)product) & LONG_MASK))) ? 1:0); + } + return (int)carry; + } + + /** + * Right shift this MutableBigInteger n bits, where n is + * less than 32. + * Assumes that intLen > 0, n > 0 for speed + */ + private final void primitiveRightShift(int n) { + int[] val = value; + int n2 = 32 - n; + for (int i=offset+intLen-1, c=val[i]; i>offset; i--) { + int b = c; + c = val[i-1]; + val[i] = (c << n2) | (b >>> n); + } + val[offset] >>>= n; + } + + /** + * Left shift this MutableBigInteger n bits, where n is + * less than 32. + * Assumes that intLen > 0, n > 0 for speed + */ + private final void primitiveLeftShift(int n) { + int[] val = value; + int n2 = 32 - n; + for (int i=offset, c=val[i], m=i+intLen-1; i>> n2); + } + val[offset+intLen-1] <<= n; + } + + /** + * Adds the contents of two MutableBigInteger objects.The result + * is placed within this MutableBigInteger. + * The contents of the addend are not changed. + */ + void add(MutableBigInteger addend) { + int x = intLen; + int y = addend.intLen; + int resultLen = (intLen > addend.intLen ? intLen : addend.intLen); + int[] result = (value.length < resultLen ? new int[resultLen] : value); + + int rstart = result.length-1; + long sum; + long carry = 0; + + // Add common parts of both numbers + while(x>0 && y>0) { + x--; y--; + sum = (value[x+offset] & LONG_MASK) + + (addend.value[y+addend.offset] & LONG_MASK) + carry; + result[rstart--] = (int)sum; + carry = sum >>> 32; + } + + // Add remainder of the longer number + while(x>0) { + x--; + if (carry == 0 && result == value && rstart == (x + offset)) + return; + sum = (value[x+offset] & LONG_MASK) + carry; + result[rstart--] = (int)sum; + carry = sum >>> 32; + } + while(y>0) { + y--; + sum = (addend.value[y+addend.offset] & LONG_MASK) + carry; + result[rstart--] = (int)sum; + carry = sum >>> 32; + } + + if (carry > 0) { // Result must grow in length + resultLen++; + if (result.length < resultLen) { + int temp[] = new int[resultLen]; + // Result one word longer from carry-out; copy low-order + // bits into new result. + System.arraycopy(result, 0, temp, 1, result.length); + temp[0] = 1; + result = temp; + } else { + result[rstart--] = 1; + } + } + + value = result; + intLen = resultLen; + offset = result.length - resultLen; + } + + + /** + * Subtracts the smaller of this and b from the larger and places the + * result into this MutableBigInteger. + */ + int subtract(MutableBigInteger b) { + MutableBigInteger a = this; + + int[] result = value; + int sign = a.compare(b); + + if (sign == 0) { + reset(); + return 0; + } + if (sign < 0) { + MutableBigInteger tmp = a; + a = b; + b = tmp; + } + + int resultLen = a.intLen; + if (result.length < resultLen) + result = new int[resultLen]; + + long diff = 0; + int x = a.intLen; + int y = b.intLen; + int rstart = result.length - 1; + + // Subtract common parts of both numbers + while (y>0) { + x--; y--; + + diff = (a.value[x+a.offset] & LONG_MASK) - + (b.value[y+b.offset] & LONG_MASK) - ((int)-(diff>>32)); + result[rstart--] = (int)diff; + } + // Subtract remainder of longer number + while (x>0) { + x--; + diff = (a.value[x+a.offset] & LONG_MASK) - ((int)-(diff>>32)); + result[rstart--] = (int)diff; + } + + value = result; + intLen = resultLen; + offset = value.length - resultLen; + normalize(); + return sign; + } + + /** + * Subtracts the smaller of a and b from the larger and places the result + * into the larger. Returns 1 if the answer is in a, -1 if in b, 0 if no + * operation was performed. + */ + private int difference(MutableBigInteger b) { + MutableBigInteger a = this; + int sign = a.compare(b); + if (sign ==0) + return 0; + if (sign < 0) { + MutableBigInteger tmp = a; + a = b; + b = tmp; + } + + long diff = 0; + int x = a.intLen; + int y = b.intLen; + + // Subtract common parts of both numbers + while (y>0) { + x--; y--; + diff = (a.value[a.offset+ x] & LONG_MASK) - + (b.value[b.offset+ y] & LONG_MASK) - ((int)-(diff>>32)); + a.value[a.offset+x] = (int)diff; + } + // Subtract remainder of longer number + while (x>0) { + x--; + diff = (a.value[a.offset+ x] & LONG_MASK) - ((int)-(diff>>32)); + a.value[a.offset+x] = (int)diff; + } + + a.normalize(); + return sign; + } + + /** + * Multiply the contents of two MutableBigInteger objects. The result is + * placed into MutableBigInteger z. The contents of y are not changed. + */ + void multiply(MutableBigInteger y, MutableBigInteger z) { + int xLen = intLen; + int yLen = y.intLen; + int newLen = xLen + yLen; + + // Put z into an appropriate state to receive product + if (z.value.length < newLen) + z.value = new int[newLen]; + z.offset = 0; + z.intLen = newLen; + + // The first iteration is hoisted out of the loop to avoid extra add + long carry = 0; + for (int j=yLen-1, k=yLen+xLen-1; j >= 0; j--, k--) { + long product = (y.value[j+y.offset] & LONG_MASK) * + (value[xLen-1+offset] & LONG_MASK) + carry; + z.value[k] = (int)product; + carry = product >>> 32; + } + z.value[xLen-1] = (int)carry; + + // Perform the multiplication word by word + for (int i = xLen-2; i >= 0; i--) { + carry = 0; + for (int j=yLen-1, k=yLen+i; j >= 0; j--, k--) { + long product = (y.value[j+y.offset] & LONG_MASK) * + (value[i+offset] & LONG_MASK) + + (z.value[k] & LONG_MASK) + carry; + z.value[k] = (int)product; + carry = product >>> 32; + } + z.value[i] = (int)carry; + } + + // Remove leading zeros from product + z.normalize(); + } + + /** + * Multiply the contents of this MutableBigInteger by the word y. The + * result is placed into z. + */ + void mul(int y, MutableBigInteger z) { + if (y == 1) { + z.copyValue(this); + return; + } + + if (y == 0) { + z.clear(); + return; + } + + // Perform the multiplication word by word + long ylong = y & LONG_MASK; + int[] zval = (z.value.length= 0; i--) { + long product = ylong * (value[i+offset] & LONG_MASK) + carry; + zval[i+1] = (int)product; + carry = product >>> 32; + } + + if (carry == 0) { + z.offset = 1; + z.intLen = intLen; + } else { + z.offset = 0; + z.intLen = intLen + 1; + zval[0] = (int)carry; + } + z.value = zval; + } + + /** + * This method is used for division of an n word dividend by a one word + * divisor. The quotient is placed into quotient. The one word divisor is + * specified by divisor. + * + * @return the remainder of the division is returned. + * + */ + int divideOneWord(int divisor, MutableBigInteger quotient) { + long divisorLong = divisor & LONG_MASK; + + // Special case of one word dividend + if (intLen == 1) { + long dividendValue = value[offset] & LONG_MASK; + int q = (int) (dividendValue / divisorLong); + int r = (int) (dividendValue - q * divisorLong); + quotient.value[0] = q; + quotient.intLen = (q == 0) ? 0 : 1; + quotient.offset = 0; + return r; + } + + if (quotient.value.length < intLen) + quotient.value = new int[intLen]; + quotient.offset = 0; + quotient.intLen = intLen; + + // Normalize the divisor + int shift = Integer.numberOfLeadingZeros(divisor); + + int rem = value[offset]; + long remLong = rem & LONG_MASK; + if (remLong < divisorLong) { + quotient.value[0] = 0; + } else { + quotient.value[0] = (int)(remLong / divisorLong); + rem = (int) (remLong - (quotient.value[0] * divisorLong)); + remLong = rem & LONG_MASK; + } + + int xlen = intLen; + int[] qWord = new int[2]; + while (--xlen > 0) { + long dividendEstimate = (remLong<<32) | + (value[offset + intLen - xlen] & LONG_MASK); + if (dividendEstimate >= 0) { + qWord[0] = (int) (dividendEstimate / divisorLong); + qWord[1] = (int) (dividendEstimate - qWord[0] * divisorLong); + } else { + divWord(qWord, dividendEstimate, divisor); + } + quotient.value[intLen - xlen] = qWord[0]; + rem = qWord[1]; + remLong = rem & LONG_MASK; + } + + quotient.normalize(); + // Unnormalize + if (shift > 0) + return rem % divisor; + else + return rem; + } + + /** + * Calculates the quotient of this div b and places the quotient in the + * provided MutableBigInteger objects and the remainder object is returned. + * + * Uses Algorithm D in Knuth section 4.3.1. + * Many optimizations to that algorithm have been adapted from the Colin + * Plumb C library. + * It special cases one word divisors for speed. The content of b is not + * changed. + * + */ + MutableBigInteger divide(MutableBigInteger b, MutableBigInteger quotient) { + if (b.intLen == 0) + throw new ArithmeticException("BigInteger divide by zero"); + + // Dividend is zero + if (intLen == 0) { + quotient.intLen = quotient.offset; + return new MutableBigInteger(); + } + + int cmp = compare(b); + // Dividend less than divisor + if (cmp < 0) { + quotient.intLen = quotient.offset = 0; + return new MutableBigInteger(this); + } + // Dividend equal to divisor + if (cmp == 0) { + quotient.value[0] = quotient.intLen = 1; + quotient.offset = 0; + return new MutableBigInteger(); + } + + quotient.clear(); + // Special case one word divisor + if (b.intLen == 1) { + int r = divideOneWord(b.value[b.offset], quotient); + if (r == 0) + return new MutableBigInteger(); + return new MutableBigInteger(r); + } + + // Copy divisor value to protect divisor + int[] div = Arrays.copyOfRange(b.value, b.offset, b.offset + b.intLen); + return divideMagnitude(div, quotient); + } + + /** + * Internally used to calculate the quotient of this div v and places the + * quotient in the provided MutableBigInteger object and the remainder is + * returned. + * + * @return the remainder of the division will be returned. + */ + long divide(long v, MutableBigInteger quotient) { + if (v == 0) + throw new ArithmeticException("BigInteger divide by zero"); + + // Dividend is zero + if (intLen == 0) { + quotient.intLen = quotient.offset = 0; + return 0; + } + if (v < 0) + v = -v; + + int d = (int)(v >>> 32); + quotient.clear(); + // Special case on word divisor + if (d == 0) + return divideOneWord((int)v, quotient) & LONG_MASK; + else { + int[] div = new int[]{ d, (int)(v & LONG_MASK) }; + return divideMagnitude(div, quotient).toLong(); + } + } + + /** + * Divide this MutableBigInteger by the divisor represented by its magnitude + * array. The quotient will be placed into the provided quotient object & + * the remainder object is returned. + */ + private MutableBigInteger divideMagnitude(int[] divisor, + MutableBigInteger quotient) { + + // Remainder starts as dividend with space for a leading zero + MutableBigInteger rem = new MutableBigInteger(new int[intLen + 1]); + System.arraycopy(value, offset, rem.value, 1, intLen); + rem.intLen = intLen; + rem.offset = 1; + + int nlen = rem.intLen; + + // Set the quotient size + int dlen = divisor.length; + int limit = nlen - dlen + 1; + if (quotient.value.length < limit) { + quotient.value = new int[limit]; + quotient.offset = 0; + } + quotient.intLen = limit; + int[] q = quotient.value; + + // D1 normalize the divisor + int shift = Integer.numberOfLeadingZeros(divisor[0]); + if (shift > 0) { + // First shift will not grow array + BigInteger.primitiveLeftShift(divisor, dlen, shift); + // But this one might + rem.leftShift(shift); + } + + // Must insert leading 0 in rem if its length did not change + if (rem.intLen == nlen) { + rem.offset = 0; + rem.value[0] = 0; + rem.intLen++; + } + + int dh = divisor[0]; + long dhLong = dh & LONG_MASK; + int dl = divisor[1]; + int[] qWord = new int[2]; + + // D2 Initialize j + for(int j=0; j= 0) { + qhat = (int) (nChunk / dhLong); + qrem = (int) (nChunk - (qhat * dhLong)); + } else { + divWord(qWord, nChunk, dh); + qhat = qWord[0]; + qrem = qWord[1]; + } + } + + if (qhat == 0) + continue; + + if (!skipCorrection) { // Correct qhat + long nl = rem.value[j+2+rem.offset] & LONG_MASK; + long rs = ((qrem & LONG_MASK) << 32) | nl; + long estProduct = (dl & LONG_MASK) * (qhat & LONG_MASK); + + if (unsignedLongCompare(estProduct, rs)) { + qhat--; + qrem = (int)((qrem & LONG_MASK) + dhLong); + if ((qrem & LONG_MASK) >= dhLong) { + estProduct -= (dl & LONG_MASK); + rs = ((qrem & LONG_MASK) << 32) | nl; + if (unsignedLongCompare(estProduct, rs)) + qhat--; + } + } + } + + // D4 Multiply and subtract + rem.value[j+rem.offset] = 0; + int borrow = mulsub(rem.value, divisor, qhat, dlen, j+rem.offset); + + // D5 Test remainder + if (borrow + 0x80000000 > nh2) { + // D6 Add back + divadd(divisor, rem.value, j+1+rem.offset); + qhat--; + } + + // Store the quotient digit + q[j] = qhat; + } // D7 loop on j + + // D8 Unnormalize + if (shift > 0) + rem.rightShift(shift); + + quotient.normalize(); + rem.normalize(); + return rem; + } + + /** + * Compare two longs as if they were unsigned. + * Returns true iff one is bigger than two. + */ + private boolean unsignedLongCompare(long one, long two) { + return (one+Long.MIN_VALUE) > (two+Long.MIN_VALUE); + } + + /** + * This method divides a long quantity by an int to estimate + * qhat for two multi precision numbers. It is used when + * the signed value of n is less than zero. + */ + private void divWord(int[] result, long n, int d) { + long dLong = d & LONG_MASK; + + if (dLong == 1) { + result[0] = (int)n; + result[1] = 0; + return; + } + + // Approximate the quotient and remainder + long q = (n >>> 1) / (dLong >>> 1); + long r = n - q*dLong; + + // Correct the approximation + while (r < 0) { + r += dLong; + q--; + } + while (r >= dLong) { + r -= dLong; + q++; + } + + // n - q*dlong == r && 0 <= r = 0) { + // steps B3 and B4 + t.rightShift(lb); + // step B5 + if (tsign > 0) + u = t; + else + v = t; + + // Special case one word numbers + if (u.intLen < 2 && v.intLen < 2) { + int x = u.value[u.offset]; + int y = v.value[v.offset]; + x = binaryGcd(x, y); + r.value[0] = x; + r.intLen = 1; + r.offset = 0; + if (k > 0) + r.leftShift(k); + return r; + } + + // step B6 + if ((tsign = u.difference(v)) == 0) + break; + t = (tsign >= 0) ? u : v; + } + + if (k > 0) + u.leftShift(k); + return u; + } + + /** + * Calculate GCD of a and b interpreted as unsigned integers. + */ + static int binaryGcd(int a, int b) { + if (b==0) + return a; + if (a==0) + return b; + + // Right shift a & b till their last bits equal to 1. + int aZeros = Integer.numberOfTrailingZeros(a); + int bZeros = Integer.numberOfTrailingZeros(b); + a >>>= aZeros; + b >>>= bZeros; + + int t = (aZeros < bZeros ? aZeros : bZeros); + + while (a != b) { + if ((a+0x80000000) > (b+0x80000000)) { // a > b as unsigned + a -= b; + a >>>= Integer.numberOfTrailingZeros(a); + } else { + b -= a; + b >>>= Integer.numberOfTrailingZeros(b); + } + } + return a< 64) + return euclidModInverse(k); + + int t = inverseMod32(value[offset+intLen-1]); + + if (k < 33) { + t = (k == 32 ? t : t & ((1 << k) - 1)); + return new MutableBigInteger(t); + } + + long pLong = (value[offset+intLen-1] & LONG_MASK); + if (intLen > 1) + pLong |= ((long)value[offset+intLen-2] << 32); + long tLong = t & LONG_MASK; + tLong = tLong * (2 - pLong * tLong); // 1 more Newton iter step + tLong = (k == 64 ? tLong : tLong & ((1L << k) - 1)); + + MutableBigInteger result = new MutableBigInteger(new int[2]); + result.value[0] = (int)(tLong >>> 32); + result.value[1] = (int)tLong; + result.intLen = 2; + result.normalize(); + return result; + } + + /* + * Returns the multiplicative inverse of val mod 2^32. Assumes val is odd. + */ + static int inverseMod32(int val) { + // Newton's iteration! + int t = val; + t *= 2 - val*t; + t *= 2 - val*t; + t *= 2 - val*t; + t *= 2 - val*t; + return t; + } + + /* + * Calculate the multiplicative inverse of 2^k mod mod, where mod is odd. + */ + static MutableBigInteger modInverseBP2(MutableBigInteger mod, int k) { + // Copy the mod to protect original + return fixup(new MutableBigInteger(1), new MutableBigInteger(mod), k); + } + + /** + * Calculate the multiplicative inverse of this mod mod, where mod is odd. + * This and mod are not changed by the calculation. + * + * This method implements an algorithm due to Richard Schroeppel, that uses + * the same intermediate representation as Montgomery Reduction + * ("Montgomery Form"). The algorithm is described in an unpublished + * manuscript entitled "Fast Modular Reciprocals." + */ + private MutableBigInteger modInverse(MutableBigInteger mod) { + MutableBigInteger p = new MutableBigInteger(mod); + MutableBigInteger f = new MutableBigInteger(this); + MutableBigInteger g = new MutableBigInteger(p); + SignedMutableBigInteger c = new SignedMutableBigInteger(1); + SignedMutableBigInteger d = new SignedMutableBigInteger(); + MutableBigInteger temp = null; + SignedMutableBigInteger sTemp = null; + + int k = 0; + // Right shift f k times until odd, left shift d k times + if (f.isEven()) { + int trailingZeros = f.getLowestSetBit(); + f.rightShift(trailingZeros); + d.leftShift(trailingZeros); + k = trailingZeros; + } + + // The Almost Inverse Algorithm + while(!f.isOne()) { + // If gcd(f, g) != 1, number is not invertible modulo mod + if (f.isZero()) + throw new ArithmeticException("BigInteger not invertible."); + + // If f < g exchange f, g and c, d + if (f.compare(g) < 0) { + temp = f; f = g; g = temp; + sTemp = d; d = c; c = sTemp; + } + + // If f == g (mod 4) + if (((f.value[f.offset + f.intLen - 1] ^ + g.value[g.offset + g.intLen - 1]) & 3) == 0) { + f.subtract(g); + c.signedSubtract(d); + } else { // If f != g (mod 4) + f.add(g); + c.signedAdd(d); + } + + // Right shift f k times until odd, left shift d k times + int trailingZeros = f.getLowestSetBit(); + f.rightShift(trailingZeros); + d.leftShift(trailingZeros); + k += trailingZeros; + } + + while (c.sign < 0) + c.signedAdd(p); + + return fixup(c, p, k); + } + + /* + * The Fixup Algorithm + * Calculates X such that X = C * 2^(-k) (mod P) + * Assumes C

> 5; i= 0) + c.subtract(p); + + return c; + } + + /** + * Uses the extended Euclidean algorithm to compute the modInverse of base + * mod a modulus that is a power of 2. The modulus is 2^k. + */ + MutableBigInteger euclidModInverse(int k) { + MutableBigInteger b = new MutableBigInteger(1); + b.leftShift(k); + MutableBigInteger mod = new MutableBigInteger(b); + + MutableBigInteger a = new MutableBigInteger(this); + MutableBigInteger q = new MutableBigInteger(); + MutableBigInteger r = b.divide(a, q); + + MutableBigInteger swapper = b; + // swap b & r + b = r; + r = swapper; + + MutableBigInteger t1 = new MutableBigInteger(q); + MutableBigInteger t0 = new MutableBigInteger(1); + MutableBigInteger temp = new MutableBigInteger(); + + while (!b.isOne()) { + r = a.divide(b, q); + + if (r.intLen == 0) + throw new ArithmeticException("BigInteger not invertible."); + + swapper = r; + a = swapper; + + if (q.intLen == 1) + t1.mul(q.value[q.offset], temp); + else + q.multiply(t1, temp); + swapper = q; + q = temp; + temp = swapper; + t0.add(q); + + if (a.isOne()) + return t0; + + r = b.divide(a, q); + + if (r.intLen == 0) + throw new ArithmeticException("BigInteger not invertible."); + + swapper = b; + b = r; + + if (q.intLen == 1) + t0.mul(q.value[q.offset], temp); + else + q.multiply(t0, temp); + swapper = q; q = temp; temp = swapper; + + t1.add(q); + } + mod.subtract(t1); + return mod; + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/math/RoundingMode.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/math/RoundingMode.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * Portions Copyright IBM Corporation, 2001. All Rights Reserved. + */ +package java.math; + +/** + * Specifies a rounding behavior for numerical operations + * capable of discarding precision. Each rounding mode indicates how + * the least significant returned digit of a rounded result is to be + * calculated. If fewer digits are returned than the digits needed to + * represent the exact numerical result, the discarded digits will be + * referred to as the discarded fraction regardless the digits' + * contribution to the value of the number. In other words, + * considered as a numerical value, the discarded fraction could have + * an absolute value greater than one. + * + *

Each rounding mode description includes a table listing how + * different two-digit decimal values would round to a one digit + * decimal value under the rounding mode in question. The result + * column in the tables could be gotten by creating a + * {@code BigDecimal} number with the specified value, forming a + * {@link MathContext} object with the proper settings + * ({@code precision} set to {@code 1}, and the + * {@code roundingMode} set to the rounding mode in question), and + * calling {@link BigDecimal#round round} on this number with the + * proper {@code MathContext}. A summary table showing the results + * of these rounding operations for all rounding modes appears below. + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Summary of Rounding Operations Under Different Rounding Modes
Result of rounding input to one digit with the given + * rounding mode
Input Number {@code UP}{@code DOWN}{@code CEILING}{@code FLOOR}{@code HALF_UP}{@code HALF_DOWN}{@code HALF_EVEN}{@code UNNECESSARY}
5.5 6 5 6 5 6 5 6 throw {@code ArithmeticException}
2.5 3 2 3 2 3 2 2 throw {@code ArithmeticException}
1.6 2 1 2 1 2 2 2 throw {@code ArithmeticException}
1.1 2 1 2 1 1 1 1 throw {@code ArithmeticException}
1.0 1 1 1 1 1 1 1 1
-1.0 -1 -1 -1 -1 -1 -1 -1 -1
-1.1 -2 -1 -1 -2 -1 -1 -1 throw {@code ArithmeticException}
-1.6 -2 -1 -1 -2 -2 -2 -2 throw {@code ArithmeticException}
-2.5 -3 -2 -2 -3 -3 -2 -2 throw {@code ArithmeticException}
-5.5 -6 -5 -5 -6 -6 -5 -6 throw {@code ArithmeticException}
+ * + * + *

This {@code enum} is intended to replace the integer-based + * enumeration of rounding mode constants in {@link BigDecimal} + * ({@link BigDecimal#ROUND_UP}, {@link BigDecimal#ROUND_DOWN}, + * etc. ). + * + * @see BigDecimal + * @see MathContext + * @author Josh Bloch + * @author Mike Cowlishaw + * @author Joseph D. Darcy + * @since 1.5 + */ +public enum RoundingMode { + + /** + * Rounding mode to round away from zero. Always increments the + * digit prior to a non-zero discarded fraction. Note that this + * rounding mode never decreases the magnitude of the calculated + * value. + * + *

Example: + * + * + * + * + * + * + * + * + * + * + * + * + *
Input NumberInput rounded to one digit
with {@code UP} rounding + *
5.5 6
2.5 3
1.6 2
1.1 2
1.0 1
-1.0 -1
-1.1 -2
-1.6 -2
-2.5 -3
-5.5 -6
+ */ + UP(BigDecimal.ROUND_UP), + + /** + * Rounding mode to round towards zero. Never increments the digit + * prior to a discarded fraction (i.e., truncates). Note that this + * rounding mode never increases the magnitude of the calculated value. + * + *

Example: + * + * + * + * + * + * + * + * + * + * + * + * + *
Input NumberInput rounded to one digit
with {@code DOWN} rounding + *
5.5 5
2.5 2
1.6 1
1.1 1
1.0 1
-1.0 -1
-1.1 -1
-1.6 -1
-2.5 -2
-5.5 -5
+ */ + DOWN(BigDecimal.ROUND_DOWN), + + /** + * Rounding mode to round towards positive infinity. If the + * result is positive, behaves as for {@code RoundingMode.UP}; + * if negative, behaves as for {@code RoundingMode.DOWN}. Note + * that this rounding mode never decreases the calculated value. + * + *

Example: + * + * + * + * + * + * + * + * + * + * + * + * + *
Input NumberInput rounded to one digit
with {@code CEILING} rounding + *
5.5 6
2.5 3
1.6 2
1.1 2
1.0 1
-1.0 -1
-1.1 -1
-1.6 -1
-2.5 -2
-5.5 -5
+ */ + CEILING(BigDecimal.ROUND_CEILING), + + /** + * Rounding mode to round towards negative infinity. If the + * result is positive, behave as for {@code RoundingMode.DOWN}; + * if negative, behave as for {@code RoundingMode.UP}. Note that + * this rounding mode never increases the calculated value. + * + *

Example: + * + * + * + * + * + * + * + * + * + * + * + * + *
Input NumberInput rounded to one digit
with {@code FLOOR} rounding + *
5.5 5
2.5 2
1.6 1
1.1 1
1.0 1
-1.0 -1
-1.1 -2
-1.6 -2
-2.5 -3
-5.5 -6
+ */ + FLOOR(BigDecimal.ROUND_FLOOR), + + /** + * Rounding mode to round towards {@literal "nearest neighbor"} + * unless both neighbors are equidistant, in which case round up. + * Behaves as for {@code RoundingMode.UP} if the discarded + * fraction is ≥ 0.5; otherwise, behaves as for + * {@code RoundingMode.DOWN}. Note that this is the rounding + * mode commonly taught at school. + * + *

Example: + * + * + * + * + * + * + * + * + * + * + * + * + *
Input NumberInput rounded to one digit
with {@code HALF_UP} rounding + *
5.5 6
2.5 3
1.6 2
1.1 1
1.0 1
-1.0 -1
-1.1 -1
-1.6 -2
-2.5 -3
-5.5 -6
+ */ + HALF_UP(BigDecimal.ROUND_HALF_UP), + + /** + * Rounding mode to round towards {@literal "nearest neighbor"} + * unless both neighbors are equidistant, in which case round + * down. Behaves as for {@code RoundingMode.UP} if the discarded + * fraction is > 0.5; otherwise, behaves as for + * {@code RoundingMode.DOWN}. + * + *

Example: + * + * + * + * + * + * + * + * + * + * + * + * + *
Input NumberInput rounded to one digit
with {@code HALF_DOWN} rounding + *
5.5 5
2.5 2
1.6 2
1.1 1
1.0 1
-1.0 -1
-1.1 -1
-1.6 -2
-2.5 -2
-5.5 -5
+ */ + HALF_DOWN(BigDecimal.ROUND_HALF_DOWN), + + /** + * Rounding mode to round towards the {@literal "nearest neighbor"} + * unless both neighbors are equidistant, in which case, round + * towards the even neighbor. Behaves as for + * {@code RoundingMode.HALF_UP} if the digit to the left of the + * discarded fraction is odd; behaves as for + * {@code RoundingMode.HALF_DOWN} if it's even. Note that this + * is the rounding mode that statistically minimizes cumulative + * error when applied repeatedly over a sequence of calculations. + * It is sometimes known as {@literal "Banker's rounding,"} and is + * chiefly used in the USA. This rounding mode is analogous to + * the rounding policy used for {@code float} and {@code double} + * arithmetic in Java. + * + *

Example: + * + * + * + * + * + * + * + * + * + * + * + * + *
Input NumberInput rounded to one digit
with {@code HALF_EVEN} rounding + *
5.5 6
2.5 2
1.6 2
1.1 1
1.0 1
-1.0 -1
-1.1 -1
-1.6 -2
-2.5 -2
-5.5 -6
+ */ + HALF_EVEN(BigDecimal.ROUND_HALF_EVEN), + + /** + * Rounding mode to assert that the requested operation has an exact + * result, hence no rounding is necessary. If this rounding mode is + * specified on an operation that yields an inexact result, an + * {@code ArithmeticException} is thrown. + *

Example: + * + * + * + * + * + * + * + * + * + * + * + * + *
Input NumberInput rounded to one digit
with {@code UNNECESSARY} rounding + *
5.5 throw {@code ArithmeticException}
2.5 throw {@code ArithmeticException}
1.6 throw {@code ArithmeticException}
1.1 throw {@code ArithmeticException}
1.0 1
-1.0 -1
-1.1 throw {@code ArithmeticException}
-1.6 throw {@code ArithmeticException}
-2.5 throw {@code ArithmeticException}
-5.5 throw {@code ArithmeticException}
+ */ + UNNECESSARY(BigDecimal.ROUND_UNNECESSARY); + + // Corresponding BigDecimal rounding constant + final int oldMode; + + /** + * Constructor + * + * @param oldMode The {@code BigDecimal} constant corresponding to + * this mode + */ + private RoundingMode(int oldMode) { + this.oldMode = oldMode; + } + + /** + * Returns the {@code RoundingMode} object corresponding to a + * legacy integer rounding mode constant in {@link BigDecimal}. + * + * @param rm legacy integer rounding mode to convert + * @return {@code RoundingMode} corresponding to the given integer. + * @throws IllegalArgumentException integer is out of range + */ + public static RoundingMode valueOf(int rm) { + switch(rm) { + + case BigDecimal.ROUND_UP: + return UP; + + case BigDecimal.ROUND_DOWN: + return DOWN; + + case BigDecimal.ROUND_CEILING: + return CEILING; + + case BigDecimal.ROUND_FLOOR: + return FLOOR; + + case BigDecimal.ROUND_HALF_UP: + return HALF_UP; + + case BigDecimal.ROUND_HALF_DOWN: + return HALF_DOWN; + + case BigDecimal.ROUND_HALF_EVEN: + return HALF_EVEN; + + case BigDecimal.ROUND_UNNECESSARY: + return UNNECESSARY; + + default: + throw new IllegalArgumentException("argument out of range"); + } + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/math/SignedMutableBigInteger.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/math/SignedMutableBigInteger.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,135 @@ +/* + * Copyright (c) 1999, 2007, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.math; + +/** + * A class used to represent multiprecision integers that makes efficient + * use of allocated space by allowing a number to occupy only part of + * an array so that the arrays do not have to be reallocated as often. + * When performing an operation with many iterations the array used to + * hold a number is only increased when necessary and does not have to + * be the same size as the number it represents. A mutable number allows + * calculations to occur on the same number without having to create + * a new number for every step of the calculation as occurs with + * BigIntegers. + * + * Note that SignedMutableBigIntegers only support signed addition and + * subtraction. All other operations occur as with MutableBigIntegers. + * + * @see BigInteger + * @author Michael McCloskey + * @since 1.3 + */ + +class SignedMutableBigInteger extends MutableBigInteger { + + /** + * The sign of this MutableBigInteger. + */ + int sign = 1; + + // Constructors + + /** + * The default constructor. An empty MutableBigInteger is created with + * a one word capacity. + */ + SignedMutableBigInteger() { + super(); + } + + /** + * Construct a new MutableBigInteger with a magnitude specified by + * the int val. + */ + SignedMutableBigInteger(int val) { + super(val); + } + + /** + * Construct a new MutableBigInteger with a magnitude equal to the + * specified MutableBigInteger. + */ + SignedMutableBigInteger(MutableBigInteger val) { + super(val); + } + + // Arithmetic Operations + + /** + * Signed addition built upon unsigned add and subtract. + */ + void signedAdd(SignedMutableBigInteger addend) { + if (sign == addend.sign) + add(addend); + else + sign = sign * subtract(addend); + + } + + /** + * Signed addition built upon unsigned add and subtract. + */ + void signedAdd(MutableBigInteger addend) { + if (sign == 1) + add(addend); + else + sign = sign * subtract(addend); + + } + + /** + * Signed subtraction built upon unsigned add and subtract. + */ + void signedSubtract(SignedMutableBigInteger addend) { + if (sign == addend.sign) + sign = sign * subtract(addend); + else + add(addend); + + } + + /** + * Signed subtraction built upon unsigned add and subtract. + */ + void signedSubtract(MutableBigInteger addend) { + if (sign == 1) + sign = sign * subtract(addend); + else + add(addend); + if (intLen == 0) + sign = 1; + } + + /** + * Print out the first intLen ints of this MutableBigInteger's value + * array starting at offset. + */ + public String toString() { + return this.toBigInteger(sign).toString(); + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/math/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/math/package-info.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,45 @@ +/* + * Copyright (c) 1998, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * Provides classes for performing arbitrary-precision integer + * arithmetic ({@code BigInteger}) and arbitrary-precision decimal + * arithmetic ({@code BigDecimal}). {@code BigInteger} is analogous + * to the primitive integer types except that it provides arbitrary + * precision, hence operations on {@code BigInteger}s do not overflow + * or lose precision. In addition to standard arithmetic operations, + * {@code BigInteger} provides modular arithmetic, GCD calculation, + * primality testing, prime generation, bit manipulation, and a few + * other miscellaneous operations. + * + * {@code BigDecimal} provides arbitrary-precision signed decimal + * numbers suitable for currency calculations and the like. {@code + * BigDecimal} gives the user complete control over rounding behavior, + * allowing the user to choose from a comprehensive set of eight + * rounding modes. + * + * @since JDK1.1 + */ +package java.math; diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/net/URI.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/net/URI.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,3520 @@ +/* + * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.net; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import java.lang.Character; // for javadoc +import java.lang.NullPointerException; // for javadoc + + +/** + * Represents a Uniform Resource Identifier (URI) reference. + * + *

Aside from some minor deviations noted below, an instance of this + * class represents a URI reference as defined by + * RFC 2396: Uniform + * Resource Identifiers (URI): Generic Syntax, amended by RFC 2732: Format for + * Literal IPv6 Addresses in URLs. The Literal IPv6 address format + * also supports scope_ids. The syntax and usage of scope_ids is described + * here. + * This class provides constructors for creating URI instances from + * their components or by parsing their string forms, methods for accessing the + * various components of an instance, and methods for normalizing, resolving, + * and relativizing URI instances. Instances of this class are immutable. + * + * + *

URI syntax and components

+ * + * At the highest level a URI reference (hereinafter simply "URI") in string + * form has the syntax + * + *
+ * [scheme:]scheme-specific-part[#fragment] + *
+ * + * where square brackets [...] delineate optional components and the characters + * : and # stand for themselves. + * + *

An absolute URI specifies a scheme; a URI that is not absolute is + * said to be relative. URIs are also classified according to whether + * they are opaque or hierarchical. + * + *

An opaque URI is an absolute URI whose scheme-specific part does + * not begin with a slash character ('/'). Opaque URIs are not + * subject to further parsing. Some examples of opaque URIs are: + * + *

+ * + * + * + *
mailto:java-net@java.sun.com
news:comp.lang.java
urn:isbn:096139210x
+ * + *

A hierarchical URI is either an absolute URI whose + * scheme-specific part begins with a slash character, or a relative URI, that + * is, a URI that does not specify a scheme. Some examples of hierarchical + * URIs are: + * + *

+ * http://java.sun.com/j2se/1.3/
+ * docs/guide/collections/designfaq.html#28
+ * ../../../demo/jfc/SwingSet2/src/SwingSet2.java
+ * file:///~/calendar + *
+ * + *

A hierarchical URI is subject to further parsing according to the syntax + * + *

+ * [scheme:][//authority][path][?query][#fragment] + *
+ * + * where the characters :, /, + * ?, and # stand for themselves. The + * scheme-specific part of a hierarchical URI consists of the characters + * between the scheme and fragment components. + * + *

The authority component of a hierarchical URI is, if specified, either + * server-based or registry-based. A server-based authority + * parses according to the familiar syntax + * + *

+ * [user-info@]host[:port] + *
+ * + * where the characters @ and : stand for + * themselves. Nearly all URI schemes currently in use are server-based. An + * authority component that does not parse in this way is considered to be + * registry-based. + * + *

The path component of a hierarchical URI is itself said to be absolute + * if it begins with a slash character ('/'); otherwise it is + * relative. The path of a hierarchical URI that is either absolute or + * specifies an authority is always absolute. + * + *

All told, then, a URI instance has the following nine components: + * + *

+ * + * + * + * + * + * + * + * + * + * + *
ComponentType
schemeString
scheme-specific-part    String
authorityString
user-infoString
hostString
portint
pathString
queryString
fragmentString
+ * + * In a given instance any particular component is either undefined or + * defined with a distinct value. Undefined string components are + * represented by null, while undefined integer components are + * represented by -1. A string component may be defined to have the + * empty string as its value; this is not equivalent to that component being + * undefined. + * + *

Whether a particular component is or is not defined in an instance + * depends upon the type of the URI being represented. An absolute URI has a + * scheme component. An opaque URI has a scheme, a scheme-specific part, and + * possibly a fragment, but has no other components. A hierarchical URI always + * has a path (though it may be empty) and a scheme-specific-part (which at + * least contains the path), and may have any of the other components. If the + * authority component is present and is server-based then the host component + * will be defined and the user-information and port components may be defined. + * + * + *

Operations on URI instances

+ * + * The key operations supported by this class are those of + * normalization, resolution, and relativization. + * + *

Normalization is the process of removing unnecessary "." + * and ".." segments from the path component of a hierarchical URI. + * Each "." segment is simply removed. A ".." segment is + * removed only if it is preceded by a non-".." segment. + * Normalization has no effect upon opaque URIs. + * + *

Resolution is the process of resolving one URI against another, + * base URI. The resulting URI is constructed from components of both + * URIs in the manner specified by RFC 2396, taking components from the + * base URI for those not specified in the original. For hierarchical URIs, + * the path of the original is resolved against the path of the base and then + * normalized. The result, for example, of resolving + * + *

+ * docs/guide/collections/designfaq.html#28          (1) + *
+ * + * against the base URI http://java.sun.com/j2se/1.3/ is the result + * URI + * + *
+ * http://java.sun.com/j2se/1.3/docs/guide/collections/designfaq.html#28 + *
+ * + * Resolving the relative URI + * + *
+ * ../../../demo/jfc/SwingSet2/src/SwingSet2.java    (2) + *
+ * + * against this result yields, in turn, + * + *
+ * http://java.sun.com/j2se/1.3/demo/jfc/SwingSet2/src/SwingSet2.java + *
+ * + * Resolution of both absolute and relative URIs, and of both absolute and + * relative paths in the case of hierarchical URIs, is supported. Resolving + * the URI file:///~calendar against any other URI simply yields the + * original URI, since it is absolute. Resolving the relative URI (2) above + * against the relative base URI (1) yields the normalized, but still relative, + * URI + * + *
+ * demo/jfc/SwingSet2/src/SwingSet2.java + *
+ * + *

Relativization, finally, is the inverse of resolution: For any + * two normalized URIs u and v, + * + *

+ * u.relativize(u.resolve(v)).equals(v)  and
+ * u.resolve(u.relativize(v)).equals(v)  .
+ *
+ * + * This operation is often useful when constructing a document containing URIs + * that must be made relative to the base URI of the document wherever + * possible. For example, relativizing the URI + * + *
+ * http://java.sun.com/j2se/1.3/docs/guide/index.html + *
+ * + * against the base URI + * + *
+ * http://java.sun.com/j2se/1.3 + *
+ * + * yields the relative URI docs/guide/index.html. + * + * + *

Character categories

+ * + * RFC 2396 specifies precisely which characters are permitted in the + * various components of a URI reference. The following categories, most of + * which are taken from that specification, are used below to describe these + * constraints: + * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
alphaThe US-ASCII alphabetic characters, + * 'A' through 'Z' + * and 'a' through 'z'
digitThe US-ASCII decimal digit characters, + * '0' through '9'
alphanumAll alpha and digit characters
unreserved    All alphanum characters together with those in the string + * "_-!.~'()*"
punctThe characters in the string ",;:$&+="
reservedAll punct characters together with those in the string + * "?/[]@"
escapedEscaped octets, that is, triplets consisting of the percent + * character ('%') followed by two hexadecimal digits + * ('0'-'9', 'A'-'F', and + * 'a'-'f')
otherThe Unicode characters that are not in the US-ASCII character set, + * are not control characters (according to the {@link + * java.lang.Character#isISOControl(char) Character.isISOControl} + * method), and are not space characters (according to the {@link + * java.lang.Character#isSpaceChar(char) Character.isSpaceChar} + * method)  (Deviation from RFC 2396, which is + * limited to US-ASCII)
+ * + *

The set of all legal URI characters consists of + * the unreserved, reserved, escaped, and other + * characters. + * + * + *

Escaped octets, quotation, encoding, and decoding

+ * + * RFC 2396 allows escaped octets to appear in the user-info, path, query, and + * fragment components. Escaping serves two purposes in URIs: + * + *
    + * + *
  • To encode non-US-ASCII characters when a URI is required to + * conform strictly to RFC 2396 by not containing any other + * characters.

  • + * + *
  • To quote characters that are otherwise illegal in a + * component. The user-info, path, query, and fragment components differ + * slightly in terms of which characters are considered legal and illegal. + *

  • + * + *
+ * + * These purposes are served in this class by three related operations: + * + *
    + * + *
  • A character is encoded by replacing it + * with the sequence of escaped octets that represent that character in the + * UTF-8 character set. The Euro currency symbol ('\u20AC'), + * for example, is encoded as "%E2%82%AC". (Deviation from + * RFC 2396, which does not specify any particular character + * set.)

  • + * + *
  • An illegal character is quoted simply by + * encoding it. The space character, for example, is quoted by replacing it + * with "%20". UTF-8 contains US-ASCII, hence for US-ASCII + * characters this transformation has exactly the effect required by + * RFC 2396.

  • + * + *
  • + * A sequence of escaped octets is decoded by + * replacing it with the sequence of characters that it represents in the + * UTF-8 character set. UTF-8 contains US-ASCII, hence decoding has the + * effect of de-quoting any quoted US-ASCII characters as well as that of + * decoding any encoded non-US-ASCII characters. If a decoding error occurs + * when decoding the escaped octets then the erroneous octets are replaced by + * '\uFFFD', the Unicode replacement character.

  • + * + *
+ * + * These operations are exposed in the constructors and methods of this class + * as follows: + * + *
    + * + *
  • The {@link #URI(java.lang.String) single-argument + * constructor} requires any illegal characters in its argument to be + * quoted and preserves any escaped octets and other characters that + * are present.

  • + * + *
  • The {@link + * #URI(java.lang.String,java.lang.String,java.lang.String,int,java.lang.String,java.lang.String,java.lang.String) + * multi-argument constructors} quote illegal characters as + * required by the components in which they appear. The percent character + * ('%') is always quoted by these constructors. Any other + * characters are preserved.

  • + * + *
  • The {@link #getRawUserInfo() getRawUserInfo}, {@link #getRawPath() + * getRawPath}, {@link #getRawQuery() getRawQuery}, {@link #getRawFragment() + * getRawFragment}, {@link #getRawAuthority() getRawAuthority}, and {@link + * #getRawSchemeSpecificPart() getRawSchemeSpecificPart} methods return the + * values of their corresponding components in raw form, without interpreting + * any escaped octets. The strings returned by these methods may contain + * both escaped octets and other characters, and will not contain any + * illegal characters.

  • + * + *
  • The {@link #getUserInfo() getUserInfo}, {@link #getPath() + * getPath}, {@link #getQuery() getQuery}, {@link #getFragment() + * getFragment}, {@link #getAuthority() getAuthority}, and {@link + * #getSchemeSpecificPart() getSchemeSpecificPart} methods decode any escaped + * octets in their corresponding components. The strings returned by these + * methods may contain both other characters and illegal characters, + * and will not contain any escaped octets.

  • + * + *
  • The {@link #toString() toString} method returns a URI string with + * all necessary quotation but which may contain other characters. + *

  • + * + *
  • The {@link #toASCIIString() toASCIIString} method returns a fully + * quoted and encoded URI string that does not contain any other + * characters.

  • + * + *
+ * + * + *

Identities

+ * + * For any URI u, it is always the case that + * + *
+ * new URI(u.toString()).equals(u) . + *
+ * + * For any URI u that does not contain redundant syntax such as two + * slashes before an empty authority (as in file:///tmp/ ) or a + * colon following a host name but no port (as in + * http://java.sun.com: ), and that does not encode characters + * except those that must be quoted, the following identities also hold: + * + *
+ * new URI(u.getScheme(),
+ *         
u.getSchemeSpecificPart(),
+ *         
u.getFragment())
+ * .equals(
u) + *
+ * + * in all cases, + * + *
+ * new URI(u.getScheme(),
+ *         
u.getUserInfo(), u.getAuthority(),
+ *         
u.getPath(), u.getQuery(),
+ *         
u.getFragment())
+ * .equals(
u) + *
+ * + * if u is hierarchical, and + * + *
+ * new URI(u.getScheme(),
+ *         
u.getUserInfo(), u.getHost(), u.getPort(),
+ *         
u.getPath(), u.getQuery(),
+ *         
u.getFragment())
+ * .equals(
u) + *
+ * + * if u is hierarchical and has either no authority or a server-based + * authority. + * + * + *

URIs, URLs, and URNs

+ * + * A URI is a uniform resource identifier while a URL is a uniform + * resource locator. Hence every URL is a URI, abstractly speaking, but + * not every URI is a URL. This is because there is another subcategory of + * URIs, uniform resource names (URNs), which name resources but do not + * specify how to locate them. The mailto, news, and + * isbn URIs shown above are examples of URNs. + * + *

The conceptual distinction between URIs and URLs is reflected in the + * differences between this class and the {@link URL} class. + * + *

An instance of this class represents a URI reference in the syntactic + * sense defined by RFC 2396. A URI may be either absolute or relative. + * A URI string is parsed according to the generic syntax without regard to the + * scheme, if any, that it specifies. No lookup of the host, if any, is + * performed, and no scheme-dependent stream handler is constructed. Equality, + * hashing, and comparison are defined strictly in terms of the character + * content of the instance. In other words, a URI instance is little more than + * a structured string that supports the syntactic, scheme-independent + * operations of comparison, normalization, resolution, and relativization. + * + *

An instance of the {@link URL} class, by contrast, represents the + * syntactic components of a URL together with some of the information required + * to access the resource that it describes. A URL must be absolute, that is, + * it must always specify a scheme. A URL string is parsed according to its + * scheme. A stream handler is always established for a URL, and in fact it is + * impossible to create a URL instance for a scheme for which no handler is + * available. Equality and hashing depend upon both the scheme and the + * Internet address of the host, if any; comparison is not defined. In other + * words, a URL is a structured string that supports the syntactic operation of + * resolution as well as the network I/O operations of looking up the host and + * opening a connection to the specified resource. + * + * + * @author Mark Reinhold + * @since 1.4 + * + * @see RFC 2279: UTF-8, a + * transformation format of ISO 10646,
RFC 2373: IPv6 Addressing + * Architecture,
RFC 2396: Uniform + * Resource Identifiers (URI): Generic Syntax,
RFC 2732: Format for + * Literal IPv6 Addresses in URLs,
URISyntaxException + */ + +public final class URI + implements Comparable, Serializable +{ + + // Note: Comments containing the word "ASSERT" indicate places where a + // throw of an InternalError should be replaced by an appropriate assertion + // statement once asserts are enabled in the build. + + static final long serialVersionUID = -6052424284110960213L; + + + // -- Properties and components of this instance -- + + // Components of all URIs: [:][#] + private transient String scheme; // null ==> relative URI + private transient String fragment; + + // Hierarchical URI components: [//][?] + private transient String authority; // Registry or server + + // Server-based authority: [@][:] + private transient String userInfo; + private transient String host; // null ==> registry-based + private transient int port = -1; // -1 ==> undefined + + // Remaining components of hierarchical URIs + private transient String path; // null ==> opaque + private transient String query; + + // The remaining fields may be computed on demand + + private volatile transient String schemeSpecificPart; + private volatile transient int hash; // Zero ==> undefined + + private volatile transient String decodedUserInfo = null; + private volatile transient String decodedAuthority = null; + private volatile transient String decodedPath = null; + private volatile transient String decodedQuery = null; + private volatile transient String decodedFragment = null; + private volatile transient String decodedSchemeSpecificPart = null; + + /** + * The string form of this URI. + * + * @serial + */ + private volatile String string; // The only serializable field + + + + // -- Constructors and factories -- + + private URI() { } // Used internally + + /** + * Constructs a URI by parsing the given string. + * + *

This constructor parses the given string exactly as specified by the + * grammar in RFC 2396, + * Appendix A, except for the following deviations:

+ * + *
    + * + *
  • An empty authority component is permitted as long as it is + * followed by a non-empty path, a query component, or a fragment + * component. This allows the parsing of URIs such as + * "file:///foo/bar", which seems to be the intent of + * RFC 2396 although the grammar does not permit it. If the + * authority component is empty then the user-information, host, and port + * components are undefined.

  • + * + *
  • Empty relative paths are permitted; this seems to be the + * intent of RFC 2396 although the grammar does not permit it. The + * primary consequence of this deviation is that a standalone fragment + * such as "#foo" parses as a relative URI with an empty path + * and the given fragment, and can be usefully resolved against a base URI. + * + *

  • IPv4 addresses in host components are parsed rigorously, as + * specified by RFC 2732: Each + * element of a dotted-quad address must contain no more than three + * decimal digits. Each element is further constrained to have a value + * no greater than 255.

  • + * + *
  • Hostnames in host components that comprise only a single + * domain label are permitted to start with an alphanum + * character. This seems to be the intent of RFC 2396 + * section 3.2.2 although the grammar does not permit it. The + * consequence of this deviation is that the authority component of a + * hierarchical URI such as s://123, will parse as a server-based + * authority.

  • + * + *
  • IPv6 addresses are permitted for the host component. An IPv6 + * address must be enclosed in square brackets ('[' and + * ']') as specified by RFC 2732. The + * IPv6 address itself must parse according to RFC 2373. IPv6 + * addresses are further constrained to describe no more than sixteen + * bytes of address information, a constraint implicit in RFC 2373 + * but not expressible in the grammar.

  • + * + *
  • Characters in the other category are permitted wherever + * RFC 2396 permits escaped octets, that is, in the + * user-information, path, query, and fragment components, as well as in + * the authority component if the authority is registry-based. This + * allows URIs to contain Unicode characters beyond those in the US-ASCII + * character set.

  • + * + *
+ * + * @param str The string to be parsed into a URI + * + * @throws NullPointerException + * If str is null + * + * @throws URISyntaxException + * If the given string violates RFC 2396, as augmented + * by the above deviations + */ + public URI(String str) throws URISyntaxException { + new Parser(str).parse(false); + } + + /** + * Constructs a hierarchical URI from the given components. + * + *

If a scheme is given then the path, if also given, must either be + * empty or begin with a slash character ('/'). Otherwise a + * component of the new URI may be left undefined by passing null + * for the corresponding parameter or, in the case of the port + * parameter, by passing -1. + * + *

This constructor first builds a URI string from the given components + * according to the rules specified in RFC 2396, + * section 5.2, step 7:

+ * + *
    + * + *
  1. Initially, the result string is empty.

  2. + * + *
  3. If a scheme is given then it is appended to the result, + * followed by a colon character (':').

  4. + * + *
  5. If user information, a host, or a port are given then the + * string "//" is appended.

  6. + * + *
  7. If user information is given then it is appended, followed by + * a commercial-at character ('@'). Any character not in the + * unreserved, punct, escaped, or other + * categories is quoted.

  8. + * + *
  9. If a host is given then it is appended. If the host is a + * literal IPv6 address but is not enclosed in square brackets + * ('[' and ']') then the square brackets are added. + *

  10. + * + *
  11. If a port number is given then a colon character + * (':') is appended, followed by the port number in decimal. + *

  12. + * + *
  13. If a path is given then it is appended. Any character not in + * the unreserved, punct, escaped, or other + * categories, and not equal to the slash character ('/') or the + * commercial-at character ('@'), is quoted.

  14. + * + *
  15. If a query is given then a question-mark character + * ('?') is appended, followed by the query. Any character that + * is not a legal URI character is quoted. + *

  16. + * + *
  17. Finally, if a fragment is given then a hash character + * ('#') is appended, followed by the fragment. Any character + * that is not a legal URI character is quoted.

  18. + * + *
+ * + *

The resulting URI string is then parsed as if by invoking the {@link + * #URI(String)} constructor and then invoking the {@link + * #parseServerAuthority()} method upon the result; this may cause a {@link + * URISyntaxException} to be thrown.

+ * + * @param scheme Scheme name + * @param userInfo User name and authorization information + * @param host Host name + * @param port Port number + * @param path Path + * @param query Query + * @param fragment Fragment + * + * @throws URISyntaxException + * If both a scheme and a path are given but the path is relative, + * if the URI string constructed from the given components violates + * RFC 2396, or if the authority component of the string is + * present but cannot be parsed as a server-based authority + */ + public URI(String scheme, + String userInfo, String host, int port, + String path, String query, String fragment) + throws URISyntaxException + { + String s = toString(scheme, null, + null, userInfo, host, port, + path, query, fragment); + checkPath(s, scheme, path); + new Parser(s).parse(true); + } + + /** + * Constructs a hierarchical URI from the given components. + * + *

If a scheme is given then the path, if also given, must either be + * empty or begin with a slash character ('/'). Otherwise a + * component of the new URI may be left undefined by passing null + * for the corresponding parameter. + * + *

This constructor first builds a URI string from the given components + * according to the rules specified in RFC 2396, + * section 5.2, step 7:

+ * + *
    + * + *
  1. Initially, the result string is empty.

  2. + * + *
  3. If a scheme is given then it is appended to the result, + * followed by a colon character (':').

  4. + * + *
  5. If an authority is given then the string "//" is + * appended, followed by the authority. If the authority contains a + * literal IPv6 address then the address must be enclosed in square + * brackets ('[' and ']'). Any character not in the + * unreserved, punct, escaped, or other + * categories, and not equal to the commercial-at character + * ('@'), is quoted.

  6. + * + *
  7. If a path is given then it is appended. Any character not in + * the unreserved, punct, escaped, or other + * categories, and not equal to the slash character ('/') or the + * commercial-at character ('@'), is quoted.

  8. + * + *
  9. If a query is given then a question-mark character + * ('?') is appended, followed by the query. Any character that + * is not a legal URI character is quoted. + *

  10. + * + *
  11. Finally, if a fragment is given then a hash character + * ('#') is appended, followed by the fragment. Any character + * that is not a legal URI character is quoted.

  12. + * + *
+ * + *

The resulting URI string is then parsed as if by invoking the {@link + * #URI(String)} constructor and then invoking the {@link + * #parseServerAuthority()} method upon the result; this may cause a {@link + * URISyntaxException} to be thrown.

+ * + * @param scheme Scheme name + * @param authority Authority + * @param path Path + * @param query Query + * @param fragment Fragment + * + * @throws URISyntaxException + * If both a scheme and a path are given but the path is relative, + * if the URI string constructed from the given components violates + * RFC 2396, or if the authority component of the string is + * present but cannot be parsed as a server-based authority + */ + public URI(String scheme, + String authority, + String path, String query, String fragment) + throws URISyntaxException + { + String s = toString(scheme, null, + authority, null, null, -1, + path, query, fragment); + checkPath(s, scheme, path); + new Parser(s).parse(false); + } + + /** + * Constructs a hierarchical URI from the given components. + * + *

A component may be left undefined by passing null. + * + *

This convenience constructor works as if by invoking the + * seven-argument constructor as follows: + * + *

+ * new {@link #URI(String, String, String, int, String, String, String) + * URI}(scheme, null, host, -1, path, null, fragment); + *
+ * + * @param scheme Scheme name + * @param host Host name + * @param path Path + * @param fragment Fragment + * + * @throws URISyntaxException + * If the URI string constructed from the given components + * violates RFC 2396 + */ + public URI(String scheme, String host, String path, String fragment) + throws URISyntaxException + { + this(scheme, null, host, -1, path, null, fragment); + } + + /** + * Constructs a URI from the given components. + * + *

A component may be left undefined by passing null. + * + *

This constructor first builds a URI in string form using the given + * components as follows:

+ * + *
    + * + *
  1. Initially, the result string is empty.

  2. + * + *
  3. If a scheme is given then it is appended to the result, + * followed by a colon character (':').

  4. + * + *
  5. If a scheme-specific part is given then it is appended. Any + * character that is not a legal URI character + * is quoted.

  6. + * + *
  7. Finally, if a fragment is given then a hash character + * ('#') is appended to the string, followed by the fragment. + * Any character that is not a legal URI character is quoted.

  8. + * + *
+ * + *

The resulting URI string is then parsed in order to create the new + * URI instance as if by invoking the {@link #URI(String)} constructor; + * this may cause a {@link URISyntaxException} to be thrown.

+ * + * @param scheme Scheme name + * @param ssp Scheme-specific part + * @param fragment Fragment + * + * @throws URISyntaxException + * If the URI string constructed from the given components + * violates RFC 2396 + */ + public URI(String scheme, String ssp, String fragment) + throws URISyntaxException + { + new Parser(toString(scheme, ssp, + null, null, null, -1, + null, null, fragment)) + .parse(false); + } + + /** + * Creates a URI by parsing the given string. + * + *

This convenience factory method works as if by invoking the {@link + * #URI(String)} constructor; any {@link URISyntaxException} thrown by the + * constructor is caught and wrapped in a new {@link + * IllegalArgumentException} object, which is then thrown. + * + *

This method is provided for use in situations where it is known that + * the given string is a legal URI, for example for URI constants declared + * within in a program, and so it would be considered a programming error + * for the string not to parse as such. The constructors, which throw + * {@link URISyntaxException} directly, should be used situations where a + * URI is being constructed from user input or from some other source that + * may be prone to errors.

+ * + * @param str The string to be parsed into a URI + * @return The new URI + * + * @throws NullPointerException + * If str is null + * + * @throws IllegalArgumentException + * If the given string violates RFC 2396 + */ + public static URI create(String str) { + try { + return new URI(str); + } catch (URISyntaxException x) { + throw new IllegalArgumentException(x.getMessage(), x); + } + } + + + // -- Operations -- + + /** + * Attempts to parse this URI's authority component, if defined, into + * user-information, host, and port components. + * + *

If this URI's authority component has already been recognized as + * being server-based then it will already have been parsed into + * user-information, host, and port components. In this case, or if this + * URI has no authority component, this method simply returns this URI. + * + *

Otherwise this method attempts once more to parse the authority + * component into user-information, host, and port components, and throws + * an exception describing why the authority component could not be parsed + * in that way. + * + *

This method is provided because the generic URI syntax specified in + * RFC 2396 + * cannot always distinguish a malformed server-based authority from a + * legitimate registry-based authority. It must therefore treat some + * instances of the former as instances of the latter. The authority + * component in the URI string "//foo:bar", for example, is not a + * legal server-based authority but it is legal as a registry-based + * authority. + * + *

In many common situations, for example when working URIs that are + * known to be either URNs or URLs, the hierarchical URIs being used will + * always be server-based. They therefore must either be parsed as such or + * treated as an error. In these cases a statement such as + * + *

+ * URI u = new URI(str).parseServerAuthority(); + *
+ * + *

can be used to ensure that u always refers to a URI that, if + * it has an authority component, has a server-based authority with proper + * user-information, host, and port components. Invoking this method also + * ensures that if the authority could not be parsed in that way then an + * appropriate diagnostic message can be issued based upon the exception + * that is thrown.

+ * + * @return A URI whose authority field has been parsed + * as a server-based authority + * + * @throws URISyntaxException + * If the authority component of this URI is defined + * but cannot be parsed as a server-based authority + * according to RFC 2396 + */ + public URI parseServerAuthority() + throws URISyntaxException + { + // We could be clever and cache the error message and index from the + // exception thrown during the original parse, but that would require + // either more fields or a more-obscure representation. + if ((host != null) || (authority == null)) + return this; + defineString(); + new Parser(string).parse(true); + return this; + } + + /** + * Normalizes this URI's path. + * + *

If this URI is opaque, or if its path is already in normal form, + * then this URI is returned. Otherwise a new URI is constructed that is + * identical to this URI except that its path is computed by normalizing + * this URI's path in a manner consistent with RFC 2396, + * section 5.2, step 6, sub-steps c through f; that is: + *

+ * + *
    + * + *
  1. All "." segments are removed.

  2. + * + *
  3. If a ".." segment is preceded by a non-".." + * segment then both of these segments are removed. This step is + * repeated until it is no longer applicable.

  4. + * + *
  5. If the path is relative, and if its first segment contains a + * colon character (':'), then a "." segment is + * prepended. This prevents a relative URI with a path such as + * "a:b/c/d" from later being re-parsed as an opaque URI with a + * scheme of "a" and a scheme-specific part of "b/c/d". + * (Deviation from RFC 2396)

  6. + * + *
+ * + *

A normalized path will begin with one or more ".." segments + * if there were insufficient non-".." segments preceding them to + * allow their removal. A normalized path will begin with a "." + * segment if one was inserted by step 3 above. Otherwise, a normalized + * path will not contain any "." or ".." segments.

+ * + * @return A URI equivalent to this URI, + * but whose path is in normal form + */ + public URI normalize() { + return normalize(this); + } + + /** + * Resolves the given URI against this URI. + * + *

If the given URI is already absolute, or if this URI is opaque, then + * the given URI is returned. + * + *

If the given URI's fragment component is + * defined, its path component is empty, and its scheme, authority, and + * query components are undefined, then a URI with the given fragment but + * with all other components equal to those of this URI is returned. This + * allows a URI representing a standalone fragment reference, such as + * "#foo", to be usefully resolved against a base URI. + * + *

Otherwise this method constructs a new hierarchical URI in a manner + * consistent with RFC 2396, + * section 5.2; that is:

+ * + *
    + * + *
  1. A new URI is constructed with this URI's scheme and the given + * URI's query and fragment components.

  2. + * + *
  3. If the given URI has an authority component then the new URI's + * authority and path are taken from the given URI.

  4. + * + *
  5. Otherwise the new URI's authority component is copied from + * this URI, and its path is computed as follows:

    + * + *
      + * + *
    1. If the given URI's path is absolute then the new URI's path + * is taken from the given URI.

    2. + * + *
    3. Otherwise the given URI's path is relative, and so the new + * URI's path is computed by resolving the path of the given URI + * against the path of this URI. This is done by concatenating all but + * the last segment of this URI's path, if any, with the given URI's + * path and then normalizing the result as if by invoking the {@link + * #normalize() normalize} method.

    4. + * + *
  6. + * + *
+ * + *

The result of this method is absolute if, and only if, either this + * URI is absolute or the given URI is absolute.

+ * + * @param uri The URI to be resolved against this URI + * @return The resulting URI + * + * @throws NullPointerException + * If uri is null + */ + public URI resolve(URI uri) { + return resolve(this, uri); + } + + /** + * Constructs a new URI by parsing the given string and then resolving it + * against this URI. + * + *

This convenience method works as if invoking it were equivalent to + * evaluating the expression {@link #resolve(java.net.URI) + * resolve}(URI.{@link #create(String) create}(str)).

+ * + * @param str The string to be parsed into a URI + * @return The resulting URI + * + * @throws NullPointerException + * If str is null + * + * @throws IllegalArgumentException + * If the given string violates RFC 2396 + */ + public URI resolve(String str) { + return resolve(URI.create(str)); + } + + /** + * Relativizes the given URI against this URI. + * + *

The relativization of the given URI against this URI is computed as + * follows:

+ * + *
    + * + *
  1. If either this URI or the given URI are opaque, or if the + * scheme and authority components of the two URIs are not identical, or + * if the path of this URI is not a prefix of the path of the given URI, + * then the given URI is returned.

  2. + * + *
  3. Otherwise a new relative hierarchical URI is constructed with + * query and fragment components taken from the given URI and with a path + * component computed by removing this URI's path from the beginning of + * the given URI's path.

  4. + * + *
+ * + * @param uri The URI to be relativized against this URI + * @return The resulting URI + * + * @throws NullPointerException + * If uri is null + */ + public URI relativize(URI uri) { + return relativize(this, uri); + } + + /** + * Constructs a URL from this URI. + * + *

This convenience method works as if invoking it were equivalent to + * evaluating the expression new URL(this.toString()) after + * first checking that this URI is absolute.

+ * + * @return A URL constructed from this URI + * + * @throws IllegalArgumentException + * If this URL is not absolute + * + * @throws MalformedURLException + * If a protocol handler for the URL could not be found, + * or if some other error occurred while constructing the URL + */ + public URL toURL() + throws MalformedURLException { + if (!isAbsolute()) + throw new IllegalArgumentException("URI is not absolute"); + return new URL(toString()); + } + + // -- Component access methods -- + + /** + * Returns the scheme component of this URI. + * + *

The scheme component of a URI, if defined, only contains characters + * in the alphanum category and in the string "-.+". A + * scheme always starts with an alpha character.

+ * + * The scheme component of a URI cannot contain escaped octets, hence this + * method does not perform any decoding. + * + * @return The scheme component of this URI, + * or null if the scheme is undefined + */ + public String getScheme() { + return scheme; + } + + /** + * Tells whether or not this URI is absolute. + * + *

A URI is absolute if, and only if, it has a scheme component.

+ * + * @return true if, and only if, this URI is absolute + */ + public boolean isAbsolute() { + return scheme != null; + } + + /** + * Tells whether or not this URI is opaque. + * + *

A URI is opaque if, and only if, it is absolute and its + * scheme-specific part does not begin with a slash character ('/'). + * An opaque URI has a scheme, a scheme-specific part, and possibly + * a fragment; all other components are undefined.

+ * + * @return true if, and only if, this URI is opaque + */ + public boolean isOpaque() { + return path == null; + } + + /** + * Returns the raw scheme-specific part of this URI. The scheme-specific + * part is never undefined, though it may be empty. + * + *

The scheme-specific part of a URI only contains legal URI + * characters.

+ * + * @return The raw scheme-specific part of this URI + * (never null) + */ + public String getRawSchemeSpecificPart() { + defineSchemeSpecificPart(); + return schemeSpecificPart; + } + + /** + * Returns the decoded scheme-specific part of this URI. + * + *

The string returned by this method is equal to that returned by the + * {@link #getRawSchemeSpecificPart() getRawSchemeSpecificPart} method + * except that all sequences of escaped octets are decoded.

+ * + * @return The decoded scheme-specific part of this URI + * (never null) + */ + public String getSchemeSpecificPart() { + if (decodedSchemeSpecificPart == null) + decodedSchemeSpecificPart = decode(getRawSchemeSpecificPart()); + return decodedSchemeSpecificPart; + } + + /** + * Returns the raw authority component of this URI. + * + *

The authority component of a URI, if defined, only contains the + * commercial-at character ('@') and characters in the + * unreserved, punct, escaped, and other + * categories. If the authority is server-based then it is further + * constrained to have valid user-information, host, and port + * components.

+ * + * @return The raw authority component of this URI, + * or null if the authority is undefined + */ + public String getRawAuthority() { + return authority; + } + + /** + * Returns the decoded authority component of this URI. + * + *

The string returned by this method is equal to that returned by the + * {@link #getRawAuthority() getRawAuthority} method except that all + * sequences of escaped octets are decoded.

+ * + * @return The decoded authority component of this URI, + * or null if the authority is undefined + */ + public String getAuthority() { + if (decodedAuthority == null) + decodedAuthority = decode(authority); + return decodedAuthority; + } + + /** + * Returns the raw user-information component of this URI. + * + *

The user-information component of a URI, if defined, only contains + * characters in the unreserved, punct, escaped, and + * other categories.

+ * + * @return The raw user-information component of this URI, + * or null if the user information is undefined + */ + public String getRawUserInfo() { + return userInfo; + } + + /** + * Returns the decoded user-information component of this URI. + * + *

The string returned by this method is equal to that returned by the + * {@link #getRawUserInfo() getRawUserInfo} method except that all + * sequences of escaped octets are decoded.

+ * + * @return The decoded user-information component of this URI, + * or null if the user information is undefined + */ + public String getUserInfo() { + if ((decodedUserInfo == null) && (userInfo != null)) + decodedUserInfo = decode(userInfo); + return decodedUserInfo; + } + + /** + * Returns the host component of this URI. + * + *

The host component of a URI, if defined, will have one of the + * following forms:

+ * + *
    + * + *
  • A domain name consisting of one or more labels + * separated by period characters ('.'), optionally followed by + * a period character. Each label consists of alphanum characters + * as well as hyphen characters ('-'), though hyphens never + * occur as the first or last characters in a label. The rightmost + * label of a domain name consisting of two or more labels, begins + * with an alpha character.

  • + * + *
  • A dotted-quad IPv4 address of the form + * digit+.digit+.digit+.digit+, + * where no digit sequence is longer than three characters and no + * sequence has a value larger than 255.

  • + * + *
  • An IPv6 address enclosed in square brackets ('[' and + * ']') and consisting of hexadecimal digits, colon characters + * (':'), and possibly an embedded IPv4 address. The full + * syntax of IPv6 addresses is specified in RFC 2373: IPv6 + * Addressing Architecture.

  • + * + *
+ * + * The host component of a URI cannot contain escaped octets, hence this + * method does not perform any decoding. + * + * @return The host component of this URI, + * or null if the host is undefined + */ + public String getHost() { + return host; + } + + /** + * Returns the port number of this URI. + * + *

The port component of a URI, if defined, is a non-negative + * integer.

+ * + * @return The port component of this URI, + * or -1 if the port is undefined + */ + public int getPort() { + return port; + } + + /** + * Returns the raw path component of this URI. + * + *

The path component of a URI, if defined, only contains the slash + * character ('/'), the commercial-at character ('@'), + * and characters in the unreserved, punct, escaped, + * and other categories.

+ * + * @return The path component of this URI, + * or null if the path is undefined + */ + public String getRawPath() { + return path; + } + + /** + * Returns the decoded path component of this URI. + * + *

The string returned by this method is equal to that returned by the + * {@link #getRawPath() getRawPath} method except that all sequences of + * escaped octets are decoded.

+ * + * @return The decoded path component of this URI, + * or null if the path is undefined + */ + public String getPath() { + if ((decodedPath == null) && (path != null)) + decodedPath = decode(path); + return decodedPath; + } + + /** + * Returns the raw query component of this URI. + * + *

The query component of a URI, if defined, only contains legal URI + * characters.

+ * + * @return The raw query component of this URI, + * or null if the query is undefined + */ + public String getRawQuery() { + return query; + } + + /** + * Returns the decoded query component of this URI. + * + *

The string returned by this method is equal to that returned by the + * {@link #getRawQuery() getRawQuery} method except that all sequences of + * escaped octets are decoded.

+ * + * @return The decoded query component of this URI, + * or null if the query is undefined + */ + public String getQuery() { + if ((decodedQuery == null) && (query != null)) + decodedQuery = decode(query); + return decodedQuery; + } + + /** + * Returns the raw fragment component of this URI. + * + *

The fragment component of a URI, if defined, only contains legal URI + * characters.

+ * + * @return The raw fragment component of this URI, + * or null if the fragment is undefined + */ + public String getRawFragment() { + return fragment; + } + + /** + * Returns the decoded fragment component of this URI. + * + *

The string returned by this method is equal to that returned by the + * {@link #getRawFragment() getRawFragment} method except that all + * sequences of escaped octets are decoded.

+ * + * @return The decoded fragment component of this URI, + * or null if the fragment is undefined + */ + public String getFragment() { + if ((decodedFragment == null) && (fragment != null)) + decodedFragment = decode(fragment); + return decodedFragment; + } + + + // -- Equality, comparison, hash code, toString, and serialization -- + + /** + * Tests this URI for equality with another object. + * + *

If the given object is not a URI then this method immediately + * returns false. + * + *

For two URIs to be considered equal requires that either both are + * opaque or both are hierarchical. Their schemes must either both be + * undefined or else be equal without regard to case. Their fragments + * must either both be undefined or else be equal. + * + *

For two opaque URIs to be considered equal, their scheme-specific + * parts must be equal. + * + *

For two hierarchical URIs to be considered equal, their paths must + * be equal and their queries must either both be undefined or else be + * equal. Their authorities must either both be undefined, or both be + * registry-based, or both be server-based. If their authorities are + * defined and are registry-based, then they must be equal. If their + * authorities are defined and are server-based, then their hosts must be + * equal without regard to case, their port numbers must be equal, and + * their user-information components must be equal. + * + *

When testing the user-information, path, query, fragment, authority, + * or scheme-specific parts of two URIs for equality, the raw forms rather + * than the encoded forms of these components are compared and the + * hexadecimal digits of escaped octets are compared without regard to + * case. + * + *

This method satisfies the general contract of the {@link + * java.lang.Object#equals(Object) Object.equals} method.

+ * + * @param ob The object to which this object is to be compared + * + * @return true if, and only if, the given object is a URI that + * is identical to this URI + */ + public boolean equals(Object ob) { + if (ob == this) + return true; + if (!(ob instanceof URI)) + return false; + URI that = (URI)ob; + if (this.isOpaque() != that.isOpaque()) return false; + if (!equalIgnoringCase(this.scheme, that.scheme)) return false; + if (!equal(this.fragment, that.fragment)) return false; + + // Opaque + if (this.isOpaque()) + return equal(this.schemeSpecificPart, that.schemeSpecificPart); + + // Hierarchical + if (!equal(this.path, that.path)) return false; + if (!equal(this.query, that.query)) return false; + + // Authorities + if (this.authority == that.authority) return true; + if (this.host != null) { + // Server-based + if (!equal(this.userInfo, that.userInfo)) return false; + if (!equalIgnoringCase(this.host, that.host)) return false; + if (this.port != that.port) return false; + } else if (this.authority != null) { + // Registry-based + if (!equal(this.authority, that.authority)) return false; + } else if (this.authority != that.authority) { + return false; + } + + return true; + } + + /** + * Returns a hash-code value for this URI. The hash code is based upon all + * of the URI's components, and satisfies the general contract of the + * {@link java.lang.Object#hashCode() Object.hashCode} method. + * + * @return A hash-code value for this URI + */ + public int hashCode() { + if (hash != 0) + return hash; + int h = hashIgnoringCase(0, scheme); + h = hash(h, fragment); + if (isOpaque()) { + h = hash(h, schemeSpecificPart); + } else { + h = hash(h, path); + h = hash(h, query); + if (host != null) { + h = hash(h, userInfo); + h = hashIgnoringCase(h, host); + h += 1949 * port; + } else { + h = hash(h, authority); + } + } + hash = h; + return h; + } + + /** + * Compares this URI to another object, which must be a URI. + * + *

When comparing corresponding components of two URIs, if one + * component is undefined but the other is defined then the first is + * considered to be less than the second. Unless otherwise noted, string + * components are ordered according to their natural, case-sensitive + * ordering as defined by the {@link java.lang.String#compareTo(Object) + * String.compareTo} method. String components that are subject to + * encoding are compared by comparing their raw forms rather than their + * encoded forms. + * + *

The ordering of URIs is defined as follows:

+ * + *
    + * + *
  • Two URIs with different schemes are ordered according the + * ordering of their schemes, without regard to case.

  • + * + *
  • A hierarchical URI is considered to be less than an opaque URI + * with an identical scheme.

  • + * + *
  • Two opaque URIs with identical schemes are ordered according + * to the ordering of their scheme-specific parts.

  • + * + *
  • Two opaque URIs with identical schemes and scheme-specific + * parts are ordered according to the ordering of their + * fragments.

  • + * + *
  • Two hierarchical URIs with identical schemes are ordered + * according to the ordering of their authority components:

    + * + *
      + * + *
    • If both authority components are server-based then the URIs + * are ordered according to their user-information components; if these + * components are identical then the URIs are ordered according to the + * ordering of their hosts, without regard to case; if the hosts are + * identical then the URIs are ordered according to the ordering of + * their ports.

    • + * + *
    • If one or both authority components are registry-based then + * the URIs are ordered according to the ordering of their authority + * components.

    • + * + *
  • + * + *
  • Finally, two hierarchical URIs with identical schemes and + * authority components are ordered according to the ordering of their + * paths; if their paths are identical then they are ordered according to + * the ordering of their queries; if the queries are identical then they + * are ordered according to the order of their fragments.

  • + * + *
+ * + *

This method satisfies the general contract of the {@link + * java.lang.Comparable#compareTo(Object) Comparable.compareTo} + * method.

+ * + * @param that + * The object to which this URI is to be compared + * + * @return A negative integer, zero, or a positive integer as this URI is + * less than, equal to, or greater than the given URI + * + * @throws ClassCastException + * If the given object is not a URI + */ + public int compareTo(URI that) { + int c; + + if ((c = compareIgnoringCase(this.scheme, that.scheme)) != 0) + return c; + + if (this.isOpaque()) { + if (that.isOpaque()) { + // Both opaque + if ((c = compare(this.schemeSpecificPart, + that.schemeSpecificPart)) != 0) + return c; + return compare(this.fragment, that.fragment); + } + return +1; // Opaque > hierarchical + } else if (that.isOpaque()) { + return -1; // Hierarchical < opaque + } + + // Hierarchical + if ((this.host != null) && (that.host != null)) { + // Both server-based + if ((c = compare(this.userInfo, that.userInfo)) != 0) + return c; + if ((c = compareIgnoringCase(this.host, that.host)) != 0) + return c; + if ((c = this.port - that.port) != 0) + return c; + } else { + // If one or both authorities are registry-based then we simply + // compare them in the usual, case-sensitive way. If one is + // registry-based and one is server-based then the strings are + // guaranteed to be unequal, hence the comparison will never return + // zero and the compareTo and equals methods will remain + // consistent. + if ((c = compare(this.authority, that.authority)) != 0) return c; + } + + if ((c = compare(this.path, that.path)) != 0) return c; + if ((c = compare(this.query, that.query)) != 0) return c; + return compare(this.fragment, that.fragment); + } + + /** + * Returns the content of this URI as a string. + * + *

If this URI was created by invoking one of the constructors in this + * class then a string equivalent to the original input string, or to the + * string computed from the originally-given components, as appropriate, is + * returned. Otherwise this URI was created by normalization, resolution, + * or relativization, and so a string is constructed from this URI's + * components according to the rules specified in RFC 2396, + * section 5.2, step 7.

+ * + * @return The string form of this URI + */ + public String toString() { + defineString(); + return string; + } + + /** + * Returns the content of this URI as a US-ASCII string. + * + *

If this URI does not contain any characters in the other + * category then an invocation of this method will return the same value as + * an invocation of the {@link #toString() toString} method. Otherwise + * this method works as if by invoking that method and then encoding the result.

+ * + * @return The string form of this URI, encoded as needed + * so that it only contains characters in the US-ASCII + * charset + */ + public String toASCIIString() { + defineString(); + return encode(string); + } + + + // -- Serialization support -- + + /** + * Saves the content of this URI to the given serial stream. + * + *

The only serializable field of a URI instance is its string + * field. That field is given a value, if it does not have one already, + * and then the {@link java.io.ObjectOutputStream#defaultWriteObject()} + * method of the given object-output stream is invoked.

+ * + * @param os The object-output stream to which this object + * is to be written + */ + private void writeObject(ObjectOutputStream os) + throws IOException + { + defineString(); + os.defaultWriteObject(); // Writes the string field only + } + + /** + * Reconstitutes a URI from the given serial stream. + * + *

The {@link java.io.ObjectInputStream#defaultReadObject()} method is + * invoked to read the value of the string field. The result is + * then parsed in the usual way. + * + * @param is The object-input stream from which this object + * is being read + */ + private void readObject(ObjectInputStream is) + throws ClassNotFoundException, IOException + { + port = -1; // Argh + is.defaultReadObject(); + try { + new Parser(string).parse(false); + } catch (URISyntaxException x) { + IOException y = new InvalidObjectException("Invalid URI"); + y.initCause(x); + throw y; + } + } + + + // -- End of public methods -- + + + // -- Utility methods for string-field comparison and hashing -- + + // These methods return appropriate values for null string arguments, + // thereby simplifying the equals, hashCode, and compareTo methods. + // + // The case-ignoring methods should only be applied to strings whose + // characters are all known to be US-ASCII. Because of this restriction, + // these methods are faster than the similar methods in the String class. + + // US-ASCII only + private static int toLower(char c) { + if ((c >= 'A') && (c <= 'Z')) + return c + ('a' - 'A'); + return c; + } + + private static boolean equal(String s, String t) { + if (s == t) return true; + if ((s != null) && (t != null)) { + if (s.length() != t.length()) + return false; + if (s.indexOf('%') < 0) + return s.equals(t); + int n = s.length(); + for (int i = 0; i < n;) { + char c = s.charAt(i); + char d = t.charAt(i); + if (c != '%') { + if (c != d) + return false; + i++; + continue; + } + i++; + if (toLower(s.charAt(i)) != toLower(t.charAt(i))) + return false; + i++; + if (toLower(s.charAt(i)) != toLower(t.charAt(i))) + return false; + i++; + } + return true; + } + return false; + } + + // US-ASCII only + private static boolean equalIgnoringCase(String s, String t) { + if (s == t) return true; + if ((s != null) && (t != null)) { + int n = s.length(); + if (t.length() != n) + return false; + for (int i = 0; i < n; i++) { + if (toLower(s.charAt(i)) != toLower(t.charAt(i))) + return false; + } + return true; + } + return false; + } + + private static int hash(int hash, String s) { + if (s == null) return hash; + return hash * 127 + s.hashCode(); + } + + // US-ASCII only + private static int hashIgnoringCase(int hash, String s) { + if (s == null) return hash; + int h = hash; + int n = s.length(); + for (int i = 0; i < n; i++) + h = 31 * h + toLower(s.charAt(i)); + return h; + } + + private static int compare(String s, String t) { + if (s == t) return 0; + if (s != null) { + if (t != null) + return s.compareTo(t); + else + return +1; + } else { + return -1; + } + } + + // US-ASCII only + private static int compareIgnoringCase(String s, String t) { + if (s == t) return 0; + if (s != null) { + if (t != null) { + int sn = s.length(); + int tn = t.length(); + int n = sn < tn ? sn : tn; + for (int i = 0; i < n; i++) { + int c = toLower(s.charAt(i)) - toLower(t.charAt(i)); + if (c != 0) + return c; + } + return sn - tn; + } + return +1; + } else { + return -1; + } + } + + + // -- String construction -- + + // If a scheme is given then the path, if given, must be absolute + // + private static void checkPath(String s, String scheme, String path) + throws URISyntaxException + { + if (scheme != null) { + if ((path != null) + && ((path.length() > 0) && (path.charAt(0) != '/'))) + throw new URISyntaxException(s, + "Relative path in absolute URI"); + } + } + + private void appendAuthority(StringBuffer sb, + String authority, + String userInfo, + String host, + int port) + { + if (host != null) { + sb.append("//"); + if (userInfo != null) { + sb.append(quote(userInfo, L_USERINFO, H_USERINFO)); + sb.append('@'); + } + boolean needBrackets = ((host.indexOf(':') >= 0) + && !host.startsWith("[") + && !host.endsWith("]")); + if (needBrackets) sb.append('['); + sb.append(host); + if (needBrackets) sb.append(']'); + if (port != -1) { + sb.append(':'); + sb.append(port); + } + } else if (authority != null) { + sb.append("//"); + if (authority.startsWith("[")) { + // authority should (but may not) contain an embedded IPv6 address + int end = authority.indexOf("]"); + String doquote = authority, dontquote = ""; + if (end != -1 && authority.indexOf(":") != -1) { + // the authority contains an IPv6 address + if (end == authority.length()) { + dontquote = authority; + doquote = ""; + } else { + dontquote = authority.substring(0 , end + 1); + doquote = authority.substring(end + 1); + } + } + sb.append(dontquote); + sb.append(quote(doquote, + L_REG_NAME | L_SERVER, + H_REG_NAME | H_SERVER)); + } else { + sb.append(quote(authority, + L_REG_NAME | L_SERVER, + H_REG_NAME | H_SERVER)); + } + } + } + + private void appendSchemeSpecificPart(StringBuffer sb, + String opaquePart, + String authority, + String userInfo, + String host, + int port, + String path, + String query) + { + if (opaquePart != null) { + /* check if SSP begins with an IPv6 address + * because we must not quote a literal IPv6 address + */ + if (opaquePart.startsWith("//[")) { + int end = opaquePart.indexOf("]"); + if (end != -1 && opaquePart.indexOf(":")!=-1) { + String doquote, dontquote; + if (end == opaquePart.length()) { + dontquote = opaquePart; + doquote = ""; + } else { + dontquote = opaquePart.substring(0,end+1); + doquote = opaquePart.substring(end+1); + } + sb.append (dontquote); + sb.append(quote(doquote, L_URIC, H_URIC)); + } + } else { + sb.append(quote(opaquePart, L_URIC, H_URIC)); + } + } else { + appendAuthority(sb, authority, userInfo, host, port); + if (path != null) + sb.append(quote(path, L_PATH, H_PATH)); + if (query != null) { + sb.append('?'); + sb.append(quote(query, L_URIC, H_URIC)); + } + } + } + + private void appendFragment(StringBuffer sb, String fragment) { + if (fragment != null) { + sb.append('#'); + sb.append(quote(fragment, L_URIC, H_URIC)); + } + } + + private String toString(String scheme, + String opaquePart, + String authority, + String userInfo, + String host, + int port, + String path, + String query, + String fragment) + { + StringBuffer sb = new StringBuffer(); + if (scheme != null) { + sb.append(scheme); + sb.append(':'); + } + appendSchemeSpecificPart(sb, opaquePart, + authority, userInfo, host, port, + path, query); + appendFragment(sb, fragment); + return sb.toString(); + } + + private void defineSchemeSpecificPart() { + if (schemeSpecificPart != null) return; + StringBuffer sb = new StringBuffer(); + appendSchemeSpecificPart(sb, null, getAuthority(), getUserInfo(), + host, port, getPath(), getQuery()); + if (sb.length() == 0) return; + schemeSpecificPart = sb.toString(); + } + + private void defineString() { + if (string != null) return; + + StringBuffer sb = new StringBuffer(); + if (scheme != null) { + sb.append(scheme); + sb.append(':'); + } + if (isOpaque()) { + sb.append(schemeSpecificPart); + } else { + if (host != null) { + sb.append("//"); + if (userInfo != null) { + sb.append(userInfo); + sb.append('@'); + } + boolean needBrackets = ((host.indexOf(':') >= 0) + && !host.startsWith("[") + && !host.endsWith("]")); + if (needBrackets) sb.append('['); + sb.append(host); + if (needBrackets) sb.append(']'); + if (port != -1) { + sb.append(':'); + sb.append(port); + } + } else if (authority != null) { + sb.append("//"); + sb.append(authority); + } + if (path != null) + sb.append(path); + if (query != null) { + sb.append('?'); + sb.append(query); + } + } + if (fragment != null) { + sb.append('#'); + sb.append(fragment); + } + string = sb.toString(); + } + + + // -- Normalization, resolution, and relativization -- + + // RFC2396 5.2 (6) + private static String resolvePath(String base, String child, + boolean absolute) + { + int i = base.lastIndexOf('/'); + int cn = child.length(); + String path = ""; + + if (cn == 0) { + // 5.2 (6a) + if (i >= 0) + path = base.substring(0, i + 1); + } else { + StringBuffer sb = new StringBuffer(base.length() + cn); + // 5.2 (6a) + if (i >= 0) + sb.append(base.substring(0, i + 1)); + // 5.2 (6b) + sb.append(child); + path = sb.toString(); + } + + // 5.2 (6c-f) + String np = normalize(path); + + // 5.2 (6g): If the result is absolute but the path begins with "../", + // then we simply leave the path as-is + + return np; + } + + // RFC2396 5.2 + private static URI resolve(URI base, URI child) { + // check if child if opaque first so that NPE is thrown + // if child is null. + if (child.isOpaque() || base.isOpaque()) + return child; + + // 5.2 (2): Reference to current document (lone fragment) + if ((child.scheme == null) && (child.authority == null) + && child.path.equals("") && (child.fragment != null) + && (child.query == null)) { + if ((base.fragment != null) + && child.fragment.equals(base.fragment)) { + return base; + } + URI ru = new URI(); + ru.scheme = base.scheme; + ru.authority = base.authority; + ru.userInfo = base.userInfo; + ru.host = base.host; + ru.port = base.port; + ru.path = base.path; + ru.fragment = child.fragment; + ru.query = base.query; + return ru; + } + + // 5.2 (3): Child is absolute + if (child.scheme != null) + return child; + + URI ru = new URI(); // Resolved URI + ru.scheme = base.scheme; + ru.query = child.query; + ru.fragment = child.fragment; + + // 5.2 (4): Authority + if (child.authority == null) { + ru.authority = base.authority; + ru.host = base.host; + ru.userInfo = base.userInfo; + ru.port = base.port; + + String cp = (child.path == null) ? "" : child.path; + if ((cp.length() > 0) && (cp.charAt(0) == '/')) { + // 5.2 (5): Child path is absolute + ru.path = child.path; + } else { + // 5.2 (6): Resolve relative path + ru.path = resolvePath(base.path, cp, base.isAbsolute()); + } + } else { + ru.authority = child.authority; + ru.host = child.host; + ru.userInfo = child.userInfo; + ru.host = child.host; + ru.port = child.port; + ru.path = child.path; + } + + // 5.2 (7): Recombine (nothing to do here) + return ru; + } + + // If the given URI's path is normal then return the URI; + // o.w., return a new URI containing the normalized path. + // + private static URI normalize(URI u) { + if (u.isOpaque() || (u.path == null) || (u.path.length() == 0)) + return u; + + String np = normalize(u.path); + if (np == u.path) + return u; + + URI v = new URI(); + v.scheme = u.scheme; + v.fragment = u.fragment; + v.authority = u.authority; + v.userInfo = u.userInfo; + v.host = u.host; + v.port = u.port; + v.path = np; + v.query = u.query; + return v; + } + + // If both URIs are hierarchical, their scheme and authority components are + // identical, and the base path is a prefix of the child's path, then + // return a relative URI that, when resolved against the base, yields the + // child; otherwise, return the child. + // + private static URI relativize(URI base, URI child) { + // check if child if opaque first so that NPE is thrown + // if child is null. + if (child.isOpaque() || base.isOpaque()) + return child; + if (!equalIgnoringCase(base.scheme, child.scheme) + || !equal(base.authority, child.authority)) + return child; + + String bp = normalize(base.path); + String cp = normalize(child.path); + if (!bp.equals(cp)) { + if (!bp.endsWith("/")) + bp = bp + "/"; + if (!cp.startsWith(bp)) + return child; + } + + URI v = new URI(); + v.path = cp.substring(bp.length()); + v.query = child.query; + v.fragment = child.fragment; + return v; + } + + + + // -- Path normalization -- + + // The following algorithm for path normalization avoids the creation of a + // string object for each segment, as well as the use of a string buffer to + // compute the final result, by using a single char array and editing it in + // place. The array is first split into segments, replacing each slash + // with '\0' and creating a segment-index array, each element of which is + // the index of the first char in the corresponding segment. We then walk + // through both arrays, removing ".", "..", and other segments as necessary + // by setting their entries in the index array to -1. Finally, the two + // arrays are used to rejoin the segments and compute the final result. + // + // This code is based upon src/solaris/native/java/io/canonicalize_md.c + + + // Check the given path to see if it might need normalization. A path + // might need normalization if it contains duplicate slashes, a "." + // segment, or a ".." segment. Return -1 if no further normalization is + // possible, otherwise return the number of segments found. + // + // This method takes a string argument rather than a char array so that + // this test can be performed without invoking path.toCharArray(). + // + static private int needsNormalization(String path) { + boolean normal = true; + int ns = 0; // Number of segments + int end = path.length() - 1; // Index of last char in path + int p = 0; // Index of next char in path + + // Skip initial slashes + while (p <= end) { + if (path.charAt(p) != '/') break; + p++; + } + if (p > 1) normal = false; + + // Scan segments + while (p <= end) { + + // Looking at "." or ".." ? + if ((path.charAt(p) == '.') + && ((p == end) + || ((path.charAt(p + 1) == '/') + || ((path.charAt(p + 1) == '.') + && ((p + 1 == end) + || (path.charAt(p + 2) == '/')))))) { + normal = false; + } + ns++; + + // Find beginning of next segment + while (p <= end) { + if (path.charAt(p++) != '/') + continue; + + // Skip redundant slashes + while (p <= end) { + if (path.charAt(p) != '/') break; + normal = false; + p++; + } + + break; + } + } + + return normal ? -1 : ns; + } + + + // Split the given path into segments, replacing slashes with nulls and + // filling in the given segment-index array. + // + // Preconditions: + // segs.length == Number of segments in path + // + // Postconditions: + // All slashes in path replaced by '\0' + // segs[i] == Index of first char in segment i (0 <= i < segs.length) + // + static private void split(char[] path, int[] segs) { + int end = path.length - 1; // Index of last char in path + int p = 0; // Index of next char in path + int i = 0; // Index of current segment + + // Skip initial slashes + while (p <= end) { + if (path[p] != '/') break; + path[p] = '\0'; + p++; + } + + while (p <= end) { + + // Note start of segment + segs[i++] = p++; + + // Find beginning of next segment + while (p <= end) { + if (path[p++] != '/') + continue; + path[p - 1] = '\0'; + + // Skip redundant slashes + while (p <= end) { + if (path[p] != '/') break; + path[p++] = '\0'; + } + break; + } + } + + if (i != segs.length) + throw new InternalError(); // ASSERT + } + + + // Join the segments in the given path according to the given segment-index + // array, ignoring those segments whose index entries have been set to -1, + // and inserting slashes as needed. Return the length of the resulting + // path. + // + // Preconditions: + // segs[i] == -1 implies segment i is to be ignored + // path computed by split, as above, with '\0' having replaced '/' + // + // Postconditions: + // path[0] .. path[return value] == Resulting path + // + static private int join(char[] path, int[] segs) { + int ns = segs.length; // Number of segments + int end = path.length - 1; // Index of last char in path + int p = 0; // Index of next path char to write + + if (path[p] == '\0') { + // Restore initial slash for absolute paths + path[p++] = '/'; + } + + for (int i = 0; i < ns; i++) { + int q = segs[i]; // Current segment + if (q == -1) + // Ignore this segment + continue; + + if (p == q) { + // We're already at this segment, so just skip to its end + while ((p <= end) && (path[p] != '\0')) + p++; + if (p <= end) { + // Preserve trailing slash + path[p++] = '/'; + } + } else if (p < q) { + // Copy q down to p + while ((q <= end) && (path[q] != '\0')) + path[p++] = path[q++]; + if (q <= end) { + // Preserve trailing slash + path[p++] = '/'; + } + } else + throw new InternalError(); // ASSERT false + } + + return p; + } + + + // Remove "." segments from the given path, and remove segment pairs + // consisting of a non-".." segment followed by a ".." segment. + // + private static void removeDots(char[] path, int[] segs) { + int ns = segs.length; + int end = path.length - 1; + + for (int i = 0; i < ns; i++) { + int dots = 0; // Number of dots found (0, 1, or 2) + + // Find next occurrence of "." or ".." + do { + int p = segs[i]; + if (path[p] == '.') { + if (p == end) { + dots = 1; + break; + } else if (path[p + 1] == '\0') { + dots = 1; + break; + } else if ((path[p + 1] == '.') + && ((p + 1 == end) + || (path[p + 2] == '\0'))) { + dots = 2; + break; + } + } + i++; + } while (i < ns); + if ((i > ns) || (dots == 0)) + break; + + if (dots == 1) { + // Remove this occurrence of "." + segs[i] = -1; + } else { + // If there is a preceding non-".." segment, remove both that + // segment and this occurrence of ".."; otherwise, leave this + // ".." segment as-is. + int j; + for (j = i - 1; j >= 0; j--) { + if (segs[j] != -1) break; + } + if (j >= 0) { + int q = segs[j]; + if (!((path[q] == '.') + && (path[q + 1] == '.') + && (path[q + 2] == '\0'))) { + segs[i] = -1; + segs[j] = -1; + } + } + } + } + } + + + // DEVIATION: If the normalized path is relative, and if the first + // segment could be parsed as a scheme name, then prepend a "." segment + // + private static void maybeAddLeadingDot(char[] path, int[] segs) { + + if (path[0] == '\0') + // The path is absolute + return; + + int ns = segs.length; + int f = 0; // Index of first segment + while (f < ns) { + if (segs[f] >= 0) + break; + f++; + } + if ((f >= ns) || (f == 0)) + // The path is empty, or else the original first segment survived, + // in which case we already know that no leading "." is needed + return; + + int p = segs[f]; + while ((p < path.length) && (path[p] != ':') && (path[p] != '\0')) p++; + if (p >= path.length || path[p] == '\0') + // No colon in first segment, so no "." needed + return; + + // At this point we know that the first segment is unused, + // hence we can insert a "." segment at that position + path[0] = '.'; + path[1] = '\0'; + segs[0] = 0; + } + + + // Normalize the given path string. A normal path string has no empty + // segments (i.e., occurrences of "//"), no segments equal to ".", and no + // segments equal to ".." that are preceded by a segment not equal to "..". + // In contrast to Unix-style pathname normalization, for URI paths we + // always retain trailing slashes. + // + private static String normalize(String ps) { + + // Does this path need normalization? + int ns = needsNormalization(ps); // Number of segments + if (ns < 0) + // Nope -- just return it + return ps; + + char[] path = ps.toCharArray(); // Path in char-array form + + // Split path into segments + int[] segs = new int[ns]; // Segment-index array + split(path, segs); + + // Remove dots + removeDots(path, segs); + + // Prevent scheme-name confusion + maybeAddLeadingDot(path, segs); + + // Join the remaining segments and return the result + String s = new String(path, 0, join(path, segs)); + if (s.equals(ps)) { + // string was already normalized + return ps; + } + return s; + } + + + + // -- Character classes for parsing -- + + // RFC2396 precisely specifies which characters in the US-ASCII charset are + // permissible in the various components of a URI reference. We here + // define a set of mask pairs to aid in enforcing these restrictions. Each + // mask pair consists of two longs, a low mask and a high mask. Taken + // together they represent a 128-bit mask, where bit i is set iff the + // character with value i is permitted. + // + // This approach is more efficient than sequentially searching arrays of + // permitted characters. It could be made still more efficient by + // precompiling the mask information so that a character's presence in a + // given mask could be determined by a single table lookup. + + // Compute the low-order mask for the characters in the given string + private static long lowMask(String chars) { + int n = chars.length(); + long m = 0; + for (int i = 0; i < n; i++) { + char c = chars.charAt(i); + if (c < 64) + m |= (1L << c); + } + return m; + } + + // Compute the high-order mask for the characters in the given string + private static long highMask(String chars) { + int n = chars.length(); + long m = 0; + for (int i = 0; i < n; i++) { + char c = chars.charAt(i); + if ((c >= 64) && (c < 128)) + m |= (1L << (c - 64)); + } + return m; + } + + // Compute a low-order mask for the characters + // between first and last, inclusive + private static long lowMask(char first, char last) { + long m = 0; + int f = Math.max(Math.min(first, 63), 0); + int l = Math.max(Math.min(last, 63), 0); + for (int i = f; i <= l; i++) + m |= 1L << i; + return m; + } + + // Compute a high-order mask for the characters + // between first and last, inclusive + private static long highMask(char first, char last) { + long m = 0; + int f = Math.max(Math.min(first, 127), 64) - 64; + int l = Math.max(Math.min(last, 127), 64) - 64; + for (int i = f; i <= l; i++) + m |= 1L << i; + return m; + } + + // Tell whether the given character is permitted by the given mask pair + private static boolean match(char c, long lowMask, long highMask) { + if (c == 0) // 0 doesn't have a slot in the mask. So, it never matches. + return false; + if (c < 64) + return ((1L << c) & lowMask) != 0; + if (c < 128) + return ((1L << (c - 64)) & highMask) != 0; + return false; + } + + // Character-class masks, in reverse order from RFC2396 because + // initializers for static fields cannot make forward references. + + // digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | + // "8" | "9" + private static final long L_DIGIT = lowMask('0', '9'); + private static final long H_DIGIT = 0L; + + // upalpha = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | + // "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | + // "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" + private static final long L_UPALPHA = 0L; + private static final long H_UPALPHA = highMask('A', 'Z'); + + // lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | + // "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | + // "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" + private static final long L_LOWALPHA = 0L; + private static final long H_LOWALPHA = highMask('a', 'z'); + + // alpha = lowalpha | upalpha + private static final long L_ALPHA = L_LOWALPHA | L_UPALPHA; + private static final long H_ALPHA = H_LOWALPHA | H_UPALPHA; + + // alphanum = alpha | digit + private static final long L_ALPHANUM = L_DIGIT | L_ALPHA; + private static final long H_ALPHANUM = H_DIGIT | H_ALPHA; + + // hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | + // "a" | "b" | "c" | "d" | "e" | "f" + private static final long L_HEX = L_DIGIT; + private static final long H_HEX = highMask('A', 'F') | highMask('a', 'f'); + + // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | + // "(" | ")" + private static final long L_MARK = lowMask("-_.!~*'()"); + private static final long H_MARK = highMask("-_.!~*'()"); + + // unreserved = alphanum | mark + private static final long L_UNRESERVED = L_ALPHANUM | L_MARK; + private static final long H_UNRESERVED = H_ALPHANUM | H_MARK; + + // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | + // "$" | "," | "[" | "]" + // Added per RFC2732: "[", "]" + private static final long L_RESERVED = lowMask(";/?:@&=+$,[]"); + private static final long H_RESERVED = highMask(";/?:@&=+$,[]"); + + // The zero'th bit is used to indicate that escape pairs and non-US-ASCII + // characters are allowed; this is handled by the scanEscape method below. + private static final long L_ESCAPED = 1L; + private static final long H_ESCAPED = 0L; + + // uric = reserved | unreserved | escaped + private static final long L_URIC = L_RESERVED | L_UNRESERVED | L_ESCAPED; + private static final long H_URIC = H_RESERVED | H_UNRESERVED | H_ESCAPED; + + // pchar = unreserved | escaped | + // ":" | "@" | "&" | "=" | "+" | "$" | "," + private static final long L_PCHAR + = L_UNRESERVED | L_ESCAPED | lowMask(":@&=+$,"); + private static final long H_PCHAR + = H_UNRESERVED | H_ESCAPED | highMask(":@&=+$,"); + + // All valid path characters + private static final long L_PATH = L_PCHAR | lowMask(";/"); + private static final long H_PATH = H_PCHAR | highMask(";/"); + + // Dash, for use in domainlabel and toplabel + private static final long L_DASH = lowMask("-"); + private static final long H_DASH = highMask("-"); + + // Dot, for use in hostnames + private static final long L_DOT = lowMask("."); + private static final long H_DOT = highMask("."); + + // userinfo = *( unreserved | escaped | + // ";" | ":" | "&" | "=" | "+" | "$" | "," ) + private static final long L_USERINFO + = L_UNRESERVED | L_ESCAPED | lowMask(";:&=+$,"); + private static final long H_USERINFO + = H_UNRESERVED | H_ESCAPED | highMask(";:&=+$,"); + + // reg_name = 1*( unreserved | escaped | "$" | "," | + // ";" | ":" | "@" | "&" | "=" | "+" ) + private static final long L_REG_NAME + = L_UNRESERVED | L_ESCAPED | lowMask("$,;:@&=+"); + private static final long H_REG_NAME + = H_UNRESERVED | H_ESCAPED | highMask("$,;:@&=+"); + + // All valid characters for server-based authorities + private static final long L_SERVER + = L_USERINFO | L_ALPHANUM | L_DASH | lowMask(".:@[]"); + private static final long H_SERVER + = H_USERINFO | H_ALPHANUM | H_DASH | highMask(".:@[]"); + + // Special case of server authority that represents an IPv6 address + // In this case, a % does not signify an escape sequence + private static final long L_SERVER_PERCENT + = L_SERVER | lowMask("%"); + private static final long H_SERVER_PERCENT + = H_SERVER | highMask("%"); + private static final long L_LEFT_BRACKET = lowMask("["); + private static final long H_LEFT_BRACKET = highMask("["); + + // scheme = alpha *( alpha | digit | "+" | "-" | "." ) + private static final long L_SCHEME = L_ALPHA | L_DIGIT | lowMask("+-."); + private static final long H_SCHEME = H_ALPHA | H_DIGIT | highMask("+-."); + + // uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | + // "&" | "=" | "+" | "$" | "," + private static final long L_URIC_NO_SLASH + = L_UNRESERVED | L_ESCAPED | lowMask(";?:@&=+$,"); + private static final long H_URIC_NO_SLASH + = H_UNRESERVED | H_ESCAPED | highMask(";?:@&=+$,"); + + + // -- Escaping and encoding -- + + private final static char[] hexDigits = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + private static void appendEscape(StringBuffer sb, byte b) { + sb.append('%'); + sb.append(hexDigits[(b >> 4) & 0x0f]); + sb.append(hexDigits[(b >> 0) & 0x0f]); + } + + private static void appendEncoded(StringBuffer sb, char c) { + /* + ByteBuffer bb = null; + try { + bb = ThreadLocalCoders.encoderFor("UTF-8") + .encode(CharBuffer.wrap("" + c)); + } catch (CharacterCodingException x) { + assert false; + } + while (bb.hasRemaining()) { + int b = bb.get() & 0xff; + if (b >= 0x80) + appendEscape(sb, (byte)b); + else + sb.append((char)b); + } + */ + } + + // Quote any characters in s that are not permitted + // by the given mask pair + // + private static String quote(String s, long lowMask, long highMask) { + int n = s.length(); + StringBuffer sb = null; + boolean allowNonASCII = ((lowMask & L_ESCAPED) != 0); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c < '\u0080') { + if (!match(c, lowMask, highMask)) { + if (sb == null) { + sb = new StringBuffer(); + sb.append(s.substring(0, i)); + } + appendEscape(sb, (byte)c); + } else { + if (sb != null) + sb.append(c); + } + } else if (allowNonASCII + && (Character.isSpaceChar(c) + || Character.isISOControl(c))) { + if (sb == null) { + sb = new StringBuffer(); + sb.append(s.substring(0, i)); + } + appendEncoded(sb, c); + } else { + if (sb != null) + sb.append(c); + } + } + return (sb == null) ? s : sb.toString(); + } + + // Encodes all characters >= \u0080 into escaped, normalized UTF-8 octets, + // assuming that s is otherwise legal + // + private static String encode(String s) { + int n = s.length(); + if (n == 0) + return s; + + // First check whether we actually need to encode + for (int i = 0;;) { + if (s.charAt(i) >= '\u0080') + break; + if (++i >= n) + return s; + } +/* + String ns = Normalizer.normalize(s, Normalizer.Form.NFC); + ByteBuffer bb = null; + try { + bb = ThreadLocalCoders.encoderFor("UTF-8") + .encode(CharBuffer.wrap(ns)); + } catch (CharacterCodingException x) { + assert false; + } +*/ + StringBuffer sb = new StringBuffer(); + /* + while (bb.hasRemaining()) { + int b = bb.get() & 0xff; + if (b >= 0x80) + appendEscape(sb, (byte)b); + else + sb.append((char)b); + } + */ + return sb.toString(); + } + + private static int decode(char c) { + if ((c >= '0') && (c <= '9')) + return c - '0'; + if ((c >= 'a') && (c <= 'f')) + return c - 'a' + 10; + if ((c >= 'A') && (c <= 'F')) + return c - 'A' + 10; + assert false; + return -1; + } + + private static byte decode(char c1, char c2) { + return (byte)( ((decode(c1) & 0xf) << 4) + | ((decode(c2) & 0xf) << 0)); + } + + // Evaluates all escapes in s, applying UTF-8 decoding if needed. Assumes + // that escapes are well-formed syntactically, i.e., of the form %XX. If a + // sequence of escaped octets is not valid UTF-8 then the erroneous octets + // are replaced with '\uFFFD'. + // Exception: any "%" found between "[]" is left alone. It is an IPv6 literal + // with a scope_id + // + private static String decode(String s) { + if (s == null) + return s; + int n = s.length(); + if (n == 0) + return s; + if (s.indexOf('%') < 0) + return s; + + StringBuffer sb = new StringBuffer(n); + /* + ByteBuffer bb = ByteBuffer.allocate(n); + CharBuffer cb = CharBuffer.allocate(n); + CharsetDecoder dec = ThreadLocalCoders.decoderFor("UTF-8") + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + + // This is not horribly efficient, but it will do for now + char c = s.charAt(0); + boolean betweenBrackets = false; + + for (int i = 0; i < n;) { + assert c == s.charAt(i); // Loop invariant + if (c == '[') { + betweenBrackets = true; + } else if (betweenBrackets && c == ']') { + betweenBrackets = false; + } + if (c != '%' || betweenBrackets) { + sb.append(c); + if (++i >= n) + break; + c = s.charAt(i); + continue; + } + bb.clear(); + int ui = i; + for (;;) { + assert (n - i >= 2); + bb.put(decode(s.charAt(++i), s.charAt(++i))); + if (++i >= n) + break; + c = s.charAt(i); + if (c != '%') + break; + } + bb.flip(); + cb.clear(); + dec.reset(); + CoderResult cr = dec.decode(bb, cb, true); + assert cr.isUnderflow(); + cr = dec.flush(cb); + assert cr.isUnderflow(); + sb.append(cb.flip().toString()); + } +*/ + return sb.toString(); + } + + + // -- Parsing -- + + // For convenience we wrap the input URI string in a new instance of the + // following internal class. This saves always having to pass the input + // string as an argument to each internal scan/parse method. + + private class Parser { + + private String input; // URI input string + private boolean requireServerAuthority = false; + + Parser(String s) { + input = s; + string = s; + } + + // -- Methods for throwing URISyntaxException in various ways -- + + private void fail(String reason) throws URISyntaxException { + throw new URISyntaxException(input, reason); + } + + private void fail(String reason, int p) throws URISyntaxException { + throw new URISyntaxException(input, reason, p); + } + + private void failExpecting(String expected, int p) + throws URISyntaxException + { + fail("Expected " + expected, p); + } + + private void failExpecting(String expected, String prior, int p) + throws URISyntaxException + { + fail("Expected " + expected + " following " + prior, p); + } + + + // -- Simple access to the input string -- + + // Return a substring of the input string + // + private String substring(int start, int end) { + return input.substring(start, end); + } + + // Return the char at position p, + // assuming that p < input.length() + // + private char charAt(int p) { + return input.charAt(p); + } + + // Tells whether start < end and, if so, whether charAt(start) == c + // + private boolean at(int start, int end, char c) { + return (start < end) && (charAt(start) == c); + } + + // Tells whether start + s.length() < end and, if so, + // whether the chars at the start position match s exactly + // + private boolean at(int start, int end, String s) { + int p = start; + int sn = s.length(); + if (sn > end - p) + return false; + int i = 0; + while (i < sn) { + if (charAt(p++) != s.charAt(i)) { + break; + } + i++; + } + return (i == sn); + } + + + // -- Scanning -- + + // The various scan and parse methods that follow use a uniform + // convention of taking the current start position and end index as + // their first two arguments. The start is inclusive while the end is + // exclusive, just as in the String class, i.e., a start/end pair + // denotes the left-open interval [start, end) of the input string. + // + // These methods never proceed past the end position. They may return + // -1 to indicate outright failure, but more often they simply return + // the position of the first char after the last char scanned. Thus + // a typical idiom is + // + // int p = start; + // int q = scan(p, end, ...); + // if (q > p) + // // We scanned something + // ...; + // else if (q == p) + // // We scanned nothing + // ...; + // else if (q == -1) + // // Something went wrong + // ...; + + + // Scan a specific char: If the char at the given start position is + // equal to c, return the index of the next char; otherwise, return the + // start position. + // + private int scan(int start, int end, char c) { + if ((start < end) && (charAt(start) == c)) + return start + 1; + return start; + } + + // Scan forward from the given start position. Stop at the first char + // in the err string (in which case -1 is returned), or the first char + // in the stop string (in which case the index of the preceding char is + // returned), or the end of the input string (in which case the length + // of the input string is returned). May return the start position if + // nothing matches. + // + private int scan(int start, int end, String err, String stop) { + int p = start; + while (p < end) { + char c = charAt(p); + if (err.indexOf(c) >= 0) + return -1; + if (stop.indexOf(c) >= 0) + break; + p++; + } + return p; + } + + // Scan a potential escape sequence, starting at the given position, + // with the given first char (i.e., charAt(start) == c). + // + // This method assumes that if escapes are allowed then visible + // non-US-ASCII chars are also allowed. + // + private int scanEscape(int start, int n, char first) + throws URISyntaxException + { + int p = start; + char c = first; + if (c == '%') { + // Process escape pair + if ((p + 3 <= n) + && match(charAt(p + 1), L_HEX, H_HEX) + && match(charAt(p + 2), L_HEX, H_HEX)) { + return p + 3; + } + fail("Malformed escape pair", p); + } else if ((c > 128) + && !Character.isSpaceChar(c) + && !Character.isISOControl(c)) { + // Allow unescaped but visible non-US-ASCII chars + return p + 1; + } + return p; + } + + // Scan chars that match the given mask pair + // + private int scan(int start, int n, long lowMask, long highMask) + throws URISyntaxException + { + int p = start; + while (p < n) { + char c = charAt(p); + if (match(c, lowMask, highMask)) { + p++; + continue; + } + if ((lowMask & L_ESCAPED) != 0) { + int q = scanEscape(p, n, c); + if (q > p) { + p = q; + continue; + } + } + break; + } + return p; + } + + // Check that each of the chars in [start, end) matches the given mask + // + private void checkChars(int start, int end, + long lowMask, long highMask, + String what) + throws URISyntaxException + { + int p = scan(start, end, lowMask, highMask); + if (p < end) + fail("Illegal character in " + what, p); + } + + // Check that the char at position p matches the given mask + // + private void checkChar(int p, + long lowMask, long highMask, + String what) + throws URISyntaxException + { + checkChars(p, p + 1, lowMask, highMask, what); + } + + + // -- Parsing -- + + // [:][#] + // + void parse(boolean rsa) throws URISyntaxException { + requireServerAuthority = rsa; + int ssp; // Start of scheme-specific part + int n = input.length(); + int p = scan(0, n, "/?#", ":"); + if ((p >= 0) && at(p, n, ':')) { + if (p == 0) + failExpecting("scheme name", 0); + checkChar(0, L_ALPHA, H_ALPHA, "scheme name"); + checkChars(1, p, L_SCHEME, H_SCHEME, "scheme name"); + scheme = substring(0, p); + p++; // Skip ':' + ssp = p; + if (at(p, n, '/')) { + p = parseHierarchical(p, n); + } else { + int q = scan(p, n, "", "#"); + if (q <= p) + failExpecting("scheme-specific part", p); + checkChars(p, q, L_URIC, H_URIC, "opaque part"); + p = q; + } + } else { + ssp = 0; + p = parseHierarchical(0, n); + } + schemeSpecificPart = substring(ssp, p); + if (at(p, n, '#')) { + checkChars(p + 1, n, L_URIC, H_URIC, "fragment"); + fragment = substring(p + 1, n); + p = n; + } + if (p < n) + fail("end of URI", p); + } + + // [//authority][?] + // + // DEVIATION from RFC2396: We allow an empty authority component as + // long as it's followed by a non-empty path, query component, or + // fragment component. This is so that URIs such as "file:///foo/bar" + // will parse. This seems to be the intent of RFC2396, though the + // grammar does not permit it. If the authority is empty then the + // userInfo, host, and port components are undefined. + // + // DEVIATION from RFC2396: We allow empty relative paths. This seems + // to be the intent of RFC2396, but the grammar does not permit it. + // The primary consequence of this deviation is that "#f" parses as a + // relative URI with an empty path. + // + private int parseHierarchical(int start, int n) + throws URISyntaxException + { + int p = start; + if (at(p, n, '/') && at(p + 1, n, '/')) { + p += 2; + int q = scan(p, n, "", "/?#"); + if (q > p) { + p = parseAuthority(p, q); + } else if (q < n) { + // DEVIATION: Allow empty authority prior to non-empty + // path, query component or fragment identifier + } else + failExpecting("authority", p); + } + int q = scan(p, n, "", "?#"); // DEVIATION: May be empty + checkChars(p, q, L_PATH, H_PATH, "path"); + path = substring(p, q); + p = q; + if (at(p, n, '?')) { + p++; + q = scan(p, n, "", "#"); + checkChars(p, q, L_URIC, H_URIC, "query"); + query = substring(p, q); + p = q; + } + return p; + } + + // authority = server | reg_name + // + // Ambiguity: An authority that is a registry name rather than a server + // might have a prefix that parses as a server. We use the fact that + // the authority component is always followed by '/' or the end of the + // input string to resolve this: If the complete authority did not + // parse as a server then we try to parse it as a registry name. + // + private int parseAuthority(int start, int n) + throws URISyntaxException + { + int p = start; + int q = p; + URISyntaxException ex = null; + + boolean serverChars; + boolean regChars; + + if (scan(p, n, "", "]") > p) { + // contains a literal IPv6 address, therefore % is allowed + serverChars = (scan(p, n, L_SERVER_PERCENT, H_SERVER_PERCENT) == n); + } else { + serverChars = (scan(p, n, L_SERVER, H_SERVER) == n); + } + regChars = (scan(p, n, L_REG_NAME, H_REG_NAME) == n); + + if (regChars && !serverChars) { + // Must be a registry-based authority + authority = substring(p, n); + return n; + } + + if (serverChars) { + // Might be (probably is) a server-based authority, so attempt + // to parse it as such. If the attempt fails, try to treat it + // as a registry-based authority. + try { + q = parseServer(p, n); + if (q < n) + failExpecting("end of authority", q); + authority = substring(p, n); + } catch (URISyntaxException x) { + // Undo results of failed parse + userInfo = null; + host = null; + port = -1; + if (requireServerAuthority) { + // If we're insisting upon a server-based authority, + // then just re-throw the exception + throw x; + } else { + // Save the exception in case it doesn't parse as a + // registry either + ex = x; + q = p; + } + } + } + + if (q < n) { + if (regChars) { + // Registry-based authority + authority = substring(p, n); + } else if (ex != null) { + // Re-throw exception; it was probably due to + // a malformed IPv6 address + throw ex; + } else { + fail("Illegal character in authority", q); + } + } + + return n; + } + + + // [@][:] + // + private int parseServer(int start, int n) + throws URISyntaxException + { + int p = start; + int q; + + // userinfo + q = scan(p, n, "/?#", "@"); + if ((q >= p) && at(q, n, '@')) { + checkChars(p, q, L_USERINFO, H_USERINFO, "user info"); + userInfo = substring(p, q); + p = q + 1; // Skip '@' + } + + // hostname, IPv4 address, or IPv6 address + if (at(p, n, '[')) { + // DEVIATION from RFC2396: Support IPv6 addresses, per RFC2732 + p++; + q = scan(p, n, "/?#", "]"); + if ((q > p) && at(q, n, ']')) { + // look for a "%" scope id + int r = scan (p, q, "", "%"); + if (r > p) { + parseIPv6Reference(p, r); + if (r+1 == q) { + fail ("scope id expected"); + } + checkChars (r+1, q, L_ALPHANUM, H_ALPHANUM, + "scope id"); + } else { + parseIPv6Reference(p, q); + } + host = substring(p-1, q+1); + p = q + 1; + } else { + failExpecting("closing bracket for IPv6 address", q); + } + } else { + q = parseIPv4Address(p, n); + if (q <= p) + q = parseHostname(p, n); + p = q; + } + + // port + if (at(p, n, ':')) { + p++; + q = scan(p, n, "", "/"); + if (q > p) { + checkChars(p, q, L_DIGIT, H_DIGIT, "port number"); + try { + port = Integer.parseInt(substring(p, q)); + } catch (NumberFormatException x) { + fail("Malformed port number", p); + } + p = q; + } + } + if (p < n) + failExpecting("port number", p); + + return p; + } + + // Scan a string of decimal digits whose value fits in a byte + // + private int scanByte(int start, int n) + throws URISyntaxException + { + int p = start; + int q = scan(p, n, L_DIGIT, H_DIGIT); + if (q <= p) return q; + if (Integer.parseInt(substring(p, q)) > 255) return p; + return q; + } + + // Scan an IPv4 address. + // + // If the strict argument is true then we require that the given + // interval contain nothing besides an IPv4 address; if it is false + // then we only require that it start with an IPv4 address. + // + // If the interval does not contain or start with (depending upon the + // strict argument) a legal IPv4 address characters then we return -1 + // immediately; otherwise we insist that these characters parse as a + // legal IPv4 address and throw an exception on failure. + // + // We assume that any string of decimal digits and dots must be an IPv4 + // address. It won't parse as a hostname anyway, so making that + // assumption here allows more meaningful exceptions to be thrown. + // + private int scanIPv4Address(int start, int n, boolean strict) + throws URISyntaxException + { + int p = start; + int q; + int m = scan(p, n, L_DIGIT | L_DOT, H_DIGIT | H_DOT); + if ((m <= p) || (strict && (m != n))) + return -1; + for (;;) { + // Per RFC2732: At most three digits per byte + // Further constraint: Each element fits in a byte + if ((q = scanByte(p, m)) <= p) break; p = q; + if ((q = scan(p, m, '.')) <= p) break; p = q; + if ((q = scanByte(p, m)) <= p) break; p = q; + if ((q = scan(p, m, '.')) <= p) break; p = q; + if ((q = scanByte(p, m)) <= p) break; p = q; + if ((q = scan(p, m, '.')) <= p) break; p = q; + if ((q = scanByte(p, m)) <= p) break; p = q; + if (q < m) break; + return q; + } + fail("Malformed IPv4 address", q); + return -1; + } + + // Take an IPv4 address: Throw an exception if the given interval + // contains anything except an IPv4 address + // + private int takeIPv4Address(int start, int n, String expected) + throws URISyntaxException + { + int p = scanIPv4Address(start, n, true); + if (p <= start) + failExpecting(expected, start); + return p; + } + + // Attempt to parse an IPv4 address, returning -1 on failure but + // allowing the given interval to contain [:] after + // the IPv4 address. + // + private int parseIPv4Address(int start, int n) { + int p; + + try { + p = scanIPv4Address(start, n, false); + } catch (URISyntaxException x) { + return -1; + } catch (NumberFormatException nfe) { + return -1; + } + + if (p > start && p < n) { + // IPv4 address is followed by something - check that + // it's a ":" as this is the only valid character to + // follow an address. + if (charAt(p) != ':') { + p = -1; + } + } + + if (p > start) + host = substring(start, p); + + return p; + } + + // hostname = domainlabel [ "." ] | 1*( domainlabel "." ) toplabel [ "." ] + // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + // toplabel = alpha | alpha *( alphanum | "-" ) alphanum + // + private int parseHostname(int start, int n) + throws URISyntaxException + { + int p = start; + int q; + int l = -1; // Start of last parsed label + + do { + // domainlabel = alphanum [ *( alphanum | "-" ) alphanum ] + q = scan(p, n, L_ALPHANUM, H_ALPHANUM); + if (q <= p) + break; + l = p; + if (q > p) { + p = q; + q = scan(p, n, L_ALPHANUM | L_DASH, H_ALPHANUM | H_DASH); + if (q > p) { + if (charAt(q - 1) == '-') + fail("Illegal character in hostname", q - 1); + p = q; + } + } + q = scan(p, n, '.'); + if (q <= p) + break; + p = q; + } while (p < n); + + if ((p < n) && !at(p, n, ':')) + fail("Illegal character in hostname", p); + + if (l < 0) + failExpecting("hostname", start); + + // for a fully qualified hostname check that the rightmost + // label starts with an alpha character. + if (l > start && !match(charAt(l), L_ALPHA, H_ALPHA)) { + fail("Illegal character in hostname", l); + } + + host = substring(start, p); + return p; + } + + + // IPv6 address parsing, from RFC2373: IPv6 Addressing Architecture + // + // Bug: The grammar in RFC2373 Appendix B does not allow addresses of + // the form ::12.34.56.78, which are clearly shown in the examples + // earlier in the document. Here is the original grammar: + // + // IPv6address = hexpart [ ":" IPv4address ] + // hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ] + // hexseq = hex4 *( ":" hex4) + // hex4 = 1*4HEXDIG + // + // We therefore use the following revised grammar: + // + // IPv6address = hexseq [ ":" IPv4address ] + // | hexseq [ "::" [ hexpost ] ] + // | "::" [ hexpost ] + // hexpost = hexseq | hexseq ":" IPv4address | IPv4address + // hexseq = hex4 *( ":" hex4) + // hex4 = 1*4HEXDIG + // + // This covers all and only the following cases: + // + // hexseq + // hexseq : IPv4address + // hexseq :: + // hexseq :: hexseq + // hexseq :: hexseq : IPv4address + // hexseq :: IPv4address + // :: hexseq + // :: hexseq : IPv4address + // :: IPv4address + // :: + // + // Additionally we constrain the IPv6 address as follows :- + // + // i. IPv6 addresses without compressed zeros should contain + // exactly 16 bytes. + // + // ii. IPv6 addresses with compressed zeros should contain + // less than 16 bytes. + + private int ipv6byteCount = 0; + + private int parseIPv6Reference(int start, int n) + throws URISyntaxException + { + int p = start; + int q; + boolean compressedZeros = false; + + q = scanHexSeq(p, n); + + if (q > p) { + p = q; + if (at(p, n, "::")) { + compressedZeros = true; + p = scanHexPost(p + 2, n); + } else if (at(p, n, ':')) { + p = takeIPv4Address(p + 1, n, "IPv4 address"); + ipv6byteCount += 4; + } + } else if (at(p, n, "::")) { + compressedZeros = true; + p = scanHexPost(p + 2, n); + } + if (p < n) + fail("Malformed IPv6 address", start); + if (ipv6byteCount > 16) + fail("IPv6 address too long", start); + if (!compressedZeros && ipv6byteCount < 16) + fail("IPv6 address too short", start); + if (compressedZeros && ipv6byteCount == 16) + fail("Malformed IPv6 address", start); + + return p; + } + + private int scanHexPost(int start, int n) + throws URISyntaxException + { + int p = start; + int q; + + if (p == n) + return p; + + q = scanHexSeq(p, n); + if (q > p) { + p = q; + if (at(p, n, ':')) { + p++; + p = takeIPv4Address(p, n, "hex digits or IPv4 address"); + ipv6byteCount += 4; + } + } else { + p = takeIPv4Address(p, n, "hex digits or IPv4 address"); + ipv6byteCount += 4; + } + return p; + } + + // Scan a hex sequence; return -1 if one could not be scanned + // + private int scanHexSeq(int start, int n) + throws URISyntaxException + { + int p = start; + int q; + + q = scan(p, n, L_HEX, H_HEX); + if (q <= p) + return -1; + if (at(q, n, '.')) // Beginning of IPv4 address + return -1; + if (q > p + 4) + fail("IPv6 hexadecimal digit sequence too long", p); + ipv6byteCount += 2; + p = q; + while (p < n) { + if (!at(p, n, ':')) + break; + if (at(p + 1, n, ':')) + break; // "::" + p++; + q = scan(p, n, L_HEX, H_HEX); + if (q <= p) + failExpecting("digits for an IPv6 address", p); + if (at(q, n, '.')) { // Beginning of IPv4 address + p--; + break; + } + if (q > p + 4) + fail("IPv6 hexadecimal digit sequence too long", p); + ipv6byteCount += 2; + p = q; + } + + return p; + } + + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/net/URISyntaxException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/net/URISyntaxException.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.net; + + +/** + * Checked exception thrown to indicate that a string could not be parsed as a + * URI reference. + * + * @author Mark Reinhold + * @see URI + * @since 1.4 + */ + +public class URISyntaxException + extends Exception +{ + private static final long serialVersionUID = 2137979680897488891L; + + private String input; + private int index; + + /** + * Constructs an instance from the given input string, reason, and error + * index. + * + * @param input The input string + * @param reason A string explaining why the input could not be parsed + * @param index The index at which the parse error occurred, + * or -1 if the index is not known + * + * @throws NullPointerException + * If either the input or reason strings are null + * + * @throws IllegalArgumentException + * If the error index is less than -1 + */ + public URISyntaxException(String input, String reason, int index) { + super(reason); + if ((input == null) || (reason == null)) + throw new NullPointerException(); + if (index < -1) + throw new IllegalArgumentException(); + this.input = input; + this.index = index; + } + + /** + * Constructs an instance from the given input string and reason. The + * resulting object will have an error index of -1. + * + * @param input The input string + * @param reason A string explaining why the input could not be parsed + * + * @throws NullPointerException + * If either the input or reason strings are null + */ + public URISyntaxException(String input, String reason) { + this(input, reason, -1); + } + + /** + * Returns the input string. + * + * @return The input string + */ + public String getInput() { + return input; + } + + /** + * Returns a string explaining why the input string could not be parsed. + * + * @return The reason string + */ + public String getReason() { + return super.getMessage(); + } + + /** + * Returns an index into the input string of the position at which the + * parse error occurred, or -1 if this position is not known. + * + * @return The error index + */ + public int getIndex() { + return index; + } + + /** + * Returns a string describing the parse error. The resulting string + * consists of the reason string followed by a colon character + * (':'), a space, and the input string. If the error index is + * defined then the string " at index " followed by the index, in + * decimal, is inserted after the reason string and before the colon + * character. + * + * @return A string describing the parse error + */ + public String getMessage() { + StringBuffer sb = new StringBuffer(); + sb.append(getReason()); + if (index > -1) { + sb.append(" at index "); + sb.append(index); + } + sb.append(": "); + sb.append(input); + return sb.toString(); + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/EnumMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/EnumMap.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,797 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util; + +import java.util.Map.Entry; +import org.apidesign.bck2brwsr.core.JavaScriptBody; + +/** + * A specialized {@link Map} implementation for use with enum type keys. All + * of the keys in an enum map must come from a single enum type that is + * specified, explicitly or implicitly, when the map is created. Enum maps + * are represented internally as arrays. This representation is extremely + * compact and efficient. + * + *

Enum maps are maintained in the natural order of their keys + * (the order in which the enum constants are declared). This is reflected + * in the iterators returned by the collections views ({@link #keySet()}, + * {@link #entrySet()}, and {@link #values()}). + * + *

Iterators returned by the collection views are weakly consistent: + * they will never throw {@link ConcurrentModificationException} and they may + * or may not show the effects of any modifications to the map that occur while + * the iteration is in progress. + * + *

Null keys are not permitted. Attempts to insert a null key will + * throw {@link NullPointerException}. Attempts to test for the + * presence of a null key or to remove one will, however, function properly. + * Null values are permitted. + + *

Like most collection implementations EnumMap is not + * synchronized. If multiple threads access an enum map concurrently, and at + * least one of the threads modifies the map, it should be synchronized + * externally. This is typically accomplished by synchronizing on some + * object that naturally encapsulates the enum map. If no such object exists, + * the map should be "wrapped" using the {@link Collections#synchronizedMap} + * method. This is best done at creation time, to prevent accidental + * unsynchronized access: + * + *

+ *     Map<EnumKey, V> m
+ *         = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));
+ * 
+ * + *

Implementation note: All basic operations execute in constant time. + * They are likely (though not guaranteed) to be faster than their + * {@link HashMap} counterparts. + * + *

This class is a member of the + * + * Java Collections Framework. + * + * @author Josh Bloch + * @see EnumSet + * @since 1.5 + */ +public class EnumMap, V> extends AbstractMap + implements java.io.Serializable, Cloneable +{ + /** + * The Class object for the enum type of all the keys of this map. + * + * @serial + */ + private final Class keyType; + + /** + * All of the values comprising K. (Cached for performance.) + */ + private transient K[] keyUniverse; + + /** + * Array representation of this map. The ith element is the value + * to which universe[i] is currently mapped, or null if it isn't + * mapped to anything, or NULL if it's mapped to null. + */ + private transient Object[] vals; + + /** + * The number of mappings in this map. + */ + private transient int size = 0; + + /** + * Distinguished non-null value for representing null values. + */ + private static final Object NULL = new Integer(0); + + private Object maskNull(Object value) { + return (value == null ? NULL : value); + } + + private V unmaskNull(Object value) { + return (V) (value == NULL ? null : value); + } + + private static final Enum[] ZERO_LENGTH_ENUM_ARRAY = new Enum[0]; + + /** + * Creates an empty enum map with the specified key type. + * + * @param keyType the class object of the key type for this enum map + * @throws NullPointerException if keyType is null + */ + public EnumMap(Class keyType) { + this.keyType = keyType; + keyUniverse = getKeyUniverse(keyType); + vals = new Object[keyUniverse.length]; + } + + /** + * Creates an enum map with the same key type as the specified enum + * map, initially containing the same mappings (if any). + * + * @param m the enum map from which to initialize this enum map + * @throws NullPointerException if m is null + */ + public EnumMap(EnumMap m) { + keyType = m.keyType; + keyUniverse = m.keyUniverse; + vals = m.vals.clone(); + size = m.size; + } + + /** + * Creates an enum map initialized from the specified map. If the + * specified map is an EnumMap instance, this constructor behaves + * identically to {@link #EnumMap(EnumMap)}. Otherwise, the specified map + * must contain at least one mapping (in order to determine the new + * enum map's key type). + * + * @param m the map from which to initialize this enum map + * @throws IllegalArgumentException if m is not an + * EnumMap instance and contains no mappings + * @throws NullPointerException if m is null + */ + public EnumMap(Map m) { + if (m instanceof EnumMap) { + EnumMap em = (EnumMap) m; + keyType = em.keyType; + keyUniverse = em.keyUniverse; + vals = em.vals.clone(); + size = em.size; + } else { + if (m.isEmpty()) + throw new IllegalArgumentException("Specified map is empty"); + keyType = m.keySet().iterator().next().getDeclaringClass(); + keyUniverse = getKeyUniverse(keyType); + vals = new Object[keyUniverse.length]; + putAll(m); + } + } + + // Query Operations + + /** + * Returns the number of key-value mappings in this map. + * + * @return the number of key-value mappings in this map + */ + public int size() { + return size; + } + + /** + * Returns true if this map maps one or more keys to the + * specified value. + * + * @param value the value whose presence in this map is to be tested + * @return true if this map maps one or more keys to this value + */ + public boolean containsValue(Object value) { + value = maskNull(value); + + for (Object val : vals) + if (value.equals(val)) + return true; + + return false; + } + + /** + * Returns true if this map contains a mapping for the specified + * key. + * + * @param key the key whose presence in this map is to be tested + * @return true if this map contains a mapping for the specified + * key + */ + public boolean containsKey(Object key) { + return isValidKey(key) && vals[((Enum)key).ordinal()] != null; + } + + private boolean containsMapping(Object key, Object value) { + return isValidKey(key) && + maskNull(value).equals(vals[((Enum)key).ordinal()]); + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code (key == k)}, + * then this method returns {@code v}; otherwise it returns + * {@code null}. (There can be at most one such mapping.) + * + *

A return value of {@code null} does not necessarily + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to {@code null}. + * The {@link #containsKey containsKey} operation may be used to + * distinguish these two cases. + */ + public V get(Object key) { + return (isValidKey(key) ? + unmaskNull(vals[((Enum)key).ordinal()]) : null); + } + + // Modification Operations + + /** + * Associates the specified value with the specified key in this map. + * If the map previously contained a mapping for this key, the old + * value is replaced. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + * + * @return the previous value associated with specified key, or + * null if there was no mapping for key. (A null + * return can also indicate that the map previously associated + * null with the specified key.) + * @throws NullPointerException if the specified key is null + */ + public V put(K key, V value) { + typeCheck(key); + + int index = key.ordinal(); + Object oldValue = vals[index]; + vals[index] = maskNull(value); + if (oldValue == null) + size++; + return unmaskNull(oldValue); + } + + /** + * Removes the mapping for this key from this map if present. + * + * @param key the key whose mapping is to be removed from the map + * @return the previous value associated with specified key, or + * null if there was no entry for key. (A null + * return can also indicate that the map previously associated + * null with the specified key.) + */ + public V remove(Object key) { + if (!isValidKey(key)) + return null; + int index = ((Enum)key).ordinal(); + Object oldValue = vals[index]; + vals[index] = null; + if (oldValue != null) + size--; + return unmaskNull(oldValue); + } + + private boolean removeMapping(Object key, Object value) { + if (!isValidKey(key)) + return false; + int index = ((Enum)key).ordinal(); + if (maskNull(value).equals(vals[index])) { + vals[index] = null; + size--; + return true; + } + return false; + } + + /** + * Returns true if key is of the proper type to be a key in this + * enum map. + */ + private boolean isValidKey(Object key) { + if (key == null) + return false; + + // Cheaper than instanceof Enum followed by getDeclaringClass + Class keyClass = key.getClass(); + return keyClass == keyType || keyClass.getSuperclass() == keyType; + } + + // Bulk Operations + + /** + * Copies all of the mappings from the specified map to this map. + * These mappings will replace any mappings that this map had for + * any of the keys currently in the specified map. + * + * @param m the mappings to be stored in this map + * @throws NullPointerException the specified map is null, or if + * one or more keys in the specified map are null + */ + public void putAll(Map m) { + if (m instanceof EnumMap) { + EnumMap em = + (EnumMap)m; + if (em.keyType != keyType) { + if (em.isEmpty()) + return; + throw new ClassCastException(em.keyType + " != " + keyType); + } + + for (int i = 0; i < keyUniverse.length; i++) { + Object emValue = em.vals[i]; + if (emValue != null) { + if (vals[i] == null) + size++; + vals[i] = emValue; + } + } + } else { + super.putAll(m); + } + } + + /** + * Removes all mappings from this map. + */ + public void clear() { + Arrays.fill(vals, null); + size = 0; + } + + // Views + + /** + * This field is initialized to contain an instance of the entry set + * view the first time this view is requested. The view is stateless, + * so there's no reason to create more than one. + */ + private transient Set> entrySet = null; + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The returned set obeys the general contract outlined in + * {@link Map#keySet()}. The set's iterator will return the keys + * in their natural order (the order in which the enum constants + * are declared). + * + * @return a set view of the keys contained in this enum map + */ + public Set keySet() { + Set ks = keySet; + if (ks != null) + return ks; + else + return keySet = new KeySet(); + } + + private class KeySet extends AbstractSet { + public Iterator iterator() { + return new KeyIterator(); + } + public int size() { + return size; + } + public boolean contains(Object o) { + return containsKey(o); + } + public boolean remove(Object o) { + int oldSize = size; + EnumMap.this.remove(o); + return size != oldSize; + } + public void clear() { + EnumMap.this.clear(); + } + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The returned collection obeys the general contract outlined in + * {@link Map#values()}. The collection's iterator will return the + * values in the order their corresponding keys appear in map, + * which is their natural order (the order in which the enum constants + * are declared). + * + * @return a collection view of the values contained in this map + */ + public Collection values() { + Collection vs = values; + if (vs != null) + return vs; + else + return values = new Values(); + } + + private class Values extends AbstractCollection { + public Iterator iterator() { + return new ValueIterator(); + } + public int size() { + return size; + } + public boolean contains(Object o) { + return containsValue(o); + } + public boolean remove(Object o) { + o = maskNull(o); + + for (int i = 0; i < vals.length; i++) { + if (o.equals(vals[i])) { + vals[i] = null; + size--; + return true; + } + } + return false; + } + public void clear() { + EnumMap.this.clear(); + } + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The returned set obeys the general contract outlined in + * {@link Map#keySet()}. The set's iterator will return the + * mappings in the order their keys appear in map, which is their + * natural order (the order in which the enum constants are declared). + * + * @return a set view of the mappings contained in this enum map + */ + public Set> entrySet() { + Set> es = entrySet; + if (es != null) + return es; + else + return entrySet = new EntrySet(); + } + + private class EntrySet extends AbstractSet> { + public Iterator> iterator() { + return new EntryIterator(); + } + + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry entry = (Map.Entry)o; + return containsMapping(entry.getKey(), entry.getValue()); + } + public boolean remove(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry entry = (Map.Entry)o; + return removeMapping(entry.getKey(), entry.getValue()); + } + public int size() { + return size; + } + public void clear() { + EnumMap.this.clear(); + } + public Object[] toArray() { + return fillEntryArray(new Object[size]); + } + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + int size = size(); + if (a.length < size) + a = (T[])java.lang.reflect.Array + .newInstance(a.getClass().getComponentType(), size); + if (a.length > size) + a[size] = null; + return (T[]) fillEntryArray(a); + } + private Object[] fillEntryArray(Object[] a) { + int j = 0; + for (int i = 0; i < vals.length; i++) + if (vals[i] != null) + a[j++] = new AbstractMap.SimpleEntry<>( + keyUniverse[i], unmaskNull(vals[i])); + return a; + } + } + + private abstract class EnumMapIterator implements Iterator { + // Lower bound on index of next element to return + int index = 0; + + // Index of last returned element, or -1 if none + int lastReturnedIndex = -1; + + public boolean hasNext() { + while (index < vals.length && vals[index] == null) + index++; + return index != vals.length; + } + + public void remove() { + checkLastReturnedIndex(); + + if (vals[lastReturnedIndex] != null) { + vals[lastReturnedIndex] = null; + size--; + } + lastReturnedIndex = -1; + } + + private void checkLastReturnedIndex() { + if (lastReturnedIndex < 0) + throw new IllegalStateException(); + } + } + + private class KeyIterator extends EnumMapIterator { + public K next() { + if (!hasNext()) + throw new NoSuchElementException(); + lastReturnedIndex = index++; + return keyUniverse[lastReturnedIndex]; + } + } + + private class ValueIterator extends EnumMapIterator { + public V next() { + if (!hasNext()) + throw new NoSuchElementException(); + lastReturnedIndex = index++; + return unmaskNull(vals[lastReturnedIndex]); + } + } + + private class EntryIterator extends EnumMapIterator> { + private Entry lastReturnedEntry = null; + + public Map.Entry next() { + if (!hasNext()) + throw new NoSuchElementException(); + lastReturnedEntry = new Entry(index++); + return lastReturnedEntry; + } + + public void remove() { + lastReturnedIndex = + ((null == lastReturnedEntry) ? -1 : lastReturnedEntry.index); + super.remove(); + lastReturnedEntry.index = lastReturnedIndex; + lastReturnedEntry = null; + } + + private class Entry implements Map.Entry { + private int index; + + private Entry(int index) { + this.index = index; + } + + public K getKey() { + checkIndexForEntryUse(); + return keyUniverse[index]; + } + + public V getValue() { + checkIndexForEntryUse(); + return unmaskNull(vals[index]); + } + + public V setValue(V value) { + checkIndexForEntryUse(); + V oldValue = unmaskNull(vals[index]); + vals[index] = maskNull(value); + return oldValue; + } + + public boolean equals(Object o) { + if (index < 0) + return o == this; + + if (!(o instanceof Map.Entry)) + return false; + + Map.Entry e = (Map.Entry)o; + V ourValue = unmaskNull(vals[index]); + Object hisValue = e.getValue(); + return (e.getKey() == keyUniverse[index] && + (ourValue == hisValue || + (ourValue != null && ourValue.equals(hisValue)))); + } + + public int hashCode() { + if (index < 0) + return super.hashCode(); + + return entryHashCode(index); + } + + public String toString() { + if (index < 0) + return super.toString(); + + return keyUniverse[index] + "=" + + unmaskNull(vals[index]); + } + + private void checkIndexForEntryUse() { + if (index < 0) + throw new IllegalStateException("Entry was removed"); + } + } + } + + // Comparison and hashing + + /** + * Compares the specified object with this map for equality. Returns + * true if the given object is also a map and the two maps + * represent the same mappings, as specified in the {@link + * Map#equals(Object)} contract. + * + * @param o the object to be compared for equality with this map + * @return true if the specified object is equal to this map + */ + public boolean equals(Object o) { + if (this == o) + return true; + if (o instanceof EnumMap) + return equals((EnumMap)o); + if (!(o instanceof Map)) + return false; + + Map m = (Map)o; + if (size != m.size()) + return false; + + for (int i = 0; i < keyUniverse.length; i++) { + if (null != vals[i]) { + K key = keyUniverse[i]; + V value = unmaskNull(vals[i]); + if (null == value) { + if (!((null == m.get(key)) && m.containsKey(key))) + return false; + } else { + if (!value.equals(m.get(key))) + return false; + } + } + } + + return true; + } + + private boolean equals(EnumMap em) { + if (em.keyType != keyType) + return size == 0 && em.size == 0; + + // Key types match, compare each value + for (int i = 0; i < keyUniverse.length; i++) { + Object ourValue = vals[i]; + Object hisValue = em.vals[i]; + if (hisValue != ourValue && + (hisValue == null || !hisValue.equals(ourValue))) + return false; + } + return true; + } + + /** + * Returns the hash code value for this map. The hash code of a map is + * defined to be the sum of the hash codes of each entry in the map. + */ + public int hashCode() { + int h = 0; + + for (int i = 0; i < keyUniverse.length; i++) { + if (null != vals[i]) { + h += entryHashCode(i); + } + } + + return h; + } + + private int entryHashCode(int index) { + return (keyUniverse[index].hashCode() ^ vals[index].hashCode()); + } + + /** + * Returns a shallow copy of this enum map. (The values themselves + * are not cloned. + * + * @return a shallow copy of this enum map + */ + public EnumMap clone() { + EnumMap result = null; + try { + result = (EnumMap) super.clone(); + } catch(CloneNotSupportedException e) { + throw new AssertionError(); + } + result.vals = result.vals.clone(); + return result; + } + + /** + * Throws an exception if e is not of the correct type for this enum set. + */ + private void typeCheck(K key) { + Class keyClass = key.getClass(); + if (keyClass != keyType && keyClass.getSuperclass() != keyType) + throw new ClassCastException(keyClass + " != " + keyType); + } + + /** + * Returns all of the values comprising K. + * The result is uncloned, cached, and shared by all callers. + */ + @JavaScriptBody(args = { "enumType" }, body = "return enumType.cnstr.$VALUES;") + private static native > K[] getKeyUniverse(Class keyType); + + private static final long serialVersionUID = 458661240069192865L; + + /** + * Save the state of the EnumMap instance to a stream (i.e., + * serialize it). + * + * @serialData The size of the enum map (the number of key-value + * mappings) is emitted (int), followed by the key (Object) + * and value (Object) for each key-value mapping represented + * by the enum map. + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException + { + // Write out the key type and any hidden stuff + s.defaultWriteObject(); + + // Write out size (number of Mappings) + s.writeInt(size); + + // Write out keys and values (alternating) + int entriesToBeWritten = size; + for (int i = 0; entriesToBeWritten > 0; i++) { + if (null != vals[i]) { + s.writeObject(keyUniverse[i]); + s.writeObject(unmaskNull(vals[i])); + entriesToBeWritten--; + } + } + } + + /** + * Reconstitute the EnumMap instance from a stream (i.e., + * deserialize it). + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException + { + // Read in the key type and any hidden stuff + s.defaultReadObject(); + + keyUniverse = getKeyUniverse(keyType); + vals = new Object[keyUniverse.length]; + + // Read in size (number of Mappings) + int size = s.readInt(); + + // Read the keys and values, and put the mappings in the HashMap + for (int i = 0; i < size; i++) { + K key = (K) s.readObject(); + V value = (V) s.readObject(); + put(key, value); + } + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/EnumSet.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/EnumSet.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util; + +import org.apidesign.bck2brwsr.core.JavaScriptBody; + +/** + * A specialized {@link Set} implementation for use with enum types. All of + * the elements in an enum set must come from a single enum type that is + * specified, explicitly or implicitly, when the set is created. Enum sets + * are represented internally as bit vectors. This representation is + * extremely compact and efficient. The space and time performance of this + * class should be good enough to allow its use as a high-quality, typesafe + * alternative to traditional int-based "bit flags." Even bulk + * operations (such as containsAll and retainAll) should + * run very quickly if their argument is also an enum set. + * + *

The iterator returned by the iterator method traverses the + * elements in their natural order (the order in which the enum + * constants are declared). The returned iterator is weakly + * consistent: it will never throw {@link ConcurrentModificationException} + * and it may or may not show the effects of any modifications to the set that + * occur while the iteration is in progress. + * + *

Null elements are not permitted. Attempts to insert a null element + * will throw {@link NullPointerException}. Attempts to test for the + * presence of a null element or to remove one will, however, function + * properly. + * + *

Like most collection implementations, EnumSet is not + * synchronized. If multiple threads access an enum set concurrently, and at + * least one of the threads modifies the set, it should be synchronized + * externally. This is typically accomplished by synchronizing on some + * object that naturally encapsulates the enum set. If no such object exists, + * the set should be "wrapped" using the {@link Collections#synchronizedSet} + * method. This is best done at creation time, to prevent accidental + * unsynchronized access: + * + *

+ * Set<MyEnum> s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));
+ * 
+ * + *

Implementation note: All basic operations execute in constant time. + * They are likely (though not guaranteed) to be much faster than their + * {@link HashSet} counterparts. Even bulk operations execute in + * constant time if their argument is also an enum set. + * + *

This class is a member of the + * + * Java Collections Framework. + * + * @author Josh Bloch + * @since 1.5 + * @see EnumMap + * @serial exclude + */ +public abstract class EnumSet> extends AbstractSet + implements Cloneable, java.io.Serializable +{ + /** + * The class of all the elements of this set. + */ + final Class elementType; + + /** + * All of the values comprising T. (Cached for performance.) + */ + final Enum[] universe; + + private static Enum[] ZERO_LENGTH_ENUM_ARRAY = new Enum[0]; + + EnumSet(ClasselementType, Enum[] universe) { + this.elementType = elementType; + this.universe = universe; + } + + /** + * Creates an empty enum set with the specified element type. + * + * @param elementType the class object of the element type for this enum + * set + * @throws NullPointerException if elementType is null + */ + public static > EnumSet noneOf(Class elementType) { + Enum[] universe = getUniverse(elementType); + if (universe == null) + throw new ClassCastException(elementType + " not an enum"); + + if (universe.length <= 64) + return new RegularEnumSet<>(elementType, universe); + else + return new JumboEnumSet<>(elementType, universe); + } + + /** + * Creates an enum set containing all of the elements in the specified + * element type. + * + * @param elementType the class object of the element type for this enum + * set + * @throws NullPointerException if elementType is null + */ + public static > EnumSet allOf(Class elementType) { + EnumSet result = noneOf(elementType); + result.addAll(); + return result; + } + + /** + * Adds all of the elements from the appropriate enum type to this enum + * set, which is empty prior to the call. + */ + abstract void addAll(); + + /** + * Creates an enum set with the same element type as the specified enum + * set, initially containing the same elements (if any). + * + * @param s the enum set from which to initialize this enum set + * @throws NullPointerException if s is null + */ + public static > EnumSet copyOf(EnumSet s) { + return s.clone(); + } + + /** + * Creates an enum set initialized from the specified collection. If + * the specified collection is an EnumSet instance, this static + * factory method behaves identically to {@link #copyOf(EnumSet)}. + * Otherwise, the specified collection must contain at least one element + * (in order to determine the new enum set's element type). + * + * @param c the collection from which to initialize this enum set + * @throws IllegalArgumentException if c is not an + * EnumSet instance and contains no elements + * @throws NullPointerException if c is null + */ + public static > EnumSet copyOf(Collection c) { + if (c instanceof EnumSet) { + return ((EnumSet)c).clone(); + } else { + if (c.isEmpty()) + throw new IllegalArgumentException("Collection is empty"); + Iterator i = c.iterator(); + E first = i.next(); + EnumSet result = EnumSet.of(first); + while (i.hasNext()) + result.add(i.next()); + return result; + } + } + + /** + * Creates an enum set with the same element type as the specified enum + * set, initially containing all the elements of this type that are + * not contained in the specified set. + * + * @param s the enum set from whose complement to initialize this enum set + * @throws NullPointerException if s is null + */ + public static > EnumSet complementOf(EnumSet s) { + EnumSet result = copyOf(s); + result.complement(); + return result; + } + + /** + * Creates an enum set initially containing the specified element. + * + * Overloadings of this method exist to initialize an enum set with + * one through five elements. A sixth overloading is provided that + * uses the varargs feature. This overloading may be used to create + * an enum set initially containing an arbitrary number of elements, but + * is likely to run slower than the overloadings that do not use varargs. + * + * @param e the element that this set is to contain initially + * @throws NullPointerException if e is null + * @return an enum set initially containing the specified element + */ + public static > EnumSet of(E e) { + EnumSet result = noneOf(e.getDeclaringClass()); + result.add(e); + return result; + } + + /** + * Creates an enum set initially containing the specified elements. + * + * Overloadings of this method exist to initialize an enum set with + * one through five elements. A sixth overloading is provided that + * uses the varargs feature. This overloading may be used to create + * an enum set initially containing an arbitrary number of elements, but + * is likely to run slower than the overloadings that do not use varargs. + * + * @param e1 an element that this set is to contain initially + * @param e2 another element that this set is to contain initially + * @throws NullPointerException if any parameters are null + * @return an enum set initially containing the specified elements + */ + public static > EnumSet of(E e1, E e2) { + EnumSet result = noneOf(e1.getDeclaringClass()); + result.add(e1); + result.add(e2); + return result; + } + + /** + * Creates an enum set initially containing the specified elements. + * + * Overloadings of this method exist to initialize an enum set with + * one through five elements. A sixth overloading is provided that + * uses the varargs feature. This overloading may be used to create + * an enum set initially containing an arbitrary number of elements, but + * is likely to run slower than the overloadings that do not use varargs. + * + * @param e1 an element that this set is to contain initially + * @param e2 another element that this set is to contain initially + * @param e3 another element that this set is to contain initially + * @throws NullPointerException if any parameters are null + * @return an enum set initially containing the specified elements + */ + public static > EnumSet of(E e1, E e2, E e3) { + EnumSet result = noneOf(e1.getDeclaringClass()); + result.add(e1); + result.add(e2); + result.add(e3); + return result; + } + + /** + * Creates an enum set initially containing the specified elements. + * + * Overloadings of this method exist to initialize an enum set with + * one through five elements. A sixth overloading is provided that + * uses the varargs feature. This overloading may be used to create + * an enum set initially containing an arbitrary number of elements, but + * is likely to run slower than the overloadings that do not use varargs. + * + * @param e1 an element that this set is to contain initially + * @param e2 another element that this set is to contain initially + * @param e3 another element that this set is to contain initially + * @param e4 another element that this set is to contain initially + * @throws NullPointerException if any parameters are null + * @return an enum set initially containing the specified elements + */ + public static > EnumSet of(E e1, E e2, E e3, E e4) { + EnumSet result = noneOf(e1.getDeclaringClass()); + result.add(e1); + result.add(e2); + result.add(e3); + result.add(e4); + return result; + } + + /** + * Creates an enum set initially containing the specified elements. + * + * Overloadings of this method exist to initialize an enum set with + * one through five elements. A sixth overloading is provided that + * uses the varargs feature. This overloading may be used to create + * an enum set initially containing an arbitrary number of elements, but + * is likely to run slower than the overloadings that do not use varargs. + * + * @param e1 an element that this set is to contain initially + * @param e2 another element that this set is to contain initially + * @param e3 another element that this set is to contain initially + * @param e4 another element that this set is to contain initially + * @param e5 another element that this set is to contain initially + * @throws NullPointerException if any parameters are null + * @return an enum set initially containing the specified elements + */ + public static > EnumSet of(E e1, E e2, E e3, E e4, + E e5) + { + EnumSet result = noneOf(e1.getDeclaringClass()); + result.add(e1); + result.add(e2); + result.add(e3); + result.add(e4); + result.add(e5); + return result; + } + + /** + * Creates an enum set initially containing the specified elements. + * This factory, whose parameter list uses the varargs feature, may + * be used to create an enum set initially containing an arbitrary + * number of elements, but it is likely to run slower than the overloadings + * that do not use varargs. + * + * @param first an element that the set is to contain initially + * @param rest the remaining elements the set is to contain initially + * @throws NullPointerException if any of the specified elements are null, + * or if rest is null + * @return an enum set initially containing the specified elements + */ + @SafeVarargs + public static > EnumSet of(E first, E... rest) { + EnumSet result = noneOf(first.getDeclaringClass()); + result.add(first); + for (E e : rest) + result.add(e); + return result; + } + + /** + * Creates an enum set initially containing all of the elements in the + * range defined by the two specified endpoints. The returned set will + * contain the endpoints themselves, which may be identical but must not + * be out of order. + * + * @param from the first element in the range + * @param to the last element in the range + * @throws NullPointerException if {@code from} or {@code to} are null + * @throws IllegalArgumentException if {@code from.compareTo(to) > 0} + * @return an enum set initially containing all of the elements in the + * range defined by the two specified endpoints + */ + public static > EnumSet range(E from, E to) { + if (from.compareTo(to) > 0) + throw new IllegalArgumentException(from + " > " + to); + EnumSet result = noneOf(from.getDeclaringClass()); + result.addRange(from, to); + return result; + } + + /** + * Adds the specified range to this enum set, which is empty prior + * to the call. + */ + abstract void addRange(E from, E to); + + /** + * Returns a copy of this set. + * + * @return a copy of this set + */ + public EnumSet clone() { + try { + return (EnumSet) super.clone(); + } catch(CloneNotSupportedException e) { + throw new AssertionError(e); + } + } + + /** + * Complements the contents of this enum set. + */ + abstract void complement(); + + /** + * Throws an exception if e is not of the correct type for this enum set. + */ + final void typeCheck(E e) { + Class eClass = e.getClass(); + if (eClass != elementType && eClass.getSuperclass() != elementType) + throw new ClassCastException(eClass + " != " + elementType); + } + + /** + * Returns all of the values comprising E. + * The result is uncloned, cached, and shared by all callers. + */ + @JavaScriptBody(args = { "enumType" }, body = "return enumType.cnstr.$VALUES;") + private static native > E[] getUniverse(Class elementType); + + /** + * This class is used to serialize all EnumSet instances, regardless of + * implementation type. It captures their "logical contents" and they + * are reconstructed using public static factories. This is necessary + * to ensure that the existence of a particular implementation type is + * an implementation detail. + * + * @serial include + */ + private static class SerializationProxy > + implements java.io.Serializable + { + /** + * The element type of this enum set. + * + * @serial + */ + private final Class elementType; + + /** + * The elements contained in this enum set. + * + * @serial + */ + private final Enum[] elements; + + SerializationProxy(EnumSet set) { + elementType = set.elementType; + elements = set.toArray(ZERO_LENGTH_ENUM_ARRAY); + } + + private Object readResolve() { + EnumSet result = EnumSet.noneOf(elementType); + for (Enum e : elements) + result.add((E)e); + return result; + } + + private static final long serialVersionUID = 362491234563181265L; + } + + Object writeReplace() { + return new SerializationProxy<>(this); + } + + // readObject method for the serialization proxy pattern + // See Effective Java, Second Ed., Item 78. + private void readObject(java.io.ObjectInputStream stream) + throws java.io.InvalidObjectException { + throw new java.io.InvalidObjectException("Proxy required"); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/IdentityHashMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/IdentityHashMap.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,1243 @@ +/* + * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util; +import java.io.*; + +/** + * This class implements the Map interface with a hash table, using + * reference-equality in place of object-equality when comparing keys (and + * values). In other words, in an IdentityHashMap, two keys + * k1 and k2 are considered equal if and only if + * (k1==k2). (In normal Map implementations (like + * HashMap) two keys k1 and k2 are considered equal + * if and only if (k1==null ? k2==null : k1.equals(k2)).) + * + *

This class is not a general-purpose Map + * implementation! While this class implements the Map interface, it + * intentionally violates Map's general contract, which mandates the + * use of the equals method when comparing objects. This class is + * designed for use only in the rare cases wherein reference-equality + * semantics are required. + * + *

A typical use of this class is topology-preserving object graph + * transformations, such as serialization or deep-copying. To perform such + * a transformation, a program must maintain a "node table" that keeps track + * of all the object references that have already been processed. The node + * table must not equate distinct objects even if they happen to be equal. + * Another typical use of this class is to maintain proxy objects. For + * example, a debugging facility might wish to maintain a proxy object for + * each object in the program being debugged. + * + *

This class provides all of the optional map operations, and permits + * null values and the null key. This class makes no + * guarantees as to the order of the map; in particular, it does not guarantee + * that the order will remain constant over time. + * + *

This class provides constant-time performance for the basic + * operations (get and put), assuming the system + * identity hash function ({@link System#identityHashCode(Object)}) + * disperses elements properly among the buckets. + * + *

This class has one tuning parameter (which affects performance but not + * semantics): expected maximum size. This parameter is the maximum + * number of key-value mappings that the map is expected to hold. Internally, + * this parameter is used to determine the number of buckets initially + * comprising the hash table. The precise relationship between the expected + * maximum size and the number of buckets is unspecified. + * + *

If the size of the map (the number of key-value mappings) sufficiently + * exceeds the expected maximum size, the number of buckets is increased + * Increasing the number of buckets ("rehashing") may be fairly expensive, so + * it pays to create identity hash maps with a sufficiently large expected + * maximum size. On the other hand, iteration over collection views requires + * time proportional to the number of buckets in the hash table, so it + * pays not to set the expected maximum size too high if you are especially + * concerned with iteration performance or memory usage. + * + *

Note that this implementation is not synchronized. + * If multiple threads access an identity hash map concurrently, and at + * least one of the threads modifies the map structurally, it must + * be synchronized externally. (A structural modification is any operation + * that adds or deletes one or more mappings; merely changing the value + * associated with a key that an instance already contains is not a + * structural modification.) This is typically accomplished by + * synchronizing on some object that naturally encapsulates the map. + * + * If no such object exists, the map should be "wrapped" using the + * {@link Collections#synchronizedMap Collections.synchronizedMap} + * method. This is best done at creation time, to prevent accidental + * unsynchronized access to the map:

+ *   Map m = Collections.synchronizedMap(new IdentityHashMap(...));
+ * + *

The iterators returned by the iterator method of the + * collections returned by all of this class's "collection view + * methods" are fail-fast: if the map is structurally modified + * at any time after the iterator is created, in any way except + * through the iterator's own remove method, the iterator + * will throw a {@link ConcurrentModificationException}. Thus, in the + * face of concurrent modification, the iterator fails quickly and + * cleanly, rather than risking arbitrary, non-deterministic behavior + * at an undetermined time in the future. + * + *

Note that the fail-fast behavior of an iterator cannot be guaranteed + * as it is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators + * throw ConcurrentModificationException on a best-effort basis. + * Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: fail-fast iterators should be used only + * to detect bugs. + * + *

Implementation note: This is a simple linear-probe hash table, + * as described for example in texts by Sedgewick and Knuth. The array + * alternates holding keys and values. (This has better locality for large + * tables than does using separate arrays.) For many JRE implementations + * and operation mixes, this class will yield better performance than + * {@link HashMap} (which uses chaining rather than linear-probing). + * + *

This class is a member of the + * + * Java Collections Framework. + * + * @see System#identityHashCode(Object) + * @see Object#hashCode() + * @see Collection + * @see Map + * @see HashMap + * @see TreeMap + * @author Doug Lea and Josh Bloch + * @since 1.4 + */ + +public class IdentityHashMap + extends AbstractMap + implements Map, java.io.Serializable, Cloneable +{ + /** + * The initial capacity used by the no-args constructor. + * MUST be a power of two. The value 32 corresponds to the + * (specified) expected maximum size of 21, given a load factor + * of 2/3. + */ + private static final int DEFAULT_CAPACITY = 32; + + /** + * The minimum capacity, used if a lower value is implicitly specified + * by either of the constructors with arguments. The value 4 corresponds + * to an expected maximum size of 2, given a load factor of 2/3. + * MUST be a power of two. + */ + private static final int MINIMUM_CAPACITY = 4; + + /** + * The maximum capacity, used if a higher value is implicitly specified + * by either of the constructors with arguments. + * MUST be a power of two <= 1<<29. + */ + private static final int MAXIMUM_CAPACITY = 1 << 29; + + /** + * The table, resized as necessary. Length MUST always be a power of two. + */ + private transient Object[] table; + + /** + * The number of key-value mappings contained in this identity hash map. + * + * @serial + */ + private int size; + + /** + * The number of modifications, to support fast-fail iterators + */ + private transient int modCount; + + /** + * The next size value at which to resize (capacity * load factor). + */ + private transient int threshold; + + /** + * Value representing null keys inside tables. + */ + private static final Object NULL_KEY = new Object(); + + /** + * Use NULL_KEY for key if it is null. + */ + private static Object maskNull(Object key) { + return (key == null ? NULL_KEY : key); + } + + /** + * Returns internal representation of null key back to caller as null. + */ + private static Object unmaskNull(Object key) { + return (key == NULL_KEY ? null : key); + } + + /** + * Constructs a new, empty identity hash map with a default expected + * maximum size (21). + */ + public IdentityHashMap() { + init(DEFAULT_CAPACITY); + } + + /** + * Constructs a new, empty map with the specified expected maximum size. + * Putting more than the expected number of key-value mappings into + * the map may cause the internal data structure to grow, which may be + * somewhat time-consuming. + * + * @param expectedMaxSize the expected maximum size of the map + * @throws IllegalArgumentException if expectedMaxSize is negative + */ + public IdentityHashMap(int expectedMaxSize) { + if (expectedMaxSize < 0) + throw new IllegalArgumentException("expectedMaxSize is negative: " + + expectedMaxSize); + init(capacity(expectedMaxSize)); + } + + /** + * Returns the appropriate capacity for the specified expected maximum + * size. Returns the smallest power of two between MINIMUM_CAPACITY + * and MAXIMUM_CAPACITY, inclusive, that is greater than + * (3 * expectedMaxSize)/2, if such a number exists. Otherwise + * returns MAXIMUM_CAPACITY. If (3 * expectedMaxSize)/2 is negative, it + * is assumed that overflow has occurred, and MAXIMUM_CAPACITY is returned. + */ + private int capacity(int expectedMaxSize) { + // Compute min capacity for expectedMaxSize given a load factor of 2/3 + int minCapacity = (3 * expectedMaxSize)/2; + + // Compute the appropriate capacity + int result; + if (minCapacity > MAXIMUM_CAPACITY || minCapacity < 0) { + result = MAXIMUM_CAPACITY; + } else { + result = MINIMUM_CAPACITY; + while (result < minCapacity) + result <<= 1; + } + return result; + } + + /** + * Initializes object to be an empty map with the specified initial + * capacity, which is assumed to be a power of two between + * MINIMUM_CAPACITY and MAXIMUM_CAPACITY inclusive. + */ + private void init(int initCapacity) { + // assert (initCapacity & -initCapacity) == initCapacity; // power of 2 + // assert initCapacity >= MINIMUM_CAPACITY; + // assert initCapacity <= MAXIMUM_CAPACITY; + + threshold = (initCapacity * 2)/3; + table = new Object[2 * initCapacity]; + } + + /** + * Constructs a new identity hash map containing the keys-value mappings + * in the specified map. + * + * @param m the map whose mappings are to be placed into this map + * @throws NullPointerException if the specified map is null + */ + public IdentityHashMap(Map m) { + // Allow for a bit of growth + this((int) ((1 + m.size()) * 1.1)); + putAll(m); + } + + /** + * Returns the number of key-value mappings in this identity hash map. + * + * @return the number of key-value mappings in this map + */ + public int size() { + return size; + } + + /** + * Returns true if this identity hash map contains no key-value + * mappings. + * + * @return true if this identity hash map contains no key-value + * mappings + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Returns index for Object x. + */ + private static int hash(Object x, int length) { + int h = System.identityHashCode(x); + // Multiply by -127, and left-shift to use least bit as part of hash + return ((h << 1) - (h << 8)) & (length - 1); + } + + /** + * Circularly traverses table of size len. + */ + private static int nextKeyIndex(int i, int len) { + return (i + 2 < len ? i + 2 : 0); + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code (key == k)}, + * then this method returns {@code v}; otherwise it returns + * {@code null}. (There can be at most one such mapping.) + * + *

A return value of {@code null} does not necessarily + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to {@code null}. + * The {@link #containsKey containsKey} operation may be used to + * distinguish these two cases. + * + * @see #put(Object, Object) + */ + public V get(Object key) { + Object k = maskNull(key); + Object[] tab = table; + int len = tab.length; + int i = hash(k, len); + while (true) { + Object item = tab[i]; + if (item == k) + return (V) tab[i + 1]; + if (item == null) + return null; + i = nextKeyIndex(i, len); + } + } + + /** + * Tests whether the specified object reference is a key in this identity + * hash map. + * + * @param key possible key + * @return true if the specified object reference is a key + * in this map + * @see #containsValue(Object) + */ + public boolean containsKey(Object key) { + Object k = maskNull(key); + Object[] tab = table; + int len = tab.length; + int i = hash(k, len); + while (true) { + Object item = tab[i]; + if (item == k) + return true; + if (item == null) + return false; + i = nextKeyIndex(i, len); + } + } + + /** + * Tests whether the specified object reference is a value in this identity + * hash map. + * + * @param value value whose presence in this map is to be tested + * @return true if this map maps one or more keys to the + * specified object reference + * @see #containsKey(Object) + */ + public boolean containsValue(Object value) { + Object[] tab = table; + for (int i = 1; i < tab.length; i += 2) + if (tab[i] == value && tab[i - 1] != null) + return true; + + return false; + } + + /** + * Tests if the specified key-value mapping is in the map. + * + * @param key possible key + * @param value possible value + * @return true if and only if the specified key-value + * mapping is in the map + */ + private boolean containsMapping(Object key, Object value) { + Object k = maskNull(key); + Object[] tab = table; + int len = tab.length; + int i = hash(k, len); + while (true) { + Object item = tab[i]; + if (item == k) + return tab[i + 1] == value; + if (item == null) + return false; + i = nextKeyIndex(i, len); + } + } + + /** + * Associates the specified value with the specified key in this identity + * hash map. If the map previously contained a mapping for the key, the + * old value is replaced. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + * @see Object#equals(Object) + * @see #get(Object) + * @see #containsKey(Object) + */ + public V put(K key, V value) { + Object k = maskNull(key); + Object[] tab = table; + int len = tab.length; + int i = hash(k, len); + + Object item; + while ( (item = tab[i]) != null) { + if (item == k) { + V oldValue = (V) tab[i + 1]; + tab[i + 1] = value; + return oldValue; + } + i = nextKeyIndex(i, len); + } + + modCount++; + tab[i] = k; + tab[i + 1] = value; + if (++size >= threshold) + resize(len); // len == 2 * current capacity. + return null; + } + + /** + * Resize the table to hold given capacity. + * + * @param newCapacity the new capacity, must be a power of two. + */ + private void resize(int newCapacity) { + // assert (newCapacity & -newCapacity) == newCapacity; // power of 2 + int newLength = newCapacity * 2; + + Object[] oldTable = table; + int oldLength = oldTable.length; + if (oldLength == 2*MAXIMUM_CAPACITY) { // can't expand any further + if (threshold == MAXIMUM_CAPACITY-1) + throw new IllegalStateException("Capacity exhausted."); + threshold = MAXIMUM_CAPACITY-1; // Gigantic map! + return; + } + if (oldLength >= newLength) + return; + + Object[] newTable = new Object[newLength]; + threshold = newLength / 3; + + for (int j = 0; j < oldLength; j += 2) { + Object key = oldTable[j]; + if (key != null) { + Object value = oldTable[j+1]; + oldTable[j] = null; + oldTable[j+1] = null; + int i = hash(key, newLength); + while (newTable[i] != null) + i = nextKeyIndex(i, newLength); + newTable[i] = key; + newTable[i + 1] = value; + } + } + table = newTable; + } + + /** + * Copies all of the mappings from the specified map to this map. + * These mappings will replace any mappings that this map had for + * any of the keys currently in the specified map. + * + * @param m mappings to be stored in this map + * @throws NullPointerException if the specified map is null + */ + public void putAll(Map m) { + int n = m.size(); + if (n == 0) + return; + if (n > threshold) // conservatively pre-expand + resize(capacity(n)); + + for (Entry e : m.entrySet()) + put(e.getKey(), e.getValue()); + } + + /** + * Removes the mapping for this key from this map if present. + * + * @param key key whose mapping is to be removed from the map + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + */ + public V remove(Object key) { + Object k = maskNull(key); + Object[] tab = table; + int len = tab.length; + int i = hash(k, len); + + while (true) { + Object item = tab[i]; + if (item == k) { + modCount++; + size--; + V oldValue = (V) tab[i + 1]; + tab[i + 1] = null; + tab[i] = null; + closeDeletion(i); + return oldValue; + } + if (item == null) + return null; + i = nextKeyIndex(i, len); + } + + } + + /** + * Removes the specified key-value mapping from the map if it is present. + * + * @param key possible key + * @param value possible value + * @return true if and only if the specified key-value + * mapping was in the map + */ + private boolean removeMapping(Object key, Object value) { + Object k = maskNull(key); + Object[] tab = table; + int len = tab.length; + int i = hash(k, len); + + while (true) { + Object item = tab[i]; + if (item == k) { + if (tab[i + 1] != value) + return false; + modCount++; + size--; + tab[i] = null; + tab[i + 1] = null; + closeDeletion(i); + return true; + } + if (item == null) + return false; + i = nextKeyIndex(i, len); + } + } + + /** + * Rehash all possibly-colliding entries following a + * deletion. This preserves the linear-probe + * collision properties required by get, put, etc. + * + * @param d the index of a newly empty deleted slot + */ + private void closeDeletion(int d) { + // Adapted from Knuth Section 6.4 Algorithm R + Object[] tab = table; + int len = tab.length; + + // Look for items to swap into newly vacated slot + // starting at index immediately following deletion, + // and continuing until a null slot is seen, indicating + // the end of a run of possibly-colliding keys. + Object item; + for (int i = nextKeyIndex(d, len); (item = tab[i]) != null; + i = nextKeyIndex(i, len) ) { + // The following test triggers if the item at slot i (which + // hashes to be at slot r) should take the spot vacated by d. + // If so, we swap it in, and then continue with d now at the + // newly vacated i. This process will terminate when we hit + // the null slot at the end of this run. + // The test is messy because we are using a circular table. + int r = hash(item, len); + if ((i < r && (r <= d || d <= i)) || (r <= d && d <= i)) { + tab[d] = item; + tab[d + 1] = tab[i + 1]; + tab[i] = null; + tab[i + 1] = null; + d = i; + } + } + } + + /** + * Removes all of the mappings from this map. + * The map will be empty after this call returns. + */ + public void clear() { + modCount++; + Object[] tab = table; + for (int i = 0; i < tab.length; i++) + tab[i] = null; + size = 0; + } + + /** + * Compares the specified object with this map for equality. Returns + * true if the given object is also a map and the two maps + * represent identical object-reference mappings. More formally, this + * map is equal to another map m if and only if + * this.entrySet().equals(m.entrySet()). + * + *

Owing to the reference-equality-based semantics of this map it is + * possible that the symmetry and transitivity requirements of the + * Object.equals contract may be violated if this map is compared + * to a normal map. However, the Object.equals contract is + * guaranteed to hold among IdentityHashMap instances. + * + * @param o object to be compared for equality with this map + * @return true if the specified object is equal to this map + * @see Object#equals(Object) + */ + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof IdentityHashMap) { + IdentityHashMap m = (IdentityHashMap) o; + if (m.size() != size) + return false; + + Object[] tab = m.table; + for (int i = 0; i < tab.length; i+=2) { + Object k = tab[i]; + if (k != null && !containsMapping(k, tab[i + 1])) + return false; + } + return true; + } else if (o instanceof Map) { + Map m = (Map)o; + return entrySet().equals(m.entrySet()); + } else { + return false; // o is not a Map + } + } + + /** + * Returns the hash code value for this map. The hash code of a map is + * defined to be the sum of the hash codes of each entry in the map's + * entrySet() view. This ensures that m1.equals(m2) + * implies that m1.hashCode()==m2.hashCode() for any two + * IdentityHashMap instances m1 and m2, as + * required by the general contract of {@link Object#hashCode}. + * + *

Owing to the reference-equality-based semantics of the + * Map.Entry instances in the set returned by this map's + * entrySet method, it is possible that the contractual + * requirement of Object.hashCode mentioned in the previous + * paragraph will be violated if one of the two objects being compared is + * an IdentityHashMap instance and the other is a normal map. + * + * @return the hash code value for this map + * @see Object#equals(Object) + * @see #equals(Object) + */ + public int hashCode() { + int result = 0; + Object[] tab = table; + for (int i = 0; i < tab.length; i +=2) { + Object key = tab[i]; + if (key != null) { + Object k = unmaskNull(key); + result += System.identityHashCode(k) ^ + System.identityHashCode(tab[i + 1]); + } + } + return result; + } + + /** + * Returns a shallow copy of this identity hash map: the keys and values + * themselves are not cloned. + * + * @return a shallow copy of this map + */ + public Object clone() { + try { + IdentityHashMap m = (IdentityHashMap) super.clone(); + m.entrySet = null; + m.table = table.clone(); + return m; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + private abstract class IdentityHashMapIterator implements Iterator { + int index = (size != 0 ? 0 : table.length); // current slot. + int expectedModCount = modCount; // to support fast-fail + int lastReturnedIndex = -1; // to allow remove() + boolean indexValid; // To avoid unnecessary next computation + Object[] traversalTable = table; // reference to main table or copy + + public boolean hasNext() { + Object[] tab = traversalTable; + for (int i = index; i < tab.length; i+=2) { + Object key = tab[i]; + if (key != null) { + index = i; + return indexValid = true; + } + } + index = tab.length; + return false; + } + + protected int nextIndex() { + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + if (!indexValid && !hasNext()) + throw new NoSuchElementException(); + + indexValid = false; + lastReturnedIndex = index; + index += 2; + return lastReturnedIndex; + } + + public void remove() { + if (lastReturnedIndex == -1) + throw new IllegalStateException(); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + + expectedModCount = ++modCount; + int deletedSlot = lastReturnedIndex; + lastReturnedIndex = -1; + // back up index to revisit new contents after deletion + index = deletedSlot; + indexValid = false; + + // Removal code proceeds as in closeDeletion except that + // it must catch the rare case where an element already + // seen is swapped into a vacant slot that will be later + // traversed by this iterator. We cannot allow future + // next() calls to return it again. The likelihood of + // this occurring under 2/3 load factor is very slim, but + // when it does happen, we must make a copy of the rest of + // the table to use for the rest of the traversal. Since + // this can only happen when we are near the end of the table, + // even in these rare cases, this is not very expensive in + // time or space. + + Object[] tab = traversalTable; + int len = tab.length; + + int d = deletedSlot; + K key = (K) tab[d]; + tab[d] = null; // vacate the slot + tab[d + 1] = null; + + // If traversing a copy, remove in real table. + // We can skip gap-closure on copy. + if (tab != IdentityHashMap.this.table) { + IdentityHashMap.this.remove(key); + expectedModCount = modCount; + return; + } + + size--; + + Object item; + for (int i = nextKeyIndex(d, len); (item = tab[i]) != null; + i = nextKeyIndex(i, len)) { + int r = hash(item, len); + // See closeDeletion for explanation of this conditional + if ((i < r && (r <= d || d <= i)) || + (r <= d && d <= i)) { + + // If we are about to swap an already-seen element + // into a slot that may later be returned by next(), + // then clone the rest of table for use in future + // next() calls. It is OK that our copy will have + // a gap in the "wrong" place, since it will never + // be used for searching anyway. + + if (i < deletedSlot && d >= deletedSlot && + traversalTable == IdentityHashMap.this.table) { + int remaining = len - deletedSlot; + Object[] newTable = new Object[remaining]; + System.arraycopy(tab, deletedSlot, + newTable, 0, remaining); + traversalTable = newTable; + index = 0; + } + + tab[d] = item; + tab[d + 1] = tab[i + 1]; + tab[i] = null; + tab[i + 1] = null; + d = i; + } + } + } + } + + private class KeyIterator extends IdentityHashMapIterator { + public K next() { + return (K) unmaskNull(traversalTable[nextIndex()]); + } + } + + private class ValueIterator extends IdentityHashMapIterator { + public V next() { + return (V) traversalTable[nextIndex() + 1]; + } + } + + private class EntryIterator + extends IdentityHashMapIterator> + { + private Entry lastReturnedEntry = null; + + public Map.Entry next() { + lastReturnedEntry = new Entry(nextIndex()); + return lastReturnedEntry; + } + + public void remove() { + lastReturnedIndex = + ((null == lastReturnedEntry) ? -1 : lastReturnedEntry.index); + super.remove(); + lastReturnedEntry.index = lastReturnedIndex; + lastReturnedEntry = null; + } + + private class Entry implements Map.Entry { + private int index; + + private Entry(int index) { + this.index = index; + } + + public K getKey() { + checkIndexForEntryUse(); + return (K) unmaskNull(traversalTable[index]); + } + + public V getValue() { + checkIndexForEntryUse(); + return (V) traversalTable[index+1]; + } + + public V setValue(V value) { + checkIndexForEntryUse(); + V oldValue = (V) traversalTable[index+1]; + traversalTable[index+1] = value; + // if shadowing, force into main table + if (traversalTable != IdentityHashMap.this.table) + put((K) traversalTable[index], value); + return oldValue; + } + + public boolean equals(Object o) { + if (index < 0) + return super.equals(o); + + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + return (e.getKey() == unmaskNull(traversalTable[index]) && + e.getValue() == traversalTable[index+1]); + } + + public int hashCode() { + if (lastReturnedIndex < 0) + return super.hashCode(); + + return (System.identityHashCode(unmaskNull(traversalTable[index])) ^ + System.identityHashCode(traversalTable[index+1])); + } + + public String toString() { + if (index < 0) + return super.toString(); + + return (unmaskNull(traversalTable[index]) + "=" + + traversalTable[index+1]); + } + + private void checkIndexForEntryUse() { + if (index < 0) + throw new IllegalStateException("Entry was removed"); + } + } + } + + // Views + + /** + * This field is initialized to contain an instance of the entry set + * view the first time this view is requested. The view is stateless, + * so there's no reason to create more than one. + */ + private transient Set> entrySet = null; + + /** + * Returns an identity-based set view of the keys contained in this map. + * The set is backed by the map, so changes to the map are reflected in + * the set, and vice-versa. If the map is modified while an iteration + * over the set is in progress, the results of the iteration are + * undefined. The set supports element removal, which removes the + * corresponding mapping from the map, via the Iterator.remove, + * Set.remove, removeAll, retainAll, and + * clear methods. It does not support the add or + * addAll methods. + * + *

While the object returned by this method implements the + * Set interface, it does not obey Set's general + * contract. Like its backing map, the set returned by this method + * defines element equality as reference-equality rather than + * object-equality. This affects the behavior of its contains, + * remove, containsAll, equals, and + * hashCode methods. + * + *

The equals method of the returned set returns true + * only if the specified object is a set containing exactly the same + * object references as the returned set. The symmetry and transitivity + * requirements of the Object.equals contract may be violated if + * the set returned by this method is compared to a normal set. However, + * the Object.equals contract is guaranteed to hold among sets + * returned by this method. + * + *

The hashCode method of the returned set returns the sum of + * the identity hashcodes of the elements in the set, rather than + * the sum of their hashcodes. This is mandated by the change in the + * semantics of the equals method, in order to enforce the + * general contract of the Object.hashCode method among sets + * returned by this method. + * + * @return an identity-based set view of the keys contained in this map + * @see Object#equals(Object) + * @see System#identityHashCode(Object) + */ + public Set keySet() { + Set ks = keySet; + if (ks != null) + return ks; + else + return keySet = new KeySet(); + } + + private class KeySet extends AbstractSet { + public Iterator iterator() { + return new KeyIterator(); + } + public int size() { + return size; + } + public boolean contains(Object o) { + return containsKey(o); + } + public boolean remove(Object o) { + int oldSize = size; + IdentityHashMap.this.remove(o); + return size != oldSize; + } + /* + * Must revert from AbstractSet's impl to AbstractCollection's, as + * the former contains an optimization that results in incorrect + * behavior when c is a smaller "normal" (non-identity-based) Set. + */ + public boolean removeAll(Collection c) { + boolean modified = false; + for (Iterator i = iterator(); i.hasNext(); ) { + if (c.contains(i.next())) { + i.remove(); + modified = true; + } + } + return modified; + } + public void clear() { + IdentityHashMap.this.clear(); + } + public int hashCode() { + int result = 0; + for (K key : this) + result += System.identityHashCode(key); + return result; + } + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. If the map is + * modified while an iteration over the collection is in progress, + * the results of the iteration are undefined. The collection + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Collection.remove, removeAll, + * retainAll and clear methods. It does not + * support the add or addAll methods. + * + *

While the object returned by this method implements the + * Collection interface, it does not obey + * Collection's general contract. Like its backing map, + * the collection returned by this method defines element equality as + * reference-equality rather than object-equality. This affects the + * behavior of its contains, remove and + * containsAll methods. + */ + public Collection values() { + Collection vs = values; + if (vs != null) + return vs; + else + return values = new Values(); + } + + private class Values extends AbstractCollection { + public Iterator iterator() { + return new ValueIterator(); + } + public int size() { + return size; + } + public boolean contains(Object o) { + return containsValue(o); + } + public boolean remove(Object o) { + for (Iterator i = iterator(); i.hasNext(); ) { + if (i.next() == o) { + i.remove(); + return true; + } + } + return false; + } + public void clear() { + IdentityHashMap.this.clear(); + } + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * Each element in the returned set is a reference-equality-based + * Map.Entry. The set is backed by the map, so changes + * to the map are reflected in the set, and vice-versa. If the + * map is modified while an iteration over the set is in progress, + * the results of the iteration are undefined. The set supports + * element removal, which removes the corresponding mapping from + * the map, via the Iterator.remove, Set.remove, + * removeAll, retainAll and clear + * methods. It does not support the add or + * addAll methods. + * + *

Like the backing map, the Map.Entry objects in the set + * returned by this method define key and value equality as + * reference-equality rather than object-equality. This affects the + * behavior of the equals and hashCode methods of these + * Map.Entry objects. A reference-equality based Map.Entry + * e is equal to an object o if and only if o is a + * Map.Entry and e.getKey()==o.getKey() && + * e.getValue()==o.getValue(). To accommodate these equals + * semantics, the hashCode method returns + * System.identityHashCode(e.getKey()) ^ + * System.identityHashCode(e.getValue()). + * + *

Owing to the reference-equality-based semantics of the + * Map.Entry instances in the set returned by this method, + * it is possible that the symmetry and transitivity requirements of + * the {@link Object#equals(Object)} contract may be violated if any of + * the entries in the set is compared to a normal map entry, or if + * the set returned by this method is compared to a set of normal map + * entries (such as would be returned by a call to this method on a normal + * map). However, the Object.equals contract is guaranteed to + * hold among identity-based map entries, and among sets of such entries. + * + * + * @return a set view of the identity-mappings contained in this map + */ + public Set> entrySet() { + Set> es = entrySet; + if (es != null) + return es; + else + return entrySet = new EntrySet(); + } + + private class EntrySet extends AbstractSet> { + public Iterator> iterator() { + return new EntryIterator(); + } + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry entry = (Map.Entry)o; + return containsMapping(entry.getKey(), entry.getValue()); + } + public boolean remove(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry entry = (Map.Entry)o; + return removeMapping(entry.getKey(), entry.getValue()); + } + public int size() { + return size; + } + public void clear() { + IdentityHashMap.this.clear(); + } + /* + * Must revert from AbstractSet's impl to AbstractCollection's, as + * the former contains an optimization that results in incorrect + * behavior when c is a smaller "normal" (non-identity-based) Set. + */ + public boolean removeAll(Collection c) { + boolean modified = false; + for (Iterator> i = iterator(); i.hasNext(); ) { + if (c.contains(i.next())) { + i.remove(); + modified = true; + } + } + return modified; + } + + public Object[] toArray() { + int size = size(); + Object[] result = new Object[size]; + Iterator> it = iterator(); + for (int i = 0; i < size; i++) + result[i] = new AbstractMap.SimpleEntry<>(it.next()); + return result; + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + int size = size(); + if (a.length < size) + a = (T[])java.lang.reflect.Array + .newInstance(a.getClass().getComponentType(), size); + Iterator> it = iterator(); + for (int i = 0; i < size; i++) + a[i] = (T) new AbstractMap.SimpleEntry<>(it.next()); + if (a.length > size) + a[size] = null; + return a; + } + } + + + private static final long serialVersionUID = 8188218128353913216L; + + /** + * Save the state of the IdentityHashMap instance to a stream + * (i.e., serialize it). + * + * @serialData The size of the HashMap (the number of key-value + * mappings) (int), followed by the key (Object) and + * value (Object) for each key-value mapping represented by the + * IdentityHashMap. The key-value mappings are emitted in no + * particular order. + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + // Write out and any hidden stuff + s.defaultWriteObject(); + + // Write out size (number of Mappings) + s.writeInt(size); + + // Write out keys and values (alternating) + Object[] tab = table; + for (int i = 0; i < tab.length; i += 2) { + Object key = tab[i]; + if (key != null) { + s.writeObject(unmaskNull(key)); + s.writeObject(tab[i + 1]); + } + } + } + + /** + * Reconstitute the IdentityHashMap instance from a stream (i.e., + * deserialize it). + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + // Read in any hidden stuff + s.defaultReadObject(); + + // Read in size (number of Mappings) + int size = s.readInt(); + + // Allow for 33% growth (i.e., capacity is >= 2* size()). + init(capacity((size*4)/3)); + + // Read the keys and values, and put the mappings in the table + for (int i=0; i> extends EnumSet { + private static final long serialVersionUID = 334349849919042784L; + + /** + * Bit vector representation of this set. The ith bit of the jth + * element of this array represents the presence of universe[64*j +i] + * in this set. + */ + private long elements[]; + + // Redundant - maintained for performance + private int size = 0; + + JumboEnumSet(ClasselementType, Enum[] universe) { + super(elementType, universe); + elements = new long[(universe.length + 63) >>> 6]; + } + + void addRange(E from, E to) { + int fromIndex = from.ordinal() >>> 6; + int toIndex = to.ordinal() >>> 6; + + if (fromIndex == toIndex) { + elements[fromIndex] = (-1L >>> (from.ordinal() - to.ordinal() - 1)) + << from.ordinal(); + } else { + elements[fromIndex] = (-1L << from.ordinal()); + for (int i = fromIndex + 1; i < toIndex; i++) + elements[i] = -1; + elements[toIndex] = -1L >>> (63 - to.ordinal()); + } + size = to.ordinal() - from.ordinal() + 1; + } + + void addAll() { + for (int i = 0; i < elements.length; i++) + elements[i] = -1; + elements[elements.length - 1] >>>= -universe.length; + size = universe.length; + } + + void complement() { + for (int i = 0; i < elements.length; i++) + elements[i] = ~elements[i]; + elements[elements.length - 1] &= (-1L >>> -universe.length); + size = universe.length - size; + } + + /** + * Returns an iterator over the elements contained in this set. The + * iterator traverses the elements in their natural order (which is + * the order in which the enum constants are declared). The returned + * Iterator is a "weakly consistent" iterator that will never throw {@link + * ConcurrentModificationException}. + * + * @return an iterator over the elements contained in this set + */ + public Iterator iterator() { + return new EnumSetIterator<>(); + } + + private class EnumSetIterator> implements Iterator { + /** + * A bit vector representing the elements in the current "word" + * of the set not yet returned by this iterator. + */ + long unseen; + + /** + * The index corresponding to unseen in the elements array. + */ + int unseenIndex = 0; + + /** + * The bit representing the last element returned by this iterator + * but not removed, or zero if no such element exists. + */ + long lastReturned = 0; + + /** + * The index corresponding to lastReturned in the elements array. + */ + int lastReturnedIndex = 0; + + EnumSetIterator() { + unseen = elements[0]; + } + + public boolean hasNext() { + while (unseen == 0 && unseenIndex < elements.length - 1) + unseen = elements[++unseenIndex]; + return unseen != 0; + } + + public E next() { + if (!hasNext()) + throw new NoSuchElementException(); + lastReturned = unseen & -unseen; + lastReturnedIndex = unseenIndex; + unseen -= lastReturned; + return (E) universe[(lastReturnedIndex << 6) + + Long.numberOfTrailingZeros(lastReturned)]; + } + + public void remove() { + if (lastReturned == 0) + throw new IllegalStateException(); + final long oldElements = elements[lastReturnedIndex]; + elements[lastReturnedIndex] &= ~lastReturned; + if (oldElements != elements[lastReturnedIndex]) { + size--; + } + lastReturned = 0; + } + } + + /** + * Returns the number of elements in this set. + * + * @return the number of elements in this set + */ + public int size() { + return size; + } + + /** + * Returns true if this set contains no elements. + * + * @return true if this set contains no elements + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Returns true if this set contains the specified element. + * + * @param e element to be checked for containment in this collection + * @return true if this set contains the specified element + */ + public boolean contains(Object e) { + if (e == null) + return false; + Class eClass = e.getClass(); + if (eClass != elementType && eClass.getSuperclass() != elementType) + return false; + + int eOrdinal = ((Enum)e).ordinal(); + return (elements[eOrdinal >>> 6] & (1L << eOrdinal)) != 0; + } + + // Modification Operations + + /** + * Adds the specified element to this set if it is not already present. + * + * @param e element to be added to this set + * @return true if the set changed as a result of the call + * + * @throws NullPointerException if e is null + */ + public boolean add(E e) { + typeCheck(e); + + int eOrdinal = e.ordinal(); + int eWordNum = eOrdinal >>> 6; + + long oldElements = elements[eWordNum]; + elements[eWordNum] |= (1L << eOrdinal); + boolean result = (elements[eWordNum] != oldElements); + if (result) + size++; + return result; + } + + /** + * Removes the specified element from this set if it is present. + * + * @param e element to be removed from this set, if present + * @return true if the set contained the specified element + */ + public boolean remove(Object e) { + if (e == null) + return false; + Class eClass = e.getClass(); + if (eClass != elementType && eClass.getSuperclass() != elementType) + return false; + int eOrdinal = ((Enum)e).ordinal(); + int eWordNum = eOrdinal >>> 6; + + long oldElements = elements[eWordNum]; + elements[eWordNum] &= ~(1L << eOrdinal); + boolean result = (elements[eWordNum] != oldElements); + if (result) + size--; + return result; + } + + // Bulk Operations + + /** + * Returns true if this set contains all of the elements + * in the specified collection. + * + * @param c collection to be checked for containment in this set + * @return true if this set contains all of the elements + * in the specified collection + * @throws NullPointerException if the specified collection is null + */ + public boolean containsAll(Collection c) { + if (!(c instanceof JumboEnumSet)) + return super.containsAll(c); + + JumboEnumSet es = (JumboEnumSet)c; + if (es.elementType != elementType) + return es.isEmpty(); + + for (int i = 0; i < elements.length; i++) + if ((es.elements[i] & ~elements[i]) != 0) + return false; + return true; + } + + /** + * Adds all of the elements in the specified collection to this set. + * + * @param c collection whose elements are to be added to this set + * @return true if this set changed as a result of the call + * @throws NullPointerException if the specified collection or any of + * its elements are null + */ + public boolean addAll(Collection c) { + if (!(c instanceof JumboEnumSet)) + return super.addAll(c); + + JumboEnumSet es = (JumboEnumSet)c; + if (es.elementType != elementType) { + if (es.isEmpty()) + return false; + else + throw new ClassCastException( + es.elementType + " != " + elementType); + } + + for (int i = 0; i < elements.length; i++) + elements[i] |= es.elements[i]; + return recalculateSize(); + } + + /** + * Removes from this set all of its elements that are contained in + * the specified collection. + * + * @param c elements to be removed from this set + * @return true if this set changed as a result of the call + * @throws NullPointerException if the specified collection is null + */ + public boolean removeAll(Collection c) { + if (!(c instanceof JumboEnumSet)) + return super.removeAll(c); + + JumboEnumSet es = (JumboEnumSet)c; + if (es.elementType != elementType) + return false; + + for (int i = 0; i < elements.length; i++) + elements[i] &= ~es.elements[i]; + return recalculateSize(); + } + + /** + * Retains only the elements in this set that are contained in the + * specified collection. + * + * @param c elements to be retained in this set + * @return true if this set changed as a result of the call + * @throws NullPointerException if the specified collection is null + */ + public boolean retainAll(Collection c) { + if (!(c instanceof JumboEnumSet)) + return super.retainAll(c); + + JumboEnumSet es = (JumboEnumSet)c; + if (es.elementType != elementType) { + boolean changed = (size != 0); + clear(); + return changed; + } + + for (int i = 0; i < elements.length; i++) + elements[i] &= es.elements[i]; + return recalculateSize(); + } + + /** + * Removes all of the elements from this set. + */ + public void clear() { + Arrays.fill(elements, 0); + size = 0; + } + + /** + * Compares the specified object with this set for equality. Returns + * true if the given object is also a set, the two sets have + * the same size, and every member of the given set is contained in + * this set. + * + * @param e object to be compared for equality with this set + * @return true if the specified object is equal to this set + */ + public boolean equals(Object o) { + if (!(o instanceof JumboEnumSet)) + return super.equals(o); + + JumboEnumSet es = (JumboEnumSet)o; + if (es.elementType != elementType) + return size == 0 && es.size == 0; + + return Arrays.equals(es.elements, elements); + } + + /** + * Recalculates the size of the set. Returns true if it's changed. + */ + private boolean recalculateSize() { + int oldSize = size; + size = 0; + for (long elt : elements) + size += Long.bitCount(elt); + + return size != oldSize; + } + + public EnumSet clone() { + JumboEnumSet result = (JumboEnumSet) super.clone(); + result.elements = result.elements.clone(); + return result; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/LinkedHashSet.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/LinkedHashSet.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util; + +/** + *

Hash table and linked list implementation of the Set interface, + * with predictable iteration order. This implementation differs from + * HashSet in that it maintains a doubly-linked list running through + * all of its entries. This linked list defines the iteration ordering, + * which is the order in which elements were inserted into the set + * (insertion-order). Note that insertion order is not affected + * if an element is re-inserted into the set. (An element e + * is reinserted into a set s if s.add(e) is invoked when + * s.contains(e) would return true immediately prior to + * the invocation.) + * + *

This implementation spares its clients from the unspecified, generally + * chaotic ordering provided by {@link HashSet}, without incurring the + * increased cost associated with {@link TreeSet}. It can be used to + * produce a copy of a set that has the same order as the original, regardless + * of the original set's implementation: + *

+ *     void foo(Set s) {
+ *         Set copy = new LinkedHashSet(s);
+ *         ...
+ *     }
+ * 
+ * This technique is particularly useful if a module takes a set on input, + * copies it, and later returns results whose order is determined by that of + * the copy. (Clients generally appreciate having things returned in the same + * order they were presented.) + * + *

This class provides all of the optional Set operations, and + * permits null elements. Like HashSet, it provides constant-time + * performance for the basic operations (add, contains and + * remove), assuming the hash function disperses elements + * properly among the buckets. Performance is likely to be just slightly + * below that of HashSet, due to the added expense of maintaining the + * linked list, with one exception: Iteration over a LinkedHashSet + * requires time proportional to the size of the set, regardless of + * its capacity. Iteration over a HashSet is likely to be more + * expensive, requiring time proportional to its capacity. + * + *

A linked hash set has two parameters that affect its performance: + * initial capacity and load factor. They are defined precisely + * as for HashSet. Note, however, that the penalty for choosing an + * excessively high value for initial capacity is less severe for this class + * than for HashSet, as iteration times for this class are unaffected + * by capacity. + * + *

Note that this implementation is not synchronized. + * If multiple threads access a linked hash set concurrently, and at least + * one of the threads modifies the set, it must be synchronized + * externally. This is typically accomplished by synchronizing on some + * object that naturally encapsulates the set. + * + * If no such object exists, the set should be "wrapped" using the + * {@link Collections#synchronizedSet Collections.synchronizedSet} + * method. This is best done at creation time, to prevent accidental + * unsynchronized access to the set:

+ *   Set s = Collections.synchronizedSet(new LinkedHashSet(...));
+ * + *

The iterators returned by this class's iterator method are + * fail-fast: if the set is modified at any time after the iterator + * is created, in any way except through the iterator's own remove + * method, the iterator will throw a {@link ConcurrentModificationException}. + * Thus, in the face of concurrent modification, the iterator fails quickly + * and cleanly, rather than risking arbitrary, non-deterministic behavior at + * an undetermined time in the future. + * + *

Note that the fail-fast behavior of an iterator cannot be guaranteed + * as it is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators + * throw ConcurrentModificationException on a best-effort basis. + * Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: the fail-fast behavior of iterators + * should be used only to detect bugs. + * + *

This class is a member of the + * + * Java Collections Framework. + * + * @param the type of elements maintained by this set + * + * @author Josh Bloch + * @see Object#hashCode() + * @see Collection + * @see Set + * @see HashSet + * @see TreeSet + * @see Hashtable + * @since 1.4 + */ + +public class LinkedHashSet + extends HashSet + implements Set, Cloneable, java.io.Serializable { + + private static final long serialVersionUID = -2851667679971038690L; + + /** + * Constructs a new, empty linked hash set with the specified initial + * capacity and load factor. + * + * @param initialCapacity the initial capacity of the linked hash set + * @param loadFactor the load factor of the linked hash set + * @throws IllegalArgumentException if the initial capacity is less + * than zero, or if the load factor is nonpositive + */ + public LinkedHashSet(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor, true); + } + + /** + * Constructs a new, empty linked hash set with the specified initial + * capacity and the default load factor (0.75). + * + * @param initialCapacity the initial capacity of the LinkedHashSet + * @throws IllegalArgumentException if the initial capacity is less + * than zero + */ + public LinkedHashSet(int initialCapacity) { + super(initialCapacity, .75f, true); + } + + /** + * Constructs a new, empty linked hash set with the default initial + * capacity (16) and load factor (0.75). + */ + public LinkedHashSet() { + super(16, .75f, true); + } + + /** + * Constructs a new linked hash set with the same elements as the + * specified collection. The linked hash set is created with an initial + * capacity sufficient to hold the elements in the specified collection + * and the default load factor (0.75). + * + * @param c the collection whose elements are to be placed into + * this set + * @throws NullPointerException if the specified collection is null + */ + public LinkedHashSet(Collection c) { + super(Math.max(2*c.size(), 11), .75f, true); + addAll(c); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/NavigableMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/NavigableMap.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,424 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea and Josh Bloch with assistance from members of JCP + * JSR-166 Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util; + +/** + * A {@link SortedMap} extended with navigation methods returning the + * closest matches for given search targets. Methods + * {@code lowerEntry}, {@code floorEntry}, {@code ceilingEntry}, + * and {@code higherEntry} return {@code Map.Entry} objects + * associated with keys respectively less than, less than or equal, + * greater than or equal, and greater than a given key, returning + * {@code null} if there is no such key. Similarly, methods + * {@code lowerKey}, {@code floorKey}, {@code ceilingKey}, and + * {@code higherKey} return only the associated keys. All of these + * methods are designed for locating, not traversing entries. + * + *

A {@code NavigableMap} may be accessed and traversed in either + * ascending or descending key order. The {@code descendingMap} + * method returns a view of the map with the senses of all relational + * and directional methods inverted. The performance of ascending + * operations and views is likely to be faster than that of descending + * ones. Methods {@code subMap}, {@code headMap}, + * and {@code tailMap} differ from the like-named {@code + * SortedMap} methods in accepting additional arguments describing + * whether lower and upper bounds are inclusive versus exclusive. + * Submaps of any {@code NavigableMap} must implement the {@code + * NavigableMap} interface. + * + *

This interface additionally defines methods {@code firstEntry}, + * {@code pollFirstEntry}, {@code lastEntry}, and + * {@code pollLastEntry} that return and/or remove the least and + * greatest mappings, if any exist, else returning {@code null}. + * + *

Implementations of entry-returning methods are expected to + * return {@code Map.Entry} pairs representing snapshots of mappings + * at the time they were produced, and thus generally do not + * support the optional {@code Entry.setValue} method. Note however + * that it is possible to change mappings in the associated map using + * method {@code put}. + * + *

Methods + * {@link #subMap(Object, Object) subMap(K, K)}, + * {@link #headMap(Object) headMap(K)}, and + * {@link #tailMap(Object) tailMap(K)} + * are specified to return {@code SortedMap} to allow existing + * implementations of {@code SortedMap} to be compatibly retrofitted to + * implement {@code NavigableMap}, but extensions and implementations + * of this interface are encouraged to override these methods to return + * {@code NavigableMap}. Similarly, + * {@link #keySet()} can be overriden to return {@code NavigableSet}. + * + *

This interface is a member of the + * + * Java Collections Framework. + * + * @author Doug Lea + * @author Josh Bloch + * @param the type of keys maintained by this map + * @param the type of mapped values + * @since 1.6 + */ +public interface NavigableMap extends SortedMap { + /** + * Returns a key-value mapping associated with the greatest key + * strictly less than the given key, or {@code null} if there is + * no such key. + * + * @param key the key + * @return an entry with the greatest key less than {@code key}, + * or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys + */ + Map.Entry lowerEntry(K key); + + /** + * Returns the greatest key strictly less than the given key, or + * {@code null} if there is no such key. + * + * @param key the key + * @return the greatest key less than {@code key}, + * or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys + */ + K lowerKey(K key); + + /** + * Returns a key-value mapping associated with the greatest key + * less than or equal to the given key, or {@code null} if there + * is no such key. + * + * @param key the key + * @return an entry with the greatest key less than or equal to + * {@code key}, or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys + */ + Map.Entry floorEntry(K key); + + /** + * Returns the greatest key less than or equal to the given key, + * or {@code null} if there is no such key. + * + * @param key the key + * @return the greatest key less than or equal to {@code key}, + * or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys + */ + K floorKey(K key); + + /** + * Returns a key-value mapping associated with the least key + * greater than or equal to the given key, or {@code null} if + * there is no such key. + * + * @param key the key + * @return an entry with the least key greater than or equal to + * {@code key}, or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys + */ + Map.Entry ceilingEntry(K key); + + /** + * Returns the least key greater than or equal to the given key, + * or {@code null} if there is no such key. + * + * @param key the key + * @return the least key greater than or equal to {@code key}, + * or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys + */ + K ceilingKey(K key); + + /** + * Returns a key-value mapping associated with the least key + * strictly greater than the given key, or {@code null} if there + * is no such key. + * + * @param key the key + * @return an entry with the least key greater than {@code key}, + * or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys + */ + Map.Entry higherEntry(K key); + + /** + * Returns the least key strictly greater than the given key, or + * {@code null} if there is no such key. + * + * @param key the key + * @return the least key greater than {@code key}, + * or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys + */ + K higherKey(K key); + + /** + * Returns a key-value mapping associated with the least + * key in this map, or {@code null} if the map is empty. + * + * @return an entry with the least key, + * or {@code null} if this map is empty + */ + Map.Entry firstEntry(); + + /** + * Returns a key-value mapping associated with the greatest + * key in this map, or {@code null} if the map is empty. + * + * @return an entry with the greatest key, + * or {@code null} if this map is empty + */ + Map.Entry lastEntry(); + + /** + * Removes and returns a key-value mapping associated with + * the least key in this map, or {@code null} if the map is empty. + * + * @return the removed first entry of this map, + * or {@code null} if this map is empty + */ + Map.Entry pollFirstEntry(); + + /** + * Removes and returns a key-value mapping associated with + * the greatest key in this map, or {@code null} if the map is empty. + * + * @return the removed last entry of this map, + * or {@code null} if this map is empty + */ + Map.Entry pollLastEntry(); + + /** + * Returns a reverse order view of the mappings contained in this map. + * The descending map is backed by this map, so changes to the map are + * reflected in the descending map, and vice-versa. If either map is + * modified while an iteration over a collection view of either map + * is in progress (except through the iterator's own {@code remove} + * operation), the results of the iteration are undefined. + * + *

The returned map has an ordering equivalent to + * {@link Collections#reverseOrder(Comparator) Collections.reverseOrder}(comparator()). + * The expression {@code m.descendingMap().descendingMap()} returns a + * view of {@code m} essentially equivalent to {@code m}. + * + * @return a reverse order view of this map + */ + NavigableMap descendingMap(); + + /** + * Returns a {@link NavigableSet} view of the keys contained in this map. + * The set's iterator returns the keys in ascending order. + * The set is backed by the map, so changes to the map are reflected in + * the set, and vice-versa. If the map is modified while an iteration + * over the set is in progress (except through the iterator's own {@code + * remove} operation), the results of the iteration are undefined. The + * set supports element removal, which removes the corresponding mapping + * from the map, via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} operations. + * It does not support the {@code add} or {@code addAll} operations. + * + * @return a navigable set view of the keys in this map + */ + NavigableSet navigableKeySet(); + + /** + * Returns a reverse order {@link NavigableSet} view of the keys contained in this map. + * The set's iterator returns the keys in descending order. + * The set is backed by the map, so changes to the map are reflected in + * the set, and vice-versa. If the map is modified while an iteration + * over the set is in progress (except through the iterator's own {@code + * remove} operation), the results of the iteration are undefined. The + * set supports element removal, which removes the corresponding mapping + * from the map, via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} operations. + * It does not support the {@code add} or {@code addAll} operations. + * + * @return a reverse order navigable set view of the keys in this map + */ + NavigableSet descendingKeySet(); + + /** + * Returns a view of the portion of this map whose keys range from + * {@code fromKey} to {@code toKey}. If {@code fromKey} and + * {@code toKey} are equal, the returned map is empty unless + * {@code fromInclusive} and {@code toInclusive} are both true. The + * returned map is backed by this map, so changes in the returned map are + * reflected in this map, and vice-versa. The returned map supports all + * optional map operations that this map supports. + * + *

The returned map will throw an {@code IllegalArgumentException} + * on an attempt to insert a key outside of its range, or to construct a + * submap either of whose endpoints lie outside its range. + * + * @param fromKey low endpoint of the keys in the returned map + * @param fromInclusive {@code true} if the low endpoint + * is to be included in the returned view + * @param toKey high endpoint of the keys in the returned map + * @param toInclusive {@code true} if the high endpoint + * is to be included in the returned view + * @return a view of the portion of this map whose keys range from + * {@code fromKey} to {@code toKey} + * @throws ClassCastException if {@code fromKey} and {@code toKey} + * cannot be compared to one another using this map's comparator + * (or, if the map has no comparator, using natural ordering). + * Implementations may, but are not required to, throw this + * exception if {@code fromKey} or {@code toKey} + * cannot be compared to keys currently in the map. + * @throws NullPointerException if {@code fromKey} or {@code toKey} + * is null and this map does not permit null keys + * @throws IllegalArgumentException if {@code fromKey} is greater than + * {@code toKey}; or if this map itself has a restricted + * range, and {@code fromKey} or {@code toKey} lies + * outside the bounds of the range + */ + NavigableMap subMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive); + + /** + * Returns a view of the portion of this map whose keys are less than (or + * equal to, if {@code inclusive} is true) {@code toKey}. The returned + * map is backed by this map, so changes in the returned map are reflected + * in this map, and vice-versa. The returned map supports all optional + * map operations that this map supports. + * + *

The returned map will throw an {@code IllegalArgumentException} + * on an attempt to insert a key outside its range. + * + * @param toKey high endpoint of the keys in the returned map + * @param inclusive {@code true} if the high endpoint + * is to be included in the returned view + * @return a view of the portion of this map whose keys are less than + * (or equal to, if {@code inclusive} is true) {@code toKey} + * @throws ClassCastException if {@code toKey} is not compatible + * with this map's comparator (or, if the map has no comparator, + * if {@code toKey} does not implement {@link Comparable}). + * Implementations may, but are not required to, throw this + * exception if {@code toKey} cannot be compared to keys + * currently in the map. + * @throws NullPointerException if {@code toKey} is null + * and this map does not permit null keys + * @throws IllegalArgumentException if this map itself has a + * restricted range, and {@code toKey} lies outside the + * bounds of the range + */ + NavigableMap headMap(K toKey, boolean inclusive); + + /** + * Returns a view of the portion of this map whose keys are greater than (or + * equal to, if {@code inclusive} is true) {@code fromKey}. The returned + * map is backed by this map, so changes in the returned map are reflected + * in this map, and vice-versa. The returned map supports all optional + * map operations that this map supports. + * + *

The returned map will throw an {@code IllegalArgumentException} + * on an attempt to insert a key outside its range. + * + * @param fromKey low endpoint of the keys in the returned map + * @param inclusive {@code true} if the low endpoint + * is to be included in the returned view + * @return a view of the portion of this map whose keys are greater than + * (or equal to, if {@code inclusive} is true) {@code fromKey} + * @throws ClassCastException if {@code fromKey} is not compatible + * with this map's comparator (or, if the map has no comparator, + * if {@code fromKey} does not implement {@link Comparable}). + * Implementations may, but are not required to, throw this + * exception if {@code fromKey} cannot be compared to keys + * currently in the map. + * @throws NullPointerException if {@code fromKey} is null + * and this map does not permit null keys + * @throws IllegalArgumentException if this map itself has a + * restricted range, and {@code fromKey} lies outside the + * bounds of the range + */ + NavigableMap tailMap(K fromKey, boolean inclusive); + + /** + * {@inheritDoc} + * + *

Equivalent to {@code subMap(fromKey, true, toKey, false)}. + * + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + */ + SortedMap subMap(K fromKey, K toKey); + + /** + * {@inheritDoc} + * + *

Equivalent to {@code headMap(toKey, false)}. + * + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + */ + SortedMap headMap(K toKey); + + /** + * {@inheritDoc} + * + *

Equivalent to {@code tailMap(fromKey, true)}. + * + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + */ + SortedMap tailMap(K fromKey); +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/NavigableSet.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/NavigableSet.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,319 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea and Josh Bloch with assistance from members of JCP + * JSR-166 Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util; + +/** + * A {@link SortedSet} extended with navigation methods reporting + * closest matches for given search targets. Methods {@code lower}, + * {@code floor}, {@code ceiling}, and {@code higher} return elements + * respectively less than, less than or equal, greater than or equal, + * and greater than a given element, returning {@code null} if there + * is no such element. A {@code NavigableSet} may be accessed and + * traversed in either ascending or descending order. The {@code + * descendingSet} method returns a view of the set with the senses of + * all relational and directional methods inverted. The performance of + * ascending operations and views is likely to be faster than that of + * descending ones. This interface additionally defines methods + * {@code pollFirst} and {@code pollLast} that return and remove the + * lowest and highest element, if one exists, else returning {@code + * null}. Methods {@code subSet}, {@code headSet}, + * and {@code tailSet} differ from the like-named {@code + * SortedSet} methods in accepting additional arguments describing + * whether lower and upper bounds are inclusive versus exclusive. + * Subsets of any {@code NavigableSet} must implement the {@code + * NavigableSet} interface. + * + *

The return values of navigation methods may be ambiguous in + * implementations that permit {@code null} elements. However, even + * in this case the result can be disambiguated by checking + * {@code contains(null)}. To avoid such issues, implementations of + * this interface are encouraged to not permit insertion of + * {@code null} elements. (Note that sorted sets of {@link + * Comparable} elements intrinsically do not permit {@code null}.) + * + *

Methods + * {@link #subSet(Object, Object) subSet(E, E)}, + * {@link #headSet(Object) headSet(E)}, and + * {@link #tailSet(Object) tailSet(E)} + * are specified to return {@code SortedSet} to allow existing + * implementations of {@code SortedSet} to be compatibly retrofitted to + * implement {@code NavigableSet}, but extensions and implementations + * of this interface are encouraged to override these methods to return + * {@code NavigableSet}. + * + *

This interface is a member of the + * + * Java Collections Framework. + * + * @author Doug Lea + * @author Josh Bloch + * @param the type of elements maintained by this set + * @since 1.6 + */ +public interface NavigableSet extends SortedSet { + /** + * Returns the greatest element in this set strictly less than the + * given element, or {@code null} if there is no such element. + * + * @param e the value to match + * @return the greatest element less than {@code e}, + * or {@code null} if there is no such element + * @throws ClassCastException if the specified element cannot be + * compared with the elements currently in the set + * @throws NullPointerException if the specified element is null + * and this set does not permit null elements + */ + E lower(E e); + + /** + * Returns the greatest element in this set less than or equal to + * the given element, or {@code null} if there is no such element. + * + * @param e the value to match + * @return the greatest element less than or equal to {@code e}, + * or {@code null} if there is no such element + * @throws ClassCastException if the specified element cannot be + * compared with the elements currently in the set + * @throws NullPointerException if the specified element is null + * and this set does not permit null elements + */ + E floor(E e); + + /** + * Returns the least element in this set greater than or equal to + * the given element, or {@code null} if there is no such element. + * + * @param e the value to match + * @return the least element greater than or equal to {@code e}, + * or {@code null} if there is no such element + * @throws ClassCastException if the specified element cannot be + * compared with the elements currently in the set + * @throws NullPointerException if the specified element is null + * and this set does not permit null elements + */ + E ceiling(E e); + + /** + * Returns the least element in this set strictly greater than the + * given element, or {@code null} if there is no such element. + * + * @param e the value to match + * @return the least element greater than {@code e}, + * or {@code null} if there is no such element + * @throws ClassCastException if the specified element cannot be + * compared with the elements currently in the set + * @throws NullPointerException if the specified element is null + * and this set does not permit null elements + */ + E higher(E e); + + /** + * Retrieves and removes the first (lowest) element, + * or returns {@code null} if this set is empty. + * + * @return the first element, or {@code null} if this set is empty + */ + E pollFirst(); + + /** + * Retrieves and removes the last (highest) element, + * or returns {@code null} if this set is empty. + * + * @return the last element, or {@code null} if this set is empty + */ + E pollLast(); + + /** + * Returns an iterator over the elements in this set, in ascending order. + * + * @return an iterator over the elements in this set, in ascending order + */ + Iterator iterator(); + + /** + * Returns a reverse order view of the elements contained in this set. + * The descending set is backed by this set, so changes to the set are + * reflected in the descending set, and vice-versa. If either set is + * modified while an iteration over either set is in progress (except + * through the iterator's own {@code remove} operation), the results of + * the iteration are undefined. + * + *

The returned set has an ordering equivalent to + * {@link Collections#reverseOrder(Comparator) Collections.reverseOrder}(comparator()). + * The expression {@code s.descendingSet().descendingSet()} returns a + * view of {@code s} essentially equivalent to {@code s}. + * + * @return a reverse order view of this set + */ + NavigableSet descendingSet(); + + /** + * Returns an iterator over the elements in this set, in descending order. + * Equivalent in effect to {@code descendingSet().iterator()}. + * + * @return an iterator over the elements in this set, in descending order + */ + Iterator descendingIterator(); + + /** + * Returns a view of the portion of this set whose elements range from + * {@code fromElement} to {@code toElement}. If {@code fromElement} and + * {@code toElement} are equal, the returned set is empty unless {@code + * fromInclusive} and {@code toInclusive} are both true. The returned set + * is backed by this set, so changes in the returned set are reflected in + * this set, and vice-versa. The returned set supports all optional set + * operations that this set supports. + * + *

The returned set will throw an {@code IllegalArgumentException} + * on an attempt to insert an element outside its range. + * + * @param fromElement low endpoint of the returned set + * @param fromInclusive {@code true} if the low endpoint + * is to be included in the returned view + * @param toElement high endpoint of the returned set + * @param toInclusive {@code true} if the high endpoint + * is to be included in the returned view + * @return a view of the portion of this set whose elements range from + * {@code fromElement}, inclusive, to {@code toElement}, exclusive + * @throws ClassCastException if {@code fromElement} and + * {@code toElement} cannot be compared to one another using this + * set's comparator (or, if the set has no comparator, using + * natural ordering). Implementations may, but are not required + * to, throw this exception if {@code fromElement} or + * {@code toElement} cannot be compared to elements currently in + * the set. + * @throws NullPointerException if {@code fromElement} or + * {@code toElement} is null and this set does + * not permit null elements + * @throws IllegalArgumentException if {@code fromElement} is + * greater than {@code toElement}; or if this set itself + * has a restricted range, and {@code fromElement} or + * {@code toElement} lies outside the bounds of the range. + */ + NavigableSet subSet(E fromElement, boolean fromInclusive, + E toElement, boolean toInclusive); + + /** + * Returns a view of the portion of this set whose elements are less than + * (or equal to, if {@code inclusive} is true) {@code toElement}. The + * returned set is backed by this set, so changes in the returned set are + * reflected in this set, and vice-versa. The returned set supports all + * optional set operations that this set supports. + * + *

The returned set will throw an {@code IllegalArgumentException} + * on an attempt to insert an element outside its range. + * + * @param toElement high endpoint of the returned set + * @param inclusive {@code true} if the high endpoint + * is to be included in the returned view + * @return a view of the portion of this set whose elements are less than + * (or equal to, if {@code inclusive} is true) {@code toElement} + * @throws ClassCastException if {@code toElement} is not compatible + * with this set's comparator (or, if the set has no comparator, + * if {@code toElement} does not implement {@link Comparable}). + * Implementations may, but are not required to, throw this + * exception if {@code toElement} cannot be compared to elements + * currently in the set. + * @throws NullPointerException if {@code toElement} is null and + * this set does not permit null elements + * @throws IllegalArgumentException if this set itself has a + * restricted range, and {@code toElement} lies outside the + * bounds of the range + */ + NavigableSet headSet(E toElement, boolean inclusive); + + /** + * Returns a view of the portion of this set whose elements are greater + * than (or equal to, if {@code inclusive} is true) {@code fromElement}. + * The returned set is backed by this set, so changes in the returned set + * are reflected in this set, and vice-versa. The returned set supports + * all optional set operations that this set supports. + * + *

The returned set will throw an {@code IllegalArgumentException} + * on an attempt to insert an element outside its range. + * + * @param fromElement low endpoint of the returned set + * @param inclusive {@code true} if the low endpoint + * is to be included in the returned view + * @return a view of the portion of this set whose elements are greater + * than or equal to {@code fromElement} + * @throws ClassCastException if {@code fromElement} is not compatible + * with this set's comparator (or, if the set has no comparator, + * if {@code fromElement} does not implement {@link Comparable}). + * Implementations may, but are not required to, throw this + * exception if {@code fromElement} cannot be compared to elements + * currently in the set. + * @throws NullPointerException if {@code fromElement} is null + * and this set does not permit null elements + * @throws IllegalArgumentException if this set itself has a + * restricted range, and {@code fromElement} lies outside the + * bounds of the range + */ + NavigableSet tailSet(E fromElement, boolean inclusive); + + /** + * {@inheritDoc} + * + *

Equivalent to {@code subSet(fromElement, true, toElement, false)}. + * + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + */ + SortedSet subSet(E fromElement, E toElement); + + /** + * {@inheritDoc} + * + *

Equivalent to {@code headSet(toElement, false)}. + * + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} +na */ + SortedSet headSet(E toElement); + + /** + * {@inheritDoc} + * + *

Equivalent to {@code tailSet(fromElement, true)}. + * + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + */ + SortedSet tailSet(E fromElement); +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/RegularEnumSet.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/RegularEnumSet.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util; + +/** + * Private implementation class for EnumSet, for "regular sized" enum types + * (i.e., those with 64 or fewer enum constants). + * + * @author Josh Bloch + * @since 1.5 + * @serial exclude + */ +class RegularEnumSet> extends EnumSet { + private static final long serialVersionUID = 3411599620347842686L; + /** + * Bit vector representation of this set. The 2^k bit indicates the + * presence of universe[k] in this set. + */ + private long elements = 0L; + + RegularEnumSet(ClasselementType, Enum[] universe) { + super(elementType, universe); + } + + void addRange(E from, E to) { + elements = (-1L >>> (from.ordinal() - to.ordinal() - 1)) << from.ordinal(); + } + + void addAll() { + if (universe.length != 0) + elements = -1L >>> -universe.length; + } + + void complement() { + if (universe.length != 0) { + elements = ~elements; + elements &= -1L >>> -universe.length; // Mask unused bits + } + } + + /** + * Returns an iterator over the elements contained in this set. The + * iterator traverses the elements in their natural order (which is + * the order in which the enum constants are declared). The returned + * Iterator is a "snapshot" iterator that will never throw {@link + * ConcurrentModificationException}; the elements are traversed as they + * existed when this call was invoked. + * + * @return an iterator over the elements contained in this set + */ + public Iterator iterator() { + return new EnumSetIterator<>(); + } + + private class EnumSetIterator> implements Iterator { + /** + * A bit vector representing the elements in the set not yet + * returned by this iterator. + */ + long unseen; + + /** + * The bit representing the last element returned by this iterator + * but not removed, or zero if no such element exists. + */ + long lastReturned = 0; + + EnumSetIterator() { + unseen = elements; + } + + public boolean hasNext() { + return unseen != 0; + } + + public E next() { + if (unseen == 0) + throw new NoSuchElementException(); + lastReturned = unseen & -unseen; + unseen -= lastReturned; + return (E) universe[Long.numberOfTrailingZeros(lastReturned)]; + } + + public void remove() { + if (lastReturned == 0) + throw new IllegalStateException(); + elements &= ~lastReturned; + lastReturned = 0; + } + } + + /** + * Returns the number of elements in this set. + * + * @return the number of elements in this set + */ + public int size() { + return Long.bitCount(elements); + } + + /** + * Returns true if this set contains no elements. + * + * @return true if this set contains no elements + */ + public boolean isEmpty() { + return elements == 0; + } + + /** + * Returns true if this set contains the specified element. + * + * @param e element to be checked for containment in this collection + * @return true if this set contains the specified element + */ + public boolean contains(Object e) { + if (e == null) + return false; + Class eClass = e.getClass(); + if (eClass != elementType && eClass.getSuperclass() != elementType) + return false; + + return (elements & (1L << ((Enum)e).ordinal())) != 0; + } + + // Modification Operations + + /** + * Adds the specified element to this set if it is not already present. + * + * @param e element to be added to this set + * @return true if the set changed as a result of the call + * + * @throws NullPointerException if e is null + */ + public boolean add(E e) { + typeCheck(e); + + long oldElements = elements; + elements |= (1L << ((Enum)e).ordinal()); + return elements != oldElements; + } + + /** + * Removes the specified element from this set if it is present. + * + * @param e element to be removed from this set, if present + * @return true if the set contained the specified element + */ + public boolean remove(Object e) { + if (e == null) + return false; + Class eClass = e.getClass(); + if (eClass != elementType && eClass.getSuperclass() != elementType) + return false; + + long oldElements = elements; + elements &= ~(1L << ((Enum)e).ordinal()); + return elements != oldElements; + } + + // Bulk Operations + + /** + * Returns true if this set contains all of the elements + * in the specified collection. + * + * @param c collection to be checked for containment in this set + * @return true if this set contains all of the elements + * in the specified collection + * @throws NullPointerException if the specified collection is null + */ + public boolean containsAll(Collection c) { + if (!(c instanceof RegularEnumSet)) + return super.containsAll(c); + + RegularEnumSet es = (RegularEnumSet)c; + if (es.elementType != elementType) + return es.isEmpty(); + + return (es.elements & ~elements) == 0; + } + + /** + * Adds all of the elements in the specified collection to this set. + * + * @param c collection whose elements are to be added to this set + * @return true if this set changed as a result of the call + * @throws NullPointerException if the specified collection or any + * of its elements are null + */ + public boolean addAll(Collection c) { + if (!(c instanceof RegularEnumSet)) + return super.addAll(c); + + RegularEnumSet es = (RegularEnumSet)c; + if (es.elementType != elementType) { + if (es.isEmpty()) + return false; + else + throw new ClassCastException( + es.elementType + " != " + elementType); + } + + long oldElements = elements; + elements |= es.elements; + return elements != oldElements; + } + + /** + * Removes from this set all of its elements that are contained in + * the specified collection. + * + * @param c elements to be removed from this set + * @return true if this set changed as a result of the call + * @throws NullPointerException if the specified collection is null + */ + public boolean removeAll(Collection c) { + if (!(c instanceof RegularEnumSet)) + return super.removeAll(c); + + RegularEnumSet es = (RegularEnumSet)c; + if (es.elementType != elementType) + return false; + + long oldElements = elements; + elements &= ~es.elements; + return elements != oldElements; + } + + /** + * Retains only the elements in this set that are contained in the + * specified collection. + * + * @param c elements to be retained in this set + * @return true if this set changed as a result of the call + * @throws NullPointerException if the specified collection is null + */ + public boolean retainAll(Collection c) { + if (!(c instanceof RegularEnumSet)) + return super.retainAll(c); + + RegularEnumSet es = (RegularEnumSet)c; + if (es.elementType != elementType) { + boolean changed = (elements != 0); + elements = 0; + return changed; + } + + long oldElements = elements; + elements &= es.elements; + return elements != oldElements; + } + + /** + * Removes all of the elements from this set. + */ + public void clear() { + elements = 0; + } + + /** + * Compares the specified object with this set for equality. Returns + * true if the given object is also a set, the two sets have + * the same size, and every member of the given set is contained in + * this set. + * + * @param e object to be compared for equality with this set + * @return true if the specified object is equal to this set + */ + public boolean equals(Object o) { + if (!(o instanceof RegularEnumSet)) + return super.equals(o); + + RegularEnumSet es = (RegularEnumSet)o; + if (es.elementType != elementType) + return elements == 0 && es.elements == 0; + return es.elements == elements; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/TreeMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/TreeMap.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,2442 @@ +/* + * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util; + +/** + * A Red-Black tree based {@link NavigableMap} implementation. + * The map is sorted according to the {@linkplain Comparable natural + * ordering} of its keys, or by a {@link Comparator} provided at map + * creation time, depending on which constructor is used. + * + *

This implementation provides guaranteed log(n) time cost for the + * {@code containsKey}, {@code get}, {@code put} and {@code remove} + * operations. Algorithms are adaptations of those in Cormen, Leiserson, and + * Rivest's Introduction to Algorithms. + * + *

Note that the ordering maintained by a tree map, like any sorted map, and + * whether or not an explicit comparator is provided, must be consistent + * with {@code equals} if this sorted map is to correctly implement the + * {@code Map} interface. (See {@code Comparable} or {@code Comparator} for a + * precise definition of consistent with equals.) This is so because + * the {@code Map} interface is defined in terms of the {@code equals} + * operation, but a sorted map performs all key comparisons using its {@code + * compareTo} (or {@code compare}) method, so two keys that are deemed equal by + * this method are, from the standpoint of the sorted map, equal. The behavior + * of a sorted map is well-defined even if its ordering is + * inconsistent with {@code equals}; it just fails to obey the general contract + * of the {@code Map} interface. + * + *

Note that this implementation is not synchronized. + * If multiple threads access a map concurrently, and at least one of the + * threads modifies the map structurally, it must be synchronized + * externally. (A structural modification is any operation that adds or + * deletes one or more mappings; merely changing the value associated + * with an existing key is not a structural modification.) This is + * typically accomplished by synchronizing on some object that naturally + * encapsulates the map. + * If no such object exists, the map should be "wrapped" using the + * {@link Collections#synchronizedSortedMap Collections.synchronizedSortedMap} + * method. This is best done at creation time, to prevent accidental + * unsynchronized access to the map:

+ *   SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
+ * + *

The iterators returned by the {@code iterator} method of the collections + * returned by all of this class's "collection view methods" are + * fail-fast: if the map is structurally modified at any time after + * the iterator is created, in any way except through the iterator's own + * {@code remove} method, the iterator will throw a {@link + * ConcurrentModificationException}. Thus, in the face of concurrent + * modification, the iterator fails quickly and cleanly, rather than risking + * arbitrary, non-deterministic behavior at an undetermined time in the future. + * + *

Note that the fail-fast behavior of an iterator cannot be guaranteed + * as it is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators + * throw {@code ConcurrentModificationException} on a best-effort basis. + * Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: the fail-fast behavior of iterators + * should be used only to detect bugs. + * + *

All {@code Map.Entry} pairs returned by methods in this class + * and its views represent snapshots of mappings at the time they were + * produced. They do not support the {@code Entry.setValue} + * method. (Note however that it is possible to change mappings in the + * associated map using {@code put}.) + * + *

This class is a member of the + * + * Java Collections Framework. + * + * @param the type of keys maintained by this map + * @param the type of mapped values + * + * @author Josh Bloch and Doug Lea + * @see Map + * @see HashMap + * @see Hashtable + * @see Comparable + * @see Comparator + * @see Collection + * @since 1.2 + */ + +public class TreeMap + extends AbstractMap + implements NavigableMap, Cloneable, java.io.Serializable +{ + /** + * The comparator used to maintain order in this tree map, or + * null if it uses the natural ordering of its keys. + * + * @serial + */ + private final Comparator comparator; + + private transient Entry root = null; + + /** + * The number of entries in the tree + */ + private transient int size = 0; + + /** + * The number of structural modifications to the tree. + */ + private transient int modCount = 0; + + /** + * Constructs a new, empty tree map, using the natural ordering of its + * keys. All keys inserted into the map must implement the {@link + * Comparable} interface. Furthermore, all such keys must be + * mutually comparable: {@code k1.compareTo(k2)} must not throw + * a {@code ClassCastException} for any keys {@code k1} and + * {@code k2} in the map. If the user attempts to put a key into the + * map that violates this constraint (for example, the user attempts to + * put a string key into a map whose keys are integers), the + * {@code put(Object key, Object value)} call will throw a + * {@code ClassCastException}. + */ + public TreeMap() { + comparator = null; + } + + /** + * Constructs a new, empty tree map, ordered according to the given + * comparator. All keys inserted into the map must be mutually + * comparable by the given comparator: {@code comparator.compare(k1, + * k2)} must not throw a {@code ClassCastException} for any keys + * {@code k1} and {@code k2} in the map. If the user attempts to put + * a key into the map that violates this constraint, the {@code put(Object + * key, Object value)} call will throw a + * {@code ClassCastException}. + * + * @param comparator the comparator that will be used to order this map. + * If {@code null}, the {@linkplain Comparable natural + * ordering} of the keys will be used. + */ + public TreeMap(Comparator comparator) { + this.comparator = comparator; + } + + /** + * Constructs a new tree map containing the same mappings as the given + * map, ordered according to the natural ordering of its keys. + * All keys inserted into the new map must implement the {@link + * Comparable} interface. Furthermore, all such keys must be + * mutually comparable: {@code k1.compareTo(k2)} must not throw + * a {@code ClassCastException} for any keys {@code k1} and + * {@code k2} in the map. This method runs in n*log(n) time. + * + * @param m the map whose mappings are to be placed in this map + * @throws ClassCastException if the keys in m are not {@link Comparable}, + * or are not mutually comparable + * @throws NullPointerException if the specified map is null + */ + public TreeMap(Map m) { + comparator = null; + putAll(m); + } + + /** + * Constructs a new tree map containing the same mappings and + * using the same ordering as the specified sorted map. This + * method runs in linear time. + * + * @param m the sorted map whose mappings are to be placed in this map, + * and whose comparator is to be used to sort this map + * @throws NullPointerException if the specified map is null + */ + public TreeMap(SortedMap m) { + comparator = m.comparator(); + try { + buildFromSorted(m.size(), m.entrySet().iterator(), null, null); + } catch (java.io.IOException cannotHappen) { + } catch (ClassNotFoundException cannotHappen) { + } + } + + + // Query Operations + + /** + * Returns the number of key-value mappings in this map. + * + * @return the number of key-value mappings in this map + */ + public int size() { + return size; + } + + /** + * Returns {@code true} if this map contains a mapping for the specified + * key. + * + * @param key key whose presence in this map is to be tested + * @return {@code true} if this map contains a mapping for the + * specified key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + */ + public boolean containsKey(Object key) { + return getEntry(key) != null; + } + + /** + * Returns {@code true} if this map maps one or more keys to the + * specified value. More formally, returns {@code true} if and only if + * this map contains at least one mapping to a value {@code v} such + * that {@code (value==null ? v==null : value.equals(v))}. This + * operation will probably require time linear in the map size for + * most implementations. + * + * @param value value whose presence in this map is to be tested + * @return {@code true} if a mapping to {@code value} exists; + * {@code false} otherwise + * @since 1.2 + */ + public boolean containsValue(Object value) { + for (Entry e = getFirstEntry(); e != null; e = successor(e)) + if (valEquals(value, e.value)) + return true; + return false; + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code key} compares + * equal to {@code k} according to the map's ordering, then this + * method returns {@code v}; otherwise it returns {@code null}. + * (There can be at most one such mapping.) + * + *

A return value of {@code null} does not necessarily + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to {@code null}. + * The {@link #containsKey containsKey} operation may be used to + * distinguish these two cases. + * + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + */ + public V get(Object key) { + Entry p = getEntry(key); + return (p==null ? null : p.value); + } + + public Comparator comparator() { + return comparator; + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public K firstKey() { + return key(getFirstEntry()); + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public K lastKey() { + return key(getLastEntry()); + } + + /** + * Copies all of the mappings from the specified map to this map. + * These mappings replace any mappings that this map had for any + * of the keys currently in the specified map. + * + * @param map mappings to be stored in this map + * @throws ClassCastException if the class of a key or value in + * the specified map prevents it from being stored in this map + * @throws NullPointerException if the specified map is null or + * the specified map contains a null key and this map does not + * permit null keys + */ + public void putAll(Map map) { + int mapSize = map.size(); + if (size==0 && mapSize!=0 && map instanceof SortedMap) { + Comparator c = ((SortedMap)map).comparator(); + if (c == comparator || (c != null && c.equals(comparator))) { + ++modCount; + try { + buildFromSorted(mapSize, map.entrySet().iterator(), + null, null); + } catch (java.io.IOException cannotHappen) { + } catch (ClassNotFoundException cannotHappen) { + } + return; + } + } + super.putAll(map); + } + + /** + * Returns this map's entry for the given key, or {@code null} if the map + * does not contain an entry for the key. + * + * @return this map's entry for the given key, or {@code null} if the map + * does not contain an entry for the key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + */ + final Entry getEntry(Object key) { + // Offload comparator-based version for sake of performance + if (comparator != null) + return getEntryUsingComparator(key); + if (key == null) + throw new NullPointerException(); + Comparable k = (Comparable) key; + Entry p = root; + while (p != null) { + int cmp = k.compareTo(p.key); + if (cmp < 0) + p = p.left; + else if (cmp > 0) + p = p.right; + else + return p; + } + return null; + } + + /** + * Version of getEntry using comparator. Split off from getEntry + * for performance. (This is not worth doing for most methods, + * that are less dependent on comparator performance, but is + * worthwhile here.) + */ + final Entry getEntryUsingComparator(Object key) { + K k = (K) key; + Comparator cpr = comparator; + if (cpr != null) { + Entry p = root; + while (p != null) { + int cmp = cpr.compare(k, p.key); + if (cmp < 0) + p = p.left; + else if (cmp > 0) + p = p.right; + else + return p; + } + } + return null; + } + + /** + * Gets the entry corresponding to the specified key; if no such entry + * exists, returns the entry for the least key greater than the specified + * key; if no such entry exists (i.e., the greatest key in the Tree is less + * than the specified key), returns {@code null}. + */ + final Entry getCeilingEntry(K key) { + Entry p = root; + while (p != null) { + int cmp = compare(key, p.key); + if (cmp < 0) { + if (p.left != null) + p = p.left; + else + return p; + } else if (cmp > 0) { + if (p.right != null) { + p = p.right; + } else { + Entry parent = p.parent; + Entry ch = p; + while (parent != null && ch == parent.right) { + ch = parent; + parent = parent.parent; + } + return parent; + } + } else + return p; + } + return null; + } + + /** + * Gets the entry corresponding to the specified key; if no such entry + * exists, returns the entry for the greatest key less than the specified + * key; if no such entry exists, returns {@code null}. + */ + final Entry getFloorEntry(K key) { + Entry p = root; + while (p != null) { + int cmp = compare(key, p.key); + if (cmp > 0) { + if (p.right != null) + p = p.right; + else + return p; + } else if (cmp < 0) { + if (p.left != null) { + p = p.left; + } else { + Entry parent = p.parent; + Entry ch = p; + while (parent != null && ch == parent.left) { + ch = parent; + parent = parent.parent; + } + return parent; + } + } else + return p; + + } + return null; + } + + /** + * Gets the entry for the least key greater than the specified + * key; if no such entry exists, returns the entry for the least + * key greater than the specified key; if no such entry exists + * returns {@code null}. + */ + final Entry getHigherEntry(K key) { + Entry p = root; + while (p != null) { + int cmp = compare(key, p.key); + if (cmp < 0) { + if (p.left != null) + p = p.left; + else + return p; + } else { + if (p.right != null) { + p = p.right; + } else { + Entry parent = p.parent; + Entry ch = p; + while (parent != null && ch == parent.right) { + ch = parent; + parent = parent.parent; + } + return parent; + } + } + } + return null; + } + + /** + * Returns the entry for the greatest key less than the specified key; if + * no such entry exists (i.e., the least key in the Tree is greater than + * the specified key), returns {@code null}. + */ + final Entry getLowerEntry(K key) { + Entry p = root; + while (p != null) { + int cmp = compare(key, p.key); + if (cmp > 0) { + if (p.right != null) + p = p.right; + else + return p; + } else { + if (p.left != null) { + p = p.left; + } else { + Entry parent = p.parent; + Entry ch = p; + while (parent != null && ch == parent.left) { + ch = parent; + parent = parent.parent; + } + return parent; + } + } + } + return null; + } + + /** + * Associates the specified value with the specified key in this map. + * If the map previously contained a mapping for the key, the old + * value is replaced. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key}. + * (A {@code null} return can also indicate that the map + * previously associated {@code null} with {@code key}.) + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + */ + public V put(K key, V value) { + Entry t = root; + if (t == null) { + compare(key, key); // type (and possibly null) check + + root = new Entry<>(key, value, null); + size = 1; + modCount++; + return null; + } + int cmp; + Entry parent; + // split comparator and comparable paths + Comparator cpr = comparator; + if (cpr != null) { + do { + parent = t; + cmp = cpr.compare(key, t.key); + if (cmp < 0) + t = t.left; + else if (cmp > 0) + t = t.right; + else + return t.setValue(value); + } while (t != null); + } + else { + if (key == null) + throw new NullPointerException(); + Comparable k = (Comparable) key; + do { + parent = t; + cmp = k.compareTo(t.key); + if (cmp < 0) + t = t.left; + else if (cmp > 0) + t = t.right; + else + return t.setValue(value); + } while (t != null); + } + Entry e = new Entry<>(key, value, parent); + if (cmp < 0) + parent.left = e; + else + parent.right = e; + fixAfterInsertion(e); + size++; + modCount++; + return null; + } + + /** + * Removes the mapping for this key from this TreeMap if present. + * + * @param key key for which mapping should be removed + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key}. + * (A {@code null} return can also indicate that the map + * previously associated {@code null} with {@code key}.) + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + */ + public V remove(Object key) { + Entry p = getEntry(key); + if (p == null) + return null; + + V oldValue = p.value; + deleteEntry(p); + return oldValue; + } + + /** + * Removes all of the mappings from this map. + * The map will be empty after this call returns. + */ + public void clear() { + modCount++; + size = 0; + root = null; + } + + /** + * Returns a shallow copy of this {@code TreeMap} instance. (The keys and + * values themselves are not cloned.) + * + * @return a shallow copy of this map + */ + public Object clone() { + TreeMap clone = null; + try { + clone = (TreeMap) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + + // Put clone into "virgin" state (except for comparator) + clone.root = null; + clone.size = 0; + clone.modCount = 0; + clone.entrySet = null; + clone.navigableKeySet = null; + clone.descendingMap = null; + + // Initialize clone with our mappings + try { + clone.buildFromSorted(size, entrySet().iterator(), null, null); + } catch (java.io.IOException cannotHappen) { + } catch (ClassNotFoundException cannotHappen) { + } + + return clone; + } + + // NavigableMap API methods + + /** + * @since 1.6 + */ + public Map.Entry firstEntry() { + return exportEntry(getFirstEntry()); + } + + /** + * @since 1.6 + */ + public Map.Entry lastEntry() { + return exportEntry(getLastEntry()); + } + + /** + * @since 1.6 + */ + public Map.Entry pollFirstEntry() { + Entry p = getFirstEntry(); + Map.Entry result = exportEntry(p); + if (p != null) + deleteEntry(p); + return result; + } + + /** + * @since 1.6 + */ + public Map.Entry pollLastEntry() { + Entry p = getLastEntry(); + Map.Entry result = exportEntry(p); + if (p != null) + deleteEntry(p); + return result; + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if the specified key is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + * @since 1.6 + */ + public Map.Entry lowerEntry(K key) { + return exportEntry(getLowerEntry(key)); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if the specified key is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + * @since 1.6 + */ + public K lowerKey(K key) { + return keyOrNull(getLowerEntry(key)); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if the specified key is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + * @since 1.6 + */ + public Map.Entry floorEntry(K key) { + return exportEntry(getFloorEntry(key)); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if the specified key is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + * @since 1.6 + */ + public K floorKey(K key) { + return keyOrNull(getFloorEntry(key)); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if the specified key is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + * @since 1.6 + */ + public Map.Entry ceilingEntry(K key) { + return exportEntry(getCeilingEntry(key)); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if the specified key is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + * @since 1.6 + */ + public K ceilingKey(K key) { + return keyOrNull(getCeilingEntry(key)); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if the specified key is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + * @since 1.6 + */ + public Map.Entry higherEntry(K key) { + return exportEntry(getHigherEntry(key)); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if the specified key is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + * @since 1.6 + */ + public K higherKey(K key) { + return keyOrNull(getHigherEntry(key)); + } + + // Views + + /** + * Fields initialized to contain an instance of the entry set view + * the first time this view is requested. Views are stateless, so + * there's no reason to create more than one. + */ + private transient EntrySet entrySet = null; + private transient KeySet navigableKeySet = null; + private transient NavigableMap descendingMap = null; + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set's iterator returns the keys in ascending order. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own {@code remove} operation), the results of + * the iteration are undefined. The set supports element removal, + * which removes the corresponding mapping from the map, via the + * {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} + * operations. It does not support the {@code add} or {@code addAll} + * operations. + */ + public Set keySet() { + return navigableKeySet(); + } + + /** + * @since 1.6 + */ + public NavigableSet navigableKeySet() { + KeySet nks = navigableKeySet; + return (nks != null) ? nks : (navigableKeySet = new KeySet(this)); + } + + /** + * @since 1.6 + */ + public NavigableSet descendingKeySet() { + return descendingMap().navigableKeySet(); + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection's iterator returns the values in ascending order + * of the corresponding keys. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. If the map is + * modified while an iteration over the collection is in progress + * (except through the iterator's own {@code remove} operation), + * the results of the iteration are undefined. The collection + * supports element removal, which removes the corresponding + * mapping from the map, via the {@code Iterator.remove}, + * {@code Collection.remove}, {@code removeAll}, + * {@code retainAll} and {@code clear} operations. It does not + * support the {@code add} or {@code addAll} operations. + */ + public Collection values() { + Collection vs = values; + return (vs != null) ? vs : (values = new Values()); + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set's iterator returns the entries in ascending key order. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own {@code remove} operation, or through the + * {@code setValue} operation on a map entry returned by the + * iterator) the results of the iteration are undefined. The set + * supports element removal, which removes the corresponding + * mapping from the map, via the {@code Iterator.remove}, + * {@code Set.remove}, {@code removeAll}, {@code retainAll} and + * {@code clear} operations. It does not support the + * {@code add} or {@code addAll} operations. + */ + public Set> entrySet() { + EntrySet es = entrySet; + return (es != null) ? es : (entrySet = new EntrySet()); + } + + /** + * @since 1.6 + */ + public NavigableMap descendingMap() { + NavigableMap km = descendingMap; + return (km != null) ? km : + (descendingMap = new DescendingSubMap(this, + true, null, true, + true, null, true)); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if {@code fromKey} or {@code toKey} is + * null and this map uses natural ordering, or its comparator + * does not permit null keys + * @throws IllegalArgumentException {@inheritDoc} + * @since 1.6 + */ + public NavigableMap subMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive) { + return new AscendingSubMap(this, + false, fromKey, fromInclusive, + false, toKey, toInclusive); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if {@code toKey} is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + * @throws IllegalArgumentException {@inheritDoc} + * @since 1.6 + */ + public NavigableMap headMap(K toKey, boolean inclusive) { + return new AscendingSubMap(this, + true, null, true, + false, toKey, inclusive); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if {@code fromKey} is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + * @throws IllegalArgumentException {@inheritDoc} + * @since 1.6 + */ + public NavigableMap tailMap(K fromKey, boolean inclusive) { + return new AscendingSubMap(this, + false, fromKey, inclusive, + true, null, true); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if {@code fromKey} or {@code toKey} is + * null and this map uses natural ordering, or its comparator + * does not permit null keys + * @throws IllegalArgumentException {@inheritDoc} + */ + public SortedMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if {@code toKey} is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + * @throws IllegalArgumentException {@inheritDoc} + */ + public SortedMap headMap(K toKey) { + return headMap(toKey, false); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if {@code fromKey} is null + * and this map uses natural ordering, or its comparator + * does not permit null keys + * @throws IllegalArgumentException {@inheritDoc} + */ + public SortedMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + // View class support + + class Values extends AbstractCollection { + public Iterator iterator() { + return new ValueIterator(getFirstEntry()); + } + + public int size() { + return TreeMap.this.size(); + } + + public boolean contains(Object o) { + return TreeMap.this.containsValue(o); + } + + public boolean remove(Object o) { + for (Entry e = getFirstEntry(); e != null; e = successor(e)) { + if (valEquals(e.getValue(), o)) { + deleteEntry(e); + return true; + } + } + return false; + } + + public void clear() { + TreeMap.this.clear(); + } + } + + class EntrySet extends AbstractSet> { + public Iterator> iterator() { + return new EntryIterator(getFirstEntry()); + } + + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry entry = (Map.Entry) o; + V value = entry.getValue(); + Entry p = getEntry(entry.getKey()); + return p != null && valEquals(p.getValue(), value); + } + + public boolean remove(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry entry = (Map.Entry) o; + V value = entry.getValue(); + Entry p = getEntry(entry.getKey()); + if (p != null && valEquals(p.getValue(), value)) { + deleteEntry(p); + return true; + } + return false; + } + + public int size() { + return TreeMap.this.size(); + } + + public void clear() { + TreeMap.this.clear(); + } + } + + /* + * Unlike Values and EntrySet, the KeySet class is static, + * delegating to a NavigableMap to allow use by SubMaps, which + * outweighs the ugliness of needing type-tests for the following + * Iterator methods that are defined appropriately in main versus + * submap classes. + */ + + Iterator keyIterator() { + return new KeyIterator(getFirstEntry()); + } + + Iterator descendingKeyIterator() { + return new DescendingKeyIterator(getLastEntry()); + } + + static final class KeySet extends AbstractSet implements NavigableSet { + private final NavigableMap m; + KeySet(NavigableMap map) { m = map; } + + public Iterator iterator() { + if (m instanceof TreeMap) + return ((TreeMap)m).keyIterator(); + else + return (Iterator)(((TreeMap.NavigableSubMap)m).keyIterator()); + } + + public Iterator descendingIterator() { + if (m instanceof TreeMap) + return ((TreeMap)m).descendingKeyIterator(); + else + return (Iterator)(((TreeMap.NavigableSubMap)m).descendingKeyIterator()); + } + + public int size() { return m.size(); } + public boolean isEmpty() { return m.isEmpty(); } + public boolean contains(Object o) { return m.containsKey(o); } + public void clear() { m.clear(); } + public E lower(E e) { return m.lowerKey(e); } + public E floor(E e) { return m.floorKey(e); } + public E ceiling(E e) { return m.ceilingKey(e); } + public E higher(E e) { return m.higherKey(e); } + public E first() { return m.firstKey(); } + public E last() { return m.lastKey(); } + public Comparator comparator() { return m.comparator(); } + public E pollFirst() { + Map.Entry e = m.pollFirstEntry(); + return (e == null) ? null : e.getKey(); + } + public E pollLast() { + Map.Entry e = m.pollLastEntry(); + return (e == null) ? null : e.getKey(); + } + public boolean remove(Object o) { + int oldSize = size(); + m.remove(o); + return size() != oldSize; + } + public NavigableSet subSet(E fromElement, boolean fromInclusive, + E toElement, boolean toInclusive) { + return new KeySet<>(m.subMap(fromElement, fromInclusive, + toElement, toInclusive)); + } + public NavigableSet headSet(E toElement, boolean inclusive) { + return new KeySet<>(m.headMap(toElement, inclusive)); + } + public NavigableSet tailSet(E fromElement, boolean inclusive) { + return new KeySet<>(m.tailMap(fromElement, inclusive)); + } + public SortedSet subSet(E fromElement, E toElement) { + return subSet(fromElement, true, toElement, false); + } + public SortedSet headSet(E toElement) { + return headSet(toElement, false); + } + public SortedSet tailSet(E fromElement) { + return tailSet(fromElement, true); + } + public NavigableSet descendingSet() { + return new KeySet(m.descendingMap()); + } + } + + /** + * Base class for TreeMap Iterators + */ + abstract class PrivateEntryIterator implements Iterator { + Entry next; + Entry lastReturned; + int expectedModCount; + + PrivateEntryIterator(Entry first) { + expectedModCount = modCount; + lastReturned = null; + next = first; + } + + public final boolean hasNext() { + return next != null; + } + + final Entry nextEntry() { + Entry e = next; + if (e == null) + throw new NoSuchElementException(); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + next = successor(e); + lastReturned = e; + return e; + } + + final Entry prevEntry() { + Entry e = next; + if (e == null) + throw new NoSuchElementException(); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + next = predecessor(e); + lastReturned = e; + return e; + } + + public void remove() { + if (lastReturned == null) + throw new IllegalStateException(); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + // deleted entries are replaced by their successors + if (lastReturned.left != null && lastReturned.right != null) + next = lastReturned; + deleteEntry(lastReturned); + expectedModCount = modCount; + lastReturned = null; + } + } + + final class EntryIterator extends PrivateEntryIterator> { + EntryIterator(Entry first) { + super(first); + } + public Map.Entry next() { + return nextEntry(); + } + } + + final class ValueIterator extends PrivateEntryIterator { + ValueIterator(Entry first) { + super(first); + } + public V next() { + return nextEntry().value; + } + } + + final class KeyIterator extends PrivateEntryIterator { + KeyIterator(Entry first) { + super(first); + } + public K next() { + return nextEntry().key; + } + } + + final class DescendingKeyIterator extends PrivateEntryIterator { + DescendingKeyIterator(Entry first) { + super(first); + } + public K next() { + return prevEntry().key; + } + } + + // Little utilities + + /** + * Compares two keys using the correct comparison method for this TreeMap. + */ + final int compare(Object k1, Object k2) { + return comparator==null ? ((Comparable)k1).compareTo((K)k2) + : comparator.compare((K)k1, (K)k2); + } + + /** + * Test two values for equality. Differs from o1.equals(o2) only in + * that it copes with {@code null} o1 properly. + */ + static final boolean valEquals(Object o1, Object o2) { + return (o1==null ? o2==null : o1.equals(o2)); + } + + /** + * Return SimpleImmutableEntry for entry, or null if null + */ + static Map.Entry exportEntry(TreeMap.Entry e) { + return (e == null) ? null : + new AbstractMap.SimpleImmutableEntry<>(e); + } + + /** + * Return key for entry, or null if null + */ + static K keyOrNull(TreeMap.Entry e) { + return (e == null) ? null : e.key; + } + + /** + * Returns the key corresponding to the specified Entry. + * @throws NoSuchElementException if the Entry is null + */ + static K key(Entry e) { + if (e==null) + throw new NoSuchElementException(); + return e.key; + } + + + // SubMaps + + /** + * Dummy value serving as unmatchable fence key for unbounded + * SubMapIterators + */ + private static final Object UNBOUNDED = new Object(); + + /** + * @serial include + */ + abstract static class NavigableSubMap extends AbstractMap + implements NavigableMap, java.io.Serializable { + /** + * The backing map. + */ + final TreeMap m; + + /** + * Endpoints are represented as triples (fromStart, lo, + * loInclusive) and (toEnd, hi, hiInclusive). If fromStart is + * true, then the low (absolute) bound is the start of the + * backing map, and the other values are ignored. Otherwise, + * if loInclusive is true, lo is the inclusive bound, else lo + * is the exclusive bound. Similarly for the upper bound. + */ + final K lo, hi; + final boolean fromStart, toEnd; + final boolean loInclusive, hiInclusive; + + NavigableSubMap(TreeMap m, + boolean fromStart, K lo, boolean loInclusive, + boolean toEnd, K hi, boolean hiInclusive) { + if (!fromStart && !toEnd) { + if (m.compare(lo, hi) > 0) + throw new IllegalArgumentException("fromKey > toKey"); + } else { + if (!fromStart) // type check + m.compare(lo, lo); + if (!toEnd) + m.compare(hi, hi); + } + + this.m = m; + this.fromStart = fromStart; + this.lo = lo; + this.loInclusive = loInclusive; + this.toEnd = toEnd; + this.hi = hi; + this.hiInclusive = hiInclusive; + } + + // internal utilities + + final boolean tooLow(Object key) { + if (!fromStart) { + int c = m.compare(key, lo); + if (c < 0 || (c == 0 && !loInclusive)) + return true; + } + return false; + } + + final boolean tooHigh(Object key) { + if (!toEnd) { + int c = m.compare(key, hi); + if (c > 0 || (c == 0 && !hiInclusive)) + return true; + } + return false; + } + + final boolean inRange(Object key) { + return !tooLow(key) && !tooHigh(key); + } + + final boolean inClosedRange(Object key) { + return (fromStart || m.compare(key, lo) >= 0) + && (toEnd || m.compare(hi, key) >= 0); + } + + final boolean inRange(Object key, boolean inclusive) { + return inclusive ? inRange(key) : inClosedRange(key); + } + + /* + * Absolute versions of relation operations. + * Subclasses map to these using like-named "sub" + * versions that invert senses for descending maps + */ + + final TreeMap.Entry absLowest() { + TreeMap.Entry e = + (fromStart ? m.getFirstEntry() : + (loInclusive ? m.getCeilingEntry(lo) : + m.getHigherEntry(lo))); + return (e == null || tooHigh(e.key)) ? null : e; + } + + final TreeMap.Entry absHighest() { + TreeMap.Entry e = + (toEnd ? m.getLastEntry() : + (hiInclusive ? m.getFloorEntry(hi) : + m.getLowerEntry(hi))); + return (e == null || tooLow(e.key)) ? null : e; + } + + final TreeMap.Entry absCeiling(K key) { + if (tooLow(key)) + return absLowest(); + TreeMap.Entry e = m.getCeilingEntry(key); + return (e == null || tooHigh(e.key)) ? null : e; + } + + final TreeMap.Entry absHigher(K key) { + if (tooLow(key)) + return absLowest(); + TreeMap.Entry e = m.getHigherEntry(key); + return (e == null || tooHigh(e.key)) ? null : e; + } + + final TreeMap.Entry absFloor(K key) { + if (tooHigh(key)) + return absHighest(); + TreeMap.Entry e = m.getFloorEntry(key); + return (e == null || tooLow(e.key)) ? null : e; + } + + final TreeMap.Entry absLower(K key) { + if (tooHigh(key)) + return absHighest(); + TreeMap.Entry e = m.getLowerEntry(key); + return (e == null || tooLow(e.key)) ? null : e; + } + + /** Returns the absolute high fence for ascending traversal */ + final TreeMap.Entry absHighFence() { + return (toEnd ? null : (hiInclusive ? + m.getHigherEntry(hi) : + m.getCeilingEntry(hi))); + } + + /** Return the absolute low fence for descending traversal */ + final TreeMap.Entry absLowFence() { + return (fromStart ? null : (loInclusive ? + m.getLowerEntry(lo) : + m.getFloorEntry(lo))); + } + + // Abstract methods defined in ascending vs descending classes + // These relay to the appropriate absolute versions + + abstract TreeMap.Entry subLowest(); + abstract TreeMap.Entry subHighest(); + abstract TreeMap.Entry subCeiling(K key); + abstract TreeMap.Entry subHigher(K key); + abstract TreeMap.Entry subFloor(K key); + abstract TreeMap.Entry subLower(K key); + + /** Returns ascending iterator from the perspective of this submap */ + abstract Iterator keyIterator(); + + /** Returns descending iterator from the perspective of this submap */ + abstract Iterator descendingKeyIterator(); + + // public methods + + public boolean isEmpty() { + return (fromStart && toEnd) ? m.isEmpty() : entrySet().isEmpty(); + } + + public int size() { + return (fromStart && toEnd) ? m.size() : entrySet().size(); + } + + public final boolean containsKey(Object key) { + return inRange(key) && m.containsKey(key); + } + + public final V put(K key, V value) { + if (!inRange(key)) + throw new IllegalArgumentException("key out of range"); + return m.put(key, value); + } + + public final V get(Object key) { + return !inRange(key) ? null : m.get(key); + } + + public final V remove(Object key) { + return !inRange(key) ? null : m.remove(key); + } + + public final Map.Entry ceilingEntry(K key) { + return exportEntry(subCeiling(key)); + } + + public final K ceilingKey(K key) { + return keyOrNull(subCeiling(key)); + } + + public final Map.Entry higherEntry(K key) { + return exportEntry(subHigher(key)); + } + + public final K higherKey(K key) { + return keyOrNull(subHigher(key)); + } + + public final Map.Entry floorEntry(K key) { + return exportEntry(subFloor(key)); + } + + public final K floorKey(K key) { + return keyOrNull(subFloor(key)); + } + + public final Map.Entry lowerEntry(K key) { + return exportEntry(subLower(key)); + } + + public final K lowerKey(K key) { + return keyOrNull(subLower(key)); + } + + public final K firstKey() { + return key(subLowest()); + } + + public final K lastKey() { + return key(subHighest()); + } + + public final Map.Entry firstEntry() { + return exportEntry(subLowest()); + } + + public final Map.Entry lastEntry() { + return exportEntry(subHighest()); + } + + public final Map.Entry pollFirstEntry() { + TreeMap.Entry e = subLowest(); + Map.Entry result = exportEntry(e); + if (e != null) + m.deleteEntry(e); + return result; + } + + public final Map.Entry pollLastEntry() { + TreeMap.Entry e = subHighest(); + Map.Entry result = exportEntry(e); + if (e != null) + m.deleteEntry(e); + return result; + } + + // Views + transient NavigableMap descendingMapView = null; + transient EntrySetView entrySetView = null; + transient KeySet navigableKeySetView = null; + + public final NavigableSet navigableKeySet() { + KeySet nksv = navigableKeySetView; + return (nksv != null) ? nksv : + (navigableKeySetView = new TreeMap.KeySet(this)); + } + + public final Set keySet() { + return navigableKeySet(); + } + + public NavigableSet descendingKeySet() { + return descendingMap().navigableKeySet(); + } + + public final SortedMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + public final SortedMap headMap(K toKey) { + return headMap(toKey, false); + } + + public final SortedMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + // View classes + + abstract class EntrySetView extends AbstractSet> { + private transient int size = -1, sizeModCount; + + public int size() { + if (fromStart && toEnd) + return m.size(); + if (size == -1 || sizeModCount != m.modCount) { + sizeModCount = m.modCount; + size = 0; + Iterator i = iterator(); + while (i.hasNext()) { + size++; + i.next(); + } + } + return size; + } + + public boolean isEmpty() { + TreeMap.Entry n = absLowest(); + return n == null || tooHigh(n.key); + } + + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry entry = (Map.Entry) o; + K key = entry.getKey(); + if (!inRange(key)) + return false; + TreeMap.Entry node = m.getEntry(key); + return node != null && + valEquals(node.getValue(), entry.getValue()); + } + + public boolean remove(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry entry = (Map.Entry) o; + K key = entry.getKey(); + if (!inRange(key)) + return false; + TreeMap.Entry node = m.getEntry(key); + if (node!=null && valEquals(node.getValue(), + entry.getValue())) { + m.deleteEntry(node); + return true; + } + return false; + } + } + + /** + * Iterators for SubMaps + */ + abstract class SubMapIterator implements Iterator { + TreeMap.Entry lastReturned; + TreeMap.Entry next; + final Object fenceKey; + int expectedModCount; + + SubMapIterator(TreeMap.Entry first, + TreeMap.Entry fence) { + expectedModCount = m.modCount; + lastReturned = null; + next = first; + fenceKey = fence == null ? UNBOUNDED : fence.key; + } + + public final boolean hasNext() { + return next != null && next.key != fenceKey; + } + + final TreeMap.Entry nextEntry() { + TreeMap.Entry e = next; + if (e == null || e.key == fenceKey) + throw new NoSuchElementException(); + if (m.modCount != expectedModCount) + throw new ConcurrentModificationException(); + next = successor(e); + lastReturned = e; + return e; + } + + final TreeMap.Entry prevEntry() { + TreeMap.Entry e = next; + if (e == null || e.key == fenceKey) + throw new NoSuchElementException(); + if (m.modCount != expectedModCount) + throw new ConcurrentModificationException(); + next = predecessor(e); + lastReturned = e; + return e; + } + + final void removeAscending() { + if (lastReturned == null) + throw new IllegalStateException(); + if (m.modCount != expectedModCount) + throw new ConcurrentModificationException(); + // deleted entries are replaced by their successors + if (lastReturned.left != null && lastReturned.right != null) + next = lastReturned; + m.deleteEntry(lastReturned); + lastReturned = null; + expectedModCount = m.modCount; + } + + final void removeDescending() { + if (lastReturned == null) + throw new IllegalStateException(); + if (m.modCount != expectedModCount) + throw new ConcurrentModificationException(); + m.deleteEntry(lastReturned); + lastReturned = null; + expectedModCount = m.modCount; + } + + } + + final class SubMapEntryIterator extends SubMapIterator> { + SubMapEntryIterator(TreeMap.Entry first, + TreeMap.Entry fence) { + super(first, fence); + } + public Map.Entry next() { + return nextEntry(); + } + public void remove() { + removeAscending(); + } + } + + final class SubMapKeyIterator extends SubMapIterator { + SubMapKeyIterator(TreeMap.Entry first, + TreeMap.Entry fence) { + super(first, fence); + } + public K next() { + return nextEntry().key; + } + public void remove() { + removeAscending(); + } + } + + final class DescendingSubMapEntryIterator extends SubMapIterator> { + DescendingSubMapEntryIterator(TreeMap.Entry last, + TreeMap.Entry fence) { + super(last, fence); + } + + public Map.Entry next() { + return prevEntry(); + } + public void remove() { + removeDescending(); + } + } + + final class DescendingSubMapKeyIterator extends SubMapIterator { + DescendingSubMapKeyIterator(TreeMap.Entry last, + TreeMap.Entry fence) { + super(last, fence); + } + public K next() { + return prevEntry().key; + } + public void remove() { + removeDescending(); + } + } + } + + /** + * @serial include + */ + static final class AscendingSubMap extends NavigableSubMap { + private static final long serialVersionUID = 912986545866124060L; + + AscendingSubMap(TreeMap m, + boolean fromStart, K lo, boolean loInclusive, + boolean toEnd, K hi, boolean hiInclusive) { + super(m, fromStart, lo, loInclusive, toEnd, hi, hiInclusive); + } + + public Comparator comparator() { + return m.comparator(); + } + + public NavigableMap subMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive) { + if (!inRange(fromKey, fromInclusive)) + throw new IllegalArgumentException("fromKey out of range"); + if (!inRange(toKey, toInclusive)) + throw new IllegalArgumentException("toKey out of range"); + return new AscendingSubMap(m, + false, fromKey, fromInclusive, + false, toKey, toInclusive); + } + + public NavigableMap headMap(K toKey, boolean inclusive) { + if (!inRange(toKey, inclusive)) + throw new IllegalArgumentException("toKey out of range"); + return new AscendingSubMap(m, + fromStart, lo, loInclusive, + false, toKey, inclusive); + } + + public NavigableMap tailMap(K fromKey, boolean inclusive) { + if (!inRange(fromKey, inclusive)) + throw new IllegalArgumentException("fromKey out of range"); + return new AscendingSubMap(m, + false, fromKey, inclusive, + toEnd, hi, hiInclusive); + } + + public NavigableMap descendingMap() { + NavigableMap mv = descendingMapView; + return (mv != null) ? mv : + (descendingMapView = + new DescendingSubMap(m, + fromStart, lo, loInclusive, + toEnd, hi, hiInclusive)); + } + + Iterator keyIterator() { + return new SubMapKeyIterator(absLowest(), absHighFence()); + } + + Iterator descendingKeyIterator() { + return new DescendingSubMapKeyIterator(absHighest(), absLowFence()); + } + + final class AscendingEntrySetView extends EntrySetView { + public Iterator> iterator() { + return new SubMapEntryIterator(absLowest(), absHighFence()); + } + } + + public Set> entrySet() { + EntrySetView es = entrySetView; + return (es != null) ? es : new AscendingEntrySetView(); + } + + TreeMap.Entry subLowest() { return absLowest(); } + TreeMap.Entry subHighest() { return absHighest(); } + TreeMap.Entry subCeiling(K key) { return absCeiling(key); } + TreeMap.Entry subHigher(K key) { return absHigher(key); } + TreeMap.Entry subFloor(K key) { return absFloor(key); } + TreeMap.Entry subLower(K key) { return absLower(key); } + } + + /** + * @serial include + */ + static final class DescendingSubMap extends NavigableSubMap { + private static final long serialVersionUID = 912986545866120460L; + DescendingSubMap(TreeMap m, + boolean fromStart, K lo, boolean loInclusive, + boolean toEnd, K hi, boolean hiInclusive) { + super(m, fromStart, lo, loInclusive, toEnd, hi, hiInclusive); + } + + private final Comparator reverseComparator = + Collections.reverseOrder(m.comparator); + + public Comparator comparator() { + return reverseComparator; + } + + public NavigableMap subMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive) { + if (!inRange(fromKey, fromInclusive)) + throw new IllegalArgumentException("fromKey out of range"); + if (!inRange(toKey, toInclusive)) + throw new IllegalArgumentException("toKey out of range"); + return new DescendingSubMap(m, + false, toKey, toInclusive, + false, fromKey, fromInclusive); + } + + public NavigableMap headMap(K toKey, boolean inclusive) { + if (!inRange(toKey, inclusive)) + throw new IllegalArgumentException("toKey out of range"); + return new DescendingSubMap(m, + false, toKey, inclusive, + toEnd, hi, hiInclusive); + } + + public NavigableMap tailMap(K fromKey, boolean inclusive) { + if (!inRange(fromKey, inclusive)) + throw new IllegalArgumentException("fromKey out of range"); + return new DescendingSubMap(m, + fromStart, lo, loInclusive, + false, fromKey, inclusive); + } + + public NavigableMap descendingMap() { + NavigableMap mv = descendingMapView; + return (mv != null) ? mv : + (descendingMapView = + new AscendingSubMap(m, + fromStart, lo, loInclusive, + toEnd, hi, hiInclusive)); + } + + Iterator keyIterator() { + return new DescendingSubMapKeyIterator(absHighest(), absLowFence()); + } + + Iterator descendingKeyIterator() { + return new SubMapKeyIterator(absLowest(), absHighFence()); + } + + final class DescendingEntrySetView extends EntrySetView { + public Iterator> iterator() { + return new DescendingSubMapEntryIterator(absHighest(), absLowFence()); + } + } + + public Set> entrySet() { + EntrySetView es = entrySetView; + return (es != null) ? es : new DescendingEntrySetView(); + } + + TreeMap.Entry subLowest() { return absHighest(); } + TreeMap.Entry subHighest() { return absLowest(); } + TreeMap.Entry subCeiling(K key) { return absFloor(key); } + TreeMap.Entry subHigher(K key) { return absLower(key); } + TreeMap.Entry subFloor(K key) { return absCeiling(key); } + TreeMap.Entry subLower(K key) { return absHigher(key); } + } + + /** + * This class exists solely for the sake of serialization + * compatibility with previous releases of TreeMap that did not + * support NavigableMap. It translates an old-version SubMap into + * a new-version AscendingSubMap. This class is never otherwise + * used. + * + * @serial include + */ + private class SubMap extends AbstractMap + implements SortedMap, java.io.Serializable { + private static final long serialVersionUID = -6520786458950516097L; + private boolean fromStart = false, toEnd = false; + private K fromKey, toKey; + private Object readResolve() { + return new AscendingSubMap(TreeMap.this, + fromStart, fromKey, true, + toEnd, toKey, false); + } + public Set> entrySet() { throw new InternalError(); } + public K lastKey() { throw new InternalError(); } + public K firstKey() { throw new InternalError(); } + public SortedMap subMap(K fromKey, K toKey) { throw new InternalError(); } + public SortedMap headMap(K toKey) { throw new InternalError(); } + public SortedMap tailMap(K fromKey) { throw new InternalError(); } + public Comparator comparator() { throw new InternalError(); } + } + + + // Red-black mechanics + + private static final boolean RED = false; + private static final boolean BLACK = true; + + /** + * Node in the Tree. Doubles as a means to pass key-value pairs back to + * user (see Map.Entry). + */ + + static final class Entry implements Map.Entry { + K key; + V value; + Entry left = null; + Entry right = null; + Entry parent; + boolean color = BLACK; + + /** + * Make a new cell with given key, value, and parent, and with + * {@code null} child links, and BLACK color. + */ + Entry(K key, V value, Entry parent) { + this.key = key; + this.value = value; + this.parent = parent; + } + + /** + * Returns the key. + * + * @return the key + */ + public K getKey() { + return key; + } + + /** + * Returns the value associated with the key. + * + * @return the value associated with the key + */ + public V getValue() { + return value; + } + + /** + * Replaces the value currently associated with the key with the given + * value. + * + * @return the value associated with the key before this method was + * called + */ + public V setValue(V value) { + V oldValue = this.value; + this.value = value; + return oldValue; + } + + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + + return valEquals(key,e.getKey()) && valEquals(value,e.getValue()); + } + + public int hashCode() { + int keyHash = (key==null ? 0 : key.hashCode()); + int valueHash = (value==null ? 0 : value.hashCode()); + return keyHash ^ valueHash; + } + + public String toString() { + return key + "=" + value; + } + } + + /** + * Returns the first Entry in the TreeMap (according to the TreeMap's + * key-sort function). Returns null if the TreeMap is empty. + */ + final Entry getFirstEntry() { + Entry p = root; + if (p != null) + while (p.left != null) + p = p.left; + return p; + } + + /** + * Returns the last Entry in the TreeMap (according to the TreeMap's + * key-sort function). Returns null if the TreeMap is empty. + */ + final Entry getLastEntry() { + Entry p = root; + if (p != null) + while (p.right != null) + p = p.right; + return p; + } + + /** + * Returns the successor of the specified Entry, or null if no such. + */ + static TreeMap.Entry successor(Entry t) { + if (t == null) + return null; + else if (t.right != null) { + Entry p = t.right; + while (p.left != null) + p = p.left; + return p; + } else { + Entry p = t.parent; + Entry ch = t; + while (p != null && ch == p.right) { + ch = p; + p = p.parent; + } + return p; + } + } + + /** + * Returns the predecessor of the specified Entry, or null if no such. + */ + static Entry predecessor(Entry t) { + if (t == null) + return null; + else if (t.left != null) { + Entry p = t.left; + while (p.right != null) + p = p.right; + return p; + } else { + Entry p = t.parent; + Entry ch = t; + while (p != null && ch == p.left) { + ch = p; + p = p.parent; + } + return p; + } + } + + /** + * Balancing operations. + * + * Implementations of rebalancings during insertion and deletion are + * slightly different than the CLR version. Rather than using dummy + * nilnodes, we use a set of accessors that deal properly with null. They + * are used to avoid messiness surrounding nullness checks in the main + * algorithms. + */ + + private static boolean colorOf(Entry p) { + return (p == null ? BLACK : p.color); + } + + private static Entry parentOf(Entry p) { + return (p == null ? null: p.parent); + } + + private static void setColor(Entry p, boolean c) { + if (p != null) + p.color = c; + } + + private static Entry leftOf(Entry p) { + return (p == null) ? null: p.left; + } + + private static Entry rightOf(Entry p) { + return (p == null) ? null: p.right; + } + + /** From CLR */ + private void rotateLeft(Entry p) { + if (p != null) { + Entry r = p.right; + p.right = r.left; + if (r.left != null) + r.left.parent = p; + r.parent = p.parent; + if (p.parent == null) + root = r; + else if (p.parent.left == p) + p.parent.left = r; + else + p.parent.right = r; + r.left = p; + p.parent = r; + } + } + + /** From CLR */ + private void rotateRight(Entry p) { + if (p != null) { + Entry l = p.left; + p.left = l.right; + if (l.right != null) l.right.parent = p; + l.parent = p.parent; + if (p.parent == null) + root = l; + else if (p.parent.right == p) + p.parent.right = l; + else p.parent.left = l; + l.right = p; + p.parent = l; + } + } + + /** From CLR */ + private void fixAfterInsertion(Entry x) { + x.color = RED; + + while (x != null && x != root && x.parent.color == RED) { + if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { + Entry y = rightOf(parentOf(parentOf(x))); + if (colorOf(y) == RED) { + setColor(parentOf(x), BLACK); + setColor(y, BLACK); + setColor(parentOf(parentOf(x)), RED); + x = parentOf(parentOf(x)); + } else { + if (x == rightOf(parentOf(x))) { + x = parentOf(x); + rotateLeft(x); + } + setColor(parentOf(x), BLACK); + setColor(parentOf(parentOf(x)), RED); + rotateRight(parentOf(parentOf(x))); + } + } else { + Entry y = leftOf(parentOf(parentOf(x))); + if (colorOf(y) == RED) { + setColor(parentOf(x), BLACK); + setColor(y, BLACK); + setColor(parentOf(parentOf(x)), RED); + x = parentOf(parentOf(x)); + } else { + if (x == leftOf(parentOf(x))) { + x = parentOf(x); + rotateRight(x); + } + setColor(parentOf(x), BLACK); + setColor(parentOf(parentOf(x)), RED); + rotateLeft(parentOf(parentOf(x))); + } + } + } + root.color = BLACK; + } + + /** + * Delete node p, and then rebalance the tree. + */ + private void deleteEntry(Entry p) { + modCount++; + size--; + + // If strictly internal, copy successor's element to p and then make p + // point to successor. + if (p.left != null && p.right != null) { + Entry s = successor(p); + p.key = s.key; + p.value = s.value; + p = s; + } // p has 2 children + + // Start fixup at replacement node, if it exists. + Entry replacement = (p.left != null ? p.left : p.right); + + if (replacement != null) { + // Link replacement to parent + replacement.parent = p.parent; + if (p.parent == null) + root = replacement; + else if (p == p.parent.left) + p.parent.left = replacement; + else + p.parent.right = replacement; + + // Null out links so they are OK to use by fixAfterDeletion. + p.left = p.right = p.parent = null; + + // Fix replacement + if (p.color == BLACK) + fixAfterDeletion(replacement); + } else if (p.parent == null) { // return if we are the only node. + root = null; + } else { // No children. Use self as phantom replacement and unlink. + if (p.color == BLACK) + fixAfterDeletion(p); + + if (p.parent != null) { + if (p == p.parent.left) + p.parent.left = null; + else if (p == p.parent.right) + p.parent.right = null; + p.parent = null; + } + } + } + + /** From CLR */ + private void fixAfterDeletion(Entry x) { + while (x != root && colorOf(x) == BLACK) { + if (x == leftOf(parentOf(x))) { + Entry sib = rightOf(parentOf(x)); + + if (colorOf(sib) == RED) { + setColor(sib, BLACK); + setColor(parentOf(x), RED); + rotateLeft(parentOf(x)); + sib = rightOf(parentOf(x)); + } + + if (colorOf(leftOf(sib)) == BLACK && + colorOf(rightOf(sib)) == BLACK) { + setColor(sib, RED); + x = parentOf(x); + } else { + if (colorOf(rightOf(sib)) == BLACK) { + setColor(leftOf(sib), BLACK); + setColor(sib, RED); + rotateRight(sib); + sib = rightOf(parentOf(x)); + } + setColor(sib, colorOf(parentOf(x))); + setColor(parentOf(x), BLACK); + setColor(rightOf(sib), BLACK); + rotateLeft(parentOf(x)); + x = root; + } + } else { // symmetric + Entry sib = leftOf(parentOf(x)); + + if (colorOf(sib) == RED) { + setColor(sib, BLACK); + setColor(parentOf(x), RED); + rotateRight(parentOf(x)); + sib = leftOf(parentOf(x)); + } + + if (colorOf(rightOf(sib)) == BLACK && + colorOf(leftOf(sib)) == BLACK) { + setColor(sib, RED); + x = parentOf(x); + } else { + if (colorOf(leftOf(sib)) == BLACK) { + setColor(rightOf(sib), BLACK); + setColor(sib, RED); + rotateLeft(sib); + sib = leftOf(parentOf(x)); + } + setColor(sib, colorOf(parentOf(x))); + setColor(parentOf(x), BLACK); + setColor(leftOf(sib), BLACK); + rotateRight(parentOf(x)); + x = root; + } + } + } + + setColor(x, BLACK); + } + + private static final long serialVersionUID = 919286545866124006L; + + /** + * Save the state of the {@code TreeMap} instance to a stream (i.e., + * serialize it). + * + * @serialData The size of the TreeMap (the number of key-value + * mappings) is emitted (int), followed by the key (Object) + * and value (Object) for each key-value mapping represented + * by the TreeMap. The key-value mappings are emitted in + * key-order (as determined by the TreeMap's Comparator, + * or by the keys' natural ordering if the TreeMap has no + * Comparator). + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + // Write out the Comparator and any hidden stuff + s.defaultWriteObject(); + + // Write out size (number of Mappings) + s.writeInt(size); + + // Write out keys and values (alternating) + for (Iterator> i = entrySet().iterator(); i.hasNext(); ) { + Map.Entry e = i.next(); + s.writeObject(e.getKey()); + s.writeObject(e.getValue()); + } + } + + /** + * Reconstitute the {@code TreeMap} instance from a stream (i.e., + * deserialize it). + */ + private void readObject(final java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + // Read in the Comparator and any hidden stuff + s.defaultReadObject(); + + // Read in size + int size = s.readInt(); + + buildFromSorted(size, null, s, null); + } + + /** Intended to be called only from TreeSet.readObject */ + void readTreeSet(int size, java.io.ObjectInputStream s, V defaultVal) + throws java.io.IOException, ClassNotFoundException { + buildFromSorted(size, null, s, defaultVal); + } + + /** Intended to be called only from TreeSet.addAll */ + void addAllForTreeSet(SortedSet set, V defaultVal) { + try { + buildFromSorted(set.size(), set.iterator(), null, defaultVal); + } catch (java.io.IOException cannotHappen) { + } catch (ClassNotFoundException cannotHappen) { + } + } + + + /** + * Linear time tree building algorithm from sorted data. Can accept keys + * and/or values from iterator or stream. This leads to too many + * parameters, but seems better than alternatives. The four formats + * that this method accepts are: + * + * 1) An iterator of Map.Entries. (it != null, defaultVal == null). + * 2) An iterator of keys. (it != null, defaultVal != null). + * 3) A stream of alternating serialized keys and values. + * (it == null, defaultVal == null). + * 4) A stream of serialized keys. (it == null, defaultVal != null). + * + * It is assumed that the comparator of the TreeMap is already set prior + * to calling this method. + * + * @param size the number of keys (or key-value pairs) to be read from + * the iterator or stream + * @param it If non-null, new entries are created from entries + * or keys read from this iterator. + * @param str If non-null, new entries are created from keys and + * possibly values read from this stream in serialized form. + * Exactly one of it and str should be non-null. + * @param defaultVal if non-null, this default value is used for + * each value in the map. If null, each value is read from + * iterator or stream, as described above. + * @throws IOException propagated from stream reads. This cannot + * occur if str is null. + * @throws ClassNotFoundException propagated from readObject. + * This cannot occur if str is null. + */ + private void buildFromSorted(int size, Iterator it, + java.io.ObjectInputStream str, + V defaultVal) + throws java.io.IOException, ClassNotFoundException { + this.size = size; + root = buildFromSorted(0, 0, size-1, computeRedLevel(size), + it, str, defaultVal); + } + + /** + * Recursive "helper method" that does the real work of the + * previous method. Identically named parameters have + * identical definitions. Additional parameters are documented below. + * It is assumed that the comparator and size fields of the TreeMap are + * already set prior to calling this method. (It ignores both fields.) + * + * @param level the current level of tree. Initial call should be 0. + * @param lo the first element index of this subtree. Initial should be 0. + * @param hi the last element index of this subtree. Initial should be + * size-1. + * @param redLevel the level at which nodes should be red. + * Must be equal to computeRedLevel for tree of this size. + */ + private final Entry buildFromSorted(int level, int lo, int hi, + int redLevel, + Iterator it, + java.io.ObjectInputStream str, + V defaultVal) + throws java.io.IOException, ClassNotFoundException { + /* + * Strategy: The root is the middlemost element. To get to it, we + * have to first recursively construct the entire left subtree, + * so as to grab all of its elements. We can then proceed with right + * subtree. + * + * The lo and hi arguments are the minimum and maximum + * indices to pull out of the iterator or stream for current subtree. + * They are not actually indexed, we just proceed sequentially, + * ensuring that items are extracted in corresponding order. + */ + + if (hi < lo) return null; + + int mid = (lo + hi) >>> 1; + + Entry left = null; + if (lo < mid) + left = buildFromSorted(level+1, lo, mid - 1, redLevel, + it, str, defaultVal); + + // extract key and/or value from iterator or stream + K key; + V value; + if (it != null) { + if (defaultVal==null) { + Map.Entry entry = (Map.Entry)it.next(); + key = entry.getKey(); + value = entry.getValue(); + } else { + key = (K)it.next(); + value = defaultVal; + } + } else { // use stream + key = (K) str.readObject(); + value = (defaultVal != null ? defaultVal : (V) str.readObject()); + } + + Entry middle = new Entry<>(key, value, null); + + // color nodes in non-full bottommost level red + if (level == redLevel) + middle.color = RED; + + if (left != null) { + middle.left = left; + left.parent = middle; + } + + if (mid < hi) { + Entry right = buildFromSorted(level+1, mid+1, hi, redLevel, + it, str, defaultVal); + middle.right = right; + right.parent = middle; + } + + return middle; + } + + /** + * Find the level down to which to assign all nodes BLACK. This is the + * last `full' level of the complete binary tree produced by + * buildTree. The remaining nodes are colored RED. (This makes a `nice' + * set of color assignments wrt future insertions.) This level number is + * computed by finding the number of splits needed to reach the zeroeth + * node. (The answer is ~lg(N), but in any case must be computed by same + * quick O(lg(N)) loop.) + */ + private static int computeRedLevel(int sz) { + int level = 0; + for (int m = sz - 1; m >= 0; m = m / 2 - 1) + level++; + return level; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/TreeSet.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/TreeSet.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,539 @@ +/* + * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util; + +/** + * A {@link NavigableSet} implementation based on a {@link TreeMap}. + * The elements are ordered using their {@linkplain Comparable natural + * ordering}, or by a {@link Comparator} provided at set creation + * time, depending on which constructor is used. + * + *

This implementation provides guaranteed log(n) time cost for the basic + * operations ({@code add}, {@code remove} and {@code contains}). + * + *

Note that the ordering maintained by a set (whether or not an explicit + * comparator is provided) must be consistent with equals if it is to + * correctly implement the {@code Set} interface. (See {@code Comparable} + * or {@code Comparator} for a precise definition of consistent with + * equals.) This is so because the {@code Set} interface is defined in + * terms of the {@code equals} operation, but a {@code TreeSet} instance + * performs all element comparisons using its {@code compareTo} (or + * {@code compare}) method, so two elements that are deemed equal by this method + * are, from the standpoint of the set, equal. The behavior of a set + * is well-defined even if its ordering is inconsistent with equals; it + * just fails to obey the general contract of the {@code Set} interface. + * + *

Note that this implementation is not synchronized. + * If multiple threads access a tree set concurrently, and at least one + * of the threads modifies the set, it must be synchronized + * externally. This is typically accomplished by synchronizing on some + * object that naturally encapsulates the set. + * If no such object exists, the set should be "wrapped" using the + * {@link Collections#synchronizedSortedSet Collections.synchronizedSortedSet} + * method. This is best done at creation time, to prevent accidental + * unsynchronized access to the set:

+ *   SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));
+ * + *

The iterators returned by this class's {@code iterator} method are + * fail-fast: if the set is modified at any time after the iterator is + * created, in any way except through the iterator's own {@code remove} + * method, the iterator will throw a {@link ConcurrentModificationException}. + * Thus, in the face of concurrent modification, the iterator fails quickly + * and cleanly, rather than risking arbitrary, non-deterministic behavior at + * an undetermined time in the future. + * + *

Note that the fail-fast behavior of an iterator cannot be guaranteed + * as it is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators + * throw {@code ConcurrentModificationException} on a best-effort basis. + * Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: the fail-fast behavior of iterators + * should be used only to detect bugs. + * + *

This class is a member of the + * + * Java Collections Framework. + * + * @param the type of elements maintained by this set + * + * @author Josh Bloch + * @see Collection + * @see Set + * @see HashSet + * @see Comparable + * @see Comparator + * @see TreeMap + * @since 1.2 + */ + +public class TreeSet extends AbstractSet + implements NavigableSet, Cloneable, java.io.Serializable +{ + /** + * The backing map. + */ + private transient NavigableMap m; + + // Dummy value to associate with an Object in the backing Map + private static final Object PRESENT = new Object(); + + /** + * Constructs a set backed by the specified navigable map. + */ + TreeSet(NavigableMap m) { + this.m = m; + } + + /** + * Constructs a new, empty tree set, sorted according to the + * natural ordering of its elements. All elements inserted into + * the set must implement the {@link Comparable} interface. + * Furthermore, all such elements must be mutually + * comparable: {@code e1.compareTo(e2)} must not throw a + * {@code ClassCastException} for any elements {@code e1} and + * {@code e2} in the set. If the user attempts to add an element + * to the set that violates this constraint (for example, the user + * attempts to add a string element to a set whose elements are + * integers), the {@code add} call will throw a + * {@code ClassCastException}. + */ + public TreeSet() { + this(new TreeMap()); + } + + /** + * Constructs a new, empty tree set, sorted according to the specified + * comparator. All elements inserted into the set must be mutually + * comparable by the specified comparator: {@code comparator.compare(e1, + * e2)} must not throw a {@code ClassCastException} for any elements + * {@code e1} and {@code e2} in the set. If the user attempts to add + * an element to the set that violates this constraint, the + * {@code add} call will throw a {@code ClassCastException}. + * + * @param comparator the comparator that will be used to order this set. + * If {@code null}, the {@linkplain Comparable natural + * ordering} of the elements will be used. + */ + public TreeSet(Comparator comparator) { + this(new TreeMap<>(comparator)); + } + + /** + * Constructs a new tree set containing the elements in the specified + * collection, sorted according to the natural ordering of its + * elements. All elements inserted into the set must implement the + * {@link Comparable} interface. Furthermore, all such elements must be + * mutually comparable: {@code e1.compareTo(e2)} must not throw a + * {@code ClassCastException} for any elements {@code e1} and + * {@code e2} in the set. + * + * @param c collection whose elements will comprise the new set + * @throws ClassCastException if the elements in {@code c} are + * not {@link Comparable}, or are not mutually comparable + * @throws NullPointerException if the specified collection is null + */ + public TreeSet(Collection c) { + this(); + addAll(c); + } + + /** + * Constructs a new tree set containing the same elements and + * using the same ordering as the specified sorted set. + * + * @param s sorted set whose elements will comprise the new set + * @throws NullPointerException if the specified sorted set is null + */ + public TreeSet(SortedSet s) { + this(s.comparator()); + addAll(s); + } + + /** + * Returns an iterator over the elements in this set in ascending order. + * + * @return an iterator over the elements in this set in ascending order + */ + public Iterator iterator() { + return m.navigableKeySet().iterator(); + } + + /** + * Returns an iterator over the elements in this set in descending order. + * + * @return an iterator over the elements in this set in descending order + * @since 1.6 + */ + public Iterator descendingIterator() { + return m.descendingKeySet().iterator(); + } + + /** + * @since 1.6 + */ + public NavigableSet descendingSet() { + return new TreeSet<>(m.descendingMap()); + } + + /** + * Returns the number of elements in this set (its cardinality). + * + * @return the number of elements in this set (its cardinality) + */ + public int size() { + return m.size(); + } + + /** + * Returns {@code true} if this set contains no elements. + * + * @return {@code true} if this set contains no elements + */ + public boolean isEmpty() { + return m.isEmpty(); + } + + /** + * Returns {@code true} if this set contains the specified element. + * More formally, returns {@code true} if and only if this set + * contains an element {@code e} such that + * (o==null ? e==null : o.equals(e)). + * + * @param o object to be checked for containment in this set + * @return {@code true} if this set contains the specified element + * @throws ClassCastException if the specified object cannot be compared + * with the elements currently in the set + * @throws NullPointerException if the specified element is null + * and this set uses natural ordering, or its comparator + * does not permit null elements + */ + public boolean contains(Object o) { + return m.containsKey(o); + } + + /** + * Adds the specified element to this set if it is not already present. + * More formally, adds the specified element {@code e} to this set if + * the set contains no element {@code e2} such that + * (e==null ? e2==null : e.equals(e2)). + * If this set already contains the element, the call leaves the set + * unchanged and returns {@code false}. + * + * @param e element to be added to this set + * @return {@code true} if this set did not already contain the specified + * element + * @throws ClassCastException if the specified object cannot be compared + * with the elements currently in this set + * @throws NullPointerException if the specified element is null + * and this set uses natural ordering, or its comparator + * does not permit null elements + */ + public boolean add(E e) { + return m.put(e, PRESENT)==null; + } + + /** + * Removes the specified element from this set if it is present. + * More formally, removes an element {@code e} such that + * (o==null ? e==null : o.equals(e)), + * if this set contains such an element. Returns {@code true} if + * this set contained the element (or equivalently, if this set + * changed as a result of the call). (This set will not contain the + * element once the call returns.) + * + * @param o object to be removed from this set, if present + * @return {@code true} if this set contained the specified element + * @throws ClassCastException if the specified object cannot be compared + * with the elements currently in this set + * @throws NullPointerException if the specified element is null + * and this set uses natural ordering, or its comparator + * does not permit null elements + */ + public boolean remove(Object o) { + return m.remove(o)==PRESENT; + } + + /** + * Removes all of the elements from this set. + * The set will be empty after this call returns. + */ + public void clear() { + m.clear(); + } + + /** + * Adds all of the elements in the specified collection to this set. + * + * @param c collection containing elements to be added to this set + * @return {@code true} if this set changed as a result of the call + * @throws ClassCastException if the elements provided cannot be compared + * with the elements currently in the set + * @throws NullPointerException if the specified collection is null or + * if any element is null and this set uses natural ordering, or + * its comparator does not permit null elements + */ + public boolean addAll(Collection c) { + // Use linear-time version if applicable + if (m.size()==0 && c.size() > 0 && + c instanceof SortedSet && + m instanceof TreeMap) { + SortedSet set = (SortedSet) c; + TreeMap map = (TreeMap) m; + Comparator cc = (Comparator) set.comparator(); + Comparator mc = map.comparator(); + if (cc==mc || (cc != null && cc.equals(mc))) { + map.addAllForTreeSet(set, PRESENT); + return true; + } + } + return super.addAll(c); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if {@code fromElement} or {@code toElement} + * is null and this set uses natural ordering, or its comparator + * does not permit null elements + * @throws IllegalArgumentException {@inheritDoc} + * @since 1.6 + */ + public NavigableSet subSet(E fromElement, boolean fromInclusive, + E toElement, boolean toInclusive) { + return new TreeSet<>(m.subMap(fromElement, fromInclusive, + toElement, toInclusive)); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if {@code toElement} is null and + * this set uses natural ordering, or its comparator does + * not permit null elements + * @throws IllegalArgumentException {@inheritDoc} + * @since 1.6 + */ + public NavigableSet headSet(E toElement, boolean inclusive) { + return new TreeSet<>(m.headMap(toElement, inclusive)); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if {@code fromElement} is null and + * this set uses natural ordering, or its comparator does + * not permit null elements + * @throws IllegalArgumentException {@inheritDoc} + * @since 1.6 + */ + public NavigableSet tailSet(E fromElement, boolean inclusive) { + return new TreeSet<>(m.tailMap(fromElement, inclusive)); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if {@code fromElement} or + * {@code toElement} is null and this set uses natural ordering, + * or its comparator does not permit null elements + * @throws IllegalArgumentException {@inheritDoc} + */ + public SortedSet subSet(E fromElement, E toElement) { + return subSet(fromElement, true, toElement, false); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if {@code toElement} is null + * and this set uses natural ordering, or its comparator does + * not permit null elements + * @throws IllegalArgumentException {@inheritDoc} + */ + public SortedSet headSet(E toElement) { + return headSet(toElement, false); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if {@code fromElement} is null + * and this set uses natural ordering, or its comparator does + * not permit null elements + * @throws IllegalArgumentException {@inheritDoc} + */ + public SortedSet tailSet(E fromElement) { + return tailSet(fromElement, true); + } + + public Comparator comparator() { + return m.comparator(); + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E first() { + return m.firstKey(); + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E last() { + return m.lastKey(); + } + + // NavigableSet API methods + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if the specified element is null + * and this set uses natural ordering, or its comparator + * does not permit null elements + * @since 1.6 + */ + public E lower(E e) { + return m.lowerKey(e); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if the specified element is null + * and this set uses natural ordering, or its comparator + * does not permit null elements + * @since 1.6 + */ + public E floor(E e) { + return m.floorKey(e); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if the specified element is null + * and this set uses natural ordering, or its comparator + * does not permit null elements + * @since 1.6 + */ + public E ceiling(E e) { + return m.ceilingKey(e); + } + + /** + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException if the specified element is null + * and this set uses natural ordering, or its comparator + * does not permit null elements + * @since 1.6 + */ + public E higher(E e) { + return m.higherKey(e); + } + + /** + * @since 1.6 + */ + public E pollFirst() { + Map.Entry e = m.pollFirstEntry(); + return (e == null) ? null : e.getKey(); + } + + /** + * @since 1.6 + */ + public E pollLast() { + Map.Entry e = m.pollLastEntry(); + return (e == null) ? null : e.getKey(); + } + + /** + * Returns a shallow copy of this {@code TreeSet} instance. (The elements + * themselves are not cloned.) + * + * @return a shallow copy of this set + */ + public Object clone() { + TreeSet clone = null; + try { + clone = (TreeSet) super.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + + clone.m = new TreeMap<>(m); + return clone; + } + + /** + * Save the state of the {@code TreeSet} instance to a stream (that is, + * serialize it). + * + * @serialData Emits the comparator used to order this set, or + * {@code null} if it obeys its elements' natural ordering + * (Object), followed by the size of the set (the number of + * elements it contains) (int), followed by all of its + * elements (each an Object) in order (as determined by the + * set's Comparator, or by the elements' natural ordering if + * the set has no Comparator). + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + // Write out any hidden stuff + s.defaultWriteObject(); + + // Write out Comparator + s.writeObject(m.comparator()); + + // Write out size + s.writeInt(m.size()); + + // Write out all elements in the proper order. + for (E e : m.keySet()) + s.writeObject(e); + } + + /** + * Reconstitute the {@code TreeSet} instance from a stream (that is, + * deserialize it). + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + // Read in any hidden stuff + s.defaultReadObject(); + + // Read in Comparator + Comparator c = (Comparator) s.readObject(); + + // Create backing TreeMap + TreeMap tm; + if (c==null) + tm = new TreeMap<>(); + else + tm = new TreeMap<>(c); + m = tm; + + // Read in size + int size = s.readInt(); + + tm.readTreeSet(size, s, PRESENT); + } + + private static final long serialVersionUID = -2479143000061671589L; +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/WeakHashMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/WeakHashMap.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,972 @@ +/* + * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util; +import java.lang.ref.WeakReference; +import java.lang.ref.ReferenceQueue; + + +/** + * Hash table based implementation of the Map interface, with + * weak keys. + * An entry in a WeakHashMap will automatically be removed when + * its key is no longer in ordinary use. More precisely, the presence of a + * mapping for a given key will not prevent the key from being discarded by the + * garbage collector, that is, made finalizable, finalized, and then reclaimed. + * When a key has been discarded its entry is effectively removed from the map, + * so this class behaves somewhat differently from other Map + * implementations. + * + *

Both null values and the null key are supported. This class has + * performance characteristics similar to those of the HashMap + * class, and has the same efficiency parameters of initial capacity + * and load factor. + * + *

Like most collection classes, this class is not synchronized. + * A synchronized WeakHashMap may be constructed using the + * {@link Collections#synchronizedMap Collections.synchronizedMap} + * method. + * + *

This class is intended primarily for use with key objects whose + * equals methods test for object identity using the + * == operator. Once such a key is discarded it can never be + * recreated, so it is impossible to do a lookup of that key in a + * WeakHashMap at some later time and be surprised that its entry + * has been removed. This class will work perfectly well with key objects + * whose equals methods are not based upon object identity, such + * as String instances. With such recreatable key objects, + * however, the automatic removal of WeakHashMap entries whose + * keys have been discarded may prove to be confusing. + * + *

The behavior of the WeakHashMap class depends in part upon + * the actions of the garbage collector, so several familiar (though not + * required) Map invariants do not hold for this class. Because + * the garbage collector may discard keys at any time, a + * WeakHashMap may behave as though an unknown thread is silently + * removing entries. In particular, even if you synchronize on a + * WeakHashMap instance and invoke none of its mutator methods, it + * is possible for the size method to return smaller values over + * time, for the isEmpty method to return false and + * then true, for the containsKey method to return + * true and later false for a given key, for the + * get method to return a value for a given key but later return + * null, for the put method to return + * null and the remove method to return + * false for a key that previously appeared to be in the map, and + * for successive examinations of the key set, the value collection, and + * the entry set to yield successively smaller numbers of elements. + * + *

Each key object in a WeakHashMap is stored indirectly as + * the referent of a weak reference. Therefore a key will automatically be + * removed only after the weak references to it, both inside and outside of the + * map, have been cleared by the garbage collector. + * + *

Implementation note: The value objects in a + * WeakHashMap are held by ordinary strong references. Thus care + * should be taken to ensure that value objects do not strongly refer to their + * own keys, either directly or indirectly, since that will prevent the keys + * from being discarded. Note that a value object may refer indirectly to its + * key via the WeakHashMap itself; that is, a value object may + * strongly refer to some other key object whose associated value object, in + * turn, strongly refers to the key of the first value object. One way + * to deal with this is to wrap values themselves within + * WeakReferences before + * inserting, as in: m.put(key, new WeakReference(value)), + * and then unwrapping upon each get. + * + *

The iterators returned by the iterator method of the collections + * returned by all of this class's "collection view methods" are + * fail-fast: if the map is structurally modified at any time after the + * iterator is created, in any way except through the iterator's own + * remove method, the iterator will throw a {@link + * ConcurrentModificationException}. Thus, in the face of concurrent + * modification, the iterator fails quickly and cleanly, rather than risking + * arbitrary, non-deterministic behavior at an undetermined time in the future. + * + *

Note that the fail-fast behavior of an iterator cannot be guaranteed + * as it is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators + * throw ConcurrentModificationException on a best-effort basis. + * Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: the fail-fast behavior of iterators + * should be used only to detect bugs. + * + *

This class is a member of the + * + * Java Collections Framework. + * + * @param the type of keys maintained by this map + * @param the type of mapped values + * + * @author Doug Lea + * @author Josh Bloch + * @author Mark Reinhold + * @since 1.2 + * @see java.util.HashMap + * @see java.lang.ref.WeakReference + */ +public class WeakHashMap + extends AbstractMap + implements Map { + + /** + * The default initial capacity -- MUST be a power of two. + */ + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * The maximum capacity, used if a higher value is implicitly specified + * by either of the constructors with arguments. + * MUST be a power of two <= 1<<30. + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The load factor used when none specified in constructor. + */ + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * The table, resized as necessary. Length MUST Always be a power of two. + */ + Entry[] table; + + /** + * The number of key-value mappings contained in this weak hash map. + */ + private int size; + + /** + * The next size value at which to resize (capacity * load factor). + */ + private int threshold; + + /** + * The load factor for the hash table. + */ + private final float loadFactor; + + /** + * Reference queue for cleared WeakEntries + */ + private final ReferenceQueue queue = new ReferenceQueue<>(); + + /** + * The number of times this WeakHashMap has been structurally modified. + * Structural modifications are those that change the number of + * mappings in the map or otherwise modify its internal structure + * (e.g., rehash). This field is used to make iterators on + * Collection-views of the map fail-fast. + * + * @see ConcurrentModificationException + */ + int modCount; + + @SuppressWarnings("unchecked") + private Entry[] newTable(int n) { + return (Entry[]) new Entry[n]; + } + + /** + * Constructs a new, empty WeakHashMap with the given initial + * capacity and the given load factor. + * + * @param initialCapacity The initial capacity of the WeakHashMap + * @param loadFactor The load factor of the WeakHashMap + * @throws IllegalArgumentException if the initial capacity is negative, + * or if the load factor is nonpositive. + */ + public WeakHashMap(int initialCapacity, float loadFactor) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal Initial Capacity: "+ + initialCapacity); + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal Load factor: "+ + loadFactor); + int capacity = 1; + while (capacity < initialCapacity) + capacity <<= 1; + table = newTable(capacity); + this.loadFactor = loadFactor; + threshold = (int)(capacity * loadFactor); + } + + /** + * Constructs a new, empty WeakHashMap with the given initial + * capacity and the default load factor (0.75). + * + * @param initialCapacity The initial capacity of the WeakHashMap + * @throws IllegalArgumentException if the initial capacity is negative + */ + public WeakHashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + + /** + * Constructs a new, empty WeakHashMap with the default initial + * capacity (16) and load factor (0.75). + */ + public WeakHashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; + threshold = DEFAULT_INITIAL_CAPACITY; + table = newTable(DEFAULT_INITIAL_CAPACITY); + } + + /** + * Constructs a new WeakHashMap with the same mappings as the + * specified map. The WeakHashMap is created with the default + * load factor (0.75) and an initial capacity sufficient to hold the + * mappings in the specified map. + * + * @param m the map whose mappings are to be placed in this map + * @throws NullPointerException if the specified map is null + * @since 1.3 + */ + public WeakHashMap(Map m) { + this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, 16), + DEFAULT_LOAD_FACTOR); + putAll(m); + } + + // internal utilities + + /** + * Value representing null keys inside tables. + */ + private static final Object NULL_KEY = new Object(); + + /** + * Use NULL_KEY for key if it is null. + */ + private static Object maskNull(Object key) { + return (key == null) ? NULL_KEY : key; + } + + /** + * Returns internal representation of null key back to caller as null. + */ + static Object unmaskNull(Object key) { + return (key == NULL_KEY) ? null : key; + } + + /** + * Checks for equality of non-null reference x and possibly-null y. By + * default uses Object.equals. + */ + private static boolean eq(Object x, Object y) { + return x == y || x.equals(y); + } + + /** + * Returns index for hash code h. + */ + private static int indexFor(int h, int length) { + return h & (length-1); + } + + /** + * Expunges stale entries from the table. + */ + private void expungeStaleEntries() { + for (Object x; (x = queue.poll()) != null; ) { + synchronized (queue) { + @SuppressWarnings("unchecked") + Entry e = (Entry) x; + int i = indexFor(e.hash, table.length); + + Entry prev = table[i]; + Entry p = prev; + while (p != null) { + Entry next = p.next; + if (p == e) { + if (prev == e) + table[i] = next; + else + prev.next = next; + // Must not null out e.next; + // stale entries may be in use by a HashIterator + e.value = null; // Help GC + size--; + break; + } + prev = p; + p = next; + } + } + } + } + + /** + * Returns the table after first expunging stale entries. + */ + private Entry[] getTable() { + expungeStaleEntries(); + return table; + } + + /** + * Returns the number of key-value mappings in this map. + * This result is a snapshot, and may not reflect unprocessed + * entries that will be removed before next attempted access + * because they are no longer referenced. + */ + public int size() { + if (size == 0) + return 0; + expungeStaleEntries(); + return size; + } + + /** + * Returns true if this map contains no key-value mappings. + * This result is a snapshot, and may not reflect unprocessed + * entries that will be removed before next attempted access + * because they are no longer referenced. + */ + public boolean isEmpty() { + return size() == 0; + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code (key==null ? k==null : + * key.equals(k))}, then this method returns {@code v}; otherwise + * it returns {@code null}. (There can be at most one such mapping.) + * + *

A return value of {@code null} does not necessarily + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to {@code null}. + * The {@link #containsKey containsKey} operation may be used to + * distinguish these two cases. + * + * @see #put(Object, Object) + */ + public V get(Object key) { + Object k = maskNull(key); + int h = HashMap.hash(k.hashCode()); + Entry[] tab = getTable(); + int index = indexFor(h, tab.length); + Entry e = tab[index]; + while (e != null) { + if (e.hash == h && eq(k, e.get())) + return e.value; + e = e.next; + } + return null; + } + + /** + * Returns true if this map contains a mapping for the + * specified key. + * + * @param key The key whose presence in this map is to be tested + * @return true if there is a mapping for key; + * false otherwise + */ + public boolean containsKey(Object key) { + return getEntry(key) != null; + } + + /** + * Returns the entry associated with the specified key in this map. + * Returns null if the map contains no mapping for this key. + */ + Entry getEntry(Object key) { + Object k = maskNull(key); + int h = HashMap.hash(k.hashCode()); + Entry[] tab = getTable(); + int index = indexFor(h, tab.length); + Entry e = tab[index]; + while (e != null && !(e.hash == h && eq(k, e.get()))) + e = e.next; + return e; + } + + /** + * Associates the specified value with the specified key in this map. + * If the map previously contained a mapping for this key, the old + * value is replaced. + * + * @param key key with which the specified value is to be associated. + * @param value value to be associated with the specified key. + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + */ + public V put(K key, V value) { + Object k = maskNull(key); + int h = HashMap.hash(k.hashCode()); + Entry[] tab = getTable(); + int i = indexFor(h, tab.length); + + for (Entry e = tab[i]; e != null; e = e.next) { + if (h == e.hash && eq(k, e.get())) { + V oldValue = e.value; + if (value != oldValue) + e.value = value; + return oldValue; + } + } + + modCount++; + Entry e = tab[i]; + tab[i] = new Entry<>(k, value, queue, h, e); + if (++size >= threshold) + resize(tab.length * 2); + return null; + } + + /** + * Rehashes the contents of this map into a new array with a + * larger capacity. This method is called automatically when the + * number of keys in this map reaches its threshold. + * + * If current capacity is MAXIMUM_CAPACITY, this method does not + * resize the map, but sets threshold to Integer.MAX_VALUE. + * This has the effect of preventing future calls. + * + * @param newCapacity the new capacity, MUST be a power of two; + * must be greater than current capacity unless current + * capacity is MAXIMUM_CAPACITY (in which case value + * is irrelevant). + */ + void resize(int newCapacity) { + Entry[] oldTable = getTable(); + int oldCapacity = oldTable.length; + if (oldCapacity == MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return; + } + + Entry[] newTable = newTable(newCapacity); + transfer(oldTable, newTable); + table = newTable; + + /* + * If ignoring null elements and processing ref queue caused massive + * shrinkage, then restore old table. This should be rare, but avoids + * unbounded expansion of garbage-filled tables. + */ + if (size >= threshold / 2) { + threshold = (int)(newCapacity * loadFactor); + } else { + expungeStaleEntries(); + transfer(newTable, oldTable); + table = oldTable; + } + } + + /** Transfers all entries from src to dest tables */ + private void transfer(Entry[] src, Entry[] dest) { + for (int j = 0; j < src.length; ++j) { + Entry e = src[j]; + src[j] = null; + while (e != null) { + Entry next = e.next; + Object key = e.get(); + if (key == null) { + e.next = null; // Help GC + e.value = null; // " " + size--; + } else { + int i = indexFor(e.hash, dest.length); + e.next = dest[i]; + dest[i] = e; + } + e = next; + } + } + } + + /** + * Copies all of the mappings from the specified map to this map. + * These mappings will replace any mappings that this map had for any + * of the keys currently in the specified map. + * + * @param m mappings to be stored in this map. + * @throws NullPointerException if the specified map is null. + */ + public void putAll(Map m) { + int numKeysToBeAdded = m.size(); + if (numKeysToBeAdded == 0) + return; + + /* + * Expand the map if the map if the number of mappings to be added + * is greater than or equal to threshold. This is conservative; the + * obvious condition is (m.size() + size) >= threshold, but this + * condition could result in a map with twice the appropriate capacity, + * if the keys to be added overlap with the keys already in this map. + * By using the conservative calculation, we subject ourself + * to at most one extra resize. + */ + if (numKeysToBeAdded > threshold) { + int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1); + if (targetCapacity > MAXIMUM_CAPACITY) + targetCapacity = MAXIMUM_CAPACITY; + int newCapacity = table.length; + while (newCapacity < targetCapacity) + newCapacity <<= 1; + if (newCapacity > table.length) + resize(newCapacity); + } + + for (Map.Entry e : m.entrySet()) + put(e.getKey(), e.getValue()); + } + + /** + * Removes the mapping for a key from this weak hash map if it is present. + * More formally, if this map contains a mapping from key k to + * value v such that (key==null ? k==null : + * key.equals(k)), that mapping is removed. (The map can contain + * at most one such mapping.) + * + *

Returns the value to which this map previously associated the key, + * or null if the map contained no mapping for the key. A + * return value of null does not necessarily indicate + * that the map contained no mapping for the key; it's also possible + * that the map explicitly mapped the key to null. + * + *

The map will not contain a mapping for the specified key once the + * call returns. + * + * @param key key whose mapping is to be removed from the map + * @return the previous value associated with key, or + * null if there was no mapping for key + */ + public V remove(Object key) { + Object k = maskNull(key); + int h = HashMap.hash(k.hashCode()); + Entry[] tab = getTable(); + int i = indexFor(h, tab.length); + Entry prev = tab[i]; + Entry e = prev; + + while (e != null) { + Entry next = e.next; + if (h == e.hash && eq(k, e.get())) { + modCount++; + size--; + if (prev == e) + tab[i] = next; + else + prev.next = next; + return e.value; + } + prev = e; + e = next; + } + + return null; + } + + /** Special version of remove needed by Entry set */ + boolean removeMapping(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Entry[] tab = getTable(); + Map.Entry entry = (Map.Entry)o; + Object k = maskNull(entry.getKey()); + int h = HashMap.hash(k.hashCode()); + int i = indexFor(h, tab.length); + Entry prev = tab[i]; + Entry e = prev; + + while (e != null) { + Entry next = e.next; + if (h == e.hash && e.equals(entry)) { + modCount++; + size--; + if (prev == e) + tab[i] = next; + else + prev.next = next; + return true; + } + prev = e; + e = next; + } + + return false; + } + + /** + * Removes all of the mappings from this map. + * The map will be empty after this call returns. + */ + public void clear() { + // clear out ref queue. We don't need to expunge entries + // since table is getting cleared. + while (queue.poll() != null) + ; + + modCount++; + Arrays.fill(table, null); + size = 0; + + // Allocation of array may have caused GC, which may have caused + // additional entries to go stale. Removing these entries from the + // reference queue will make them eligible for reclamation. + while (queue.poll() != null) + ; + } + + /** + * Returns true if this map maps one or more keys to the + * specified value. + * + * @param value value whose presence in this map is to be tested + * @return true if this map maps one or more keys to the + * specified value + */ + public boolean containsValue(Object value) { + if (value==null) + return containsNullValue(); + + Entry[] tab = getTable(); + for (int i = tab.length; i-- > 0;) + for (Entry e = tab[i]; e != null; e = e.next) + if (value.equals(e.value)) + return true; + return false; + } + + /** + * Special-case code for containsValue with null argument + */ + private boolean containsNullValue() { + Entry[] tab = getTable(); + for (int i = tab.length; i-- > 0;) + for (Entry e = tab[i]; e != null; e = e.next) + if (e.value==null) + return true; + return false; + } + + /** + * The entries in this hash table extend WeakReference, using its main ref + * field as the key. + */ + private static class Entry extends WeakReference implements Map.Entry { + V value; + final int hash; + Entry next; + + /** + * Creates new entry. + */ + Entry(Object key, V value, + ReferenceQueue queue, + int hash, Entry next) { + super(key, queue); + this.value = value; + this.hash = hash; + this.next = next; + } + + @SuppressWarnings("unchecked") + public K getKey() { + return (K) WeakHashMap.unmaskNull(get()); + } + + public V getValue() { + return value; + } + + public V setValue(V newValue) { + V oldValue = value; + value = newValue; + return oldValue; + } + + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + K k1 = getKey(); + Object k2 = e.getKey(); + if (k1 == k2 || (k1 != null && k1.equals(k2))) { + V v1 = getValue(); + Object v2 = e.getValue(); + if (v1 == v2 || (v1 != null && v1.equals(v2))) + return true; + } + return false; + } + + public int hashCode() { + K k = getKey(); + V v = getValue(); + return ((k==null ? 0 : k.hashCode()) ^ + (v==null ? 0 : v.hashCode())); + } + + public String toString() { + return getKey() + "=" + getValue(); + } + } + + private abstract class HashIterator implements Iterator { + private int index; + private Entry entry = null; + private Entry lastReturned = null; + private int expectedModCount = modCount; + + /** + * Strong reference needed to avoid disappearance of key + * between hasNext and next + */ + private Object nextKey = null; + + /** + * Strong reference needed to avoid disappearance of key + * between nextEntry() and any use of the entry + */ + private Object currentKey = null; + + HashIterator() { + index = isEmpty() ? 0 : table.length; + } + + public boolean hasNext() { + Entry[] t = table; + + while (nextKey == null) { + Entry e = entry; + int i = index; + while (e == null && i > 0) + e = t[--i]; + entry = e; + index = i; + if (e == null) { + currentKey = null; + return false; + } + nextKey = e.get(); // hold on to key in strong ref + if (nextKey == null) + entry = entry.next; + } + return true; + } + + /** The common parts of next() across different types of iterators */ + protected Entry nextEntry() { + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + if (nextKey == null && !hasNext()) + throw new NoSuchElementException(); + + lastReturned = entry; + entry = entry.next; + currentKey = nextKey; + nextKey = null; + return lastReturned; + } + + public void remove() { + if (lastReturned == null) + throw new IllegalStateException(); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + + WeakHashMap.this.remove(currentKey); + expectedModCount = modCount; + lastReturned = null; + currentKey = null; + } + + } + + private class ValueIterator extends HashIterator { + public V next() { + return nextEntry().value; + } + } + + private class KeyIterator extends HashIterator { + public K next() { + return nextEntry().getKey(); + } + } + + private class EntryIterator extends HashIterator> { + public Map.Entry next() { + return nextEntry(); + } + } + + // Views + + private transient Set> entrySet = null; + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation), the results of + * the iteration are undefined. The set supports element removal, + * which removes the corresponding mapping from the map, via the + * Iterator.remove, Set.remove, + * removeAll, retainAll, and clear + * operations. It does not support the add or addAll + * operations. + */ + public Set keySet() { + Set ks = keySet; + return (ks != null ? ks : (keySet = new KeySet())); + } + + private class KeySet extends AbstractSet { + public Iterator iterator() { + return new KeyIterator(); + } + + public int size() { + return WeakHashMap.this.size(); + } + + public boolean contains(Object o) { + return containsKey(o); + } + + public boolean remove(Object o) { + if (containsKey(o)) { + WeakHashMap.this.remove(o); + return true; + } + else + return false; + } + + public void clear() { + WeakHashMap.this.clear(); + } + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. If the map is + * modified while an iteration over the collection is in progress + * (except through the iterator's own remove operation), + * the results of the iteration are undefined. The collection + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Collection.remove, removeAll, + * retainAll and clear operations. It does not + * support the add or addAll operations. + */ + public Collection values() { + Collection vs = values; + return (vs != null) ? vs : (values = new Values()); + } + + private class Values extends AbstractCollection { + public Iterator iterator() { + return new ValueIterator(); + } + + public int size() { + return WeakHashMap.this.size(); + } + + public boolean contains(Object o) { + return containsValue(o); + } + + public void clear() { + WeakHashMap.this.clear(); + } + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation, or through the + * setValue operation on a map entry returned by the + * iterator) the results of the iteration are undefined. The set + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Set.remove, removeAll, retainAll and + * clear operations. It does not support the + * add or addAll operations. + */ + public Set> entrySet() { + Set> es = entrySet; + return es != null ? es : (entrySet = new EntrySet()); + } + + private class EntrySet extends AbstractSet> { + public Iterator> iterator() { + return new EntryIterator(); + } + + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + Entry candidate = getEntry(e.getKey()); + return candidate != null && candidate.equals(e); + } + + public boolean remove(Object o) { + return removeMapping(o); + } + + public int size() { + return WeakHashMap.this.size(); + } + + public void clear() { + WeakHashMap.this.clear(); + } + + private List> deepCopy() { + List> list = new ArrayList<>(size()); + for (Map.Entry e : this) + list.add(new AbstractMap.SimpleEntry<>(e)); + return list; + } + + public Object[] toArray() { + return deepCopy().toArray(); + } + + public T[] toArray(T[] a) { + return deepCopy().toArray(a); + } + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/concurrent/Executor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/Executor.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,141 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent; + +/** + * An object that executes submitted {@link Runnable} tasks. This + * interface provides a way of decoupling task submission from the + * mechanics of how each task will be run, including details of thread + * use, scheduling, etc. An Executor is normally used + * instead of explicitly creating threads. For example, rather than + * invoking new Thread(new(RunnableTask())).start() for each + * of a set of tasks, you might use: + * + *
+ * Executor executor = anExecutor;
+ * executor.execute(new RunnableTask1());
+ * executor.execute(new RunnableTask2());
+ * ...
+ * 
+ * + * However, the Executor interface does not strictly + * require that execution be asynchronous. In the simplest case, an + * executor can run the submitted task immediately in the caller's + * thread: + * + *
+ * class DirectExecutor implements Executor {
+ *     public void execute(Runnable r) {
+ *         r.run();
+ *     }
+ * }
+ * + * More typically, tasks are executed in some thread other + * than the caller's thread. The executor below spawns a new thread + * for each task. + * + *
+ * class ThreadPerTaskExecutor implements Executor {
+ *     public void execute(Runnable r) {
+ *         new Thread(r).start();
+ *     }
+ * }
+ * + * Many Executor implementations impose some sort of + * limitation on how and when tasks are scheduled. The executor below + * serializes the submission of tasks to a second executor, + * illustrating a composite executor. + * + *
 {@code
+ * class SerialExecutor implements Executor {
+ *   final Queue tasks = new ArrayDeque();
+ *   final Executor executor;
+ *   Runnable active;
+ *
+ *   SerialExecutor(Executor executor) {
+ *     this.executor = executor;
+ *   }
+ *
+ *   public synchronized void execute(final Runnable r) {
+ *     tasks.offer(new Runnable() {
+ *       public void run() {
+ *         try {
+ *           r.run();
+ *         } finally {
+ *           scheduleNext();
+ *         }
+ *       }
+ *     });
+ *     if (active == null) {
+ *       scheduleNext();
+ *     }
+ *   }
+ *
+ *   protected synchronized void scheduleNext() {
+ *     if ((active = tasks.poll()) != null) {
+ *       executor.execute(active);
+ *     }
+ *   }
+ * }}
+ * + * The Executor implementations provided in this package + * implement {@link ExecutorService}, which is a more extensive + * interface. The {@link ThreadPoolExecutor} class provides an + * extensible thread pool implementation. The {@link Executors} class + * provides convenient factory methods for these Executors. + * + *

Memory consistency effects: Actions in a thread prior to + * submitting a {@code Runnable} object to an {@code Executor} + * happen-before + * its execution begins, perhaps in another thread. + * + * @since 1.5 + * @author Doug Lea + */ +public interface Executor { + + /** + * Executes the given command at some time in the future. The command + * may execute in a new thread, in a pooled thread, or in the calling + * thread, at the discretion of the Executor implementation. + * + * @param command the runnable task + * @throws RejectedExecutionException if this task cannot be + * accepted for execution. + * @throws NullPointerException if command is null + */ + void execute(Runnable command); +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/logging/Level.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/logging/Level.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.logging; + +/** + * The Level class defines a set of standard logging levels that + * can be used to control logging output. The logging Level objects + * are ordered and are specified by ordered integers. Enabling logging + * at a given level also enables logging at all higher levels. + *

+ * Clients should normally use the predefined Level constants such + * as Level.SEVERE. + *

+ * The levels in descending order are: + *

    + *
  • SEVERE (highest value) + *
  • WARNING + *
  • INFO + *
  • CONFIG + *
  • FINE + *
  • FINER + *
  • FINEST (lowest value) + *
+ * In addition there is a level OFF that can be used to turn + * off logging, and a level ALL that can be used to enable + * logging of all messages. + *

+ * It is possible for third parties to define additional logging + * levels by subclassing Level. In such cases subclasses should + * take care to chose unique integer level values and to ensure that + * they maintain the Object uniqueness property across serialization + * by defining a suitable readResolve method. + * + * @since 1.4 + */ + +public class Level implements java.io.Serializable { + private static java.util.ArrayList known = new java.util.ArrayList<>(); + private static String defaultBundle = "sun.util.logging.resources.logging"; + + /** + * @serial The non-localized name of the level. + */ + private final String name; + + /** + * @serial The integer value of the level. + */ + private final int value; + + /** + * @serial The resource bundle name to be used in localizing the level name. + */ + private final String resourceBundleName; + + /** + * OFF is a special level that can be used to turn off logging. + * This level is initialized to Integer.MAX_VALUE. + */ + public static final Level OFF = new Level("OFF",Integer.MAX_VALUE, defaultBundle); + + /** + * SEVERE is a message level indicating a serious failure. + *

+ * In general SEVERE messages should describe events that are + * of considerable importance and which will prevent normal + * program execution. They should be reasonably intelligible + * to end users and to system administrators. + * This level is initialized to 1000. + */ + public static final Level SEVERE = new Level("SEVERE",1000, defaultBundle); + + /** + * WARNING is a message level indicating a potential problem. + *

+ * In general WARNING messages should describe events that will + * be of interest to end users or system managers, or which + * indicate potential problems. + * This level is initialized to 900. + */ + public static final Level WARNING = new Level("WARNING", 900, defaultBundle); + + /** + * INFO is a message level for informational messages. + *

+ * Typically INFO messages will be written to the console + * or its equivalent. So the INFO level should only be + * used for reasonably significant messages that will + * make sense to end users and system administrators. + * This level is initialized to 800. + */ + public static final Level INFO = new Level("INFO", 800, defaultBundle); + + /** + * CONFIG is a message level for static configuration messages. + *

+ * CONFIG messages are intended to provide a variety of static + * configuration information, to assist in debugging problems + * that may be associated with particular configurations. + * For example, CONFIG message might include the CPU type, + * the graphics depth, the GUI look-and-feel, etc. + * This level is initialized to 700. + */ + public static final Level CONFIG = new Level("CONFIG", 700, defaultBundle); + + /** + * FINE is a message level providing tracing information. + *

+ * All of FINE, FINER, and FINEST are intended for relatively + * detailed tracing. The exact meaning of the three levels will + * vary between subsystems, but in general, FINEST should be used + * for the most voluminous detailed output, FINER for somewhat + * less detailed output, and FINE for the lowest volume (and + * most important) messages. + *

+ * In general the FINE level should be used for information + * that will be broadly interesting to developers who do not have + * a specialized interest in the specific subsystem. + *

+ * FINE messages might include things like minor (recoverable) + * failures. Issues indicating potential performance problems + * are also worth logging as FINE. + * This level is initialized to 500. + */ + public static final Level FINE = new Level("FINE", 500, defaultBundle); + + /** + * FINER indicates a fairly detailed tracing message. + * By default logging calls for entering, returning, or throwing + * an exception are traced at this level. + * This level is initialized to 400. + */ + public static final Level FINER = new Level("FINER", 400, defaultBundle); + + /** + * FINEST indicates a highly detailed tracing message. + * This level is initialized to 300. + */ + public static final Level FINEST = new Level("FINEST", 300, defaultBundle); + + /** + * ALL indicates that all messages should be logged. + * This level is initialized to Integer.MIN_VALUE. + */ + public static final Level ALL = new Level("ALL", Integer.MIN_VALUE, defaultBundle); + + /** + * Create a named Level with a given integer value. + *

+ * Note that this constructor is "protected" to allow subclassing. + * In general clients of logging should use one of the constant Level + * objects such as SEVERE or FINEST. However, if clients need to + * add new logging levels, they may subclass Level and define new + * constants. + * @param name the name of the Level, for example "SEVERE". + * @param value an integer value for the level. + * @throws NullPointerException if the name is null + */ + protected Level(String name, int value) { + this(name, value, null); + } + + /** + * Create a named Level with a given integer value and a + * given localization resource name. + *

+ * @param name the name of the Level, for example "SEVERE". + * @param value an integer value for the level. + * @param resourceBundleName name of a resource bundle to use in + * localizing the given name. If the resourceBundleName is null + * or an empty string, it is ignored. + * @throws NullPointerException if the name is null + */ + protected Level(String name, int value, String resourceBundleName) { + if (name == null) { + throw new NullPointerException(); + } + this.name = name; + this.value = value; + this.resourceBundleName = resourceBundleName; + synchronized (Level.class) { + known.add(this); + } + } + + /** + * Return the level's localization resource bundle name, or + * null if no localization bundle is defined. + * + * @return localization resource bundle name + */ + public String getResourceBundleName() { + return resourceBundleName; + } + + /** + * Return the non-localized string name of the Level. + * + * @return non-localized name + */ + public String getName() { + return name; + } + + /** + * Return the localized string name of the Level, for + * the current default locale. + *

+ * If no localization information is available, the + * non-localized name is returned. + * + * @return localized name + */ + public String getLocalizedName() { + return getName(); + } + + /** + * Returns a string representation of this Level. + * + * @return the non-localized name of the Level, for example "INFO". + */ + public final String toString() { + return name; + } + + /** + * Get the integer value for this level. This integer value + * can be used for efficient ordering comparisons between + * Level objects. + * @return the integer value for this level. + */ + public final int intValue() { + return value; + } + + private static final long serialVersionUID = -8176160795706313070L; + + // Serialization magic to prevent "doppelgangers". + // This is a performance optimization. + private Object readResolve() { + synchronized (Level.class) { + for (int i = 0; i < known.size(); i++) { + Level other = known.get(i); + if (this.name.equals(other.name) && this.value == other.value + && (this.resourceBundleName == other.resourceBundleName || + (this.resourceBundleName != null && + this.resourceBundleName.equals(other.resourceBundleName)))) { + return other; + } + } + // Woops. Whoever sent us this object knows + // about a new log level. Add it to our list. + known.add(this); + return this; + } + } + + /** + * Parse a level name string into a Level. + *

+ * The argument string may consist of either a level name + * or an integer value. + *

+ * For example: + *

    + *
  • "SEVERE" + *
  • "1000" + *
+ * @param name string to be parsed + * @throws NullPointerException if the name is null + * @throws IllegalArgumentException if the value is not valid. + * Valid values are integers between Integer.MIN_VALUE + * and Integer.MAX_VALUE, and all known level names. + * Known names are the levels defined by this class (e.g., FINE, + * FINER, FINEST), or created by this class with + * appropriate package access, or new levels defined or created + * by subclasses. + * + * @return The parsed value. Passing an integer that corresponds to a known name + * (e.g., 700) will return the associated name (e.g., CONFIG). + * Passing an integer that does not (e.g., 1) will return a new level name + * initialized to that value. + */ + public static synchronized Level parse(String name) throws IllegalArgumentException { + // Check that name is not null. + name.length(); + + // Look for a known Level with the given non-localized name. + for (int i = 0; i < known.size(); i++) { + Level l = known.get(i); + if (name.equals(l.name)) { + return l; + } + } + + // Now, check if the given name is an integer. If so, + // first look for a Level with the given value and then + // if necessary create one. + try { + int x = Integer.parseInt(name); + for (int i = 0; i < known.size(); i++) { + Level l = known.get(i); + if (l.value == x) { + return l; + } + } + // Create a new Level. + return new Level(name, x); + } catch (NumberFormatException ex) { + // Not an integer. + // Drop through. + } + + // Finally, look for a known level with the given localized name, + // in the current default locale. + // This is relatively expensive, but not excessively so. + for (int i = 0; i < known.size(); i++) { + Level l = known.get(i); + if (name.equals(l.getLocalizedName())) { + return l; + } + } + + // OK, we've tried everything and failed + throw new IllegalArgumentException("Bad level \"" + name + "\""); + } + + /** + * Compare two objects for value equality. + * @return true if and only if the two objects have the same level value. + */ + public boolean equals(Object ox) { + try { + Level lx = (Level)ox; + return (lx.value == this.value); + } catch (Exception ex) { + return false; + } + } + + /** + * Generate a hashcode. + * @return a hashcode based on the level value + */ + public int hashCode() { + return this.value; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/logging/LogRecord.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/logging/LogRecord.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util.logging; +import java.io.*; + +/** + * LogRecord objects are used to pass logging requests between + * the logging framework and individual log Handlers. + *

+ * When a LogRecord is passed into the logging framework it + * logically belongs to the framework and should no longer be + * used or updated by the client application. + *

+ * Note that if the client application has not specified an + * explicit source method name and source class name, then the + * LogRecord class will infer them automatically when they are + * first accessed (due to a call on getSourceMethodName or + * getSourceClassName) by analyzing the call stack. Therefore, + * if a logging Handler wants to pass off a LogRecord to another + * thread, or to transmit it over RMI, and if it wishes to subsequently + * obtain method name or class name information it should call + * one of getSourceClassName or getSourceMethodName to force + * the values to be filled in. + *

+ * Serialization notes: + *

    + *
  • The LogRecord class is serializable. + * + *
  • Because objects in the parameters array may not be serializable, + * during serialization all objects in the parameters array are + * written as the corresponding Strings (using Object.toString). + * + *
  • The ResourceBundle is not transmitted as part of the serialized + * form, but the resource bundle name is, and the recipient object's + * readObject method will attempt to locate a suitable resource bundle. + * + *
+ * + * @since 1.4 + */ + +public class LogRecord implements java.io.Serializable { + private static long globalSequenceNumber = 0; + + /** + * The default value of threadID will be the current thread's + * thread id, for ease of correlation, unless it is greater than + * MIN_SEQUENTIAL_THREAD_ID, in which case we try harder to keep + * our promise to keep threadIDs unique by avoiding collisions due + * to 32-bit wraparound. Unfortunately, LogRecord.getThreadID() + * returns int, while Thread.getId() returns long. + */ + private static final int MIN_SEQUENTIAL_THREAD_ID = Integer.MAX_VALUE / 2; + + /** + * @serial Logging message level + */ + private Level level; + + /** + * @serial Sequence number + */ + private long sequenceNumber; + + /** + * @serial Class that issued logging call + */ + private String sourceClassName; + + /** + * @serial Method that issued logging call + */ + private String sourceMethodName; + + /** + * @serial Non-localized raw message text + */ + private String message; + + /** + * @serial Thread ID for thread that issued logging call. + */ + private int threadID; + + /** + * @serial Event time in milliseconds since 1970 + */ + private long millis; + + /** + * @serial The Throwable (if any) associated with log message + */ + private Throwable thrown; + + /** + * @serial Name of the source Logger. + */ + private String loggerName; + + /** + * @serial Resource bundle name to localized log message. + */ + private String resourceBundleName; + + private transient boolean needToInferCaller; + private transient Object parameters[]; + + /** + * Returns the default value for a new LogRecord's threadID. + */ + private int defaultThreadID() { + return 0; + } + + /** + * Construct a LogRecord with the given level and message values. + *

+ * The sequence property will be initialized with a new unique value. + * These sequence values are allocated in increasing order within a VM. + *

+ * The millis property will be initialized to the current time. + *

+ * The thread ID property will be initialized with a unique ID for + * the current thread. + *

+ * All other properties will be initialized to "null". + * + * @param level a logging level value + * @param msg the raw non-localized logging message (may be null) + */ + public LogRecord(Level level, String msg) { + // Make sure level isn't null, by calling random method. + level.getClass(); + this.level = level; + message = msg; + // Assign a thread ID and a unique sequence number. + sequenceNumber = globalSequenceNumber++; + threadID = defaultThreadID(); + millis = System.currentTimeMillis(); + needToInferCaller = true; + } + + /** + * Get the source Logger's name. + * + * @return source logger name (may be null) + */ + public String getLoggerName() { + return loggerName; + } + + /** + * Set the source Logger's name. + * + * @param name the source logger name (may be null) + */ + public void setLoggerName(String name) { + loggerName = name; + } + + /** + * Get the localization resource bundle + *

+ * This is the ResourceBundle that should be used to localize + * the message string before formatting it. The result may + * be null if the message is not localizable, or if no suitable + * ResourceBundle is available. + */ +// public ResourceBundle getResourceBundle() { +// return resourceBundle; +// } + + /** + * Set the localization resource bundle. + * + * @param bundle localization bundle (may be null) + */ +// public void setResourceBundle(ResourceBundle bundle) { +// resourceBundle = bundle; +// } + + /** + * Get the localization resource bundle name + *

+ * This is the name for the ResourceBundle that should be + * used to localize the message string before formatting it. + * The result may be null if the message is not localizable. + */ + public String getResourceBundleName() { + return resourceBundleName; + } + + /** + * Set the localization resource bundle name. + * + * @param name localization bundle name (may be null) + */ + public void setResourceBundleName(String name) { + resourceBundleName = name; + } + + /** + * Get the logging message level, for example Level.SEVERE. + * @return the logging message level + */ + public Level getLevel() { + return level; + } + + /** + * Set the logging message level, for example Level.SEVERE. + * @param level the logging message level + */ + public void setLevel(Level level) { + if (level == null) { + throw new NullPointerException(); + } + this.level = level; + } + + /** + * Get the sequence number. + *

+ * Sequence numbers are normally assigned in the LogRecord + * constructor, which assigns unique sequence numbers to + * each new LogRecord in increasing order. + * @return the sequence number + */ + public long getSequenceNumber() { + return sequenceNumber; + } + + /** + * Set the sequence number. + *

+ * Sequence numbers are normally assigned in the LogRecord constructor, + * so it should not normally be necessary to use this method. + */ + public void setSequenceNumber(long seq) { + sequenceNumber = seq; + } + + /** + * Get the name of the class that (allegedly) issued the logging request. + *

+ * Note that this sourceClassName is not verified and may be spoofed. + * This information may either have been provided as part of the + * logging call, or it may have been inferred automatically by the + * logging framework. In the latter case, the information may only + * be approximate and may in fact describe an earlier call on the + * stack frame. + *

+ * May be null if no information could be obtained. + * + * @return the source class name + */ + public String getSourceClassName() { + return sourceClassName; + } + + /** + * Set the name of the class that (allegedly) issued the logging request. + * + * @param sourceClassName the source class name (may be null) + */ + public void setSourceClassName(String sourceClassName) { + this.sourceClassName = sourceClassName; + needToInferCaller = false; + } + + /** + * Get the name of the method that (allegedly) issued the logging request. + *

+ * Note that this sourceMethodName is not verified and may be spoofed. + * This information may either have been provided as part of the + * logging call, or it may have been inferred automatically by the + * logging framework. In the latter case, the information may only + * be approximate and may in fact describe an earlier call on the + * stack frame. + *

+ * May be null if no information could be obtained. + * + * @return the source method name + */ + public String getSourceMethodName() { + return sourceMethodName; + } + + /** + * Set the name of the method that (allegedly) issued the logging request. + * + * @param sourceMethodName the source method name (may be null) + */ + public void setSourceMethodName(String sourceMethodName) { + this.sourceMethodName = sourceMethodName; + needToInferCaller = false; + } + + /** + * Get the "raw" log message, before localization or formatting. + *

+ * May be null, which is equivalent to the empty string "". + *

+ * This message may be either the final text or a localization key. + *

+ * During formatting, if the source logger has a localization + * ResourceBundle and if that ResourceBundle has an entry for + * this message string, then the message string is replaced + * with the localized value. + * + * @return the raw message string + */ + public String getMessage() { + return message; + } + + /** + * Set the "raw" log message, before localization or formatting. + * + * @param message the raw message string (may be null) + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * Get the parameters to the log message. + * + * @return the log message parameters. May be null if + * there are no parameters. + */ + public Object[] getParameters() { + return parameters; + } + + /** + * Set the parameters to the log message. + * + * @param parameters the log message parameters. (may be null) + */ + public void setParameters(Object parameters[]) { + this.parameters = parameters; + } + + /** + * Get an identifier for the thread where the message originated. + *

+ * This is a thread identifier within the Java VM and may or + * may not map to any operating system ID. + * + * @return thread ID + */ + public int getThreadID() { + return threadID; + } + + /** + * Set an identifier for the thread where the message originated. + * @param threadID the thread ID + */ + public void setThreadID(int threadID) { + this.threadID = threadID; + } + + /** + * Get event time in milliseconds since 1970. + * + * @return event time in millis since 1970 + */ + public long getMillis() { + return millis; + } + + /** + * Set event time. + * + * @param millis event time in millis since 1970 + */ + public void setMillis(long millis) { + this.millis = millis; + } + + /** + * Get any throwable associated with the log record. + *

+ * If the event involved an exception, this will be the + * exception object. Otherwise null. + * + * @return a throwable + */ + public Throwable getThrown() { + return thrown; + } + + /** + * Set a throwable associated with the log event. + * + * @param thrown a throwable (may be null) + */ + public void setThrown(Throwable thrown) { + this.thrown = thrown; + } + + private static final long serialVersionUID = 5372048053134512534L; + + /** + * @serialData Default fields, followed by a two byte version number + * (major byte, followed by minor byte), followed by information on + * the log record parameter array. If there is no parameter array, + * then -1 is written. If there is a parameter array (possible of zero + * length) then the array length is written as an integer, followed + * by String values for each parameter. If a parameter is null, then + * a null String is written. Otherwise the output of Object.toString() + * is written. + */ + private void writeObject(ObjectOutputStream out) throws IOException { + // We have to call defaultWriteObject first. + out.defaultWriteObject(); + + // Write our version number. + out.writeByte(1); + out.writeByte(0); + if (parameters == null) { + out.writeInt(-1); + return; + } + out.writeInt(parameters.length); + // Write string values for the parameters. + for (int i = 0; i < parameters.length; i++) { + if (parameters[i] == null) { + out.writeObject(null); + } else { + out.writeObject(parameters[i].toString()); + } + } + } + + + private boolean isLoggerImplFrame(String cname) { + // the log record could be created for a platform logger + return (cname.equals("java.util.logging.Logger") || + cname.startsWith("java.util.logging.LoggingProxyImpl") || + cname.startsWith("sun.util.logging.")); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/main/java/java/util/logging/Logger.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/logging/Logger.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,1258 @@ +/* + * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +package java.util.logging; + +import java.util.HashMap; +import java.util.Map; +import org.apidesign.bck2brwsr.core.JavaScriptBody; + +/** + * A Logger object is used to log messages for a specific + * system or application component. Loggers are normally named, + * using a hierarchical dot-separated namespace. Logger names + * can be arbitrary strings, but they should normally be based on + * the package name or class name of the logged component, such + * as java.net or javax.swing. In addition it is possible to create + * "anonymous" Loggers that are not stored in the Logger namespace. + *

+ * Logger objects may be obtained by calls on one of the getLogger + * factory methods. These will either create a new Logger or + * return a suitable existing Logger. It is important to note that + * the Logger returned by one of the {@code getLogger} factory methods + * may be garbage collected at any time if a strong reference to the + * Logger is not kept. + *

+ * Logging messages will be forwarded to registered Handler + * objects, which can forward the messages to a variety of + * destinations, including consoles, files, OS logs, etc. + *

+ * Each Logger keeps track of a "parent" Logger, which is its + * nearest existing ancestor in the Logger namespace. + *

+ * Each Logger has a "Level" associated with it. This reflects + * a minimum Level that this logger cares about. If a Logger's + * level is set to null, then its effective level is inherited + * from its parent, which may in turn obtain it recursively from its + * parent, and so on up the tree. + *

+ * The log level can be configured based on the properties from the + * logging configuration file, as described in the description + * of the LogManager class. However it may also be dynamically changed + * by calls on the Logger.setLevel method. If a logger's level is + * changed the change may also affect child loggers, since any child + * logger that has null as its level will inherit its + * effective level from its parent. + *

+ * On each logging call the Logger initially performs a cheap + * check of the request level (e.g., SEVERE or FINE) against the + * effective log level of the logger. If the request level is + * lower than the log level, the logging call returns immediately. + *

+ * After passing this initial (cheap) test, the Logger will allocate + * a LogRecord to describe the logging message. It will then call a + * Filter (if present) to do a more detailed check on whether the + * record should be published. If that passes it will then publish + * the LogRecord to its output Handlers. By default, loggers also + * publish to their parent's Handlers, recursively up the tree. + *

+ * Each Logger may have a ResourceBundle name associated with it. + * The named bundle will be used for localizing logging messages. + * If a Logger does not have its own ResourceBundle name, then + * it will inherit the ResourceBundle name from its parent, + * recursively up the tree. + *

+ * Most of the logger output methods take a "msg" argument. This + * msg argument may be either a raw value or a localization key. + * During formatting, if the logger has (or inherits) a localization + * ResourceBundle and if the ResourceBundle has a mapping for the msg + * string, then the msg string is replaced by the localized value. + * Otherwise the original msg string is used. Typically, formatters use + * java.text.MessageFormat style formatting to format parameters, so + * for example a format string "{0} {1}" would format two parameters + * as strings. + *

+ * When mapping ResourceBundle names to ResourceBundles, the Logger + * will first try to use the Thread's ContextClassLoader. If that + * is null it will try the SystemClassLoader instead. As a temporary + * transition feature in the initial implementation, if the Logger is + * unable to locate a ResourceBundle from the ContextClassLoader or + * SystemClassLoader the Logger will also search up the class stack + * and use successive calling ClassLoaders to try to locate a ResourceBundle. + * (This call stack search is to allow containers to transition to + * using ContextClassLoaders and is likely to be removed in future + * versions.) + *

+ * Formatting (including localization) is the responsibility of + * the output Handler, which will typically call a Formatter. + *

+ * Note that formatting need not occur synchronously. It may be delayed + * until a LogRecord is actually written to an external sink. + *

+ * The logging methods are grouped in five main categories: + *

    + *
  • + * There are a set of "log" methods that take a log level, a message + * string, and optionally some parameters to the message string. + *

  • + * There are a set of "logp" methods (for "log precise") that are + * like the "log" methods, but also take an explicit source class name + * and method name. + *

  • + * There are a set of "logrb" method (for "log with resource bundle") + * that are like the "logp" method, but also take an explicit resource + * bundle name for use in localizing the log message. + *

  • + * There are convenience methods for tracing method entries (the + * "entering" methods), method returns (the "exiting" methods) and + * throwing exceptions (the "throwing" methods). + *

  • + * Finally, there are a set of convenience methods for use in the + * very simplest cases, when a developer simply wants to log a + * simple string at a given log level. These methods are named + * after the standard Level names ("severe", "warning", "info", etc.) + * and take a single argument, a message string. + *

+ *

+ * For the methods that do not take an explicit source name and + * method name, the Logging framework will make a "best effort" + * to determine which class and method called into the logging method. + * However, it is important to realize that this automatically inferred + * information may only be approximate (or may even be quite wrong!). + * Virtual machines are allowed to do extensive optimizations when + * JITing and may entirely remove stack frames, making it impossible + * to reliably locate the calling class and method. + *

+ * All methods on Logger are multi-thread safe. + *

+ * Subclassing Information: Note that a LogManager class may + * provide its own implementation of named Loggers for any point in + * the namespace. Therefore, any subclasses of Logger (unless they + * are implemented in conjunction with a new LogManager class) should + * take care to obtain a Logger instance from the LogManager class and + * should delegate operations such as "isLoggable" and "log(LogRecord)" + * to that instance. Note that in order to intercept all logging + * output, subclasses need only override the log(LogRecord) method. + * All the other logging methods are implemented as calls on this + * log(LogRecord) method. + * + * @since 1.4 + */ + + +public class Logger { + private static int offValue = Level.OFF.intValue(); + private static final Map ALL = new HashMap<>(); + private String name; + + private volatile int levelValue; // current effective level value + private Level levelObject; + + /** + * GLOBAL_LOGGER_NAME is a name for the global logger. + * + * @since 1.6 + */ + public static final String GLOBAL_LOGGER_NAME = "global"; + + /** + * Return global logger object with the name Logger.GLOBAL_LOGGER_NAME. + * + * @return global logger object + * @since 1.7 + */ + public static final Logger getGlobal() { + return global; + } + + /** + * The "global" Logger object is provided as a convenience to developers + * who are making casual use of the Logging package. Developers + * who are making serious use of the logging package (for example + * in products) should create and use their own Logger objects, + * with appropriate names, so that logging can be controlled on a + * suitable per-Logger granularity. Developers also need to keep a + * strong reference to their Logger objects to prevent them from + * being garbage collected. + *

+ * @deprecated Initialization of this field is prone to deadlocks. + * The field must be initialized by the Logger class initialization + * which may cause deadlocks with the LogManager class initialization. + * In such cases two class initialization wait for each other to complete. + * The preferred way to get the global logger object is via the call + * Logger.getGlobal(). + * For compatibility with old JDK versions where the + * Logger.getGlobal() is not available use the call + * Logger.getLogger(Logger.GLOBAL_LOGGER_NAME) + * or Logger.getLogger("global"). + */ + @Deprecated + public static final Logger global = new Logger(GLOBAL_LOGGER_NAME); + + /** + * Protected method to construct a logger for a named subsystem. + *

+ * The logger will be initially configured with a null Level + * and with useParentHandlers set to true. + * + * @param name A name for the logger. This should + * be a dot-separated name and should normally + * be based on the package name or class name + * of the subsystem, such as java.net + * or javax.swing. It may be null for anonymous Loggers. + * @param resourceBundleName name of ResourceBundle to be used for localizing + * messages for this logger. May be null if none + * of the messages require localization. + * @throws MissingResourceException if the resourceBundleName is non-null and + * no corresponding resource can be found. + */ + protected Logger(String name, String resourceBundleName) { + this.name = name; + levelValue = Level.INFO.intValue(); + } + + // This constructor is used only to create the global Logger. + // It is needed to break a cyclic dependence between the LogManager + // and Logger static initializers causing deadlocks. + private Logger(String name) { + // The manager field is not initialized here. + this.name = name; + levelValue = Level.INFO.intValue(); + } + + private void checkAccess() throws SecurityException { + throw new SecurityException(); + } + + /** + * Find or create a logger for a named subsystem. If a logger has + * already been created with the given name it is returned. Otherwise + * a new logger is created. + *

+ * If a new logger is created its log level will be configured + * based on the LogManager configuration and it will configured + * to also send logging output to its parent's Handlers. It will + * be registered in the LogManager global namespace. + *

+ * Note: The LogManager may only retain a weak reference to the newly + * created Logger. It is important to understand that a previously + * created Logger with the given name may be garbage collected at any + * time if there is no strong reference to the Logger. In particular, + * this means that two back-to-back calls like + * {@code getLogger("MyLogger").log(...)} may use different Logger + * objects named "MyLogger" if there is no strong reference to the + * Logger named "MyLogger" elsewhere in the program. + * + * @param name A name for the logger. This should + * be a dot-separated name and should normally + * be based on the package name or class name + * of the subsystem, such as java.net + * or javax.swing + * @return a suitable Logger + * @throws NullPointerException if the name is null. + */ + + // Synchronization is not required here. All synchronization for + // adding a new Logger object is handled by LogManager.addLogger(). + public static Logger getLogger(String name) { + return getLogger(name, null); + } + + /** + * Find or create a logger for a named subsystem. If a logger has + * already been created with the given name it is returned. Otherwise + * a new logger is created. + *

+ * If a new logger is created its log level will be configured + * based on the LogManager and it will configured to also send logging + * output to its parent's Handlers. It will be registered in + * the LogManager global namespace. + *

+ * Note: The LogManager may only retain a weak reference to the newly + * created Logger. It is important to understand that a previously + * created Logger with the given name may be garbage collected at any + * time if there is no strong reference to the Logger. In particular, + * this means that two back-to-back calls like + * {@code getLogger("MyLogger", ...).log(...)} may use different Logger + * objects named "MyLogger" if there is no strong reference to the + * Logger named "MyLogger" elsewhere in the program. + *

+ * If the named Logger already exists and does not yet have a + * localization resource bundle then the given resource bundle + * name is used. If the named Logger already exists and has + * a different resource bundle name then an IllegalArgumentException + * is thrown. + *

+ * @param name A name for the logger. This should + * be a dot-separated name and should normally + * be based on the package name or class name + * of the subsystem, such as java.net + * or javax.swing + * @param resourceBundleName name of ResourceBundle to be used for localizing + * messages for this logger. May be null if none of + * the messages require localization. + * @return a suitable Logger + * @throws MissingResourceException if the resourceBundleName is non-null and + * no corresponding resource can be found. + * @throws IllegalArgumentException if the Logger already exists and uses + * a different resource bundle name. + * @throws NullPointerException if the name is null. + */ + + // Synchronization is not required here. All synchronization for + // adding a new Logger object is handled by LogManager.addLogger(). + public static Logger getLogger(String name, String resourceBundleName) { + Logger l = ALL.get(name); + if (l == null) { + l = new Logger(name, resourceBundleName); + ALL.put(name, l); + } + return l; + } + + + /** + * Create an anonymous Logger. The newly created Logger is not + * registered in the LogManager namespace. There will be no + * access checks on updates to the logger. + *

+ * This factory method is primarily intended for use from applets. + * Because the resulting Logger is anonymous it can be kept private + * by the creating class. This removes the need for normal security + * checks, which in turn allows untrusted applet code to update + * the control state of the Logger. For example an applet can do + * a setLevel or an addHandler on an anonymous Logger. + *

+ * Even although the new logger is anonymous, it is configured + * to have the root logger ("") as its parent. This means that + * by default it inherits its effective level and handlers + * from the root logger. + *

+ * + * @return a newly created private Logger + */ + public static Logger getAnonymousLogger() { + return getAnonymousLogger(null); + } + + /** + * Create an anonymous Logger. The newly created Logger is not + * registered in the LogManager namespace. There will be no + * access checks on updates to the logger. + *

+ * This factory method is primarily intended for use from applets. + * Because the resulting Logger is anonymous it can be kept private + * by the creating class. This removes the need for normal security + * checks, which in turn allows untrusted applet code to update + * the control state of the Logger. For example an applet can do + * a setLevel or an addHandler on an anonymous Logger. + *

+ * Even although the new logger is anonymous, it is configured + * to have the root logger ("") as its parent. This means that + * by default it inherits its effective level and handlers + * from the root logger. + *

+ * @param resourceBundleName name of ResourceBundle to be used for localizing + * messages for this logger. + * May be null if none of the messages require localization. + * @return a newly created private Logger + * @throws MissingResourceException if the resourceBundleName is non-null and + * no corresponding resource can be found. + */ + + // Synchronization is not required here. All synchronization for + // adding a new anonymous Logger object is handled by doSetParent(). + public static Logger getAnonymousLogger(String resourceBundleName) { + return new Logger(null, resourceBundleName); + } + + /** + * Retrieve the localization resource bundle for this + * logger for the current default locale. Note that if + * the result is null, then the Logger will use a resource + * bundle inherited from its parent. + * + * @return localization bundle (may be null) + */ +// public ResourceBundle getResourceBundle() { +// return findResourceBundle(getResourceBundleName()); +// } + + /** + * Retrieve the localization resource bundle name for this + * logger. Note that if the result is null, then the Logger + * will use a resource bundle name inherited from its parent. + * + * @return localization bundle name (may be null) + */ + public String getResourceBundleName() { + return null; + } + + /** + * Set a filter to control output on this Logger. + *

+ * After passing the initial "level" check, the Logger will + * call this Filter to check if a log record should really + * be published. + * + * @param newFilter a filter object (may be null) + * @exception SecurityException if a security manager exists and if + * the caller does not have LoggingPermission("control"). + */ +// public void setFilter(Filter newFilter) throws SecurityException { +// checkAccess(); +// } + + /** + * Get the current filter for this Logger. + * + * @return a filter object (may be null) + */ +// public Filter getFilter() { +// return filter; +// } + + /** + * Log a LogRecord. + *

+ * All the other logging methods in this class call through + * this method to actually perform any logging. Subclasses can + * override this single method to capture all log activity. + * + * @param record the LogRecord to be published + */ + public void log(LogRecord record) { + if (record.getLevel().intValue() < levelValue) { + return; + } + + String method; + switch (record.getLevel().toString()) { + case "INFO": method = "info"; break; + case "SEVERE": method = "error"; break; + case "WARNING": method = "warn"; break; + default: method = "log"; break; + } + + consoleLog( + method, + record.getLoggerName(), + record.getMessage() + ); + } + + @JavaScriptBody(args = { "method", "logger", "msg" }, body = + "window.console[method]('[' + logger + ']: ' + msg);" + ) + private static native void consoleLog( + String method, String logger, String msg + ); + + // private support method for logging. + // We fill in the logger name, resource bundle name, and + // resource bundle and then call "void log(LogRecord)". + private void doLog(LogRecord lr) { + doLog(lr, lr.getResourceBundleName()); + } + private void doLog(LogRecord lr, String bundleName) { + lr.setLoggerName(name); + log(lr); + } + + + //================================================================ + // Start of convenience methods WITHOUT className and methodName + //================================================================ + + /** + * Log a message, with no arguments. + *

+ * If the logger is currently enabled for the given message + * level then the given message is forwarded to all the + * registered output Handler objects. + *

+ * @param level One of the message level identifiers, e.g., SEVERE + * @param msg The string message (or a key in the message catalog) + */ + public void log(Level level, String msg) { + if (level.intValue() < levelValue || levelValue == offValue) { + return; + } + LogRecord lr = new LogRecord(level, msg); + doLog(lr); + } + + /** + * Log a message, with one object parameter. + *

+ * If the logger is currently enabled for the given message + * level then a corresponding LogRecord is created and forwarded + * to all the registered output Handler objects. + *

+ * @param level One of the message level identifiers, e.g., SEVERE + * @param msg The string message (or a key in the message catalog) + * @param param1 parameter to the message + */ + public void log(Level level, String msg, Object param1) { + if (level.intValue() < levelValue || levelValue == offValue) { + return; + } + LogRecord lr = new LogRecord(level, msg); + Object params[] = { param1 }; + lr.setParameters(params); + doLog(lr); + } + + /** + * Log a message, with an array of object arguments. + *

+ * If the logger is currently enabled for the given message + * level then a corresponding LogRecord is created and forwarded + * to all the registered output Handler objects. + *

+ * @param level One of the message level identifiers, e.g., SEVERE + * @param msg The string message (or a key in the message catalog) + * @param params array of parameters to the message + */ + public void log(Level level, String msg, Object params[]) { + if (level.intValue() < levelValue || levelValue == offValue) { + return; + } + LogRecord lr = new LogRecord(level, msg); + lr.setParameters(params); + doLog(lr); + } + + /** + * Log a message, with associated Throwable information. + *

+ * If the logger is currently enabled for the given message + * level then the given arguments are stored in a LogRecord + * which is forwarded to all registered output handlers. + *

+ * Note that the thrown argument is stored in the LogRecord thrown + * property, rather than the LogRecord parameters property. Thus is it + * processed specially by output Formatters and is not treated + * as a formatting parameter to the LogRecord message property. + *

+ * @param level One of the message level identifiers, e.g., SEVERE + * @param msg The string message (or a key in the message catalog) + * @param thrown Throwable associated with log message. + */ + public void log(Level level, String msg, Throwable thrown) { + if (level.intValue() < levelValue || levelValue == offValue) { + return; + } + LogRecord lr = new LogRecord(level, msg); + lr.setThrown(thrown); + doLog(lr); + } + + //================================================================ + // Start of convenience methods WITH className and methodName + //================================================================ + + /** + * Log a message, specifying source class and method, + * with no arguments. + *

+ * If the logger is currently enabled for the given message + * level then the given message is forwarded to all the + * registered output Handler objects. + *

+ * @param level One of the message level identifiers, e.g., SEVERE + * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of method that issued the logging request + * @param msg The string message (or a key in the message catalog) + */ + public void logp(Level level, String sourceClass, String sourceMethod, String msg) { + if (level.intValue() < levelValue || levelValue == offValue) { + return; + } + LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + doLog(lr); + } + + /** + * Log a message, specifying source class and method, + * with a single object parameter to the log message. + *

+ * If the logger is currently enabled for the given message + * level then a corresponding LogRecord is created and forwarded + * to all the registered output Handler objects. + *

+ * @param level One of the message level identifiers, e.g., SEVERE + * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of method that issued the logging request + * @param msg The string message (or a key in the message catalog) + * @param param1 Parameter to the log message. + */ + public void logp(Level level, String sourceClass, String sourceMethod, + String msg, Object param1) { + if (level.intValue() < levelValue || levelValue == offValue) { + return; + } + LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + Object params[] = { param1 }; + lr.setParameters(params); + doLog(lr); + } + + /** + * Log a message, specifying source class and method, + * with an array of object arguments. + *

+ * If the logger is currently enabled for the given message + * level then a corresponding LogRecord is created and forwarded + * to all the registered output Handler objects. + *

+ * @param level One of the message level identifiers, e.g., SEVERE + * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of method that issued the logging request + * @param msg The string message (or a key in the message catalog) + * @param params Array of parameters to the message + */ + public void logp(Level level, String sourceClass, String sourceMethod, + String msg, Object params[]) { + if (level.intValue() < levelValue || levelValue == offValue) { + return; + } + LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + lr.setParameters(params); + doLog(lr); + } + + /** + * Log a message, specifying source class and method, + * with associated Throwable information. + *

+ * If the logger is currently enabled for the given message + * level then the given arguments are stored in a LogRecord + * which is forwarded to all registered output handlers. + *

+ * Note that the thrown argument is stored in the LogRecord thrown + * property, rather than the LogRecord parameters property. Thus is it + * processed specially by output Formatters and is not treated + * as a formatting parameter to the LogRecord message property. + *

+ * @param level One of the message level identifiers, e.g., SEVERE + * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of method that issued the logging request + * @param msg The string message (or a key in the message catalog) + * @param thrown Throwable associated with log message. + */ + public void logp(Level level, String sourceClass, String sourceMethod, + String msg, Throwable thrown) { + if (level.intValue() < levelValue || levelValue == offValue) { + return; + } + LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + lr.setThrown(thrown); + doLog(lr); + } + + + //========================================================================= + // Start of convenience methods WITH className, methodName and bundle name. + //========================================================================= + + + /** + * Log a message, specifying source class, method, and resource bundle name + * with no arguments. + *

+ * If the logger is currently enabled for the given message + * level then the given message is forwarded to all the + * registered output Handler objects. + *

+ * The msg string is localized using the named resource bundle. If the + * resource bundle name is null, or an empty String or invalid + * then the msg string is not localized. + *

+ * @param level One of the message level identifiers, e.g., SEVERE + * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of method that issued the logging request + * @param bundleName name of resource bundle to localize msg, + * can be null + * @param msg The string message (or a key in the message catalog) + */ + + public void logrb(Level level, String sourceClass, String sourceMethod, + String bundleName, String msg) { + if (level.intValue() < levelValue || levelValue == offValue) { + return; + } + LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + doLog(lr, bundleName); + } + + /** + * Log a message, specifying source class, method, and resource bundle name, + * with a single object parameter to the log message. + *

+ * If the logger is currently enabled for the given message + * level then a corresponding LogRecord is created and forwarded + * to all the registered output Handler objects. + *

+ * The msg string is localized using the named resource bundle. If the + * resource bundle name is null, or an empty String or invalid + * then the msg string is not localized. + *

+ * @param level One of the message level identifiers, e.g., SEVERE + * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of method that issued the logging request + * @param bundleName name of resource bundle to localize msg, + * can be null + * @param msg The string message (or a key in the message catalog) + * @param param1 Parameter to the log message. + */ + public void logrb(Level level, String sourceClass, String sourceMethod, + String bundleName, String msg, Object param1) { + if (level.intValue() < levelValue || levelValue == offValue) { + return; + } + LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + Object params[] = { param1 }; + lr.setParameters(params); + doLog(lr, bundleName); + } + + /** + * Log a message, specifying source class, method, and resource bundle name, + * with an array of object arguments. + *

+ * If the logger is currently enabled for the given message + * level then a corresponding LogRecord is created and forwarded + * to all the registered output Handler objects. + *

+ * The msg string is localized using the named resource bundle. If the + * resource bundle name is null, or an empty String or invalid + * then the msg string is not localized. + *

+ * @param level One of the message level identifiers, e.g., SEVERE + * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of method that issued the logging request + * @param bundleName name of resource bundle to localize msg, + * can be null. + * @param msg The string message (or a key in the message catalog) + * @param params Array of parameters to the message + */ + public void logrb(Level level, String sourceClass, String sourceMethod, + String bundleName, String msg, Object params[]) { + if (level.intValue() < levelValue || levelValue == offValue) { + return; + } + LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + lr.setParameters(params); + doLog(lr, bundleName); + } + + /** + * Log a message, specifying source class, method, and resource bundle name, + * with associated Throwable information. + *

+ * If the logger is currently enabled for the given message + * level then the given arguments are stored in a LogRecord + * which is forwarded to all registered output handlers. + *

+ * The msg string is localized using the named resource bundle. If the + * resource bundle name is null, or an empty String or invalid + * then the msg string is not localized. + *

+ * Note that the thrown argument is stored in the LogRecord thrown + * property, rather than the LogRecord parameters property. Thus is it + * processed specially by output Formatters and is not treated + * as a formatting parameter to the LogRecord message property. + *

+ * @param level One of the message level identifiers, e.g., SEVERE + * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of method that issued the logging request + * @param bundleName name of resource bundle to localize msg, + * can be null + * @param msg The string message (or a key in the message catalog) + * @param thrown Throwable associated with log message. + */ + public void logrb(Level level, String sourceClass, String sourceMethod, + String bundleName, String msg, Throwable thrown) { + if (level.intValue() < levelValue || levelValue == offValue) { + return; + } + LogRecord lr = new LogRecord(level, msg); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + lr.setThrown(thrown); + doLog(lr, bundleName); + } + + + //====================================================================== + // Start of convenience methods for logging method entries and returns. + //====================================================================== + + /** + * Log a method entry. + *

+ * This is a convenience method that can be used to log entry + * to a method. A LogRecord with message "ENTRY", log level + * FINER, and the given sourceMethod and sourceClass is logged. + *

+ * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of method that is being entered + */ + public void entering(String sourceClass, String sourceMethod) { + if (Level.FINER.intValue() < levelValue) { + return; + } + logp(Level.FINER, sourceClass, sourceMethod, "ENTRY"); + } + + /** + * Log a method entry, with one parameter. + *

+ * This is a convenience method that can be used to log entry + * to a method. A LogRecord with message "ENTRY {0}", log level + * FINER, and the given sourceMethod, sourceClass, and parameter + * is logged. + *

+ * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of method that is being entered + * @param param1 parameter to the method being entered + */ + public void entering(String sourceClass, String sourceMethod, Object param1) { + if (Level.FINER.intValue() < levelValue) { + return; + } + Object params[] = { param1 }; + logp(Level.FINER, sourceClass, sourceMethod, "ENTRY {0}", params); + } + + /** + * Log a method entry, with an array of parameters. + *

+ * This is a convenience method that can be used to log entry + * to a method. A LogRecord with message "ENTRY" (followed by a + * format {N} indicator for each entry in the parameter array), + * log level FINER, and the given sourceMethod, sourceClass, and + * parameters is logged. + *

+ * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of method that is being entered + * @param params array of parameters to the method being entered + */ + public void entering(String sourceClass, String sourceMethod, Object params[]) { + if (Level.FINER.intValue() < levelValue) { + return; + } + String msg = "ENTRY"; + if (params == null ) { + logp(Level.FINER, sourceClass, sourceMethod, msg); + return; + } + for (int i = 0; i < params.length; i++) { + msg = msg + " {" + i + "}"; + } + logp(Level.FINER, sourceClass, sourceMethod, msg, params); + } + + /** + * Log a method return. + *

+ * This is a convenience method that can be used to log returning + * from a method. A LogRecord with message "RETURN", log level + * FINER, and the given sourceMethod and sourceClass is logged. + *

+ * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of the method + */ + public void exiting(String sourceClass, String sourceMethod) { + if (Level.FINER.intValue() < levelValue) { + return; + } + logp(Level.FINER, sourceClass, sourceMethod, "RETURN"); + } + + + /** + * Log a method return, with result object. + *

+ * This is a convenience method that can be used to log returning + * from a method. A LogRecord with message "RETURN {0}", log level + * FINER, and the gives sourceMethod, sourceClass, and result + * object is logged. + *

+ * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of the method + * @param result Object that is being returned + */ + public void exiting(String sourceClass, String sourceMethod, Object result) { + if (Level.FINER.intValue() < levelValue) { + return; + } + Object params[] = { result }; + logp(Level.FINER, sourceClass, sourceMethod, "RETURN {0}", result); + } + + /** + * Log throwing an exception. + *

+ * This is a convenience method to log that a method is + * terminating by throwing an exception. The logging is done + * using the FINER level. + *

+ * If the logger is currently enabled for the given message + * level then the given arguments are stored in a LogRecord + * which is forwarded to all registered output handlers. The + * LogRecord's message is set to "THROW". + *

+ * Note that the thrown argument is stored in the LogRecord thrown + * property, rather than the LogRecord parameters property. Thus is it + * processed specially by output Formatters and is not treated + * as a formatting parameter to the LogRecord message property. + *

+ * @param sourceClass name of class that issued the logging request + * @param sourceMethod name of the method. + * @param thrown The Throwable that is being thrown. + */ + public void throwing(String sourceClass, String sourceMethod, Throwable thrown) { + if (Level.FINER.intValue() < levelValue || levelValue == offValue ) { + return; + } + LogRecord lr = new LogRecord(Level.FINER, "THROW"); + lr.setSourceClassName(sourceClass); + lr.setSourceMethodName(sourceMethod); + lr.setThrown(thrown); + doLog(lr); + } + + //======================================================================= + // Start of simple convenience methods using level names as method names + //======================================================================= + + /** + * Log a SEVERE message. + *

+ * If the logger is currently enabled for the SEVERE message + * level then the given message is forwarded to all the + * registered output Handler objects. + *

+ * @param msg The string message (or a key in the message catalog) + */ + public void severe(String msg) { + if (Level.SEVERE.intValue() < levelValue) { + return; + } + log(Level.SEVERE, msg); + } + + /** + * Log a WARNING message. + *

+ * If the logger is currently enabled for the WARNING message + * level then the given message is forwarded to all the + * registered output Handler objects. + *

+ * @param msg The string message (or a key in the message catalog) + */ + public void warning(String msg) { + if (Level.WARNING.intValue() < levelValue) { + return; + } + log(Level.WARNING, msg); + } + + /** + * Log an INFO message. + *

+ * If the logger is currently enabled for the INFO message + * level then the given message is forwarded to all the + * registered output Handler objects. + *

+ * @param msg The string message (or a key in the message catalog) + */ + public void info(String msg) { + if (Level.INFO.intValue() < levelValue) { + return; + } + log(Level.INFO, msg); + } + + /** + * Log a CONFIG message. + *

+ * If the logger is currently enabled for the CONFIG message + * level then the given message is forwarded to all the + * registered output Handler objects. + *

+ * @param msg The string message (or a key in the message catalog) + */ + public void config(String msg) { + if (Level.CONFIG.intValue() < levelValue) { + return; + } + log(Level.CONFIG, msg); + } + + /** + * Log a FINE message. + *

+ * If the logger is currently enabled for the FINE message + * level then the given message is forwarded to all the + * registered output Handler objects. + *

+ * @param msg The string message (or a key in the message catalog) + */ + public void fine(String msg) { + if (Level.FINE.intValue() < levelValue) { + return; + } + log(Level.FINE, msg); + } + + /** + * Log a FINER message. + *

+ * If the logger is currently enabled for the FINER message + * level then the given message is forwarded to all the + * registered output Handler objects. + *

+ * @param msg The string message (or a key in the message catalog) + */ + public void finer(String msg) { + if (Level.FINER.intValue() < levelValue) { + return; + } + log(Level.FINER, msg); + } + + /** + * Log a FINEST message. + *

+ * If the logger is currently enabled for the FINEST message + * level then the given message is forwarded to all the + * registered output Handler objects. + *

+ * @param msg The string message (or a key in the message catalog) + */ + public void finest(String msg) { + if (Level.FINEST.intValue() < levelValue) { + return; + } + log(Level.FINEST, msg); + } + + //================================================================ + // End of convenience methods + //================================================================ + + /** + * Set the log level specifying which message levels will be + * logged by this logger. Message levels lower than this + * value will be discarded. The level value Level.OFF + * can be used to turn off logging. + *

+ * If the new level is null, it means that this node should + * inherit its level from its nearest ancestor with a specific + * (non-null) level value. + * + * @param newLevel the new value for the log level (may be null) + * @exception SecurityException if a security manager exists and if + * the caller does not have LoggingPermission("control"). + */ + public void setLevel(Level newLevel) throws SecurityException { + levelValue = newLevel.intValue(); + levelObject = newLevel; + } + + /** + * Get the log Level that has been specified for this Logger. + * The result may be null, which means that this logger's + * effective level will be inherited from its parent. + * + * @return this Logger's level + */ + public Level getLevel() { + return levelObject; + } + + /** + * Check if a message of the given level would actually be logged + * by this logger. This check is based on the Loggers effective level, + * which may be inherited from its parent. + * + * @param level a message logging level + * @return true if the given message level is currently being logged. + */ + public boolean isLoggable(Level level) { + if (level.intValue() < levelValue || levelValue == offValue) { + return false; + } + return true; + } + + /** + * Get the name for this logger. + * @return logger name. Will be null for anonymous Loggers. + */ + public String getName() { + return name; + } + + /** + * Add a log Handler to receive logging messages. + *

+ * By default, Loggers also send their output to their parent logger. + * Typically the root Logger is configured with a set of Handlers + * that essentially act as default handlers for all loggers. + * + * @param handler a logging Handler + * @exception SecurityException if a security manager exists and if + * the caller does not have LoggingPermission("control"). + */ +// public void addHandler(Handler handler) throws SecurityException { +// // Check for null handler +// handler.getClass(); +// checkAccess(); +// handlers.add(handler); +// } + + /** + * Remove a log Handler. + *

+ * Returns silently if the given Handler is not found or is null + * + * @param handler a logging Handler + * @exception SecurityException if a security manager exists and if + * the caller does not have LoggingPermission("control"). + */ +// public void removeHandler(Handler handler) throws SecurityException { +// checkAccess(); +// if (handler == null) { +// return; +// } +// handlers.remove(handler); +// } + + /** + * Get the Handlers associated with this logger. + *

+ * @return an array of all registered Handlers + */ +// public Handler[] getHandlers() { +// return handlers.toArray(emptyHandlers); +// } + + /** + * Specify whether or not this logger should send its output + * to its parent Logger. This means that any LogRecords will + * also be written to the parent's Handlers, and potentially + * to its parent, recursively up the namespace. + * + * @param useParentHandlers true if output is to be sent to the + * logger's parent. + * @exception SecurityException if a security manager exists and if + * the caller does not have LoggingPermission("control"). + */ + public void setUseParentHandlers(boolean useParentHandlers) { + checkAccess(); + } + + /** + * Discover whether or not this logger is sending its output + * to its parent logger. + * + * @return true if output is to be sent to the logger's parent + */ + public boolean getUseParentHandlers() { + return true; + } + + /** + * Return the parent for this Logger. + *

+ * This method returns the nearest extant parent in the namespace. + * Thus if a Logger is called "a.b.c.d", and a Logger called "a.b" + * has been created but no logger "a.b.c" exists, then a call of + * getParent on the Logger "a.b.c.d" will return the Logger "a.b". + *

+ * The result will be null if it is called on the root Logger + * in the namespace. + * + * @return nearest existing parent Logger + */ + public Logger getParent() { + // Note: this used to be synchronized on treeLock. However, this only + // provided memory semantics, as there was no guarantee that the caller + // would synchronize on treeLock (in fact, there is no way for external + // callers to so synchronize). Therefore, we have made parent volatile + // instead. + String n = getName(); + int at = n.length(); + for (;;) { + int last = n.lastIndexOf('.', at - 1); + if (last == -1) { + return getGlobal(); + } + Logger p = ALL.get(n.substring(0, last)); + if (p != null) { + return p; + } + at = last; + } + } + + /** + * Set the parent for this Logger. This method is used by + * the LogManager to update a Logger when the namespace changes. + *

+ * It should not be called from application code. + *

+ * @param parent the new parent logger + * @exception SecurityException if a security manager exists and if + * the caller does not have LoggingPermission("control"). + */ + public void setParent(Logger parent) { + if (parent == null) { + throw new NullPointerException(); + } + checkAccess(); + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/compact/tck/ReaderTest.java --- a/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/compact/tck/ReaderTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/compact/tck/ReaderTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -18,8 +18,10 @@ package org.apidesign.bck2brwsr.compact.tck; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.util.Arrays; import org.apidesign.bck2brwsr.vmtest.Compare; @@ -40,7 +42,10 @@ }; ByteArrayInputStream is = new ByteArrayInputStream(arr); InputStreamReader r = new InputStreamReader(is, "UTF-8"); - + return readReader(r); + } + + private String readReader(InputStreamReader r) throws IOException { StringBuilder sb = new StringBuilder(); for (;;) { int ch = r.read(); @@ -52,7 +57,19 @@ return sb.toString().toString(); } @Compare public String stringToBytes() throws UnsupportedEncodingException { - return Arrays.toString("\u017dlu\u0165ou\u010dk\u00fd k\u016f\u0148".getBytes("UTF-8")); + return Arrays.toString(YellowHorse.getBytes("UTF-8")); + } + private final String YellowHorse = "\u017dlu\u0165ou\u010dk\u00fd k\u016f\u0148"; + + @Compare public String readAndWrite() throws Exception { + ByteArrayOutputStream arr = new ByteArrayOutputStream(); + OutputStreamWriter w = new OutputStreamWriter(arr); + w.write(YellowHorse); + w.close(); + + ByteArrayInputStream is = new ByteArrayInputStream(arr.toByteArray()); + InputStreamReader r = new InputStreamReader(is, "UTF-8"); + return readReader(r); } @Factory public static Object[] create() { diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/compact/tck/ZipCompatibilityTest.java --- a/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/compact/tck/ZipCompatibilityTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/compact/tck/ZipCompatibilityTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -30,9 +30,10 @@ public class ZipCompatibilityTest { @Compare public String testDemoStaticCalculator() throws IOException { - InputStream is = getClass().getResourceAsStream("demo.static.calculator-0.3-SNAPSHOT.jar"); + InputStream is = getClass().getResourceAsStream("demo.static.calculator-TEST.jar"); ZipArchive zip = ZipArchive.createZip(is); - return zip.toString(); + final String ts = zip.toString(); + return ts.substring(0, 4096) + ts.hashCode(); } @Factory diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/compact/tck/ZipVsJzLibTest.java --- a/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/compact/tck/ZipVsJzLibTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/compact/tck/ZipVsJzLibTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -27,10 +27,10 @@ */ public class ZipVsJzLibTest { @Test public void r() throws IOException { - InputStream is = getClass().getResourceAsStream("demo.static.calculator-0.3-SNAPSHOT.jar"); + InputStream is = getClass().getResourceAsStream("demo.static.calculator-TEST.jar"); ZipArchive zip = ZipArchive.createZip(is); - is = getClass().getResourceAsStream("demo.static.calculator-0.3-SNAPSHOT.jar"); + is = getClass().getResourceAsStream("demo.static.calculator-TEST.jar"); ZipArchive real = ZipArchive.createReal(is); real.assertEquals(zip, "Are they the same?"); diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/AssertionTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/AssertionTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,43 @@ +/** + * 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.tck; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class AssertionTest { + + @Compare public Object checkAssert() throws ClassNotFoundException { + try { + assert false : "Is assertion status on?"; + return null; + } catch (AssertionError ex) { + return ex.getClass().getName(); + } + } + + @Factory + public static Object[] create() { + return VMTest.create(AssertionTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/BrwsrCheckTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/BrwsrCheckTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,57 @@ +/** + * 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.tck; + +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.HtmlFragment; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class BrwsrCheckTest { + + @BrwsrTest public void assertWindowObjectIsDefined() { + assert window() != null : "No window object found!"; + } + + + + + @HtmlFragment("

\n" + + "Hello!\n" + + "

\n") + @BrwsrTest public void accessProvidedFragment() { + assert getElementById("hello") != null : "Element with 'hello' ID found"; + } + + @Factory + public static Object[] create() { + return VMTest.create(BrwsrCheckTest.class); + } + + + @JavaScriptBody(args = {}, body = "return window;") + private static native Object window(); + + @JavaScriptBody(args = { "id" }, body = "return window.document.getElementById(id);") + private static native Object getElementById(String id); +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ByteArithmeticTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ByteArithmeticTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,147 @@ +/** + * 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.tck; + +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class ByteArithmeticTest { + + private static byte add(byte x, byte y) { + return (byte)(x + y); + } + + private static byte sub(byte x, byte y) { + return (byte)(x - y); + } + + private static byte mul(byte x, byte y) { + return (byte)(x * y); + } + + private static byte div(byte x, byte y) { + return (byte)(x / y); + } + + private static byte mod(byte x, byte y) { + return (byte)(x % y); + } + + @Compare public byte conversion() { + return (byte)123456; + } + + @Compare public byte addOverflow() { + return add(Byte.MAX_VALUE, (byte)1); + } + + @Compare public byte subUnderflow() { + return sub(Byte.MIN_VALUE, (byte)1); + } + + @Compare public byte addMaxByteAndMaxByte() { + return add(Byte.MAX_VALUE, Byte.MAX_VALUE); + } + + @Compare public byte subMinByteAndMinByte() { + return sub(Byte.MIN_VALUE, Byte.MIN_VALUE); + } + + @Compare public byte multiplyMaxByte() { + return mul(Byte.MAX_VALUE, (byte)2); + } + + @Compare public byte multiplyMaxByteAndMaxByte() { + return mul(Byte.MAX_VALUE, Byte.MAX_VALUE); + } + + @Compare public byte multiplyMinByte() { + return mul(Byte.MIN_VALUE, (byte)2); + } + + @Compare public byte multiplyMinByteAndMinByte() { + return mul(Byte.MIN_VALUE, Byte.MIN_VALUE); + } + + @Compare public byte multiplyPrecision() { + return mul((byte)17638, (byte)1103); + } + + @Compare public byte division() { + return div((byte)1, (byte)2); + } + + @Compare public byte divisionReminder() { + return mod((byte)1, (byte)2); + } + + private static int readShort(byte[] byteCodes, int offset) { + int signed = byteCodes[offset]; + byte b0 = (byte)signed; + return (b0 << 8) | (byteCodes[offset + 1] & 0xff); + } + + private static int readShortArg(byte[] byteCodes, int offsetInstruction) { + return readShort(byteCodes, offsetInstruction + 1); + } + + @Compare public int readIntArgs255and156() { + final byte[] arr = new byte[] { (byte)0, (byte)255, (byte)156 }; + + assert arr[1] == -1 : "First byte: " + arr[1]; + assert arr[2] == -100 : "Second byte: " + arr[2]; + final int ret = readShortArg(arr, 0); + assert ret < 65000: "Value: " + ret; + return ret; + } + + @JavaScriptBody(args = { "arr" }, body = "arr[1] = 255; arr[2] = 156; return arr;") + private static byte[] fill255and156(byte[] arr) { + arr[1] = (byte)255; + arr[2] = (byte)156; + return arr; + } + + @Compare public int readIntArgs255and156JSArray() { + final byte[] arr = fill255and156(new byte[] { 0, 0, 0 }); + + final int ret = readShortArg(arr, 0); + assert ret < 65000: "Value: " + ret; + return ret; + } + + @Compare public int readIntArgsMinus1andMinus100() { + final byte[] arr = new byte[] { (byte)0, (byte)-1, (byte)-100 }; + + assert arr[1] == -1 : "First byte: " + arr[1]; + assert arr[2] == -100 : "Second byte: " + arr[2]; + + return readShortArg(arr, 0); + } + + @Factory + public static Object[] create() { + return VMTest.create(ByteArithmeticTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CloneTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CloneTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,73 @@ +/** + * 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.tck; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class CloneTest { + private int value; + + @Compare + public Object notSupported() throws CloneNotSupportedException { + return this.clone(); + } + + @Compare public String sameClass() throws CloneNotSupportedException { + return new Clnbl().clone().getClass().getName(); + } + + @Compare public boolean differentInstance() throws CloneNotSupportedException { + Clnbl orig = new Clnbl(); + return orig == orig.clone(); + } + + @Compare public int sameReference() throws CloneNotSupportedException { + CloneTest self = this; + Clnbl orig = new Clnbl(); + self.value = 33; + orig.ref = self; + return ((Clnbl)orig.clone()).ref.value; + } + + @Compare public int sameValue() throws CloneNotSupportedException { + Clnbl orig = new Clnbl(); + orig.value = 10; + return ((Clnbl)orig.clone()).value; + } + + @Factory + public static Object[] create() { + return VMTest.create(CloneTest.class); + } + + public static final class Clnbl implements Cloneable { + public CloneTest ref; + private int value; + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareByteArrayTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareByteArrayTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,103 @@ +/** + * 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.tck; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class CompareByteArrayTest { + @Compare public int byteArraySum() { + byte[] arr = createArray(); + return sumByteArr(arr); + } + + @Compare public int countZeros() { + int zeros = 0; + for (Byte b : createArray()) { + if (b == 0) { + zeros++; + } + } + return zeros; + } + + private static int sumByteArr(byte[] arr) { + int sum = 0; + for (int i = 0; i < arr.length; i++) { + sum += arr[i]; + } + return sum; + } + + @Compare public String noOutOfBounds() { + return atIndex(1); + } + + @Compare public String outOfBounds() { + return atIndex(5); + } + + @Compare public String outOfBoundsMinus() { + return atIndex(-1); + } + + @Compare public String toOfBounds() { + return toIndex(5); + } + + @Compare public String toOfBoundsMinus() { + return toIndex(-1); + } + + @Compare public int multiArrayLength() { + int[][] arr = new int[1][0]; + return arr[0].length; + } + + @Compare public int multiObjectArrayLength() { + Object[][] arr = new Object[1][0]; + return arr[0].length; + } + + private static final int[] arr = { 0, 1, 2 }; + public static String atIndex(int at) { + return "at@" + arr[at]; + } + public static String toIndex(int at) { + arr[at] = 10; + return "ok"; + } + + + @Factory + public static Object[] create() { + return VMTest.create(CompareByteArrayTest.class); + } + + private byte[] createArray() { + byte[] arr = new byte[10]; + arr[5] = 3; + arr[7] = 8; + return arr; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareHashTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareHashTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,60 @@ +/** + * 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.tck; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class CompareHashTest { + @Compare public int hashOfString() { + return "Ahoj".hashCode(); + } + + @Compare public boolean hashOfIntegerDifferentToOwnHash() { + Integer i = 120; + return System.identityHashCode(i) != i.hashCode(); + } + + @Compare public int hashOfObjectSameAsOwnHash() { + Object o = new Object(); + return System.identityHashCode(o) - o.hashCode(); + } + + @Compare public int hashRemainsYieldsZero() { + Object o = new Object(); + return o.hashCode() - o.hashCode(); + } + + @Compare public int initializeInStatic() { + return StaticUse.NON_NULL.hashCode() - StaticUse.NON_NULL.hashCode(); + } + + @Compare public int hashOfInt() { + return Integer.valueOf(Integer.MAX_VALUE).hashCode(); + } + + @Factory + public static Object[] create() { + return VMTest.create(CompareHashTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareIntArrayTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareIntArrayTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,63 @@ +/** + * 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.tck; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class CompareIntArrayTest { + @Compare public int integerArraySum() { + int[] arr = createArray(); + return sumIntArr(arr); + } + + @Compare public int countZeros() { + int zeros = 0; + for (Integer i : createArray()) { + if (i == 0) { + zeros++; + } + } + return zeros; + } + + private static int sumIntArr(int[] arr) { + int sum = 0; + for (int i = 0; i < arr.length; i++) { + sum += arr[i]; + } + return sum; + } + + @Factory + public static Object[] create() { + return VMTest.create(CompareIntArrayTest.class); + } + + private int[] createArray() { + int[] arr = new int[10]; + arr[5] = 3; + arr[7] = 8; + return arr; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareStringsTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareStringsTest.java Mon Oct 07 14:20:58 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.tck; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class CompareStringsTest { + @Compare public String firstChar() { + return "" + ("Hello".toCharArray()[0]); + } + + @Compare public String classCast() { + Object o = firstChar(); + return String.class.cast(o); + } + + @Compare public String classCastThrown() { + Object o = null; + return String.class.cast(o); + } + + @Compare public boolean equalToNull() { + return "Ahoj".equals(null); + } + + @Compare public int highByteLenght() { + byte[] arr= { 77,97,110,105,102,101,115,116,45,86,101,114,115,105,111,110 }; + return new String(arr, 0).length(); + } + + @Compare public String highByte() { + byte[] arr= { 77,97,110,105,102,101,115,116,45,86,101,114,115,105,111,110 }; + StringBuilder sb = new StringBuilder(); + sb.append("pref:"); + sb.append(new String(arr, 0)); + return sb.toString(); + } + + @Compare public static Object compareURLs() throws MalformedURLException { + return new URL("http://apidesign.org:8080/wiki/").toExternalForm().toString(); + } + + @Compare public String deleteLastTwoCharacters() { + StringBuilder sb = new StringBuilder(); + sb.append("453.0"); + if (sb.toString().endsWith(".0")) { + final int l = sb.length(); + sb.delete(l - 2, l); + } + return sb.toString().toString(); + } + + @Compare public String nameOfStringClass() throws Exception { + return Class.forName("java.lang.String").getName(); + } + @Compare public String nameOfArrayClass() throws Exception { + return Class.forName("org.apidesign.bck2brwsr.tck.CompareHashTest").getName(); + } + + @Compare public String lowerHello() { + return "HeLlO".toLowerCase(); + } + + @Compare public String lowerA() { + return String.valueOf(Character.toLowerCase('A')).toString(); + } + @Compare public String upperHello() { + return "hello".toUpperCase(); + } + + @Compare public String upperA() { + return String.valueOf(Character.toUpperCase('a')).toString(); + } + + @Compare public boolean matchRegExp() throws Exception { + return "58038503".matches("\\d*"); + } + + @Compare public boolean doesNotMatchRegExp() throws Exception { + return "58038503GH".matches("\\d*"); + } + + @Compare public boolean doesNotMatchRegExpFully() throws Exception { + return "Hello".matches("Hell"); + } + + @Compare public String emptyCharArray() { + char[] arr = new char[10]; + return new String(arr); + } + + @Compare public String variousCharacterTests() throws Exception { + StringBuilder sb = new StringBuilder(); + + sb.append(Character.isUpperCase('a')); + sb.append(Character.isUpperCase('A')); + sb.append(Character.isLowerCase('a')); + sb.append(Character.isLowerCase('A')); + + sb.append(Character.isLetter('A')); + sb.append(Character.isLetterOrDigit('9')); + sb.append(Character.isLetterOrDigit('A')); + sb.append(Character.isLetter('0')); + + return sb.toString().toString(); + } + + @Compare + public String nullFieldInitialized() { + NullField nf = new NullField(); + return ("" + nf.name).toString(); + } + @Compare + public String toUTFString() throws UnsupportedEncodingException { + byte[] arr = { + (byte) -59, (byte) -67, (byte) 108, (byte) 117, (byte) -59, (byte) -91, + (byte) 111, (byte) 117, (byte) -60, (byte) -115, (byte) 107, (byte) -61, + (byte) -67, (byte) 32, (byte) 107, (byte) -59, (byte) -81, (byte) -59, + (byte) -120 + }; + return new String(arr, "utf-8"); + } + + @Compare + public int stringToBytesLenght() throws UnsupportedEncodingException { + return "\u017dlu\u0165ou\u010dk\u00fd k\u016f\u0148".getBytes("utf8").length; + } + + @Compare public String replaceSeq() { + return "Hello World.".replace(".", "!"); + } + @Compare public String replaceSeqAll() { + return "Hello World! Hello World.".replace("World", "Jarda"); + } + @Compare public String replaceSeqAA() { + String res = "aaa".replace("aa", "b"); + assert res.equals("ba") : "Expecting ba: " + res; + return res; + } + + @Factory + public static Object[] create() { + return VMTest.create(CompareStringsTest.class); + } + + private static final class NullField { + + String name; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/DoubleTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/DoubleTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,69 @@ +/** + * 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.tck; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class DoubleTest { + @Compare public boolean parsedDoubleIsDouble() { + return Double.valueOf("1.1") instanceof Double; + } + + @Compare public String integerToString() { + return toStr(1); + } + + @Compare public String integerAndHalfToString() { + return toStr(1.5); + } + + @Compare public double longToAndBack() { + return Double.parseDouble(toStr(Long.MAX_VALUE / 10)); + } + + @Compare public String negativeIntToString() { + return toStr(-10); + } + + @Compare public String negativeIntAndHalfToString() { + return toStr(-10.5); + } + + @Compare public double negativeLongAndBack() { + return Double.parseDouble(toStr(Long.MIN_VALUE / 10)); + } + + @Compare public double canParseExp() { + return Double.parseDouble(toStr(1.7976931348623157e+308)); + } + + private static String toStr(double d) { + return Double.toString(d); + } + + @Factory + public static Object[] create() { + return VMTest.create(DoubleTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/EnumsTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/EnumsTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,61 @@ +package org.apidesign.bck2brwsr.tck; + +import java.util.EnumMap; +import java.util.EnumSet; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class EnumsTest { + enum Color { + B, W; + } + + /* + @Compare public String enumSet() { + try { throw new Exception(); } catch (Exception ex) {} + EnumSet c = EnumSet.allOf(Color.class); + return c.toString(); + } + + @Compare public String enumSetOneByOne() { + EnumSet c = EnumSet.of(Color.B, Color.W); + return c.toString(); + } + */ + + @Compare public boolean enumFirstContains() { + EnumSet c = EnumSet.of(Color.B); + return c.contains(Color.B); + } + + @Compare public boolean enumFirstDoesNotContains() { + EnumSet c = EnumSet.of(Color.B); + return c.contains(Color.W); + } + + @Compare public boolean enumSndContains() { + EnumSet c = EnumSet.of(Color.W); + return c.contains(Color.W); + } + + @Compare public boolean enumSecondDoesNotContains() { + EnumSet c = EnumSet.of(Color.W); + return c.contains(Color.B); + } + + @Compare public String enumMap() { + EnumMap c = new EnumMap(Color.class); + c.put(Color.B, "Black"); + c.put(Color.W, "White"); + return c.toString(); + } + + @Factory public static Object[] create() { + return VMTest.create(EnumsTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/HttpResourceTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/HttpResourceTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,106 @@ +/** + * 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.tck; + +import java.io.InputStream; +import java.net.URL; +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.Http; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class HttpResourceTest { + + @Http({ + @Http.Resource(path = "/xhr", content = "Hello Brwsr!", mimeType = "text/plain") + }) + @BrwsrTest + + + public String testReadContentViaXHR() throws Exception { + String msg = read("/xhr"); + assert "Hello Brwsr!".equals(msg) : "The message was " + msg; + return msg; + } + + @Http({ + @Http.Resource(path = "/url", content = "Hello via URL!", mimeType = "text/plain") + }) + @BrwsrTest + public String testReadContentViaURL() throws Exception { + URL url = new URL("http:/url"); + String msg = (String) url.getContent(); + assert "Hello via URL!".equals(msg) : "The message was " + msg; + return msg; + } + @Http({ + @Http.Resource(path = "/url", content = "Hello via URL!", mimeType = "text/plain") + }) + @BrwsrTest + public String testReadContentViaURLWithStringParam() throws Exception { + URL url = new URL("http:/url"); + String msg = (String) url.getContent(new Class[] { String.class }); + assert "Hello via URL!".equals(msg) : "The message was " + msg; + return msg; + } + + @Http({ + @Http.Resource(path = "/bytes", content = "", resource = "0xfe", mimeType = "x-application/binary") + }) + @BrwsrTest + public void testReadByte() throws Exception { + URL url = new URL("http:/bytes"); + final Object res = url.getContent(new Class[] { byte[].class }); + assert res instanceof byte[] : "Expecting byte[]: " + res; + byte[] arr = (byte[]) res; + assert arr.length == 1 : "One byte " + arr.length; + assert arr[0] == 0xfe : "It is 0xfe: " + Integer.toHexString(arr[0]); + } + + @Http({ + @Http.Resource(path = "/bytes", content = "", resource = "0xfe", mimeType = "x-application/binary") + }) + @BrwsrTest + public void testReadByteViaInputStream() throws Exception { + URL url = new URL("http:/bytes"); + InputStream is = url.openStream(); + byte[] arr = new byte[10]; + int len = is.read(arr); + assert len == 1 : "One byte " + len; + assert arr[0] == 0xfe : "It is 0xfe: " + Integer.toHexString(arr[0]); + } + + @JavaScriptBody(args = { "url" }, body = + "var req = new XMLHttpRequest();\n" + + "req.open('GET', url, false);\n" + + "req.send();\n" + + "return req.responseText;" + ) + private static native String read(String url); + + + @Factory + public static Object[] create() { + return VMTest.create(HttpResourceTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/InheritanceA.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/InheritanceA.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,34 @@ +/** + * 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.tck; + +/** + * + * @author Jaroslav Tulach + */ +public class InheritanceA { + private String name; + + public void setA(String n) { + this.name = n; + } + + public String getA() { + return name; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/InheritanceB.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/InheritanceB.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,34 @@ +/** + * 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.tck; + +/** + * + * @author Jaroslav Tulach + */ +public class InheritanceB extends InheritanceA { + private String name; + + public void setB(String n) { + this.name = n; + } + + public String getB() { + return name; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/InheritanceTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/InheritanceTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,41 @@ +/** + * 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.tck; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class InheritanceTest { + + @Compare public String checkFieldsIndependent() throws ClassNotFoundException { + InheritanceB ib = new InheritanceB(); + ib.setA("A"); + ib.setB("B"); + return "A: " + ib.getA() + " B: " + ib.getB(); + } + + @Factory + public static Object[] create() { + return VMTest.create(InheritanceTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/IntegerArithmeticTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/IntegerArithmeticTest.java Mon Oct 07 14:20:58 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.tck; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class IntegerArithmeticTest { + + private static int add(int x, int y) { + return x + y; + } + + private static int sub(int x, int y) { + return x - y; + } + + private static int mul(int x, int y) { + return x * y; + } + + private static int div(int x, int y) { + return x / y; + } + + private static int mod(int x, int y) { + return x % y; + } + + private static int neg(int x) { + return (-x); + } + + private static float fadd(float x, float y) { + return x + y; + } + + private static double dadd(double x, double y) { + return x + y; + } + + @Compare public int addOverflow() { + return add(Integer.MAX_VALUE, 1); + } + + @Compare public int subUnderflow() { + return sub(Integer.MIN_VALUE, 1); + } + + @Compare public int addMaxIntAndMaxInt() { + return add(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + @Compare public int subMinIntAndMinInt() { + return sub(Integer.MIN_VALUE, Integer.MIN_VALUE); + } + + @Compare public int multiplyMaxInt() { + return mul(Integer.MAX_VALUE, 2); + } + + @Compare public int multiplyMaxIntAndMaxInt() { + return mul(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + @Compare public int multiplyMinInt() { + return mul(Integer.MIN_VALUE, 2); + } + + @Compare public int multiplyMinIntAndMinInt() { + return mul(Integer.MIN_VALUE, Integer.MIN_VALUE); + } + + @Compare public int multiplyPrecision() { + return mul(119106029, 1103515245); + } + + @Compare public int division() { + return div(1, 2); + } + + @Compare public int divisionReminder() { + return mod(1, 2); + } + + @Compare public int negativeDivision() { + return div(-7, 3); + } + + @Compare public int negativeDivisionReminder() { + return mod(-7, 3); + } + + @Compare public int conversionFromFloat() { + return (int) fadd(-2, -0.6f); + } + + @Compare public int conversionFromDouble() { + return (int) dadd(-2, -0.6); + } + + @Compare public boolean divByZeroThrowsArithmeticException() { + try { + div(1, 0); + return false; + } catch (final ArithmeticException e) { + return true; + } + } + + @Compare public boolean modByZeroThrowsArithmeticException() { + try { + mod(1, 0); + return false; + } catch (final ArithmeticException e) { + return true; + } + } + + @Compare public int negate() { + return neg(123456); + } + + @Compare public int negateMaxInt() { + return neg(Integer.MAX_VALUE); + } + + @Compare public int negateMinInt() { + return neg(Integer.MIN_VALUE); + } + + @Compare public int sumTwoDimensions() { + int[][] matrix = createMatrix(4, 3); + matrix[0][0] += 10; + return matrix[0][0]; + } + + static int[][] createMatrix(int x, int y) { + return new int[x][y]; + } + + @Factory + public static Object[] create() { + return VMTest.create(IntegerArithmeticTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/LoggerTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/LoggerTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,43 @@ +/** + * 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.tck; + +import java.util.logging.Logger; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class LoggerTest { + @Compare public String parentLogger() { + Logger lx = Logger.getLogger("x"); + assert lx != null; + assert lx.getName().equals("x") : "Right name: " + lx.getName(); + Logger lxyz = Logger.getLogger("x.y.z"); + assert lxyz != null; + assert lxyz.getName().equals("x.y.z") : "xyz name: " + lxyz.getName(); + return lxyz.getParent().getName(); + } + + @Factory public static Object[] create() { + return VMTest.create(LoggerTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/LongArithmeticTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/LongArithmeticTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,376 @@ +/** + * 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.tck; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class LongArithmeticTest { + + private static long add(long x, long y) { + return (x + y); + } + + private static long sub(long x, long y) { + return (x - y); + } + + private static long mul(long x, long y) { + return (x * y); + } + + private static long div(long x, long y) { + return (x / y); + } + + private static long mod(long x, long y) { + return (x % y); + } + + private static long neg(long x) { + return (-x); + } + + private static long shl(long x, int b) { + return (x << b); + } + + private static long shr(long x, int b) { + return (x >> b); + } + + private static long ushr(long x, int b) { + return (x >>> b); + } + + private static long and(long x, long y) { + return (x & y); + } + + private static long or(long x, long y) { + return (x | y); + } + + private static long xor(long x, long y) { + return (x ^ y); + } + + private static float fadd(float x, float y) { + return x + y; + } + + private static double dadd(double x, double y) { + return x + y; + } + + public static int compare(long x, long y, int zero) { + final int xyResult = compareL(x, y, zero); + final int yxResult = compareL(y, x, zero); + + return ((xyResult + yxResult) == 0) ? xyResult : -2; + } + + private static int compareL(long x, long y, int zero) { + int result = -2; + int trueCount = 0; + + x += zero; + if (x == y) { + result = 0; + ++trueCount; + } + + x += zero; + if (x < y) { + result = -1; + ++trueCount; + } + + x += zero; + if (x > y) { + result = 1; + ++trueCount; + } + + return (trueCount == 1) ? result : -2; + } + + @Compare public long conversion() { + return Long.MAX_VALUE; + } + + @Compare public long negate1() { + return neg(0x00fa37d7763e0ca1l); + } + + @Compare public long negate2() { + return neg(0x80fa37d7763e0ca1l); + } + + @Compare public long negate3() { + return neg(0xfffffffffffffeddl); + } + + @Compare public long addOverflow() { + return add(Long.MAX_VALUE, 1l); + } + + @Compare public long subUnderflow() { + return sub(Long.MIN_VALUE, 1l); + } + + @Compare public long addMaxLongAndMaxLong() { + return add(Long.MAX_VALUE, Long.MAX_VALUE); + } + + @Compare public long subMinLongAndMinLong() { + return sub(Long.MIN_VALUE, Long.MIN_VALUE); + } + + @Compare public long subMinLongAndMaxLong() { + return sub(Long.MIN_VALUE, Long.MAX_VALUE); + } + + @Compare public long multiplyMaxLong() { + return mul(Long.MAX_VALUE, 2l); + } + + @Compare public long multiplyMaxLongAndMaxLong() { + return mul(Long.MAX_VALUE, Long.MAX_VALUE); + } + + @Compare public long multiplyMinLong() { + return mul(Long.MIN_VALUE, 2l); + } + + @Compare public long multiplyMinLongAndMinLong() { + return mul(Long.MIN_VALUE, Long.MIN_VALUE); + } + + @Compare public long multiplyPrecision() { + return mul(0x00fa37d7763e0ca1l, 0xa7b3432fff00123el); + } + + @Compare public long divideSmallPositiveNumbers() { + return div(0xabcdef, 0x123); + } + + @Compare public long divideSmallNegativeNumbers() { + return div(-0xabcdef, -0x123); + } + + @Compare public long divideSmallMixedNumbers() { + return div(0xabcdef, -0x123); + } + + @Compare public long dividePositiveNumbersOneDigitDenom() { + return div(0xabcdef0102ffffl, 0x654); + } + + @Compare public long divideNegativeNumbersOneDigitDenom() { + return div(-0xabcdef0102ffffl, -0x654); + } + + @Compare public long divideMixedNumbersOneDigitDenom() { + return div(-0xabcdef0102ffffl, 0x654); + } + + @Compare public long dividePositiveNumbersMultiDigitDenom() { + return div(0x7ffefc003322aabbl, 0x89ab1000l); + } + + @Compare public long divideNegativeNumbersMultiDigitDenom() { + return div(-0x7ffefc003322aabbl, -0x123489ab1001l); + } + + @Compare public long divideMixedNumbersMultiDigitDenom() { + return div(0x7ffefc003322aabbl, -0x38f49b0b7574e36l); + } + + @Compare public long divideWithOverflow() { + return div(0x8000fffe0000l, 0x8000ffffl); + } + + @Compare public long divideWithCorrection() { + return div(0x7fff800000000000l, 0x800000000001l); + } + + @Compare public long moduloSmallPositiveNumbers() { + return mod(0xabcdef, 0x123); + } + + @Compare public long moduloSmallNegativeNumbers() { + return mod(-0xabcdef, -0x123); + } + + @Compare public long moduloSmallMixedNumbers() { + return mod(0xabcdef, -0x123); + } + + @Compare public long moduloPositiveNumbersOneDigitDenom() { + return mod(0xabcdef0102ffffl, 0x654); + } + + @Compare public long moduloNegativeNumbersOneDigitDenom() { + return mod(-0xabcdef0102ffffl, -0x654); + } + + @Compare public long moduloMixedNumbersOneDigitDenom() { + return mod(-0xabcdef0102ffffl, 0x654); + } + + @Compare public long moduloPositiveNumbersMultiDigitDenom() { + return mod(0x7ffefc003322aabbl, 0x89ab1000l); + } + + @Compare public long moduloNegativeNumbersMultiDigitDenom() { + return mod(-0x7ffefc003322aabbl, -0x123489ab1001l); + } + + @Compare public long moduloMixedNumbersMultiDigitDenom() { + return mod(0x7ffefc003322aabbl, -0x38f49b0b7574e36l); + } + + @Compare public long moduloWithOverflow() { + return mod(0x8000fffe0000l, 0x8000ffffl); + } + + @Compare public long moduloWithCorrection() { + return mod(0x7fff800000000000l, 0x800000000001l); + } + + @Compare public long conversionFromFloatPositive() { + return (long) fadd(2, 0.6f); + } + + @Compare public long conversionFromFloatNegative() { + return (long) fadd(-2, -0.6f); + } + + @Compare public long conversionFromDoublePositive() { + return (long) dadd(0x20ffff0000L, 0.6); + } + + @Compare public long conversionFromDoubleNegative() { + return (long) dadd(-0x20ffff0000L, -0.6); + } + + @Compare public boolean divByZeroThrowsArithmeticException() { + try { + div(1, 0); + return false; + } catch (final ArithmeticException e) { + return true; + } + } + + @Compare public boolean modByZeroThrowsArithmeticException() { + try { + mod(1, 0); + return false; + } catch (final ArithmeticException e) { + return true; + } + } + + @Compare public long shiftL1() { + return shl(0x00fa37d7763e0ca1l, 5); + } + + @Compare public long shiftL2() { + return shl(0x00fa37d7763e0ca1l, 32); + } + + @Compare public long shiftL3() { + return shl(0x00fa37d7763e0ca1l, 45); + } + + @Compare public long shiftR1() { + return shr(0x00fa37d7763e0ca1l, 5); + } + + @Compare public long shiftR2() { + return shr(0x00fa37d7763e0ca1l, 32); + } + + @Compare public long shiftR3() { + return shr(0x00fa37d7763e0ca1l, 45); + } + + @Compare public long uShiftR1() { + return ushr(0x00fa37d7763e0ca1l, 5); + } + + @Compare public long uShiftR2() { + return ushr(0x00fa37d7763e0ca1l, 45); + } + + @Compare public long uShiftR3() { + return ushr(0xf0fa37d7763e0ca1l, 5); + } + + @Compare public long uShiftR4() { + return ushr(0xf0fa37d7763e0ca1l, 45); + } + + @Compare public long and1() { + return and(0x00fa37d7763e0ca1l, 0xa7b3432fff00123el); + } + + @Compare public long or1() { + return or(0x00fa37d7763e0ca1l, 0xa7b3432fff00123el); + } + + @Compare public long xor1() { + return xor(0x00fa37d7763e0ca1l, 0xa7b3432fff00123el); + } + + @Compare public long xor2() { + return xor(0x00fa37d7763e0ca1l, 0x00000000ff00123el); + } + + @Compare public long xor3() { + return xor(0x00000000763e0ca1l, 0x00000000ff00123el); + } + + @Compare public int compareSameNumbers() { + return compare(0x0000000000000000l, 0x0000000000000000l, 0); + } + + @Compare public int comparePositiveNumbers() { + return compare(0x0000000000200000l, 0x0000000010000000l, 0); + } + + @Compare public int compareNegativeNumbers() { + return compare(0xffffffffffffffffl, 0xffffffff00000000l, 0); + } + + @Compare public int compareMixedNumbers() { + return compare(0x8000000000000000l, 0x7fffffffffffffffl, 0); + } + + @Factory + public static Object[] create() { + return VMTest.create(LongArithmeticTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/NotifyWaitTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/NotifyWaitTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,62 @@ +/** + * 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.tck; + +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class NotifyWaitTest { + + @Compare public synchronized String canCallNotify() throws Exception { + notify(); + return "OK"; + } + + @Compare public synchronized String canCallNotifyAll() throws Exception { + notifyAll(); + return "OK"; + } + + @BrwsrTest public synchronized String throwsInterruptedException() { + try { + wait(); + throw new IllegalStateException(); + } catch (InterruptedException ex) { + return "OK"; + } + } + + @BrwsrTest public synchronized String waitMsThrowsInterruptedException() { + try { + wait(32); + throw new IllegalStateException(); + } catch (InterruptedException ex) { + return "OK"; + } + } + + @Factory public static Object[] create() { + return VMTest.create(NotifyWaitTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionArrayTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionArrayTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,161 @@ +/** + * 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.tck; + +import java.lang.reflect.Array; +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class ReflectionArrayTest { + @Compare public int lengthOfStringArray() { + String[] arr = (String[]) Array.newInstance(String.class, 10); + return arr.length; + } + + @Compare public int reflectiveLengthOfStringArray() { + Object arr = Array.newInstance(String.class, 10); + return Array.getLength(arr); + } + + @Compare public int reflectiveLengthOneNonArray() { + Object arr = "non-array"; + return Array.getLength(arr); + } + + @Compare public String compTypeOfStringArray() { + String[] arr = (String[]) Array.newInstance(String.class, 10); + return arr.getClass().getComponentType().getName(); + } + + @Compare public Object negativeArrayExcp() { + return Array.newInstance(String.class, -5); + } + + @Compare public int lengthOfIntArray() { + int[] arr = (int[]) Array.newInstance(Integer.TYPE, 10); + return arr.length; + } + + @Compare public int reflectiveLengthOfIntArray() { + Object arr = Array.newInstance(Integer.TYPE, 10); + return Array.getLength(arr); + } + + @Compare public String compTypeOfIntArray() { + int[] arr = (int[]) Array.newInstance(int.class, 10); + return arr.getClass().getComponentType().getName(); + } + + @Compare public Object intNegativeArrayExcp() { + return Array.newInstance(int.class, -5); + } + + @Compare public Integer verifyAutobox() { + int[] arr = (int[]) Array.newInstance(int.class, 5); + return (Integer) Array.get(arr, 0); + } + @Compare public String verifyObjectArray() { + String[] arr = (String[]) Array.newInstance(String.class, 5); + Array.set(arr, 0, "Hello"); + return (String) Array.get(arr, 0); + } + @Compare public int verifyInt() { + int[] arr = (int[]) Array.newInstance(int.class, 5); + return Array.getInt(arr, 0); + } + @Compare public long verifyConvertToLong() { + int[] arr = (int[]) Array.newInstance(int.class, 5); + return Array.getLong(arr, 0); + } + + @Compare public Object verifySetIntToObject() { + try { + Object[] arr = (Object[]) Array.newInstance(Object.class, 5); + Array.setInt(arr, 0, 10); + return Array.get(arr, 0); + } catch (Exception exception) { + return exception.getClass().getName(); + } + } + @Compare public long verifySetShort() { + int[] arr = (int[]) Array.newInstance(int.class, 5); + Array.setShort(arr, 0, (short)10); + return Array.getLong(arr, 0); + } + @Compare public long verifyCantSetLong() { + int[] arr = (int[]) Array.newInstance(int.class, 5); + Array.setLong(arr, 0, 10); + return Array.getLong(arr, 0); + } + @Compare public float verifyLongToFloat() { + Object arr = Array.newInstance(float.class, 5); + Array.setLong(arr, 0, 10); + return Array.getFloat(arr, 0); + } + + @Compare public double verifyConvertToDouble() { + int[] arr = (int[]) Array.newInstance(int.class, 5); + return Array.getDouble(arr, 0); + } + + @Compare public int multiIntArray() { + int[][][] arr = (int[][][]) Array.newInstance(int.class, 3, 3, 3); + return arr[0][1][2] + 5 + arr[2][2][0]; + } + + @Compare public String multiIntArrayCompType() { + return Array.newInstance(int.class, 3, 3, 3).getClass().getName(); + } + + @JavaScriptBody(args = {}, body = "return [1, 2];") + private static native Object crtarr(); + + @JavaScriptBody(args = {}, body = "return new Object();") + private static native Object newobj(); + + @BrwsrTest + public static void toStringArray() { + final Object arr = crtarr(); + final Object real = new Object[2]; + assert arr instanceof Object[] : "Any array is Java array: " + arr; + assert arr.getClass() == real.getClass() : "Same classes " + arr + " and " + real.getClass(); + final String str = arr.toString(); + assert str != null; + assert str.startsWith("[Ljava.lang.Object;@") : str; + } + + @BrwsrTest + public static void objectToString() { + String s = newobj().toString(); + assert s != null : "Some string computed"; + assert s.startsWith("java.lang.Object@") : "Regular object toString(): " + s; + } + + + @Factory + public static Object[] create() { + return VMTest.create(ReflectionArrayTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,272 @@ +/** + * 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.tck; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class ReflectionTest { + @Compare public boolean nonNullThis() { + return this == null; + } + + @Compare public String intType() { + return Integer.TYPE.toString(); + } + + @Compare public String voidType() throws Exception { + return void.class.toString(); + } + + @Compare public String longClass() { + return long.class.toString(); + } + + @Compare public boolean isRunnableInterface() { + return Runnable.class.isInterface(); + } + + @Compare public boolean isAssignableToPrimitiveType() { + return boolean.class.isAssignableFrom(Runnable.class); + } + + @Compare public boolean isAssignableFromPrimitiveType() { + return Runnable.class.isAssignableFrom(boolean.class); + } + + @Compare public boolean isAssignableLongFromInt() { + return long.class.isAssignableFrom(int.class); + } + + @Compare public boolean isAssignableIntFromLong() { + return int.class.isAssignableFrom(long.class); + } + + @Compare public String isRunnableHasRunMethod() throws NoSuchMethodException { + return Runnable.class.getMethod("run").getName(); + } + + @Compare public String namesOfMethods() { + StringBuilder sb = new StringBuilder(); + String[] arr = new String[20]; + int i = 0; + for (Method m : StaticUse.class.getMethods()) { + arr[i++] = m.getName(); + } + for (String s : sort(arr, i)) { + sb.append(s).append("\n"); + } + return sb.toString(); + } + + @Compare public String namesOfDeclaringClassesOfMethods() { + StringBuilder sb = new StringBuilder(); + String[] arr = new String[20]; + int i = 0; + for (Method m : StaticUse.class.getMethods()) { + arr[i++] = m.getName() + "@" + m.getDeclaringClass().getName(); + } + for (String s : sort(arr, i)) { + sb.append(s).append("\n"); + } + return sb.toString(); + } + + @Compare public String cannotCallNonStaticMethodWithNull() throws Exception { + StaticUse.class.getMethod("instanceMethod").invoke(null); + return "should not happen"; + } + + @Compare public String classCastException() { + try { + Integer i = (Integer)StaticUseSub.getNonNull(); + return "" + i.intValue(); + } catch (ClassCastException ex) { + return ex.getClass().getName(); + } + } + + @Compare public String methodThatThrowsException() throws Exception { + StaticUse.class.getMethod("instanceMethod").invoke(new StaticUse()); + return "should not happen"; + } + + @Compare public Object voidReturnType() throws Exception { + return StaticUse.class.getMethod("instanceMethod").getReturnType(); + } + + @Retention(RetentionPolicy.RUNTIME) + @interface Ann { + } + + @Compare public String annoClass() throws Exception { + Retention r = Ann.class.getAnnotation(Retention.class); + assert r != null : "Annotation is present"; + assert r.value() == RetentionPolicy.RUNTIME : "Policy value is OK: " + r.value(); + return r.annotationType().getName(); + } + + @Compare public boolean isAnnotation() { + return Ann.class.isAnnotation(); + } + @Compare public boolean isNotAnnotation() { + return String.class.isAnnotation(); + } + @Compare public boolean isNotAnnotationEnum() { + return E.class.isAnnotation(); + } + enum E { A, B }; + @Compare public boolean isEnum() { + return E.A.getClass().isEnum(); + } + + @Compare public boolean isNotEnum() { + return "".getClass().isEnum(); + } + + @Compare public String newInstanceFails() throws InstantiationException { + try { + return "success: " + StaticUseSub.class.newInstance(); + } catch (IllegalAccessException ex) { + return ex.getClass().getName(); + } + } + + @Compare public String paramTypes() throws Exception { + Method plus = StaticUse.class.getMethod("plus", int.class, Integer.TYPE); + final Class[] pt = plus.getParameterTypes(); + return pt[0].getName(); + } + @Compare public String paramTypesNotFound() throws Exception { + return StaticUse.class.getMethod("plus", int.class, double.class).toString(); + } + @Compare public int methodWithArgs() throws Exception { + Method plus = StaticUse.class.getMethod("plus", int.class, Integer.TYPE); + return (Integer)plus.invoke(null, 2, 3); + } + + @Compare public String classGetNameForByte() { + return byte.class.getName(); + } + @Compare public String classGetNameForBaseObject() { + return newObject().getClass().getName(); + } + @Compare public String classGetNameForJavaObject() { + return new Object().getClass().getName(); + } + @Compare public String classGetNameForObjectArray() { + return (new Object[3]).getClass().getName(); + } + @Compare public String classGetNameForSimpleIntArray() { + return (new int[3]).getClass().getName(); + } + @Compare public boolean sameClassGetNameForSimpleCharArray() { + return (new char[3]).getClass() == (new char[34]).getClass(); + } + @Compare public String classGetNameForMultiIntArray() { + return (new int[3][4][5][6][7][8][9]).getClass().getName(); + } + @Compare public String classGetNameForMultiIntArrayInner() { + final int[][][][][][][] arr = new int[3][4][5][6][7][8][9]; + int[][][][][][] subarr = arr[0]; + int[][][][][] subsubarr = subarr[0]; + return subsubarr.getClass().getName(); + } + @Compare public String classGetNameForMultiStringArray() { + return (new String[3][4][5][6][7][8][9]).getClass().getName(); + } + + @Compare public String classForByte() throws Exception { + return Class.forName("[Z").getName(); + } + + @Compare public String classForUnknownArray() { + try { + return Class.forName("[W").getName(); + } catch (Exception ex) { + return ex.getClass().getName(); + } + } + + @Compare public String classForUnknownDeepArray() { + try { + return Class.forName("[[[[[W").getName(); + } catch (Exception ex) { + return ex.getClass().getName(); + } + } + + @Compare public String componentGetNameForObjectArray() { + return (new Object[3]).getClass().getComponentType().getName(); + } + @Compare public boolean sameComponentGetNameForObjectArray() { + return (new Object[3]).getClass().getComponentType() == Object.class; + } + @Compare public String componentGetNameForSimpleIntArray() { + return (new int[3]).getClass().getComponentType().getName(); + } + @Compare public String componentGetNameForMultiIntArray() { + return (new int[3][4][5][6][7][8][9]).getClass().getComponentType().getName(); + } + @Compare public String componentGetNameForMultiStringArray() { + Class c = (new String[3][4][5][6][7][8][9]).getClass(); + StringBuilder sb = new StringBuilder(); + for (;;) { + sb.append(c.getName()).append("\n"); + c = c.getComponentType(); + if (c == null) { + break; + } + } + return sb.toString(); + } + + @Compare public boolean isArray() { + return new Object[0].getClass().isArray(); + } + + @JavaScriptBody(args = { "arr", "len" }, body="var a = arr.slice(0, len); a.sort(); return a;") + private static String[] sort(String[] arr, int len) { + List list = Arrays.asList(arr).subList(0, len); + Collections.sort(list); + return list.toArray(new String[0]); + } + + @JavaScriptBody(args = {}, body = "return new Object();") + private static Object newObject() { + return new Object(); + } + + @Factory + public static Object[] create() { + return VMTest.create(ReflectionTest.class); + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/RegExpSplitTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/RegExpSplitTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,50 @@ +/** + * 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.tck; + +import java.util.Arrays; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class RegExpSplitTest { + + public @Compare Object splitSpace() { + return Arrays.asList("How are you today?".split(" ")); + } + + public @Compare Object splitSpaceTrimMinusOne() { + return Arrays.asList(" How are you today? ".split(" ", -1)); + } + + public @Compare Object splitSpaceTrimZero() { + return Arrays.asList(" How are you today? ".split(" ", 0)); + } + + public @Compare Object splitSpaceLimit2() { + return Arrays.asList("How are you today?".split(" ", 2)); + } + + @Factory public static Object[] create() { + return VMTest.create(RegExpSplitTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ResourcesTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ResourcesTest.java Mon Oct 07 14:20:58 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.tck; + +import java.io.InputStream; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class ResourcesTest { + + @Compare public String readResourceAsStream() throws Exception { + InputStream is = getClass().getResourceAsStream("Resources.txt"); + byte[] b = new byte[30]; + int len = is.read(b); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i++) { + sb.append((char)b[i]); + } + return sb.toString(); + } + + @Factory public static Object[] create() { + return VMTest.create(ResourcesTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ShortArithmeticTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ShortArithmeticTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,102 @@ +/** + * 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.tck; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class ShortArithmeticTest { + + private static short add(short x, short y) { + return (short)(x + y); + } + + private static short sub(short x, short y) { + return (short)(x - y); + } + + private static short mul(short x, short y) { + return (short)(x * y); + } + + private static short div(short x, short y) { + return (short)(x / y); + } + + private static short mod(short x, short y) { + return (short)(x % y); + } + + @Compare public short conversion() { + return (short)123456; + } + + @Compare public short addOverflow() { + return add(Short.MAX_VALUE, (short)1); + } + + @Compare public short subUnderflow() { + return sub(Short.MIN_VALUE, (short)1); + } + + @Compare public short addMaxShortAndMaxShort() { + return add(Short.MAX_VALUE, Short.MAX_VALUE); + } + + @Compare public short subMinShortAndMinShort() { + return sub(Short.MIN_VALUE, Short.MIN_VALUE); + } + + @Compare public short multiplyMaxShort() { + return mul(Short.MAX_VALUE, (short)2); + } + + @Compare public short multiplyMaxShortAndMaxShort() { + return mul(Short.MAX_VALUE, Short.MAX_VALUE); + } + + @Compare public short multiplyMinShort() { + return mul(Short.MIN_VALUE, (short)2); + } + + @Compare public short multiplyMinShortAndMinShort() { + return mul(Short.MIN_VALUE, Short.MIN_VALUE); + } + + @Compare public short multiplyPrecision() { + return mul((short)17638, (short)1103); + } + + @Compare public short division() { + return div((short)1, (short)2); + } + + @Compare public short divisionReminder() { + return mod((short)1, (short)2); + } + + @Factory + public static Object[] create() { + return VMTest.create(ShortArithmeticTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/StaticUse.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/StaticUse.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,39 @@ +/** + * 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.tck; + +class StaticUse { + public static final Object NON_NULL = new Object(); + public static int cnt; + static { + if (cnt++ != 0) { + throw new IllegalStateException("Multiple initialization of a "); + } + } + + StaticUse() { + } + + public void instanceMethod() { + throw new IllegalStateException(); + } + + public static int plus(int a, int b) { + return a + b; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/StaticUseSub.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/StaticUseSub.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,30 @@ +/** + * 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.tck; + +public class StaticUseSub extends StaticUse { + private StaticUseSub() { + } + + public static Object getNonNull() { + return NON_NULL; + } + static Object getNull() { + return null; + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/StaticUseSubTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/StaticUseSubTest.java Mon Oct 07 14:20:58 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.tck; + +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class StaticUseSubTest { + @Compare public String staticFieldInitializationInSuperClass() throws Exception { + Object ret = StaticUseSub.getNonNull(); + return ret.getClass().getName(); + } + + @Compare public String isNullPointerTheSame() throws Exception { + try { + return StaticUseSub.getNull().getClass().toString(); + } catch (NullPointerException ex) { + return ex.getClass().getName(); + } + } + + @Factory public static Object[] create() { + return VMTest.create(StaticUseSubTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/CRC32Test.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/CRC32Test.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,41 @@ +/** + * 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.vmtest.impl; + +import java.io.UnsupportedEncodingException; +import java.util.zip.CRC32; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class CRC32Test { + + @Compare public long crc1() throws UnsupportedEncodingException { + CRC32 crc = new CRC32(); + crc.update("Hello World!".getBytes("UTF-8")); + return crc.getValue(); + } + + @Factory public static Object[] create() { + return VMTest.create(CRC32Test.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/CallMeTwiceTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/CallMeTwiceTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,43 @@ +/** + * 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.vmtest.impl; + +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class CallMeTwiceTest { + int cnt; + + @BrwsrTest public void callMeTwice() throws InterruptedException { + if (cnt++ == 0) { + throw new InterruptedException(); + } + int prevCnt = cnt; + cnt = 0; + assert prevCnt == 2 : "We need to receive two calls " + prevCnt; + } + + @Factory public static Object[] create() { + return VMTest.create(CallMeTwiceTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/HtmlAnnotations.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/HtmlAnnotations.java Mon Oct 07 14:20:58 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.bck2brwsr.vmtest.impl; + +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.bck2brwsr.vmtest.impl.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.bck2brwsr.vmtest.impl.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.bck2brwsr.vmtest.impl.HtmlAnnotations::onError(Ljava/lang/Object;)(d);" + + "return impl.@org.apidesign.bck2brwsr.vmtest.impl.HtmlAnnotations::getError()();" + ) + public static native Double onError(Object impl, Double d); +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/HtmlAnnotationsTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/HtmlAnnotationsTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,71 @@ +/** + * 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.vmtest.impl; + +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** Verify cooperation with net.java.html.js annotations. + * + * @author Jaroslav Tulach + */ +public class HtmlAnnotationsTest { + @BrwsrTest public void fourtyTwo() throws Exception { + assertEquals(HtmlAnnotations.fourtyTwo(), 42); + } + + @BrwsrTest public void externalMul() throws Exception { + assertEquals(HtmlAnnotations.useExternalMul(7, 6), 42); + } + + @BrwsrTest public void callRunnableFromJS() throws Exception { + assertEquals(HtmlAnnotations.callback(), 1); + } + + @BrwsrTest public void callStaticMethodFromJS() throws Exception { + assertEquals(HtmlAnnotations.staticCallback(), 1); + } + + @BrwsrTest public void callbackWithFourParamsAndReturnType() throws Exception { + Object instance = HtmlAnnotations.create(); + assertNotNull(instance, "Instance created"); + assertEquals(HtmlAnnotations.first(instance, 42, 31), 42); + } + + @BrwsrTest public void callbackWithObjectParamsAndReturnType() throws Exception { + Object instance = HtmlAnnotations.create(); + assertNotNull(instance, "Instance created"); + assertEquals(HtmlAnnotations.onError(instance, 42.0), 42.0); + } + + private static void assertEquals(double real, double exp) { + if (real - exp < 0.01) { + return; + } + assert false : "Expecting " + exp + " but was " + real; + } + + private static void assertNotNull(Object obj, String msg) { + assert obj != null : msg; + } + + @Factory public static Object[] create() { + return VMTest.create(HtmlAnnotationsTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/ZipEntryTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/ZipEntryTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,67 @@ +/** + * 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.vmtest.impl; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import org.apidesign.bck2brwsr.emul.zip.FastJar; +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +/** + * + * @author Jaroslav Tulach + */ +@GenerateZip(name = "five.zip", contents = { + "1.txt", "one", + "2.txt", "duo", + "3.txt", "three", + "4.txt", "four", + "5.txt", "five" +}) +public class ZipEntryTest { + @Test + public void readEntriesEffectively() throws IOException { + InputStream is = ZipEntryTest.class.getResourceAsStream("five.zip"); + byte[] arr = new byte[is.available()]; + int len = is.read(arr); + assertEquals(len, arr.length, "Read fully"); + + FastJar fj = new FastJar(arr); + FastJar.Entry[] entrs = fj.list(); + + assertEquals(5, entrs.length, "Five entries"); + + for (int i = 1; i <= 5; i++) { + FastJar.Entry en = entrs[i - 1]; + assertEquals(en.name, i + ".txt"); +// assertEquals(cis.cnt, 0, "Content of the file should be skipped, not read"); + } + + assertContent("three", fj.getInputStream(entrs[3 - 1]), "read OK"); + assertContent("five", fj.getInputStream(entrs[5 - 1]), "read OK"); + } + + private static void assertContent(String exp, InputStream is, String msg) throws IOException { + byte[] arr = new byte[512]; + int len = is.read(arr); + String s = new String(arr, 0, len); + assertEquals(exp, s, msg); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/ZipFileTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/ZipFileTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,108 @@ +/** + * 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.vmtest.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.Http; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +@GenerateZip(name = "readAnEntry.zip", contents = { + "my/main/file.txt", "Hello World!" +}) +public class ZipFileTest { + + @Compare public String readAnEntry() throws IOException { + InputStream is = ZipFileTest.class.getResourceAsStream("readAnEntry.zip"); + ZipInputStream zip = new ZipInputStream(is); + ZipEntry entry = zip.getNextEntry(); + assertEquals(entry.getName(), "my/main/file.txt", "Correct entry"); + + byte[] arr = new byte[4096]; + int len = zip.read(arr); + + assertEquals(zip.getNextEntry(), null, "No next entry"); + + final String ret = new String(arr, 0, len, "UTF-8"); + return ret; + } + + @JavaScriptBody(args = { "res", "path" }, body = + "var myvm = bck2brwsr.apply(null, path);\n" + + "var cls = myvm.loadClass('java.lang.String');\n" + + "return cls.getClass__Ljava_lang_Class_2().getResourceAsStream__Ljava_io_InputStream_2Ljava_lang_String_2(res);\n" + ) + private static native Object loadVMResource(String res, String...path); + + @Http({ + @Http.Resource(path = "/readAnEntry.jar", mimeType = "x-application/zip", content = "", resource="readAnEntry.zip") + }) + @BrwsrTest public void canVmLoadResourceFromZip() throws IOException { + Object res = loadVMResource("/my/main/file.txt", "/readAnEntry.jar"); + assert res instanceof InputStream : "Got array of bytes: " + res; + InputStream is = (InputStream)res; + + byte[] arr = new byte[4096]; + int len = is.read(arr); + + final String ret = new String(arr, 0, len, "UTF-8"); + + assertEquals(ret, "Hello World!", "Can read the bytes"); + } + + @GenerateZip(name = "cpattr.zip", contents = { + "META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" + + "Created-By: hand\n" + + "Class-Path: realJar.jar\n\n\n" + }) + @Http({ + @Http.Resource(path = "/readComplexEntry.jar", mimeType = "x-application/zip", content = "", resource="cpattr.zip"), + @Http.Resource(path = "/realJar.jar", mimeType = "x-application/zip", content = "", resource="readAnEntry.zip"), + }) + @BrwsrTest public void understandsClassPathAttr() throws IOException { + Object res = loadVMResource("/my/main/file.txt", "/readComplexEntry.jar"); + assert res instanceof InputStream : "Got array of bytes: " + res; + InputStream is = (InputStream)res; + + byte[] arr = new byte[4096]; + int len = is.read(arr); + + final String ret = new String(arr, 0, len, "UTF-8"); + + assertEquals(ret, "Hello World!", "Can read the bytes from secondary JAR"); + } + + private static void assertEquals(Object real, Object exp, String msg) { + assert Objects.equals(exp, real) : msg + " exp: " + exp + " real: " + real; + } + + @Factory public static Object[] create() { + return VMTest.create(ZipFileTest.class); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/compact/tck/demo.static.calculator-0.3-SNAPSHOT.jar Binary file rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/compact/tck/demo.static.calculator-0.3-SNAPSHOT.jar has changed diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/compact/tck/demo.static.calculator-TEST.jar Binary file rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/compact/tck/demo.static.calculator-TEST.jar has changed diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/tck/0xfe --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/tck/0xfe Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,1 @@ +þ \ No newline at end of file diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/tck/Resources.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/tck/Resources.txt Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,1 @@ +Ahoj diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/vmtest/impl/htmlannotations.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/vmtest/impl/htmlannotations.js Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,20 @@ +/* + * 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; } +window.mul = mul; diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/pom.xml --- a/rt/emul/mini/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -1,15 +1,14 @@ - + 4.0.0 org.apidesign.bck2brwsr emul.pom - 0.3-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr emul.mini - 0.3-SNAPSHOT + 0.9-SNAPSHOT Minimal API Profile http://maven.apache.org @@ -19,7 +18,7 @@ org.apidesign.bck2brwsr core - 0.3-SNAPSHOT + 0.9-SNAPSHOT jar @@ -43,6 +42,28 @@ 1.7 + + org.apache.maven.plugins + maven-javadoc-plugin + + org.apidesign.bck2brwsr.emul.* + false + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + prepare-sources + + jar + + package + + + diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/java/lang/AbstractStringBuilder.java --- a/rt/emul/mini/src/main/java/java/lang/AbstractStringBuilder.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/AbstractStringBuilder.java Mon Oct 07 14:20:58 2013 +0200 @@ -126,7 +126,7 @@ throw new OutOfMemoryError(); newCapacity = Integer.MAX_VALUE; } - value = copyOf(value, newCapacity); + value = System.expandArray(value, newCapacity); } /** diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/java/lang/ArithmeticException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/mini/src/main/java/java/lang/ArithmeticException.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,61 @@ +/* + * Copyright (c) 1994, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang; + +/** + * Thrown when an exceptional arithmetic condition has occurred. For + * example, an integer "divide by zero" throws an + * instance of this class. + * + * {@code ArithmeticException} objects may be constructed by the + * virtual machine as if {@linkplain Throwable#Throwable(String, + * Throwable, boolean, boolean) suppression were disabled and/or the + * stack trace was not writable}. + * + * @author unascribed + * @since JDK1.0 + */ +public class ArithmeticException extends RuntimeException { + private static final long serialVersionUID = 2256477558314496007L; + + /** + * Constructs an {@code ArithmeticException} with no detail + * message. + */ + public ArithmeticException() { + super(); + } + + /** + * Constructs an {@code ArithmeticException} with the specified + * detail message. + * + * @param s the detail message. + */ + public ArithmeticException(String s) { + super(s); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/java/lang/Boolean.java --- a/rt/emul/mini/src/main/java/java/lang/Boolean.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/Boolean.java Mon Oct 07 14:20:58 2013 +0200 @@ -25,6 +25,9 @@ package java.lang; +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.core.JavaScriptPrototype; + /** * The Boolean class wraps a value of the primitive type * {@code boolean} in an object. An object of type @@ -40,6 +43,7 @@ * @author Arthur van Hoff * @since JDK1.0 */ +@JavaScriptPrototype(container = "Boolean.prototype", prototype = "new Boolean") public final class Boolean implements java.io.Serializable, Comparable { @@ -127,6 +131,7 @@ * * @return the primitive {@code boolean} value of this object. */ + @JavaScriptBody(args = {}, body = "return this.valueOf();") public boolean booleanValue() { return value; } @@ -279,4 +284,13 @@ private static boolean toBoolean(String name) { return ((name != null) && name.equalsIgnoreCase("true")); } + static { + // as last step of initialization, initialize valueOf method + initValueOf(); + } + @JavaScriptBody(args = { }, body = + "vm.java_lang_Boolean(false)" + + ".valueOf = function() { return this._value() ? true : false; };" + ) + private native static void initValueOf(); } diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/java/lang/Character.java --- a/rt/emul/mini/src/main/java/java/lang/Character.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/Character.java Mon Oct 07 14:20:58 2013 +0200 @@ -2298,6 +2298,10 @@ */ @Deprecated public static boolean isSpace(char ch) { + return isSpaceChar(ch); + } + + public static boolean isSpaceChar(int ch) { return (ch <= 0x0020) && (((((1L << 0x0009) | (1L << 0x000A) | @@ -2307,7 +2311,6 @@ } - /** * Determines if the specified character is white space according to Java. * A character is a Java whitespace character if and only if it satisfies @@ -2372,7 +2375,14 @@ * @since 1.5 */ public static boolean isWhitespace(int codePoint) { - throw new UnsupportedOperationException(); + if ( + codePoint == SPACE_SEPARATOR || + codePoint == LINE_SEPARATOR || + codePoint == PARAGRAPH_SEPARATOR + ) { + return true; + } + return false; } /** @@ -2516,4 +2526,14 @@ return (char) (((ch & 0xFF00) >> 8) | (ch << 8)); } + static { + // as last step of initialization, initialize valueOf method + initValueOf(); + } + @JavaScriptBody(args = {}, body = + "vm.java_lang_Character(false)." + + "valueOf = function() { return this._value(); };" + ) + private native static void initValueOf(); + } diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/java/lang/Class.java --- a/rt/emul/mini/src/main/java/java/lang/Class.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/Class.java Mon Oct 07 14:20:58 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; } @@ -402,8 +406,15 @@ } return cmpType != null && getComponentType().isAssignableFrom(cmpType); } - String prop = "$instOf_" + getName().replace('.', '_'); - return hasCnstrProperty(cls, prop); + if (isPrimitive()) { + return false; + } else { + if (cls.isPrimitive()) { + return false; + } + String prop = "$instOf_" + getName().replace('.', '_'); + return hasCnstrProperty(cls, prop); + } } @JavaScriptBody(args = { "who", "prop" }, body = @@ -1245,6 +1256,7 @@ } @JavaScriptBody(args = { "sig" }, body = + "if (!sig) sig = '[Ljava/lang/Object;';\n" + "var c = Array[sig];\n" + "if (c) return c;\n" + "c = vm.java_lang_Class(true);\n" + diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/java/lang/Double.java --- a/rt/emul/mini/src/main/java/java/lang/Double.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/Double.java Mon Oct 07 14:20:58 2013 +0200 @@ -190,12 +190,12 @@ * @param d the {@code double} to be converted. * @return a string representation of the argument. */ - @JavaScriptBody(args="d", body="var r = d.toString();" - + "if (isFinite(d) && (r.indexOf('.') === -1)) r = r + '.0';" - + "return r;") - public static String toString(double d) { - throw new UnsupportedOperationException(); - } + @JavaScriptBody(args="d", body="var f = Math.floor(d);\n" + + "var r = d.toString();" + + "if (f === d && isFinite(d) && r.indexOf('e') === -1) return r + '.0';\n" + + " else return r;" + ) + public static native String toString(double d); /** * Returns a hexadecimal string representation of the @@ -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)); } /** @@ -810,15 +808,17 @@ * @return the bits that represent the floating-point number. */ public static long doubleToLongBits(double value) { - throw new UnsupportedOperationException(); -// long result = doubleToRawLongBits(value); -// // Check for NaN based on values of bit fields, maximum -// // exponent and nonzero significand. -// if ( ((result & DoubleConsts.EXP_BIT_MASK) == -// DoubleConsts.EXP_BIT_MASK) && -// (result & DoubleConsts.SIGNIF_BIT_MASK) != 0L) -// result = 0x7ff8000000000000L; -// return result; + final long EXP_BIT_MASK = 9218868437227405312L; + final long SIGNIF_BIT_MASK = 4503599627370495L; + + long result = doubleToRawLongBits(value); + // Check for NaN based on values of bit fields, maximum + // exponent and nonzero significand. + if ( ((result & EXP_BIT_MASK) == + EXP_BIT_MASK) && + (result & SIGNIF_BIT_MASK) != 0L) + result = 0x7ff8000000000000L; + return result; } /** @@ -857,7 +857,21 @@ * @return the bits that represent the floating-point number. * @since 1.3 */ - public static native long doubleToRawLongBits(double value); + public static long doubleToRawLongBits(double value) { + int[] arr = { 0, 0 }; + doubleToRawLongBits(value, arr); + long l = arr[1]; + return (l << 32) | arr[0]; + } + + @JavaScriptBody(args = { "value", "arr" }, body = "" + + "var a = new ArrayBuffer(8);" + + "new Float64Array(a)[0] = value;" + + "var out = new Int32Array(a);" + + "arr[0] = out[0];" + + "arr[1] = out[1];" + ) + private static native void doubleToRawLongBits(double value, int[] arr); /** * Returns the {@code double} value corresponding to a given diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/java/lang/Enum.java --- a/rt/emul/mini/src/main/java/java/lang/Enum.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/Enum.java Mon Oct 07 14:20:58 2013 +0200 @@ -27,6 +27,7 @@ import java.io.Serializable; import java.io.IOException; +import org.apidesign.bck2brwsr.core.JavaScriptBody; /** * This is the common base class of all Java language enumeration types. @@ -225,15 +226,17 @@ */ public static > T valueOf(Class enumType, String name) { - throw new UnsupportedOperationException(); -// T result = enumType.enumConstantDirectory().get(name); -// if (result != null) -// return result; -// if (name == null) -// throw new NullPointerException("Name is null"); -// throw new IllegalArgumentException( -// "No enum constant " + enumType.getCanonicalName() + "." + name); + for (Object o : values(enumType)) { + T t = enumType.cast(o); + if (name.equals(((Enum)t).name)) { + return t; + } + } + throw new IllegalArgumentException(); } + + @JavaScriptBody(args = { "enumType" }, body = "return enumType.cnstr.$VALUES;") + private static native Object[] values(Class enumType); /** * enum classes cannot have finalize methods. diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/java/lang/Float.java --- a/rt/emul/mini/src/main/java/java/lang/Float.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/Float.java Mon Oct 07 14:20:58 2013 +0200 @@ -710,15 +710,17 @@ * @return the bits that represent the floating-point number. */ public static int floatToIntBits(float value) { - throw new UnsupportedOperationException(); -// int result = floatToRawIntBits(value); -// // Check for NaN based on values of bit fields, maximum -// // exponent and nonzero significand. -// if ( ((result & FloatConsts.EXP_BIT_MASK) == -// FloatConsts.EXP_BIT_MASK) && -// (result & FloatConsts.SIGNIF_BIT_MASK) != 0) -// result = 0x7fc00000; -// return result; + final int EXP_BIT_MASK = 2139095040; + final int SIGNIF_BIT_MASK = 8388607; + + int result = floatToRawIntBits(value); + // Check for NaN based on values of bit fields, maximum + // exponent and nonzero significand. + if ( ((result & EXP_BIT_MASK) == + EXP_BIT_MASK) && + (result & SIGNIF_BIT_MASK) != 0) + result = 0x7fc00000; + return result; } /** @@ -756,6 +758,11 @@ * @return the bits that represent the floating-point number. * @since 1.3 */ + @JavaScriptBody(args = { "value" }, body = "" + + "var a = new ArrayBuffer(4);" + + "new Float32Array(a)[0] = value;" + + "return new Int32Array(a)[0];" + ) public static native int floatToRawIntBits(float value); /** diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/java/lang/Number.java --- a/rt/emul/mini/src/main/java/java/lang/Number.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/Number.java Mon Oct 07 14:20:58 2013 +0200 @@ -27,7 +27,6 @@ import org.apidesign.bck2brwsr.core.ExtraJavaScript; import org.apidesign.bck2brwsr.core.JavaScriptBody; -import org.apidesign.bck2brwsr.core.JavaScriptOnly; import org.apidesign.bck2brwsr.core.JavaScriptPrototype; /** diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/java/lang/Object.java --- a/rt/emul/mini/src/main/java/java/lang/Object.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/Object.java Mon Oct 07 14:20:58 2013 +0200 @@ -42,14 +42,21 @@ public class Object { private static void registerNatives() { - try { + boolean assertsOn = false; + assert assertsOn = false; + if (assertsOn) try { Array.get(null, 0); } catch (Throwable ex) { // ignore } } + @JavaScriptBody(args = {}, body = "var p = vm.java_lang_Object(false);" + + "p.toString = function() { return this.toString__Ljava_lang_String_2(); };" + ) + private static native void registerToString(); static { registerNatives(); + registerToString(); } /** @@ -72,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 @@ -110,12 +125,15 @@ * @see java.lang.Object#equals(java.lang.Object) * @see java.lang.System#identityHashCode */ + public int hashCode() { + return defaultHashCode(); + } @JavaScriptBody(args = {}, body = "if (this.$hashCode) return this.$hashCode;\n" + "var h = this.computeHashCode__I();\n" + "return this.$hashCode = h & h;" ) - public native int hashCode(); + final native int defaultHashCode(); @JavaScriptBody(args = {}, body = "return Math.random() * Math.pow(2, 32);") native int computeHashCode(); @@ -310,7 +328,8 @@ * @see java.lang.Object#notifyAll() * @see java.lang.Object#wait() */ - public final native void notify(); + public final void notify() { + } /** * Wakes up all threads that are waiting on this object's monitor. A @@ -334,7 +353,8 @@ * @see java.lang.Object#notify() * @see java.lang.Object#wait() */ - public final native void notifyAll(); + public final void notifyAll() { + } /** * Causes the current thread to wait until either another thread invokes the @@ -421,7 +441,9 @@ * @see java.lang.Object#notify() * @see java.lang.Object#notifyAll() */ - public final native void wait(long timeout) throws InterruptedException; + public final void wait(long timeout) throws InterruptedException { + throw new InterruptedException(); + } /** * Causes the current thread to wait until another thread invokes the @@ -486,20 +508,7 @@ * this exception is thrown. */ public final void wait(long timeout, int nanos) throws InterruptedException { - if (timeout < 0) { - throw new IllegalArgumentException("timeout value is negative"); - } - - if (nanos < 0 || nanos > 999999) { - throw new IllegalArgumentException( - "nanosecond timeout value out of range"); - } - - if (nanos >= 500000 || (nanos != 0 && timeout == 0)) { - timeout++; - } - - wait(timeout); + throw new InterruptedException(); } /** @@ -541,7 +550,7 @@ * @see java.lang.Object#notifyAll() */ public final void wait() throws InterruptedException { - wait(0); + throw new InterruptedException(); } /** diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/java/lang/String.java --- a/rt/emul/mini/src/main/java/java/lang/String.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/String.java Mon Oct 07 14:20:58 2013 +0200 @@ -115,15 +115,17 @@ /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; + + static { + registerToString(); + } + @JavaScriptBody(args = {}, body = + "var p = vm.java_lang_String(false);\n" + + "p.toString = function() {\nreturn this._r().toString();\n};\n" + + "p.valueOf = function() {\nreturn this._r().valueOf();\n}\n" + ) + private static native void registerToString(); - @JavaScriptOnly(name="toString", value="String.prototype._r") - private static void jsToString() { - } - - @JavaScriptOnly(name="valueOf", value="function() { return this.toString().valueOf(); }") - private static void jsValudOf() { - } - /** * Class String is special cased within the Serialization Stream Protocol. * @@ -2218,9 +2220,19 @@ * replacement is null. * @since 1.5 */ - public String replace(CharSequence target, CharSequence replacement) { - throw new UnsupportedOperationException("This one should be supported, but without dep on rest of regexp"); - } + @JavaScriptBody(args = { "target", "replacement" }, body = + "var s = this.toString();\n" + + "target = target.toString();\n" + + "replacement = replacement.toString();\n" + + "for (;;) {\n" + + " var ret = s.replace(target, replacement);\n" + + " if (ret === s) {\n" + + " return ret;\n" + + " }\n" + + " s = ret;\n" + + "}" + ) + public native String replace(CharSequence target, CharSequence replacement); /** * Splits this string around matches of the given @@ -2303,8 +2315,35 @@ * @spec JSR-51 */ public String[] split(String regex, int limit) { - throw new UnsupportedOperationException("Needs regexp"); + if (limit <= 0) { + Object[] arr = splitImpl(this, regex, Integer.MAX_VALUE); + int to = arr.length; + if (limit == 0) { + while (to > 1 && ((String)arr[--to]).isEmpty()) { + } + to++; + } + String[] ret = new String[to]; + System.arraycopy(arr, 0, ret, 0, to); + return ret; + } else { + Object[] arr = splitImpl(this, regex, limit); + String[] ret = new String[arr.length]; + int pos = 0; + for (int i = 0; i < arr.length; i++) { + final String s = (String)arr[i]; + ret[i] = s; + pos = indexOf(s, pos) + s.length(); + } + ret[arr.length - 1] += substring(pos); + return ret; + } } + + @JavaScriptBody(args = { "str", "regex", "limit"}, body = + "return str.split(new RegExp(regex), limit);" + ) + private static native Object[] splitImpl(String str, String regex, int limit); /** * Splits this string around matches of the given */ -// 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. // * @@ -1085,4 +1084,22 @@ // else // return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY); } + + private static Object bck2BrwsrCnvrt(Object o) { + if (o instanceof Throwable) { + return o; + } + final String msg = msg(o); + if (msg == null || msg.startsWith("TypeError")) { + return new NullPointerException(msg); + } + return new Throwable(msg); + } + + @JavaScriptBody(args = { "o" }, body = "return o ? o.toString() : null;") + private static native String msg(Object o); + + @JavaScriptOnly(name = "bck2BrwsrCnvrt", value = "c.bck2BrwsrCnvrt__Ljava_lang_Object_2Ljava_lang_Object_2") + private static void bck2BrwsrCnvrtVM() { + } } diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/java/lang/reflect/Array.java --- a/rt/emul/mini/src/main/java/java/lang/reflect/Array.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/reflect/Array.java Mon Oct 07 14:20:58 2013 +0200 @@ -636,9 +636,9 @@ + "arr.jvmName = sig;\n" + "return arr;" ) - private static native Object newArray(boolean primitive, String sig, int length); + static native Object newArray(boolean primitive, String sig, int length); - private static Object multiNewArray(String sig, int[] dims, int index) + static Object multiNewArray(String sig, int[] dims, int index) throws IllegalArgumentException, NegativeArraySizeException { if (dims.length == index + 1) { return newArray(sig.length() == 2, sig, dims[index]); diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/java/lang/reflect/Method.java --- a/rt/emul/mini/src/main/java/java/lang/reflect/Method.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/java/java/lang/reflect/Method.java Mon Oct 07 14:20:58 2013 +0200 @@ -501,8 +501,8 @@ throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { - final boolean isStatic = (getModifiers() & Modifier.STATIC) == 0; - if (isStatic && obj == null) { + final boolean nonStatic = (getModifiers() & Modifier.STATIC) == 0; + if (nonStatic && obj == null) { throw new NullPointerException(); } Class[] types = getParameterTypes(); @@ -512,12 +512,12 @@ args = args.clone(); for (int i = 0; i < types.length; i++) { Class c = types[i]; - if (c.isPrimitive()) { - args[i] = toPrimitive(c, args[i]); + if (c.isPrimitive() && args[i] != null) { + args[i] = toPrimitive(args[i]); } } } - Object res = invoke0(isStatic, this, obj, args); + Object res = invokeTry(nonStatic, this, obj, args); if (getReturnType().isPrimitive()) { res = fromPrimitive(getReturnType(), res); } @@ -536,6 +536,15 @@ + "return method._data().apply(self, p);\n" ) private static native Object invoke0(boolean isStatic, Method m, Object self, Object[] args); + + private static Object invokeTry(boolean isStatic, Method m, Object self, Object[] args) + throws InvocationTargetException { + try { + return invoke0(isStatic, m, self, args); + } catch (Throwable ex) { + throw new InvocationTargetException(ex, ex.getMessage()); + } + } static Object fromPrimitive(Class type, Object o) { if (type == Integer.TYPE) { @@ -573,41 +582,8 @@ ) private static native Integer fromRaw(Class cls, String m, Object o); - private static Object toPrimitive(Class type, Object o) { - if (type == Integer.TYPE) { - return toRaw("intValue__I", o); - } - if (type == Long.TYPE) { - return toRaw("longValue__J", o); - } - if (type == Double.TYPE) { - return toRaw("doubleValue__D", o); - } - if (type == Float.TYPE) { - return toRaw("floatValue__F", o); - } - if (type == Byte.TYPE) { - return toRaw("byteValue__B", o); - } - if (type == Boolean.TYPE) { - return toRaw("booleanValue__Z", o); - } - if (type == Short.TYPE) { - return toRaw("shortValue__S", o); - } - if (type == Character.TYPE) { - return toRaw("charValue__C", o); - } - if (type.getName().equals("void")) { - return o; - } - throw new IllegalStateException("Can't convert " + o); - } - - @JavaScriptBody(args = { "m", "o" }, - body = "return o[m](o);" - ) - private static native Object toRaw(String m, Object o); + @JavaScriptBody(args = { "o" }, body = "return o.valueOf();") + private static native Object toPrimitive(Object o); /** * Returns {@code true} if this method is a bridge diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/lang/System.java --- a/rt/emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/lang/System.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/lang/System.java Mon Oct 07 14:20:58 2013 +0200 @@ -54,6 +54,11 @@ ) public static native byte[] expandArray(byte[] arr, int expectedSize); + @JavaScriptBody(args = { "arr", "expectedSize" }, body = + "while (expectedSize-- > arr.length) { arr.push(0); }; return arr;" + ) + public static native char[] expandArray(char[] arr, int expectedSize); + @JavaScriptBody(args = {}, body = "return new Date().getTime();") private static native double currentTimeMillisDouble(); diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/mini/src/main/resources/org/apidesign/vm4brwsr/emul/lang/java_lang_Number.js --- a/rt/emul/mini/src/main/resources/org/apidesign/vm4brwsr/emul/lang/java_lang_Number.js Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/mini/src/main/resources/org/apidesign/vm4brwsr/emul/lang/java_lang_Number.js Mon Oct 07 14:20:58 2013 +0200 @@ -1,226 +1,244 @@ // empty line needed here -Number.prototype.add32 = function(x) { return (this + x) | 0; }; -Number.prototype.sub32 = function(x) { return (this - x) | 0; }; -Number.prototype.mul32 = function(x) { - return (((this * (x >> 16)) << 16) + this * (x & 0xFFFF)) | 0; -}; -Number.prototype.neg32 = function() { return (-this) | 0; }; -Number.prototype.toInt8 = function() { return (this << 24) >> 24; }; -Number.prototype.toInt16 = function() { return (this << 16) >> 16; }; +(function(numberPrototype) { + numberPrototype.add32 = function(x) { + return (this + x) | 0; + }; + numberPrototype.sub32 = function(x) { + return (this - x) | 0; + }; + numberPrototype.mul32 = function(x) { + return (((this * (x >> 16)) << 16) + this * (x & 0xFFFF)) | 0; + }; + numberPrototype.neg32 = function() { + return (-this) | 0; + }; -var __m32 = 0xFFFFFFFF; + numberPrototype.toInt8 = function() { + return (this << 24) >> 24; + }; + numberPrototype.toInt16 = function() { + return (this << 16) >> 16; + }; -Number.prototype.next32 = function(low) { - if (this === 0) { - return low; - } - var l = new Number(low); - l.hi = this | 0; - return l; -}; + var __m32 = 0xFFFFFFFF; -Number.prototype.high32 = function() { - return this.hi ? this.hi : (Math.floor(this / (__m32+1))) | 0; -}; -Number.prototype.toInt32 = function() { return this | 0; }; -Number.prototype.toFP = function() { - return this.hi ? this.hi * (__m32+1) + this : this; -}; -Number.prototype.toLong = function() { - var hi = (this / (__m32+1)) | 0; - var low = (this % (__m32+1)) | 0; - if (low < 0) { - low += __m32+1; - } - - if (this < 0) { - hi -= 1; - } + numberPrototype.next32 = function(low) { + if (this === 0) { + return low; + } + var l = new Number(low); + l.hi = this | 0; + return l; + }; - return hi.next32(low); -}; + numberPrototype.high32 = function() { + return this.hi ? this.hi : (Math.floor(this / (__m32 + 1))) | 0; + }; + numberPrototype.toInt32 = function() { + return this | 0; + }; + numberPrototype.toFP = function() { + return this.hi ? this.hi * (__m32 + 1) + this : this; + }; + numberPrototype.toLong = function() { + var hi = (this / (__m32 + 1)) | 0; + var low = (this % (__m32 + 1)) | 0; + if (low < 0) { + low += __m32 + 1; + } -Number.prototype.toExactString = function() { - if (this.hi) { - // check for Long.MIN_VALUE - if ((this.hi == (0x80000000 | 0)) && (this == 0)) { - return '-9223372036854775808'; + if (this < 0) { + hi -= 1; } - var res = 0; - var a = [ 6,9,2,7,6,9,4,9,2,4 ]; - var s = ''; - var digit; - var neg = this.hi < 0; - if (neg) { - var x = this.neg64(); - var hi = x.hi; - var low = x; - } else { - var hi = this.hi; - var low = this; - } - for (var i = 0; i < a.length; i++) { - res += hi * a[i]; - var low_digit = low % 10; - digit = (res % 10) + low_digit; - low = Math.floor(low / 10); - res = Math.floor(res / 10); - - if (digit >= 10) { - digit -= 10; - res++; - } - s = String(digit).concat(s); - } - s = String(res).concat(s).replace(/^0+/, ''); - return (neg ? '-' : '').concat(s); - } - return String(this); -}; - -Number.prototype.add64 = function(x) { - var low = this + x; - carry = 0; - if (low > __m32) { - carry = 1; - low -= (__m32+1); - } - var hi = (this.high32() + x.high32() + carry) | 0; - return hi.next32(low); -}; - -Number.prototype.sub64 = function(x) { - var low = this - x; - carry = 0; - if (low < 0) { - carry = 1; - low += (__m32+1); - } - var hi = (this.high32() - x.high32() - carry) | 0; - return hi.next32(low); -}; - -Number.prototype.mul64 = function(x) { - var low = this.mul32(x); - low += (low < 0) ? (__m32+1) : 0; - // first count upper 32 bits of (this.low * x.low) - var hi_hi = 0; - var hi_low = 0; - var m = 1; - for (var i = 0; i < 32; i++) { - if (x & m) { - hi_hi += this >>> 16; - hi_low += this & 0xFFFF - } - hi_low >>= 1; - hi_low += (hi_hi & 1) ? 0x8000 : 0; - hi_hi >>= 1; - m <<= 1; - } - var hi = (hi_hi << 16) + hi_low; - - var m1 = this.high32().mul32(x); - var m2 = this.mul32(x.high32()); - hi = hi.add32(m1).add32(m2); - - return hi.next32(low); -}; - -Number.prototype.and64 = function(x) { - var low = this & x; - low += (low < 0) ? (__m32+1) : 0; - if (this.hi && x.hi) { - var hi = this.hi & x.hi; return hi.next32(low); }; - return low; -}; -Number.prototype.or64 = function(x) { - var low = this | x; - low += (low < 0) ? (__m32+1) : 0; - if (this.hi || x.hi) { - var hi = this.hi | x.hi; + numberPrototype.toExactString = function() { + if (this.hi) { + // check for Long.MIN_VALUE + if ((this.hi == (0x80000000 | 0)) && (this == 0)) { + return '-9223372036854775808'; + } + var res = 0; + var a = [6, 9, 2, 7, 6, 9, 4, 9, 2, 4]; + var s = ''; + var digit; + var neg = this.hi < 0; + if (neg) { + var x = this.neg64(); + var hi = x.hi; + var low = x; + } else { + var hi = this.hi; + var low = this; + } + for (var i = 0; i < a.length; i++) { + res += hi * a[i]; + var low_digit = low % 10; + digit = (res % 10) + low_digit; + + low = Math.floor(low / 10); + res = Math.floor(res / 10); + + if (digit >= 10) { + digit -= 10; + res++; + } + s = String(digit).concat(s); + } + s = String(res).concat(s).replace(/^0+/, ''); + return (neg ? '-' : '').concat(s); + } + return String(this); + }; + + numberPrototype.add64 = function(x) { + var low = this + x; + carry = 0; + if (low > __m32) { + carry = 1; + low -= (__m32 + 1); + } + var hi = (this.high32() + x.high32() + carry) | 0; return hi.next32(low); }; - return low; -}; -Number.prototype.xor64 = function(x) { - var low = this ^ x; - low += (low < 0) ? (__m32+1) : 0; - if (this.hi || x.hi) { - var hi = this.hi ^ x.hi; + numberPrototype.sub64 = function(x) { + var low = this - x; + carry = 0; + if (low < 0) { + carry = 1; + low += (__m32 + 1); + } + var hi = (this.high32() - x.high32() - carry) | 0; return hi.next32(low); }; - return low; -}; -Number.prototype.shl64 = function(x) { - if (x >= 32) { - var hi = this << (x - 32); - return hi.next32(0); - } else { - var hi = this.high32() << x; - var low_reminder = this >> (32 - x); - hi |= low_reminder; - var low = this << x; - low += (low < 0) ? (__m32+1) : 0; + numberPrototype.mul64 = function(x) { + var low = this.mul32(x); + low += (low < 0) ? (__m32 + 1) : 0; + // first count upper 32 bits of (this.low * x.low) + var hi_hi = 0; + var hi_low = 0; + var m = 1; + for (var i = 0; i < 32; i++) { + if (x & m) { + hi_hi += this >>> 16; + hi_low += this & 0xFFFF + } + hi_low >>= 1; + hi_low += (hi_hi & 1) ? 0x8000 : 0; + hi_hi >>= 1; + m <<= 1; + } + var hi = (hi_hi << 16) + hi_low; + + var m1 = this.high32().mul32(x); + var m2 = this.mul32(x.high32()); + hi = hi.add32(m1).add32(m2); + return hi.next32(low); - } -}; + }; -Number.prototype.shr64 = function(x) { - if (x >= 32) { - var low = this.high32() >> (x - 32); - low += (low < 0) ? (__m32+1) : 0; + numberPrototype.and64 = function(x) { + var low = this & x; + low += (low < 0) ? (__m32 + 1) : 0; + if (this.hi && x.hi) { + var hi = this.hi & x.hi; + return hi.next32(low); + } + ; return low; - } else { - var low = this >> x; - var hi_reminder = this.high32() << (32 - x); - low |= hi_reminder; - low += (low < 0) ? (__m32+1) : 0; - var hi = this.high32() >> x; - return hi.next32(low); - } -}; + }; -Number.prototype.ushr64 = function(x) { - if (x >= 32) { - var low = this.high32() >>> (x - 32); - low += (low < 0) ? (__m32+1) : 0; + numberPrototype.or64 = function(x) { + var low = this | x; + low += (low < 0) ? (__m32 + 1) : 0; + if (this.hi || x.hi) { + var hi = this.hi | x.hi; + return hi.next32(low); + } + ; return low; - } else { - var low = this >>> x; - var hi_reminder = this.high32() << (32 - x); - low |= hi_reminder; - low += (low < 0) ? (__m32+1) : 0; - var hi = this.high32() >>> x; - return hi.next32(low); - } -}; + }; -Number.prototype.compare64 = function(x) { - if (this.high32() === x.high32()) { - return (this < x) ? -1 : ((this > x) ? 1 : 0); - } - return (this.high32() < x.high32()) ? -1 : 1; -}; + numberPrototype.xor64 = function(x) { + var low = this ^ x; + low += (low < 0) ? (__m32 + 1) : 0; + if (this.hi || x.hi) { + var hi = this.hi ^ x.hi; + return hi.next32(low); + } + ; + return low; + }; -Number.prototype.neg64 = function() { - var hi = this.high32(); - var low = this; - if ((hi === 0) && (low < 0)) { return -low; } - hi = ~hi; - low = ~low; - low += (low < 0) ? (__m32+1) : 0; - var ret = hi.next32(low); - return ret.add64(1); -}; + numberPrototype.shl64 = function(x) { + if (x >= 32) { + var hi = this << (x - 32); + return hi.next32(0); + } else { + var hi = this.high32() << x; + var low_reminder = this >> (32 - x); + hi |= low_reminder; + var low = this << x; + low += (low < 0) ? (__m32 + 1) : 0; + return hi.next32(low); + } + }; -(function(numberPrototype) { + numberPrototype.shr64 = function(x) { + if (x >= 32) { + var low = this.high32() >> (x - 32); + low += (low < 0) ? (__m32 + 1) : 0; + return low; + } else { + var low = this >> x; + var hi_reminder = this.high32() << (32 - x); + low |= hi_reminder; + low += (low < 0) ? (__m32 + 1) : 0; + var hi = this.high32() >> x; + return hi.next32(low); + } + }; + + numberPrototype.ushr64 = function(x) { + if (x >= 32) { + var low = this.high32() >>> (x - 32); + low += (low < 0) ? (__m32 + 1) : 0; + return low; + } else { + var low = this >>> x; + var hi_reminder = this.high32() << (32 - x); + low |= hi_reminder; + low += (low < 0) ? (__m32 + 1) : 0; + var hi = this.high32() >>> x; + return hi.next32(low); + } + }; + + numberPrototype.compare64 = function(x) { + if (this.high32() === x.high32()) { + return (this < x) ? -1 : ((this > x) ? 1 : 0); + } + return (this.high32() < x.high32()) ? -1 : 1; + }; + + numberPrototype.neg64 = function() { + var hi = this.high32(); + var low = this; + if ((hi === 0) && (low < 0)) { + return -low; + } + hi = ~hi; + low = ~low; + low += (low < 0) ? (__m32 + 1) : 0; + var ret = hi.next32(low); + return ret.add64(1); + }; + function __handleDivByZero() { var exception = new vm.java_lang_ArithmeticException; vm.java_lang_ArithmeticException(false).constructor @@ -551,3 +569,5 @@ return negateResult ? result.neg64() : result; } })(Number.prototype); + +vm.java_lang_Number(false); diff -r 8264f07b1f46 -r f73c1a0234fb rt/emul/pom.xml --- a/rt/emul/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/emul/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -4,15 +4,16 @@ org.apidesign.bck2brwsr rt - 0.3-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr emul.pom - 0.3-SNAPSHOT + 0.9-SNAPSHOT pom Emulation of Core Libraries mini compact + brwsrtest diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/pom.xml --- a/rt/javap/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ - - - 4.0.0 - - org.apidesign.bck2brwsr - rt - 0.3-SNAPSHOT - - org.apidesign.bck2brwsr - javap - 0.3-SNAPSHOT - ByteCode Parser - http://maven.apache.org - - UTF-8 - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.5.1 - - - - - 1.6 - 1.6 - - - - - - - org.apidesign.bck2brwsr - emul.mini - 0.3-SNAPSHOT - - - diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/AnnotationParser.java --- a/rt/javap/src/main/java/org/apidesign/javap/AnnotationParser.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package org.apidesign.javap; - -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.IOException; - -/** An abstract parser for annotation definitions. Analyses the bytes and - * performs some callbacks to the overriden parser methods. - * - * @author Jaroslav Tulach - */ -public class AnnotationParser { - private final boolean textual; - private final boolean iterateArray; - - protected AnnotationParser(boolean textual, boolean iterateArray) { - this.textual = textual; - this.iterateArray = iterateArray; - } - - protected void visitAnnotationStart(String type, boolean top) throws IOException { - } - - protected void visitAnnotationEnd(String type, boolean top) throws IOException { - } - - protected void visitValueStart(String attrName, char type) throws IOException { - } - - protected void visitValueEnd(String attrName, char type) throws IOException { - } - - - protected void visitAttr( - String annoType, String attr, String attrType, String value - ) throws IOException { - } - - protected void visitEnumAttr( - String annoType, String attr, String attrType, String value - ) throws IOException { - visitAttr(annoType, attr, attrType, value); - } - - /** Initialize the parsing with constant pool from cd. - * - * @param attr the attribute defining annotations - * @param cd constant pool - * @throws IOException in case I/O fails - */ - public final void parse(byte[] attr, ClassData cd) throws IOException { - ByteArrayInputStream is = new ByteArrayInputStream(attr); - DataInputStream dis = new DataInputStream(is); - try { - read(dis, cd); - } finally { - is.close(); - } - } - - private void read(DataInputStream dis, ClassData cd) throws IOException { - int cnt = dis.readUnsignedShort(); - for (int i = 0; i < cnt; i++) { - readAnno(dis, cd, true); - } - } - - private void readAnno(DataInputStream dis, ClassData cd, boolean top) throws IOException { - int type = dis.readUnsignedShort(); - String typeName = cd.StringValue(type); - visitAnnotationStart(typeName, top); - int cnt = dis.readUnsignedShort(); - for (int i = 0; i < cnt; i++) { - String attrName = cd.StringValue(dis.readUnsignedShort()); - readValue(dis, cd, typeName, attrName); - } - visitAnnotationEnd(typeName, top); - if (cnt == 0) { - visitAttr(typeName, null, null, null); - } - } - - private void readValue( - DataInputStream dis, ClassData cd, String typeName, String attrName - ) throws IOException { - char type = (char)dis.readByte(); - visitValueStart(attrName, type); - if (type == '@') { - readAnno(dis, cd, false); - } else if ("CFJZsSIDB".indexOf(type) >= 0) { // NOI18N - int primitive = dis.readUnsignedShort(); - String val = cd.stringValue(primitive, textual); - String attrType; - if (type == 's') { - attrType = "Ljava_lang_String_2"; - if (textual) { - val = '"' + val + '"'; - } - } else { - attrType = "" + type; - } - visitAttr(typeName, attrName, attrType, val); - } else if (type == 'c') { - int cls = dis.readUnsignedShort(); - } else if (type == '[') { - int cnt = dis.readUnsignedShort(); - for (int i = 0; i < cnt; i++) { - readValue(dis, cd, typeName, iterateArray ? attrName : null); - } - } else if (type == 'e') { - int enumT = dis.readUnsignedShort(); - String attrType = cd.stringValue(enumT, textual); - int enumN = dis.readUnsignedShort(); - String val = cd.stringValue(enumN, textual); - visitEnumAttr(typeName, attrName, attrType, val); - } else { - throw new IOException("Unknown type " + type); - } - visitValueEnd(attrName, type); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/AttrData.java --- a/rt/javap/src/main/java/org/apidesign/javap/AttrData.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - - -package org.apidesign.javap; - -import java.io.*; - -/** - * Reads and stores attribute information. - * - * @author Sucheta Dambalkar (Adopted code from jdis) - */ -class AttrData { - ClassData cls; - int name_cpx; - int datalen; - byte data[]; - - public AttrData (ClassData cls) { - this.cls=cls; - } - - /** - * Reads unknown attribute. - */ - public void read(int name_cpx, DataInputStream in) throws IOException { - this.name_cpx=name_cpx; - datalen=in.readInt(); - data=new byte[datalen]; - in.readFully(data); - } - - /** - * Reads just the name of known attribute. - */ - public void read(int name_cpx){ - this.name_cpx=name_cpx; - } - - /** - * Returns attribute name. - */ - public String getAttrName(){ - return cls.getString(name_cpx); - } - - /** - * Returns attribute data. - */ - public byte[] getData(){ - return data; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/CPX.java --- a/rt/javap/src/main/java/org/apidesign/javap/CPX.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - -package org.apidesign.javap; - -/** - * Stores constant pool entry information with one field. - * - * @author Sucheta Dambalkar (Adopted code from jdis) - */ -class CPX { - int cpx; - - CPX (int cpx) { - this.cpx=cpx; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/CPX2.java --- a/rt/javap/src/main/java/org/apidesign/javap/CPX2.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - -package org.apidesign.javap; - -/** - * Stores constant pool entry information with two fields. - * - * @author Sucheta Dambalkar (Adopted code from jdis) - */ -class CPX2 { - int cpx1,cpx2; - - CPX2 (int cpx1, int cpx2) { - this.cpx1=cpx1; - this.cpx2=cpx2; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/ClassData.java --- a/rt/javap/src/main/java/org/apidesign/javap/ClassData.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,713 +0,0 @@ -/* - * Copyright (c) 2002, 2004, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - -package org.apidesign.javap; - -import java.io.*; - -/** - * Central data repository of the Java Disassembler. - * Stores all the information in java class file. - * - * @author Sucheta Dambalkar (Adopted code from jdis) - */ -public final class ClassData implements RuntimeConstants { - - private int magic; - private int minor_version; - private int major_version; - private int cpool_count; - private Object cpool[]; - private int access; - private int this_class = 0;; - private int super_class; - private int interfaces_count; - private int[] interfaces = new int[0];; - private int fields_count; - private FieldData[] fields; - private int methods_count; - private MethodData[] methods; - private InnerClassData[] innerClasses; - private int attributes_count; - private AttrData[] attrs; - private String classname; - private String superclassname; - private int source_cpx=0; - private byte tags[]; - private Hashtable indexHashAscii = new Hashtable(); - private String pkgPrefix=""; - private int pkgPrefixLen=0; - - /** - * Read classfile to disassemble. - */ - public ClassData(InputStream infile) throws IOException { - this.read(new DataInputStream(infile)); - } - - /** - * Reads and stores class file information. - */ - public void read(DataInputStream in) throws IOException { - // Read the header - magic = in.readInt(); - if (magic != JAVA_MAGIC) { - throw new ClassFormatError("wrong magic: " + - toHex(magic) + ", expected " + - toHex(JAVA_MAGIC)); - } - minor_version = in.readShort(); - major_version = in.readShort(); - if (major_version != JAVA_VERSION) { - } - - // Read the constant pool - readCP(in); - access = in.readUnsignedShort(); - this_class = in.readUnsignedShort(); - super_class = in.readUnsignedShort(); - - //Read interfaces. - interfaces_count = in.readUnsignedShort(); - if(interfaces_count > 0){ - interfaces = new int[interfaces_count]; - } - for (int i = 0; i < interfaces_count; i++) { - interfaces[i]=in.readShort(); - } - - // Read the fields - readFields(in); - - // Read the methods - readMethods(in); - - // Read the attributes - attributes_count = in.readUnsignedShort(); - attrs=new AttrData[attributes_count]; - for (int k = 0; k < attributes_count; k++) { - int name_cpx=in.readUnsignedShort(); - if (getTag(name_cpx)==CONSTANT_UTF8 - && getString(name_cpx).equals("SourceFile") - ){ if (in.readInt()!=2) - throw new ClassFormatError("invalid attr length"); - source_cpx=in.readUnsignedShort(); - AttrData attr=new AttrData(this); - attr.read(name_cpx); - attrs[k]=attr; - - } else if (getTag(name_cpx)==CONSTANT_UTF8 - && getString(name_cpx).equals("InnerClasses") - ){ int length=in.readInt(); - int num=in.readUnsignedShort(); - if (2+num*8 != length) - throw new ClassFormatError("invalid attr length"); - innerClasses=new InnerClassData[num]; - for (int j = 0; j < num; j++) { - InnerClassData innerClass=new InnerClassData(this); - innerClass.read(in); - innerClasses[j]=innerClass; - } - AttrData attr=new AttrData(this); - attr.read(name_cpx); - attrs[k]=attr; - } else { - AttrData attr=new AttrData(this); - attr.read(name_cpx, in); - attrs[k]=attr; - } - } - in.close(); - } // end ClassData.read() - - /** - * Reads and stores constant pool info. - */ - void readCP(DataInputStream in) throws IOException { - cpool_count = in.readUnsignedShort(); - tags = new byte[cpool_count]; - cpool = new Object[cpool_count]; - for (int i = 1; i < cpool_count; i++) { - byte tag = in.readByte(); - - switch(tags[i] = tag) { - case CONSTANT_UTF8: - String str=in.readUTF(); - indexHashAscii.put(cpool[i] = str, new Integer(i)); - break; - case CONSTANT_INTEGER: - cpool[i] = new Integer(in.readInt()); - break; - case CONSTANT_FLOAT: - cpool[i] = new Float(in.readFloat()); - break; - case CONSTANT_LONG: - cpool[i++] = new Long(in.readLong()); - break; - case CONSTANT_DOUBLE: - cpool[i++] = new Double(in.readDouble()); - break; - case CONSTANT_CLASS: - case CONSTANT_STRING: - cpool[i] = new CPX(in.readUnsignedShort()); - break; - - case CONSTANT_FIELD: - case CONSTANT_METHOD: - case CONSTANT_INTERFACEMETHOD: - case CONSTANT_NAMEANDTYPE: - cpool[i] = new CPX2(in.readUnsignedShort(), in.readUnsignedShort()); - break; - - case 0: - default: - throw new ClassFormatError("invalid constant type: " + (int)tags[i]); - } - } - } - - /** - * Reads and strores field info. - */ - protected void readFields(DataInputStream in) throws IOException { - int fields_count = in.readUnsignedShort(); - fields=new FieldData[fields_count]; - for (int k = 0; k < fields_count; k++) { - FieldData field=new FieldData(this); - field.read(in); - fields[k]=field; - } - } - - /** - * Reads and strores Method info. - */ - protected void readMethods(DataInputStream in) throws IOException { - int methods_count = in.readUnsignedShort(); - methods=new MethodData[methods_count]; - for (int k = 0; k < methods_count ; k++) { - MethodData method=new MethodData(this); - method.read(in); - methods[k]=method; - } - } - - /** - * get a string - */ - public String getString(int n) { - if (n == 0) { - return null; - } else { - return (String)cpool[n]; - } - } - - /** - * get the type of constant given an index - */ - public byte getTag(int n) { - try{ - return tags[n]; - } catch (ArrayIndexOutOfBoundsException e) { - return (byte)100; - } - } - - static final String hexString="0123456789ABCDEF"; - - public static char hexTable[]=hexString.toCharArray(); - - static String toHex(long val, int width) { - StringBuffer s = new StringBuffer(); - for (int i=width-1; i>=0; i--) - s.append(hexTable[((int)(val>>(4*i)))&0xF]); - return "0x"+s.toString(); - } - - static String toHex(long val) { - int width; - for (width=16; width>0; width--) { - if ((val>>(width-1)*4)!=0) break; - } - return toHex(val, width); - } - - static String toHex(int val) { - int width; - for (width=8; width>0; width--) { - if ((val>>(width-1)*4)!=0) break; - } - return toHex(val, width); - } - - /** - * Returns the name of this class. - */ - public String getClassName() { - String res=null; - if (this_class==0) { - return res; - } - int tcpx; - try { - if (tags[this_class]!=CONSTANT_CLASS) { - return res; //" "; - } - tcpx=((CPX)cpool[this_class]).cpx; - } catch (ArrayIndexOutOfBoundsException e) { - return res; // "#"+cpx+"// invalid constant pool index"; - } catch (Throwable e) { - return res; // "#"+cpx+"// ERROR IN DISASSEMBLER"; - } - - try { - return (String)(cpool[tcpx]); - } catch (ArrayIndexOutOfBoundsException e) { - return res; // "class #"+scpx+"// invalid constant pool index"; - } catch (ClassCastException e) { - return res; // "class #"+scpx+"// invalid constant pool reference"; - } catch (Throwable e) { - return res; // "#"+cpx+"// ERROR IN DISASSEMBLER"; - } - - } - - /** - * Returns the name of class at perticular index. - */ - public String getClassName(int cpx) { - String res="#"+cpx; - if (cpx==0) { - return res; - } - int scpx; - try { - if (tags[cpx]!=CONSTANT_CLASS) { - return res; //" "; - } - scpx=((CPX)cpool[cpx]).cpx; - } catch (ArrayIndexOutOfBoundsException e) { - return res; // "#"+cpx+"// invalid constant pool index"; - } catch (Throwable e) { - return res; // "#"+cpx+"// ERROR IN DISASSEMBLER"; - } - res="#"+scpx; - try { - return (String)(cpool[scpx]); - } catch (ArrayIndexOutOfBoundsException e) { - return res; // "class #"+scpx+"// invalid constant pool index"; - } catch (ClassCastException e) { - return res; // "class #"+scpx+"// invalid constant pool reference"; - } catch (Throwable e) { - return res; // "#"+cpx+"// ERROR IN DISASSEMBLER"; - } - } - - public int getAccessFlags() { - return access; - } - - /** - * Returns true if it is a class - */ - public boolean isClass() { - if((access & ACC_INTERFACE) == 0) return true; - return false; - } - - /** - * Returns true if it is a interface. - */ - public boolean isInterface(){ - if((access & ACC_INTERFACE) != 0) return true; - return false; - } - - /** - * Returns true if this member is public, false otherwise. - */ - public boolean isPublic(){ - return (access & ACC_PUBLIC) != 0; - } - - /** - * Returns the access of this class or interface. - */ - public String[] getAccess(){ - Vector v = new Vector(); - if ((access & ACC_PUBLIC) !=0) v.addElement("public"); - if ((access & ACC_FINAL) !=0) v.addElement("final"); - if ((access & ACC_ABSTRACT) !=0) v.addElement("abstract"); - String[] accflags = new String[v.size()]; - v.copyInto(accflags); - return accflags; - } - - /** - * Returns list of innerclasses. - */ - public InnerClassData[] getInnerClasses(){ - return innerClasses; - } - - /** - * Returns list of attributes. - */ - final AttrData[] getAttributes(){ - return attrs; - } - - public byte[] findAnnotationData(boolean classRetention) { - String n = classRetention ? - "RuntimeInvisibleAnnotations" : // NOI18N - "RuntimeVisibleAnnotations"; // NOI18N - return findAttr(n, attrs); - } - - /** - * Returns true if superbit is set. - */ - public boolean isSuperSet(){ - if ((access & ACC_SUPER) !=0) return true; - return false; - } - - /** - * Returns super class name. - */ - public String getSuperClassName(){ - String res=null; - if (super_class==0) { - return res; - } - int scpx; - try { - if (tags[super_class]!=CONSTANT_CLASS) { - return res; //" "; - } - scpx=((CPX)cpool[super_class]).cpx; - } catch (ArrayIndexOutOfBoundsException e) { - return res; // "#"+cpx+"// invalid constant pool index"; - } catch (Throwable e) { - return res; // "#"+cpx+"// ERROR IN DISASSEMBLER"; - } - - try { - return (String)(cpool[scpx]); - } catch (ArrayIndexOutOfBoundsException e) { - return res; // "class #"+scpx+"// invalid constant pool index"; - } catch (ClassCastException e) { - return res; // "class #"+scpx+"// invalid constant pool reference"; - } catch (Throwable e) { - return res; // "#"+cpx+"// ERROR IN DISASSEMBLER"; - } - } - - /** - * Returns list of super interfaces. - */ - public String[] getSuperInterfaces(){ - String interfacenames[] = new String[interfaces.length]; - int interfacecpx = -1; - for(int i = 0; i < interfaces.length; i++){ - interfacecpx=((CPX)cpool[interfaces[i]]).cpx; - interfacenames[i] = (String)(cpool[interfacecpx]); - } - return interfacenames; - } - - /** - * Returns string at prticular constant pool index. - */ - public String getStringValue(int cpoolx) { - try { - return ((String)cpool[cpoolx]); - } catch (ArrayIndexOutOfBoundsException e) { - return "//invalid constant pool index:"+cpoolx; - } catch (ClassCastException e) { - return "//invalid constant pool ref:"+cpoolx; - } - } - - /** - * Returns list of field info. - */ - public FieldData[] getFields(){ - return fields; - } - - /** - * Returns list of method info. - */ - public MethodData[] getMethods(){ - return methods; - } - - /** - * Returns constant pool entry at that index. - */ - public CPX2 getCpoolEntry(int cpx){ - return ((CPX2)(cpool[cpx])); - } - - public Object getCpoolEntryobj(int cpx){ - return (cpool[cpx]); - } - - /** - * Returns index of this class. - */ - public int getthis_cpx(){ - return this_class; - } - - /** - * Returns string at that index. - */ - public String StringValue(int cpx) { - return stringValue(cpx, false); - } - public String stringValue(int cpx, boolean textual) { - return stringValue(cpx, textual, null); - } - public String stringValue(int cpx, String[] classRefs) { - return stringValue(cpx, true, classRefs); - } - private String stringValue(int cpx, boolean textual, String[] refs) { - if (cpx==0) return "#0"; - int tag; - Object x; - String suffix=""; - try { - tag=tags[cpx]; - x=cpool[cpx]; - } catch (IndexOutOfBoundsException e) { - return ""; - } - - if (x==null) return ""; - switch (tag) { - case CONSTANT_UTF8: { - if (!textual) { - return (String)x; - } - StringBuilder sb=new StringBuilder(); - String s=(String)x; - for (int k=0; k"; - } catch (ClassCastException e) { - return ""; - } - } - - /** - * Returns unqualified class name. - */ - public String getShortClassName(int cpx) { - String classname=javaName(getClassName(cpx)); - pkgPrefixLen=classname.lastIndexOf("/")+1; - if (pkgPrefixLen!=0) { - pkgPrefix=classname.substring(0,pkgPrefixLen); - if (classname.startsWith(pkgPrefix)) { - return classname.substring(pkgPrefixLen); - } - } - return classname; - } - - /** - * Returns source file name. - */ - public String getSourceName(){ - return getName(source_cpx); - } - - /** - * Returns package name. - */ - public String getPkgName(){ - String classname=getClassName(this_class); - pkgPrefixLen=classname.lastIndexOf("/")+1; - if (pkgPrefixLen!=0) { - pkgPrefix=classname.substring(0,pkgPrefixLen); - return("package "+pkgPrefix.substring(0,pkgPrefixLen-1)+";\n"); - }else return null; - } - - /** - * Returns total constant pool entry count. - */ - public int getCpoolCount(){ - return cpool_count; - } - - /** - * Returns minor version of class file. - */ - public int getMinor_version(){ - return minor_version; - } - - /** - * Returns major version of class file. - */ - public int getMajor_version(){ - return major_version; - } - - private boolean isJavaIdentifierStart(int cp) { - return ('a' <= cp && cp <= 'z') || ('A' <= cp && cp <= 'Z'); - } - - private boolean isJavaIdentifierPart(int cp) { - return isJavaIdentifierStart(cp) || ('0' <= cp && cp <= '9'); - } - - public String[] getNameAndType(int indx) { - return getNameAndType(indx, 0, new String[2]); - } - - private String[] getNameAndType(int indx, int at, String[] arr) { - CPX2 c2 = getCpoolEntry(indx); - arr[at] = StringValue(c2.cpx1); - arr[at + 1] = StringValue(c2.cpx2); - return arr; - } - - public String[] getFieldInfoName(int indx) { - CPX2 c2 = getCpoolEntry(indx); - String[] arr = new String[3]; - arr[0] = getClassName(c2.cpx1); - return getNameAndType(c2.cpx2, 1, arr); - } - - static byte[] findAttr(String n, AttrData[] attrs) { - for (AttrData ad : attrs) { - if (n.equals(ad.getAttrName())) { - return ad.getData(); - } - } - return null; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/Constants.java --- a/rt/javap/src/main/java/org/apidesign/javap/Constants.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,372 +0,0 @@ -/* - * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - - -package org.apidesign.javap; - -/** - * This interface defines constant that are used - * throughout the compiler. It inherits from RuntimeConstants, - * which is an autogenerated class that contains contstants - * defined in the interpreter. - */ - -public -interface Constants extends RuntimeConstants { - - /** - * End of input - */ - public static final int EOF = -1; - - /* - * Flags - */ - public static final int F_VERBOSE = 1 << 0; - public static final int F_DUMP = 1 << 1; - public static final int F_WARNINGS = 1 << 2; - public static final int F_DEBUG = 1 << 3; - public static final int F_OPTIMIZE = 1 << 4; - public static final int F_DEPENDENCIES = 1 << 5; - - /* - * Type codes - */ - public static final int TC_BOOLEAN = 0; - public static final int TC_BYTE = 1; - public static final int TC_CHAR = 2; - public static final int TC_SHORT = 3; - public static final int TC_INT = 4; - public static final int TC_LONG = 5; - public static final int TC_FLOAT = 6; - public static final int TC_DOUBLE = 7; - public static final int TC_NULL = 8; - public static final int TC_ARRAY = 9; - public static final int TC_CLASS = 10; - public static final int TC_VOID = 11; - public static final int TC_METHOD = 12; - public static final int TC_ERROR = 13; - - /* - * Type Masks - */ - public static final int TM_NULL = 1 << TC_NULL; - public static final int TM_VOID = 1 << TC_VOID; - public static final int TM_BOOLEAN = 1 << TC_BOOLEAN; - public static final int TM_BYTE = 1 << TC_BYTE; - public static final int TM_CHAR = 1 << TC_CHAR; - public static final int TM_SHORT = 1 << TC_SHORT; - public static final int TM_INT = 1 << TC_INT; - public static final int TM_LONG = 1 << TC_LONG; - public static final int TM_FLOAT = 1 << TC_FLOAT; - public static final int TM_DOUBLE = 1 << TC_DOUBLE; - public static final int TM_ARRAY = 1 << TC_ARRAY; - public static final int TM_CLASS = 1 << TC_CLASS; - public static final int TM_METHOD = 1 << TC_METHOD; - public static final int TM_ERROR = 1 << TC_ERROR; - - public static final int TM_INT32 = TM_BYTE | TM_SHORT | TM_CHAR | TM_INT; - public static final int TM_NUM32 = TM_INT32 | TM_FLOAT; - public static final int TM_NUM64 = TM_LONG | TM_DOUBLE; - public static final int TM_INTEGER = TM_INT32 | TM_LONG; - public static final int TM_REAL = TM_FLOAT | TM_DOUBLE; - public static final int TM_NUMBER = TM_INTEGER | TM_REAL; - public static final int TM_REFERENCE = TM_ARRAY | TM_CLASS | TM_NULL; - - /* - * Class status - */ - public static final int CS_UNDEFINED = 0; - public static final int CS_UNDECIDED = 1; - public static final int CS_BINARY = 2; - public static final int CS_SOURCE = 3; - public static final int CS_PARSED = 4; - public static final int CS_COMPILED = 5; - public static final int CS_NOTFOUND = 6; - - /* - * Attributes - */ - public static final int ATT_ALL = -1; - public static final int ATT_CODE = 1; - - /* - * Number of bits used in file offsets - */ - public static final int OFFSETBITS = 19; - public static final int MAXFILESIZE = (1 << OFFSETBITS) - 1; - public static final int MAXLINENUMBER = (1 << (32 - OFFSETBITS)) - 1; - - /* - * Operators - */ - public final int COMMA = 0; - public final int ASSIGN = 1; - - public final int ASGMUL = 2; - public final int ASGDIV = 3; - public final int ASGREM = 4; - public final int ASGADD = 5; - public final int ASGSUB = 6; - public final int ASGLSHIFT = 7; - public final int ASGRSHIFT = 8; - public final int ASGURSHIFT = 9; - public final int ASGBITAND = 10; - public final int ASGBITOR = 11; - public final int ASGBITXOR = 12; - - public final int COND = 13; - public final int OR = 14; - public final int AND = 15; - public final int BITOR = 16; - public final int BITXOR = 17; - public final int BITAND = 18; - public final int NE = 19; - public final int EQ = 20; - public final int GE = 21; - public final int GT = 22; - public final int LE = 23; - public final int LT = 24; - public final int INSTANCEOF = 25; - public final int LSHIFT = 26; - public final int RSHIFT = 27; - public final int URSHIFT = 28; - public final int ADD = 29; - public final int SUB = 30; - public final int DIV = 31; - public final int REM = 32; - public final int MUL = 33; - public final int CAST = 34; // (x)y - public final int POS = 35; // +x - public final int NEG = 36; // -x - public final int NOT = 37; - public final int BITNOT = 38; - public final int PREINC = 39; // ++x - public final int PREDEC = 40; // --x - public final int NEWARRAY = 41; - public final int NEWINSTANCE = 42; - public final int NEWFROMNAME = 43; - public final int POSTINC = 44; // x++ - public final int POSTDEC = 45; // x-- - public final int FIELD = 46; - public final int METHOD = 47; // x(y) - public final int ARRAYACCESS = 48; // x[y] - public final int NEW = 49; - public final int INC = 50; - public final int DEC = 51; - - public final int CONVERT = 55; // implicit conversion - public final int EXPR = 56; // (x) - public final int ARRAY = 57; // {x, y, ...} - public final int GOTO = 58; - - /* - * Value tokens - */ - public final int IDENT = 60; - public final int BOOLEANVAL = 61; - public final int BYTEVAL = 62; - public final int CHARVAL = 63; - public final int SHORTVAL = 64; - public final int INTVAL = 65; - public final int LONGVAL = 66; - public final int FLOATVAL = 67; - public final int DOUBLEVAL = 68; - public final int STRINGVAL = 69; - - /* - * Type keywords - */ - public final int BYTE = 70; - public final int CHAR = 71; - public final int SHORT = 72; - public final int INT = 73; - public final int LONG = 74; - public final int FLOAT = 75; - public final int DOUBLE = 76; - public final int VOID = 77; - public final int BOOLEAN = 78; - - /* - * Expression keywords - */ - public final int TRUE = 80; - public final int FALSE = 81; - public final int THIS = 82; - public final int SUPER = 83; - public final int NULL = 84; - - /* - * Statement keywords - */ - public final int IF = 90; - public final int ELSE = 91; - public final int FOR = 92; - public final int WHILE = 93; - public final int DO = 94; - public final int SWITCH = 95; - public final int CASE = 96; - public final int DEFAULT = 97; - public final int BREAK = 98; - public final int CONTINUE = 99; - public final int RETURN = 100; - public final int TRY = 101; - public final int CATCH = 102; - public final int FINALLY = 103; - public final int THROW = 104; - public final int STAT = 105; - public final int EXPRESSION = 106; - public final int DECLARATION = 107; - public final int VARDECLARATION = 108; - - /* - * Declaration keywords - */ - public final int IMPORT = 110; - public final int CLASS = 111; - public final int EXTENDS = 112; - public final int IMPLEMENTS = 113; - public final int INTERFACE = 114; - public final int PACKAGE = 115; - - /* - * Modifier keywords - */ - public final int PRIVATE = 120; - public final int PUBLIC = 121; - public final int PROTECTED = 122; - public final int CONST = 123; - public final int STATIC = 124; - public final int TRANSIENT = 125; - public final int SYNCHRONIZED = 126; - public final int NATIVE = 127; - public final int FINAL = 128; - public final int VOLATILE = 129; - public final int ABSTRACT = 130; - public final int STRICT = 165; - - /* - * Punctuation - */ - public final int SEMICOLON = 135; - public final int COLON = 136; - public final int QUESTIONMARK = 137; - public final int LBRACE = 138; - public final int RBRACE = 139; - public final int LPAREN = 140; - public final int RPAREN = 141; - public final int LSQBRACKET = 142; - public final int RSQBRACKET = 143; - public final int THROWS = 144; - - /* - * Special tokens - */ - public final int ERROR = 145; // an error - public final int COMMENT = 146; // not used anymore. - public final int TYPE = 147; - public final int LENGTH = 148; - public final int INLINERETURN = 149; - public final int INLINEMETHOD = 150; - public final int INLINENEWINSTANCE = 151; - - /* - * Added for jasm - */ - public final int METHODREF = 152; - public final int FIELDREF = 153; - public final int STACK = 154; - public final int LOCAL = 155; - public final int CPINDEX = 156; - public final int CPNAME = 157; - public final int SIGN = 158; - public final int BITS = 159; - public final int INF = 160; - public final int NAN = 161; - public final int INNERCLASS = 162; - public final int OF = 163; - public final int SYNTHETIC = 164; -// last used=165; - - /* - * Operator precedence - */ - public static final int opPrecedence[] = { - 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, - 11, 11, 11, 12, 13, 14, 15, 16, 17, 18, - 18, 19, 19, 19, 19, 19, 20, 20, 20, 21, - 21, 22, 22, 22, 23, 24, 24, 24, 24, 24, - 24, 25, 25, 26, 26, 26, 26, 26, 26 - }; - - /* - * Operator names - */ - public static final String opNames[] = { - ",", "=", "*=", "/=", "%=", - "+=", "-=", "<<=", ">>=", "<<<=", - "&=", "|=", "^=", "?:", "||", - "&&", "|", "^", "&", "!=", - "==", ">=", ">", "<=", "<", - "instanceof", "<<", ">>", "<<<", "+", - "-", "/", "%", "*", "cast", - "+", "-", "!", "~", "++", - "--", "new", "new", "new", "++", - "--", "field", "method", "[]", "new", - "++", "--", null, null, null, - - "convert", "expr", "array", "goto", null, - - "Identifier", "Boolean", "Byte", "Char", "Short", - "Integer", "Long", "Float", "Double", "String", - - "byte", "char", "short", "int", "long", - "float", "double", "void", "boolean", null, - - "true", "false", "this", "super", "null", - null, null, null, null, null, - - "if", "else", "for", "while", "do", - "switch", "case", "default", "break", "continue", - "return", "try", "catch", "finally", "throw", - "stat", "expression", "declaration", "declaration", null, - - "import", "class", "extends", "implements", "interface", - "package", null, null, null, null, - - "private", "public", "protected", "const", "static", - "transient", "synchronized", "native", "final", "volatile", - "abstract", null, null, null, null, - - ";", ":", "?", "{", "}", - "(", ")", "[", "]", "throws", - "error", "comment", "type", "length", "inline-return", - "inline-method", "inline-new", - "method", "field", "stack", "locals", "CPINDEX", "CPName", "SIGN", - "bits", "INF", "NaN", "InnerClass", "of", "synthetic" - }; - -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/FieldData.java --- a/rt/javap/src/main/java/org/apidesign/javap/FieldData.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - -package org.apidesign.javap; - -import java.io.*; - -/** - * Strores field data informastion. - * - * @author Sucheta Dambalkar (Adopted code from jdis) - */ - -public class FieldData implements RuntimeConstants { - - ClassData cls; - int access; - int name_index; - int descriptor_index; - int attributes_count; - int value_cpx=0; - boolean isSynthetic=false; - boolean isDeprecated=false; - Vector attrs; - - public FieldData(ClassData cls){ - this.cls=cls; - } - - /** - * Read and store field info. - */ - public void read(DataInputStream in) throws IOException { - access = in.readUnsignedShort(); - name_index = in.readUnsignedShort(); - descriptor_index = in.readUnsignedShort(); - // Read the attributes - int attributes_count = in.readUnsignedShort(); - attrs=new Vector(attributes_count); - for (int i = 0; i < attributes_count; i++) { - int attr_name_index=in.readUnsignedShort(); - if (cls.getTag(attr_name_index)!=CONSTANT_UTF8) continue; - String attr_name=cls.getString(attr_name_index); - if (attr_name.equals("ConstantValue")){ - if (in.readInt()!=2) - throw new ClassFormatError("invalid ConstantValue attr length"); - value_cpx=in.readUnsignedShort(); - AttrData attr=new AttrData(cls); - attr.read(attr_name_index); - attrs.addElement(attr); - } else if (attr_name.equals("Synthetic")){ - if (in.readInt()!=0) - throw new ClassFormatError("invalid Synthetic attr length"); - isSynthetic=true; - AttrData attr=new AttrData(cls); - attr.read(attr_name_index); - attrs.addElement(attr); - } else if (attr_name.equals("Deprecated")){ - if (in.readInt()!=0) - throw new ClassFormatError("invalid Synthetic attr length"); - isDeprecated = true; - AttrData attr=new AttrData(cls); - attr.read(attr_name_index); - attrs.addElement(attr); - } else { - AttrData attr=new AttrData(cls); - attr.read(attr_name_index, in); - attrs.addElement(attr); - } - } - - } // end read - - public boolean isStatic() { - return (access & ACC_STATIC) != 0; - } - - /** - * Returns access of a field. - */ - public String[] getAccess(){ - Vector v = new Vector(); - if ((access & ACC_PUBLIC) !=0) v.addElement("public"); - if ((access & ACC_PRIVATE) !=0) v.addElement("private"); - if ((access & ACC_PROTECTED) !=0) v.addElement("protected"); - if ((access & ACC_STATIC) !=0) v.addElement("static"); - if ((access & ACC_FINAL) !=0) v.addElement("final"); - if ((access & ACC_VOLATILE) !=0) v.addElement("volatile"); - if ((access & ACC_TRANSIENT) !=0) v.addElement("transient"); - String[] accflags = new String[v.size()]; - v.copyInto(accflags); - return accflags; - } - - /** - * Returns name of a field. - */ - public String getName(){ - return cls.getStringValue(name_index); - } - - /** - * Returns internal signature of a field - */ - public String getInternalSig(){ - return cls.getStringValue(descriptor_index); - } - - /** - * Returns true if field is synthetic. - */ - public boolean isSynthetic(){ - return isSynthetic; - } - - /** - * Returns true if field is deprecated. - */ - public boolean isDeprecated(){ - return isDeprecated; - } - - /** - * Returns index of constant value in cpool. - */ - public int getConstantValueIndex(){ - return (value_cpx); - } - - /** - * Returns list of attributes of field. - */ - public Vector getAttributes(){ - return attrs; - } - - public byte[] findAnnotationData(boolean classRetention) { - String n = classRetention ? - "RuntimeInvisibleAnnotations" : // NOI18N - "RuntimeVisibleAnnotations"; // NOI18N - AttrData[] arr = new AttrData[attrs.size()]; - attrs.copyInto(arr); - return ClassData.findAttr(n, arr); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/Hashtable.java --- a/rt/javap/src/main/java/org/apidesign/javap/Hashtable.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -/** - * 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.javap; - -/** A JavaScript optimized replacement for Hashtable. - * - * @author Jaroslav Tulach - */ -final class Hashtable { - private Object[] keys; - private Object[] values; - - Hashtable(int i) { - this(); - } - - Hashtable(int i, double d) { - this(); - } - - Hashtable() { - } - - synchronized void put(Object key, Object val) { - int[] where = { -1, -1 }; - Object found = get(key, where); - if (where[0] != -1) { - // key exists - values[where[0]] = val; - } else { - if (where[1] != -1) { - // null found - keys[where[1]] = key; - values[where[1]] = val; - } else { - if (keys == null) { - keys = new Object[11]; - values = new Object[11]; - keys[0] = key; - values[0] = val; - } else { - Object[] newKeys = new Object[keys.length * 2]; - Object[] newValues = new Object[values.length * 2]; - for (int i = 0; i < keys.length; i++) { - newKeys[i] = keys[i]; - newValues[i] = values[i]; - } - newKeys[keys.length] = key; - newValues[keys.length] = val; - keys = newKeys; - values = newValues; - } - } - } - } - - Object get(Object key) { - return get(key, null); - } - private synchronized Object get(Object key, int[] foundAndNull) { - if (keys == null) { - return null; - } - for (int i = 0; i < keys.length; i++) { - if (keys[i] == null) { - if (foundAndNull != null) { - foundAndNull[1] = i; - } - } else if (keys[i].equals(key)) { - if (foundAndNull != null) { - foundAndNull[0] = i; - } - return values[i]; - } - } - return null; - } - -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/InnerClassData.java --- a/rt/javap/src/main/java/org/apidesign/javap/InnerClassData.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - -package org.apidesign.javap; - -import java.io.*; -import java.util.*; - -/** - * Strores InnerClass data informastion. - * - * @author Sucheta Dambalkar (Adopted code from jdis) - */ -class InnerClassData implements RuntimeConstants { - ClassData cls; - - - int inner_class_info_index - ,outer_class_info_index - ,inner_name_index - ,access - ; - - public InnerClassData(ClassData cls) { - this.cls=cls; - - } - - /** - * Read Innerclass attribute data. - */ - public void read(DataInputStream in) throws IOException { - inner_class_info_index = in.readUnsignedShort(); - outer_class_info_index = in.readUnsignedShort(); - inner_name_index = in.readUnsignedShort(); - access = in.readUnsignedShort(); - } // end read - - /** - * Returns the access of this class or interface. - */ - public String[] getAccess(){ - Vector v = new Vector(); - if ((access & ACC_PUBLIC) !=0) v.addElement("public"); - if ((access & ACC_FINAL) !=0) v.addElement("final"); - if ((access & ACC_ABSTRACT) !=0) v.addElement("abstract"); - String[] accflags = new String[v.size()]; - v.copyInto(accflags); - return accflags; - } - -} // end InnerClassData diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/LineNumData.java --- a/rt/javap/src/main/java/org/apidesign/javap/LineNumData.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - -package org.apidesign.javap; - -import java.util.*; -import java.io.*; - -/** - * Strores LineNumberTable data information. - * - * @author Sucheta Dambalkar (Adopted code from jdis) - */ -class LineNumData { - short start_pc, line_number; - - public LineNumData() {} - - /** - * Read LineNumberTable attribute. - */ - public LineNumData(DataInputStream in) throws IOException { - start_pc = in.readShort(); - line_number=in.readShort(); - - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/LocVarData.java --- a/rt/javap/src/main/java/org/apidesign/javap/LocVarData.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - -package org.apidesign.javap; - -import java.util.*; -import java.io.*; - -/** - * Strores LocalVariableTable data information. - * - * @author Sucheta Dambalkar (Adopted code from jdis) - */ -class LocVarData { - short start_pc, length, name_cpx, sig_cpx, slot; - - public LocVarData() { - } - - /** - * Read LocalVariableTable attribute. - */ - public LocVarData(DataInputStream in) throws IOException { - start_pc = in.readShort(); - length=in.readShort(); - name_cpx=in.readShort(); - sig_cpx=in.readShort(); - slot=in.readShort(); - - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/MethodData.java --- a/rt/javap/src/main/java/org/apidesign/javap/MethodData.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,394 +0,0 @@ -/* - * Copyright (c) 2002, 2005, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package org.apidesign.javap; - - -import java.io.DataInputStream; -import java.io.IOException; -import static org.apidesign.javap.RuntimeConstants.*; - -/** - * Strores method data informastion. - * - * @author Sucheta Dambalkar (Adopted code from jdis) - */ -public class MethodData { - - ClassData cls; - int access; - int name_index; - int descriptor_index; - int attributes_count; - byte[] code; - Vector exception_table = new Vector(0); - Vector lin_num_tb = new Vector(0); - Vector loc_var_tb = new Vector(0); - StackMapTableData[] stackMapTable; - StackMapData[] stackMap; - int[] exc_index_table=null; - Vector attrs=new Vector(0); - Vector code_attrs=new Vector(0); - int max_stack, max_locals; - boolean isSynthetic=false; - boolean isDeprecated=false; - - public MethodData(ClassData cls){ - this.cls=cls; - } - - /** - * Read method info. - */ - public void read(DataInputStream in) throws IOException { - access = in.readUnsignedShort(); - name_index=in.readUnsignedShort(); - descriptor_index =in.readUnsignedShort(); - int attributes_count = in.readUnsignedShort(); - for (int i = 0; i < attributes_count; i++) { - int attr_name_index=in.readUnsignedShort(); - - readAttr: { - if (cls.getTag(attr_name_index)==CONSTANT_UTF8) { - String attr_name=cls.getString(attr_name_index); - if ( attr_name.equals("Code")){ - readCode (in); - AttrData attr=new AttrData(cls); - attr.read(attr_name_index); - attrs.addElement(attr); - break readAttr; - } else if ( attr_name.equals("Exceptions")){ - readExceptions(in); - AttrData attr=new AttrData(cls); - attr.read(attr_name_index); - attrs.addElement(attr); - break readAttr; - } else if (attr_name.equals("Synthetic")){ - if (in.readInt()!=0) - throw new ClassFormatError("invalid Synthetic attr length"); - isSynthetic=true; - AttrData attr=new AttrData(cls); - attr.read(attr_name_index); - attrs.addElement(attr); - break readAttr; - } else if (attr_name.equals("Deprecated")){ - if (in.readInt()!=0) - throw new ClassFormatError("invalid Synthetic attr length"); - isDeprecated = true; - AttrData attr=new AttrData(cls); - attr.read(attr_name_index); - attrs.addElement(attr); - break readAttr; - } - } - AttrData attr=new AttrData(cls); - attr.read(attr_name_index, in); - attrs.addElement(attr); - } - } - } - - /** - * Read code attribute info. - */ - public void readCode(DataInputStream in) throws IOException { - - int attr_length = in.readInt(); - max_stack=in.readUnsignedShort(); - max_locals=in.readUnsignedShort(); - int codelen=in.readInt(); - - code=new byte[codelen]; - int totalread = 0; - while(totalread < codelen){ - totalread += in.read(code, totalread, codelen-totalread); - } - // in.read(code, 0, codelen); - int clen = 0; - readExceptionTable(in); - int code_attributes_count = in.readUnsignedShort(); - - for (int k = 0 ; k < code_attributes_count ; k++) { - int table_name_index=in.readUnsignedShort(); - int table_name_tag=cls.getTag(table_name_index); - AttrData attr=new AttrData(cls); - if (table_name_tag==CONSTANT_UTF8) { - String table_name_tstr=cls.getString(table_name_index); - if (table_name_tstr.equals("LineNumberTable")) { - readLineNumTable(in); - attr.read(table_name_index); - } else if (table_name_tstr.equals("LocalVariableTable")) { - readLocVarTable(in); - attr.read(table_name_index); - } else if (table_name_tstr.equals("StackMapTable")) { - readStackMapTable(in); - attr.read(table_name_index); - } else if (table_name_tstr.equals("StackMap")) { - readStackMap(in); - attr.read(table_name_index); - } else { - attr.read(table_name_index, in); - } - code_attrs.addElement(attr); - continue; - } - - attr.read(table_name_index, in); - code_attrs.addElement(attr); - } - } - - /** - * Read exception table info. - */ - void readExceptionTable (DataInputStream in) throws IOException { - int exception_table_len=in.readUnsignedShort(); - exception_table=new Vector(exception_table_len); - for (int l = 0; l < exception_table_len; l++) { - exception_table.addElement(new TrapData(in, l)); - } - } - - /** - * Read LineNumberTable attribute info. - */ - void readLineNumTable (DataInputStream in) throws IOException { - int attr_len = in.readInt(); // attr_length - int lin_num_tb_len = in.readUnsignedShort(); - lin_num_tb=new Vector(lin_num_tb_len); - for (int l = 0; l < lin_num_tb_len; l++) { - lin_num_tb.addElement(new LineNumData(in)); - } - } - - /** - * Read LocalVariableTable attribute info. - */ - void readLocVarTable (DataInputStream in) throws IOException { - int attr_len=in.readInt(); // attr_length - int loc_var_tb_len = in.readUnsignedShort(); - loc_var_tb = new Vector(loc_var_tb_len); - for (int l = 0; l < loc_var_tb_len; l++) { - loc_var_tb.addElement(new LocVarData(in)); - } - } - - /** - * Read Exception attribute info. - */ - public void readExceptions(DataInputStream in) throws IOException { - int attr_len=in.readInt(); // attr_length in prog - int num_exceptions = in.readUnsignedShort(); - exc_index_table=new int[num_exceptions]; - for (int l = 0; l < num_exceptions; l++) { - int exc=in.readShort(); - exc_index_table[l]=exc; - } - } - - /** - * Read StackMapTable attribute info. - */ - void readStackMapTable(DataInputStream in) throws IOException { - int attr_len = in.readInt(); //attr_length - int stack_map_tb_len = in.readUnsignedShort(); - stackMapTable = new StackMapTableData[stack_map_tb_len]; - for (int i=0; i".equals(getName()); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/RuntimeConstants.java --- a/rt/javap/src/main/java/org/apidesign/javap/RuntimeConstants.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,787 +0,0 @@ -/* - * Copyright (c) 2002, 2005, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - -package org.apidesign.javap; - -public interface RuntimeConstants { - - /* Signature Characters */ - public static final char SIGC_VOID = 'V'; - public static final String SIG_VOID = "V"; - public static final char SIGC_BOOLEAN = 'Z'; - public static final String SIG_BOOLEAN = "Z"; - public static final char SIGC_BYTE = 'B'; - public static final String SIG_BYTE = "B"; - public static final char SIGC_CHAR = 'C'; - public static final String SIG_CHAR = "C"; - public static final char SIGC_SHORT = 'S'; - public static final String SIG_SHORT = "S"; - public static final char SIGC_INT = 'I'; - public static final String SIG_INT = "I"; - public static final char SIGC_LONG = 'J'; - public static final String SIG_LONG = "J"; - public static final char SIGC_FLOAT = 'F'; - public static final String SIG_FLOAT = "F"; - public static final char SIGC_DOUBLE = 'D'; - public static final String SIG_DOUBLE = "D"; - public static final char SIGC_ARRAY = '['; - public static final String SIG_ARRAY = "["; - public static final char SIGC_CLASS = 'L'; - public static final String SIG_CLASS = "L"; - public static final char SIGC_METHOD = '('; - public static final String SIG_METHOD = "("; - public static final char SIGC_ENDCLASS = ';'; - public static final String SIG_ENDCLASS = ";"; - public static final char SIGC_ENDMETHOD = ')'; - public static final String SIG_ENDMETHOD = ")"; - public static final char SIGC_PACKAGE = '/'; - public static final String SIG_PACKAGE = "/"; - - /* Class File Constants */ - public static final int JAVA_MAGIC = 0xcafebabe; - public static final int JAVA_VERSION = 45; - public static final int JAVA_MINOR_VERSION = 3; - - /* Constant table */ - public static final int CONSTANT_UTF8 = 1; - public static final int CONSTANT_UNICODE = 2; - public static final int CONSTANT_INTEGER = 3; - public static final int CONSTANT_FLOAT = 4; - public static final int CONSTANT_LONG = 5; - public static final int CONSTANT_DOUBLE = 6; - public static final int CONSTANT_CLASS = 7; - public static final int CONSTANT_STRING = 8; - public static final int CONSTANT_FIELD = 9; - public static final int CONSTANT_METHOD = 10; - public static final int CONSTANT_INTERFACEMETHOD = 11; - public static final int CONSTANT_NAMEANDTYPE = 12; - - /* Access Flags */ - public static final int ACC_PUBLIC = 0x00000001; - public static final int ACC_PRIVATE = 0x00000002; - public static final int ACC_PROTECTED = 0x00000004; - public static final int ACC_STATIC = 0x00000008; - public static final int ACC_FINAL = 0x00000010; - public static final int ACC_SYNCHRONIZED = 0x00000020; - public static final int ACC_SUPER = 0x00000020; - public static final int ACC_VOLATILE = 0x00000040; - public static final int ACC_TRANSIENT = 0x00000080; - public static final int ACC_NATIVE = 0x00000100; - public static final int ACC_INTERFACE = 0x00000200; - public static final int ACC_ABSTRACT = 0x00000400; - public static final int ACC_STRICT = 0x00000800; - public static final int ACC_EXPLICIT = 0x00001000; - public static final int ACC_SYNTHETIC = 0x00010000; // actually, this is an attribute - - /* Type codes */ - public static final int T_CLASS = 0x00000002; - public static final int T_BOOLEAN = 0x00000004; - public static final int T_CHAR = 0x00000005; - public static final int T_FLOAT = 0x00000006; - public static final int T_DOUBLE = 0x00000007; - public static final int T_BYTE = 0x00000008; - public static final int T_SHORT = 0x00000009; - public static final int T_INT = 0x0000000a; - public static final int T_LONG = 0x0000000b; - - /* Type codes for StackMap attribute */ - public static final int ITEM_Bogus =0; // an unknown or uninitialized value - public static final int ITEM_Integer =1; // a 32-bit integer - public static final int ITEM_Float =2; // not used - public static final int ITEM_Double =3; // not used - public static final int ITEM_Long =4; // a 64-bit integer - public static final int ITEM_Null =5; // the type of null - public static final int ITEM_InitObject =6; // "this" in constructor - public static final int ITEM_Object =7; // followed by 2-byte index of class name - public static final int ITEM_NewObject =8; // followed by 2-byte ref to "new" - - /* Constants used in StackMapTable attribute */ - public static final int SAME_FRAME_BOUND = 64; - public static final int SAME_LOCALS_1_STACK_ITEM_BOUND = 128; - public static final int SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247; - public static final int SAME_FRAME_EXTENDED = 251; - public static final int FULL_FRAME = 255; - - /* Opcodes */ - public static final int opc_dead = -2; - public static final int opc_label = -1; - public static final int opc_nop = 0; - public static final int opc_aconst_null = 1; - public static final int opc_iconst_m1 = 2; - public static final int opc_iconst_0 = 3; - public static final int opc_iconst_1 = 4; - public static final int opc_iconst_2 = 5; - public static final int opc_iconst_3 = 6; - public static final int opc_iconst_4 = 7; - public static final int opc_iconst_5 = 8; - public static final int opc_lconst_0 = 9; - public static final int opc_lconst_1 = 10; - public static final int opc_fconst_0 = 11; - public static final int opc_fconst_1 = 12; - public static final int opc_fconst_2 = 13; - public static final int opc_dconst_0 = 14; - public static final int opc_dconst_1 = 15; - public static final int opc_bipush = 16; - public static final int opc_sipush = 17; - public static final int opc_ldc = 18; - public static final int opc_ldc_w = 19; - public static final int opc_ldc2_w = 20; - public static final int opc_iload = 21; - public static final int opc_lload = 22; - public static final int opc_fload = 23; - public static final int opc_dload = 24; - public static final int opc_aload = 25; - public static final int opc_iload_0 = 26; - public static final int opc_iload_1 = 27; - public static final int opc_iload_2 = 28; - public static final int opc_iload_3 = 29; - public static final int opc_lload_0 = 30; - public static final int opc_lload_1 = 31; - public static final int opc_lload_2 = 32; - public static final int opc_lload_3 = 33; - public static final int opc_fload_0 = 34; - public static final int opc_fload_1 = 35; - public static final int opc_fload_2 = 36; - public static final int opc_fload_3 = 37; - public static final int opc_dload_0 = 38; - public static final int opc_dload_1 = 39; - public static final int opc_dload_2 = 40; - public static final int opc_dload_3 = 41; - public static final int opc_aload_0 = 42; - public static final int opc_aload_1 = 43; - public static final int opc_aload_2 = 44; - public static final int opc_aload_3 = 45; - public static final int opc_iaload = 46; - public static final int opc_laload = 47; - public static final int opc_faload = 48; - public static final int opc_daload = 49; - public static final int opc_aaload = 50; - public static final int opc_baload = 51; - public static final int opc_caload = 52; - public static final int opc_saload = 53; - public static final int opc_istore = 54; - public static final int opc_lstore = 55; - public static final int opc_fstore = 56; - public static final int opc_dstore = 57; - public static final int opc_astore = 58; - public static final int opc_istore_0 = 59; - public static final int opc_istore_1 = 60; - public static final int opc_istore_2 = 61; - public static final int opc_istore_3 = 62; - public static final int opc_lstore_0 = 63; - public static final int opc_lstore_1 = 64; - public static final int opc_lstore_2 = 65; - public static final int opc_lstore_3 = 66; - public static final int opc_fstore_0 = 67; - public static final int opc_fstore_1 = 68; - public static final int opc_fstore_2 = 69; - public static final int opc_fstore_3 = 70; - public static final int opc_dstore_0 = 71; - public static final int opc_dstore_1 = 72; - public static final int opc_dstore_2 = 73; - public static final int opc_dstore_3 = 74; - public static final int opc_astore_0 = 75; - public static final int opc_astore_1 = 76; - public static final int opc_astore_2 = 77; - public static final int opc_astore_3 = 78; - public static final int opc_iastore = 79; - public static final int opc_lastore = 80; - public static final int opc_fastore = 81; - public static final int opc_dastore = 82; - public static final int opc_aastore = 83; - public static final int opc_bastore = 84; - public static final int opc_castore = 85; - public static final int opc_sastore = 86; - public static final int opc_pop = 87; - public static final int opc_pop2 = 88; - public static final int opc_dup = 89; - public static final int opc_dup_x1 = 90; - public static final int opc_dup_x2 = 91; - public static final int opc_dup2 = 92; - public static final int opc_dup2_x1 = 93; - public static final int opc_dup2_x2 = 94; - public static final int opc_swap = 95; - public static final int opc_iadd = 96; - public static final int opc_ladd = 97; - public static final int opc_fadd = 98; - public static final int opc_dadd = 99; - public static final int opc_isub = 100; - public static final int opc_lsub = 101; - public static final int opc_fsub = 102; - public static final int opc_dsub = 103; - public static final int opc_imul = 104; - public static final int opc_lmul = 105; - public static final int opc_fmul = 106; - public static final int opc_dmul = 107; - public static final int opc_idiv = 108; - public static final int opc_ldiv = 109; - public static final int opc_fdiv = 110; - public static final int opc_ddiv = 111; - public static final int opc_irem = 112; - public static final int opc_lrem = 113; - public static final int opc_frem = 114; - public static final int opc_drem = 115; - public static final int opc_ineg = 116; - public static final int opc_lneg = 117; - public static final int opc_fneg = 118; - public static final int opc_dneg = 119; - public static final int opc_ishl = 120; - public static final int opc_lshl = 121; - public static final int opc_ishr = 122; - public static final int opc_lshr = 123; - public static final int opc_iushr = 124; - public static final int opc_lushr = 125; - public static final int opc_iand = 126; - public static final int opc_land = 127; - public static final int opc_ior = 128; - public static final int opc_lor = 129; - public static final int opc_ixor = 130; - public static final int opc_lxor = 131; - public static final int opc_iinc = 132; - public static final int opc_i2l = 133; - public static final int opc_i2f = 134; - public static final int opc_i2d = 135; - public static final int opc_l2i = 136; - public static final int opc_l2f = 137; - public static final int opc_l2d = 138; - public static final int opc_f2i = 139; - public static final int opc_f2l = 140; - public static final int opc_f2d = 141; - public static final int opc_d2i = 142; - public static final int opc_d2l = 143; - public static final int opc_d2f = 144; - public static final int opc_i2b = 145; - public static final int opc_int2byte = 145; - public static final int opc_i2c = 146; - public static final int opc_int2char = 146; - public static final int opc_i2s = 147; - public static final int opc_int2short = 147; - public static final int opc_lcmp = 148; - public static final int opc_fcmpl = 149; - public static final int opc_fcmpg = 150; - public static final int opc_dcmpl = 151; - public static final int opc_dcmpg = 152; - public static final int opc_ifeq = 153; - public static final int opc_ifne = 154; - public static final int opc_iflt = 155; - public static final int opc_ifge = 156; - public static final int opc_ifgt = 157; - public static final int opc_ifle = 158; - public static final int opc_if_icmpeq = 159; - public static final int opc_if_icmpne = 160; - public static final int opc_if_icmplt = 161; - public static final int opc_if_icmpge = 162; - public static final int opc_if_icmpgt = 163; - public static final int opc_if_icmple = 164; - public static final int opc_if_acmpeq = 165; - public static final int opc_if_acmpne = 166; - public static final int opc_goto = 167; - public static final int opc_jsr = 168; - public static final int opc_ret = 169; - public static final int opc_tableswitch = 170; - public static final int opc_lookupswitch = 171; - public static final int opc_ireturn = 172; - public static final int opc_lreturn = 173; - public static final int opc_freturn = 174; - public static final int opc_dreturn = 175; - public static final int opc_areturn = 176; - public static final int opc_return = 177; - public static final int opc_getstatic = 178; - public static final int opc_putstatic = 179; - public static final int opc_getfield = 180; - public static final int opc_putfield = 181; - public static final int opc_invokevirtual = 182; - public static final int opc_invokenonvirtual = 183; - public static final int opc_invokespecial = 183; - public static final int opc_invokestatic = 184; - public static final int opc_invokeinterface = 185; -// public static final int opc_xxxunusedxxx = 186; - public static final int opc_new = 187; - public static final int opc_newarray = 188; - public static final int opc_anewarray = 189; - public static final int opc_arraylength = 190; - public static final int opc_athrow = 191; - public static final int opc_checkcast = 192; - public static final int opc_instanceof = 193; - public static final int opc_monitorenter = 194; - public static final int opc_monitorexit = 195; - public static final int opc_wide = 196; - public static final int opc_multianewarray = 197; - public static final int opc_ifnull = 198; - public static final int opc_ifnonnull = 199; - public static final int opc_goto_w = 200; - public static final int opc_jsr_w = 201; - /* Pseudo-instructions */ - public static final int opc_bytecode = 203; - public static final int opc_try = 204; - public static final int opc_endtry = 205; - public static final int opc_catch = 206; - public static final int opc_var = 207; - public static final int opc_endvar = 208; - public static final int opc_localsmap = 209; - public static final int opc_stackmap = 210; - /* PicoJava prefixes */ - public static final int opc_nonpriv = 254; - public static final int opc_priv = 255; - - /* Wide instructions */ - public static final int opc_iload_w = (opc_wide<<8)|opc_iload; - public static final int opc_lload_w = (opc_wide<<8)|opc_lload; - public static final int opc_fload_w = (opc_wide<<8)|opc_fload; - public static final int opc_dload_w = (opc_wide<<8)|opc_dload; - public static final int opc_aload_w = (opc_wide<<8)|opc_aload; - public static final int opc_istore_w = (opc_wide<<8)|opc_istore; - public static final int opc_lstore_w = (opc_wide<<8)|opc_lstore; - public static final int opc_fstore_w = (opc_wide<<8)|opc_fstore; - public static final int opc_dstore_w = (opc_wide<<8)|opc_dstore; - public static final int opc_astore_w = (opc_wide<<8)|opc_astore; - public static final int opc_ret_w = (opc_wide<<8)|opc_ret; - public static final int opc_iinc_w = (opc_wide<<8)|opc_iinc; - - /* Opcode Names */ - public static final String opcNamesTab[] = { - "nop", - "aconst_null", - "iconst_m1", - "iconst_0", - "iconst_1", - "iconst_2", - "iconst_3", - "iconst_4", - "iconst_5", - "lconst_0", - "lconst_1", - "fconst_0", - "fconst_1", - "fconst_2", - "dconst_0", - "dconst_1", - "bipush", - "sipush", - "ldc", - "ldc_w", - "ldc2_w", - "iload", - "lload", - "fload", - "dload", - "aload", - "iload_0", - "iload_1", - "iload_2", - "iload_3", - "lload_0", - "lload_1", - "lload_2", - "lload_3", - "fload_0", - "fload_1", - "fload_2", - "fload_3", - "dload_0", - "dload_1", - "dload_2", - "dload_3", - "aload_0", - "aload_1", - "aload_2", - "aload_3", - "iaload", - "laload", - "faload", - "daload", - "aaload", - "baload", - "caload", - "saload", - "istore", - "lstore", - "fstore", - "dstore", - "astore", - "istore_0", - "istore_1", - "istore_2", - "istore_3", - "lstore_0", - "lstore_1", - "lstore_2", - "lstore_3", - "fstore_0", - "fstore_1", - "fstore_2", - "fstore_3", - "dstore_0", - "dstore_1", - "dstore_2", - "dstore_3", - "astore_0", - "astore_1", - "astore_2", - "astore_3", - "iastore", - "lastore", - "fastore", - "dastore", - "aastore", - "bastore", - "castore", - "sastore", - "pop", - "pop2", - "dup", - "dup_x1", - "dup_x2", - "dup2", - "dup2_x1", - "dup2_x2", - "swap", - "iadd", - "ladd", - "fadd", - "dadd", - "isub", - "lsub", - "fsub", - "dsub", - "imul", - "lmul", - "fmul", - "dmul", - "idiv", - "ldiv", - "fdiv", - "ddiv", - "irem", - "lrem", - "frem", - "drem", - "ineg", - "lneg", - "fneg", - "dneg", - "ishl", - "lshl", - "ishr", - "lshr", - "iushr", - "lushr", - "iand", - "land", - "ior", - "lor", - "ixor", - "lxor", - "iinc", - "i2l", - "i2f", - "i2d", - "l2i", - "l2f", - "l2d", - "f2i", - "f2l", - "f2d", - "d2i", - "d2l", - "d2f", - "i2b", - "i2c", - "i2s", - "lcmp", - "fcmpl", - "fcmpg", - "dcmpl", - "dcmpg", - "ifeq", - "ifne", - "iflt", - "ifge", - "ifgt", - "ifle", - "if_icmpeq", - "if_icmpne", - "if_icmplt", - "if_icmpge", - "if_icmpgt", - "if_icmple", - "if_acmpeq", - "if_acmpne", - "goto", - "jsr", - "ret", - "tableswitch", - "lookupswitch", - "ireturn", - "lreturn", - "freturn", - "dreturn", - "areturn", - "return", - "getstatic", - "putstatic", - "getfield", - "putfield", - "invokevirtual", - "invokespecial", // was "invokenonvirtual", - "invokestatic", - "invokeinterface", - "bytecode 186", //"xxxunusedxxx", - "new", - "newarray", - "anewarray", - "arraylength", - "athrow", - "checkcast", - "instanceof", - "monitorenter", - "monitorexit", - null, // "wide", - "multianewarray", - "ifnull", - "ifnonnull", - "goto_w", - "jsr_w", - "bytecode 202", // "breakpoint", - "bytecode", - "try", - "endtry", - "catch", - "var", - "endvar", - "locals_map", - "stack_map" - }; - - /* Opcode Lengths */ - public static final int opcLengthsTab[] = { - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 2, - 3, - 2, - 3, - 3, - 2, - 2, - 2, - 2, - 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 2, - 2, - 2, - 2, - 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 2, - 99, - 99, - 1, - 1, - 1, - 1, - 1, - 1, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 5, - 0, - 3, - 2, - 3, - 1, - 1, - 3, - 3, - 1, - 1, - 0, // wide - 4, - 3, - 3, - 5, - 5, - 1, - 1, 0, 0, 0, 0, 0 // pseudo - }; - -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/StackMapData.java --- a/rt/javap/src/main/java/org/apidesign/javap/StackMapData.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - -package org.apidesign.javap; - -import java.util.*; -import java.io.*; - -import static org.apidesign.javap.RuntimeConstants.*; - -/* represents one entry of StackMap attribute - */ -class StackMapData { - final int offset; - final int[] locals; - final int[] stack; - - StackMapData(int offset, int[] locals, int[] stack) { - this.offset = offset; - this.locals = locals; - this.stack = stack; - } - - StackMapData(DataInputStream in, MethodData method) throws IOException { - offset = in.readUnsignedShort(); - int local_size = in.readUnsignedShort(); - locals = readTypeArray(in, local_size, method); - int stack_size = in.readUnsignedShort(); - stack = readTypeArray(in, stack_size, method); - } - - static final int[] readTypeArray(DataInputStream in, int length, MethodData method) throws IOException { - int[] types = new int[length]; - for (int i=0; i= (stackMapTable[nextFrameIndex].offsetDelta - + 1))) { - final StackMapTableData nextFrame = stackMapTable[nextFrameIndex]; - - lastFrameByteCodeOffset += nextFrame.offsetDelta + 1; - nextFrame.applyTo(localTypes, stackTypes); - - ++nextFrameIndex; - } - } - - public void advanceTo(final int nextByteCodeOffset) { - advanceBy(nextByteCodeOffset - byteCodeOffset); - } - - private static TypeArray getArgTypes(final String methodSignature, - final boolean isStaticMethod) { - final TypeArray argTypes = new TypeArray(); - - if (!isStaticMethod) { - argTypes.add(ITEM_Object); - } - - if (methodSignature.charAt(0) != '(') { - throw new IllegalArgumentException("Invalid method signature"); - } - - final int length = methodSignature.length(); - boolean skipType = false; - int argType; - for (int i = 1; i < length; ++i) { - switch (methodSignature.charAt(i)) { - case 'B': - case 'C': - case 'S': - case 'Z': - case 'I': - argType = ITEM_Integer; - break; - case 'J': - argType = ITEM_Long; - break; - case 'F': - argType = ITEM_Float; - break; - case 'D': - argType = ITEM_Double; - break; - case 'L': { - i = methodSignature.indexOf(';', i + 1); - if (i == -1) { - throw new IllegalArgumentException( - "Invalid method signature"); - } - argType = ITEM_Object; - break; - } - case ')': - // not interested in the return value type - return argTypes; - case '[': - if (!skipType) { - argTypes.add(ITEM_Object); - skipType = true; - } - continue; - - default: - throw new IllegalArgumentException( - "Invalid method signature"); - } - - if (!skipType) { - argTypes.add(argType); - } else { - skipType = false; - } - } - - return argTypes; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/StackMapTableData.java --- a/rt/javap/src/main/java/org/apidesign/javap/StackMapTableData.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,223 +0,0 @@ -/* - * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - -package org.apidesign.javap; - -import java.io.*; - -import static org.apidesign.javap.RuntimeConstants.*; - -/* represents one entry of StackMapTable attribute - */ -abstract class StackMapTableData { - final int frameType; - int offsetDelta; - - StackMapTableData(int frameType) { - this.frameType = frameType; - } - - abstract void applyTo(TypeArray localTypes, TypeArray stackTypes); - - protected static String toString( - final String frameType, - final int offset, - final int[] localTypes, - final int[] stackTypes) { - final StringBuilder sb = new StringBuilder(frameType); - - sb.append("(off: +").append(offset); - if (localTypes != null) { - sb.append(", locals: "); - appendTypes(sb, localTypes); - } - if (stackTypes != null) { - sb.append(", stack: "); - appendTypes(sb, stackTypes); - } - sb.append(')'); - - return sb.toString(); - } - - private static void appendTypes(final StringBuilder sb, final int[] types) { - sb.append('['); - if (types.length > 0) { - sb.append(TypeArray.typeString(types[0])); - for (int i = 1; i < types.length; ++i) { - sb.append(", "); - sb.append(TypeArray.typeString(types[i])); - } - } - sb.append(']'); - } - - static class SameFrame extends StackMapTableData { - SameFrame(int frameType, int offsetDelta) { - super(frameType); - this.offsetDelta = offsetDelta; - } - - @Override - void applyTo(TypeArray localTypes, TypeArray stackTypes) { - stackTypes.clear(); - } - - @Override - public String toString() { - return toString("SAME" + ((frameType == SAME_FRAME_EXTENDED) - ? "_FRAME_EXTENDED" : ""), - offsetDelta, - null, null); - } - } - - static class SameLocals1StackItem extends StackMapTableData { - final int[] stack; - SameLocals1StackItem(int frameType, int offsetDelta, int[] stack) { - super(frameType); - this.offsetDelta = offsetDelta; - this.stack = stack; - } - - @Override - void applyTo(TypeArray localTypes, TypeArray stackTypes) { - stackTypes.setAll(stack); - } - - @Override - public String toString() { - return toString( - "SAME_LOCALS_1_STACK_ITEM" - + ((frameType == SAME_LOCALS_1_STACK_ITEM_EXTENDED) - ? "_EXTENDED" : ""), - offsetDelta, - null, stack); - } - } - - static class ChopFrame extends StackMapTableData { - ChopFrame(int frameType, int offsetDelta) { - super(frameType); - this.offsetDelta = offsetDelta; - } - - @Override - void applyTo(TypeArray localTypes, TypeArray stackTypes) { - localTypes.setSize(localTypes.getSize() - - (SAME_FRAME_EXTENDED - frameType)); - stackTypes.clear(); - } - - @Override - public String toString() { - return toString("CHOP", offsetDelta, null, null); - } - } - - static class AppendFrame extends StackMapTableData { - final int[] locals; - AppendFrame(int frameType, int offsetDelta, int[] locals) { - super(frameType); - this.offsetDelta = offsetDelta; - this.locals = locals; - } - - @Override - void applyTo(TypeArray localTypes, TypeArray stackTypes) { - localTypes.addAll(locals); - stackTypes.clear(); - } - - @Override - public String toString() { - return toString("APPEND", offsetDelta, locals, null); - } - } - - static class FullFrame extends StackMapTableData { - final int[] locals; - final int[] stack; - FullFrame(int offsetDelta, int[] locals, int[] stack) { - super(FULL_FRAME); - this.offsetDelta = offsetDelta; - this.locals = locals; - this.stack = stack; - } - - @Override - void applyTo(TypeArray localTypes, TypeArray stackTypes) { - localTypes.setAll(locals); - stackTypes.setAll(stack); - } - - @Override - public String toString() { - return toString("FULL", offsetDelta, locals, stack); - } - } - - static StackMapTableData getInstance(DataInputStream in, MethodData method) - throws IOException { - int frameType = in.readUnsignedByte(); - - if (frameType < SAME_FRAME_BOUND) { - // same_frame - return new SameFrame(frameType, frameType); - } else if (SAME_FRAME_BOUND <= frameType && frameType < SAME_LOCALS_1_STACK_ITEM_BOUND) { - // same_locals_1_stack_item_frame - // read additional single stack element - return new SameLocals1StackItem(frameType, - (frameType - SAME_FRAME_BOUND), - StackMapData.readTypeArray(in, 1, method)); - } else if (frameType == SAME_LOCALS_1_STACK_ITEM_EXTENDED) { - // same_locals_1_stack_item_extended - return new SameLocals1StackItem(frameType, - in.readUnsignedShort(), - StackMapData.readTypeArray(in, 1, method)); - } else if (SAME_LOCALS_1_STACK_ITEM_EXTENDED < frameType && frameType < SAME_FRAME_EXTENDED) { - // chop_frame or same_frame_extended - return new ChopFrame(frameType, in.readUnsignedShort()); - } else if (frameType == SAME_FRAME_EXTENDED) { - // chop_frame or same_frame_extended - return new SameFrame(frameType, in.readUnsignedShort()); - } else if (SAME_FRAME_EXTENDED < frameType && frameType < FULL_FRAME) { - // append_frame - return new AppendFrame(frameType, in.readUnsignedShort(), - StackMapData.readTypeArray(in, frameType - SAME_FRAME_EXTENDED, method)); - } else if (frameType == FULL_FRAME) { - // full_frame - int offsetDelta = in.readUnsignedShort(); - int locals_size = in.readUnsignedShort(); - int[] locals = StackMapData.readTypeArray(in, locals_size, method); - int stack_size = in.readUnsignedShort(); - int[] stack = StackMapData.readTypeArray(in, stack_size, method); - return new FullFrame(offsetDelta, locals, stack); - } else { - throw new ClassFormatError("unrecognized frame_type in StackMapTable"); - } - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/TrapData.java --- a/rt/javap/src/main/java/org/apidesign/javap/TrapData.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - -package org.apidesign.javap; - -import java.io.*; - -/** - * Stores exception table data in code attribute. - * - * @author Sucheta Dambalkar (Adopted code from jdis) - */ -public final class TrapData { - public final short start_pc; - public final short end_pc; - public final short handler_pc; - public final short catch_cpx; - final int num; - - - /** - * Read and store exception table data in code attribute. - */ - TrapData(DataInputStream in, int num) throws IOException { - this.num=num; - start_pc = in.readShort(); - end_pc=in.readShort(); - handler_pc=in.readShort(); - catch_cpx=in.readShort(); - } - - /** - * returns recommended identifier - */ - public String ident() { - return "t"+num; - } - -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/TrapDataIterator.java --- a/rt/javap/src/main/java/org/apidesign/javap/TrapDataIterator.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ -/** - * 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.javap; - -/** - * - * @author Jaroslav Tulach - */ -public final class TrapDataIterator { - private final Hashtable exStart = new Hashtable(); - private final Hashtable exStop = new Hashtable(); - private TrapData[] current = new TrapData[10]; - private int currentCount; - - TrapDataIterator(Vector exceptionTable) { - for (int i=0 ; i < exceptionTable.size(); i++) { - final TrapData td = (TrapData)exceptionTable.elementAt(i); - put(exStart, td.start_pc, td); - put(exStop, td.end_pc, td); - } - } - - private static void put(Hashtable h, short key, TrapData td) { - Short s = Short.valueOf((short)key); - Vector v = (Vector) h.get(s); - if (v == null) { - v = new Vector(1); - h.put(s, v); - } - v.add(td); - } - - private boolean processAll(Hashtable h, Short key, boolean add) { - boolean change = false; - Vector v = (Vector)h.get(key); - if (v != null) { - int s = v.size(); - for (int i = 0; i < s; i++) { - TrapData td = (TrapData)v.elementAt(i); - if (add) { - add(td); - change = true; - } else { - remove(td); - change = true; - } - } - } - return change; - } - - public boolean advanceTo(int i) { - Short s = Short.valueOf((short)i); - boolean ch1 = processAll(exStart, s, true); - boolean ch2 = processAll(exStop, s, false); - return ch1 || ch2; - } - - public boolean useTry() { - return currentCount > 0; - } - - public TrapData[] current() { - TrapData[] copy = new TrapData[currentCount]; - for (int i = 0; i < currentCount; i++) { - copy[i] = current[i]; - } - return copy; - } - - private void add(TrapData e) { - if (currentCount == current.length) { - TrapData[] data = new TrapData[currentCount * 2]; - for (int i = 0; i < currentCount; i++) { - data[i] = current[i]; - } - current = data; - } - current[currentCount++] = e; - } - - private void remove(TrapData e) { - if (currentCount == 0) { - return; - } - int from = 0; - while (from < currentCount) { - if (e == current[from++]) { - break; - } - } - while (from < currentCount) { - current[from - 1] = current[from]; - current[from] = null; - from++; - } - currentCount--; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/TypeArray.java --- a/rt/javap/src/main/java/org/apidesign/javap/TypeArray.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,186 +0,0 @@ -/* - * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code 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 - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package org.apidesign.javap; - -import static org.apidesign.javap.RuntimeConstants.ITEM_Bogus; -import static org.apidesign.javap.RuntimeConstants.ITEM_Integer; -import static org.apidesign.javap.RuntimeConstants.ITEM_Float; -import static org.apidesign.javap.RuntimeConstants.ITEM_Double; -import static org.apidesign.javap.RuntimeConstants.ITEM_Long; -import static org.apidesign.javap.RuntimeConstants.ITEM_Null; -import static org.apidesign.javap.RuntimeConstants.ITEM_InitObject; -import static org.apidesign.javap.RuntimeConstants.ITEM_Object; -import static org.apidesign.javap.RuntimeConstants.ITEM_NewObject; - -public final class TypeArray { - private static final int CAPACITY_INCREMENT = 16; - - private int[] types; - private int size; - - public TypeArray() { - } - - public TypeArray(final TypeArray initialTypes) { - setAll(initialTypes); - } - - public void add(final int newType) { - ensureCapacity(size + 1); - types[size++] = newType; - } - - public void addAll(final TypeArray newTypes) { - addAll(newTypes.types, 0, newTypes.size); - } - - public void addAll(final int[] newTypes) { - addAll(newTypes, 0, newTypes.length); - } - - public void addAll(final int[] newTypes, - final int offset, - final int count) { - if (count > 0) { - ensureCapacity(size + count); - arraycopy(newTypes, offset, types, size, count); - size += count; - } - } - - public void set(final int index, final int newType) { - types[index] = newType; - } - - public void setAll(final TypeArray newTypes) { - setAll(newTypes.types, 0, newTypes.size); - } - - public void setAll(final int[] newTypes) { - setAll(newTypes, 0, newTypes.length); - } - - public void setAll(final int[] newTypes, - final int offset, - final int count) { - if (count > 0) { - ensureCapacity(count); - arraycopy(newTypes, offset, types, 0, count); - size = count; - } else { - clear(); - } - } - - public void setSize(final int newSize) { - if (size != newSize) { - ensureCapacity(newSize); - - for (int i = size; i < newSize; ++i) { - types[i] = 0; - } - size = newSize; - } - } - - public void clear() { - size = 0; - } - - public int getSize() { - return size; - } - - public int get(final int index) { - return types[index]; - } - - public static String typeString(final int type) { - switch (type & 0xff) { - case ITEM_Bogus: - return "_top_"; - case ITEM_Integer: - return "_int_"; - case ITEM_Float: - return "_float_"; - case ITEM_Double: - return "_double_"; - case ITEM_Long: - return "_long_"; - case ITEM_Null: - return "_null_"; - case ITEM_InitObject: // UninitializedThis - return "_init_"; - case ITEM_Object: - return "_object_"; - case ITEM_NewObject: // Uninitialized - return "_new_"; - default: - throw new IllegalArgumentException("Unknown type"); - } - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("["); - if (size > 0) { - sb.append(typeString(types[0])); - for (int i = 1; i < size; ++i) { - sb.append(", "); - sb.append(typeString(types[i])); - } - } - - return sb.append(']').toString(); - } - - private void ensureCapacity(final int minCapacity) { - if ((minCapacity == 0) - || (types != null) && (minCapacity <= types.length)) { - return; - } - - final int newCapacity = - ((minCapacity + CAPACITY_INCREMENT - 1) / CAPACITY_INCREMENT) - * CAPACITY_INCREMENT; - final int[] newTypes = new int[newCapacity]; - - if (size > 0) { - arraycopy(types, 0, newTypes, 0, size); - } - - types = newTypes; - } - - // no System.arraycopy - private void arraycopy(final int[] src, final int srcPos, - final int[] dest, final int destPos, - final int length) { - for (int i = 0; i < length; ++i) { - dest[destPos + i] = src[srcPos + i]; - } - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/javap/src/main/java/org/apidesign/javap/Vector.java --- a/rt/javap/src/main/java/org/apidesign/javap/Vector.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/** - * 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.javap; - -import org.apidesign.bck2brwsr.core.JavaScriptBody; -import org.apidesign.bck2brwsr.core.JavaScriptPrototype; - -/** A JavaScript ready replacement for java.util.Vector - * - * @author Jaroslav Tulach - */ -@JavaScriptPrototype(prototype = "new Array" ) -final class Vector { - private Object[] arr; - - Vector() { - } - - Vector(int i) { - } - - void add(Object objectType) { - addElement(objectType); - } - @JavaScriptBody(args = { "obj" }, body = - "this.push(obj);" - ) - void addElement(Object obj) { - final int s = size(); - setSize(s + 1); - setElementAt(obj, s); - } - - @JavaScriptBody(args = { }, body = - "return this.length;" - ) - int size() { - return arr == null ? 0 : arr.length; - } - - @JavaScriptBody(args = { "newArr" }, body = - "for (var i = 0; i < this.length; i++) {\n" - + " newArr[i] = this[i];\n" - + "}\n") - void copyInto(Object[] newArr) { - if (arr == null) { - return; - } - int min = Math.min(newArr.length, arr.length); - for (int i = 0; i < min; i++) { - newArr[i] = arr[i]; - } - } - - @JavaScriptBody(args = { "index" }, body = - "return this[index];" - ) - Object elementAt(int index) { - return arr[index]; - } - - private void setSize(int len) { - Object[] newArr = new Object[len]; - copyInto(newArr); - arr = newArr; - } - - @JavaScriptBody(args = { "val", "index" }, body = - "this[index] = val;" - ) - void setElementAt(Object val, int index) { - arr[index] = val; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/launcher/pom.xml --- a/rt/launcher/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ - - - 4.0.0 - - org.apidesign.bck2brwsr - rt - 0.3-SNAPSHOT - - org.apidesign.bck2brwsr - launcher - 0.3-SNAPSHOT - Bck2Brwsr Launcher - 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 - 2.8.1 - - org.apidesign.bck2brwsr.launcher.impl - - - - - - UTF-8 - - - - junit - junit - 3.8.1 - test - - - org.glassfish.grizzly - grizzly-http-server - 2.2.19 - - - ${project.groupId} - vm4brwsr - ${project.version} - - - diff -r 8264f07b1f46 -r f73c1a0234fb rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,559 +0,0 @@ -/** - * 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; - -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InterruptedIOException; -import java.io.OutputStream; -import java.io.Writer; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.apidesign.bck2brwsr.launcher.InvocationContext.Resource; -import org.apidesign.vm4brwsr.Bck2Brwsr; -import org.glassfish.grizzly.PortRange; -import org.glassfish.grizzly.http.server.HttpHandler; -import org.glassfish.grizzly.http.server.HttpServer; -import org.glassfish.grizzly.http.server.NetworkListener; -import org.glassfish.grizzly.http.server.Request; -import org.glassfish.grizzly.http.server.Response; -import org.glassfish.grizzly.http.server.ServerConfiguration; -import org.glassfish.grizzly.http.util.HttpStatus; - -/** - * Lightweight server to launch Bck2Brwsr applications and tests. - * Supports execution in native browser as well as Java's internal - * execution engine. - */ -final class Bck2BrwsrLauncher extends Launcher implements Closeable { - private static final Logger LOG = Logger.getLogger(Bck2BrwsrLauncher.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 long timeOut; - private final Res resources = new Res(); - private final String cmd; - private Object[] brwsr; - private HttpServer server; - private CountDownLatch wait; - - public Bck2BrwsrLauncher(String cmd) { - this.cmd = cmd; - } - - @Override - InvocationContext runMethod(InvocationContext c) throws IOException { - loaders.add(c.clazz.getClassLoader()); - methods.add(c); - try { - c.await(timeOut); - } catch (InterruptedException ex) { - throw new IOException(ex); - } - return c; - } - - public void setTimeout(long ms) { - timeOut = ms; - } - - public void addClassLoader(ClassLoader url) { - this.loaders.add(url); - } - - public void showURL(String startpage) throws IOException { - if (!startpage.startsWith("/")) { - startpage = "/" + startpage; - } - HttpServer s = initServer(".", true); - int last = startpage.lastIndexOf('/'); - String simpleName = startpage.substring(last); - s.getServerConfiguration().addHttpHandler(new Page(resources, startpage), simpleName); - s.getServerConfiguration().addHttpHandler(new Page(resources, null), "/"); - try { - launchServerAndBrwsr(s, simpleName); - } catch (URISyntaxException | InterruptedException ex) { - throw new IOException(ex); - } - } - - void showDirectory(File dir, String startpage) throws IOException { - if (!startpage.startsWith("/")) { - startpage = "/" + startpage; - } - HttpServer s = initServer(dir.getPath(), false); - try { - launchServerAndBrwsr(s, startpage); - } catch (URISyntaxException | InterruptedException ex) { - throw new IOException(ex); - } - } - - @Override - public void initialize() throws IOException { - try { - executeInBrowser(); - } catch (InterruptedException ex) { - final InterruptedIOException iio = new InterruptedIOException(ex.getMessage()); - iio.initCause(ex); - throw iio; - } catch (Exception ex) { - if (ex instanceof IOException) { - throw (IOException)ex; - } - if (ex instanceof RuntimeException) { - throw (RuntimeException)ex; - } - throw new IOException(ex); - } - } - - private HttpServer initServer(String path, boolean addClasses) throws IOException { - HttpServer s = HttpServer.createSimpleServer(path, new PortRange(8080, 65535)); - - final ServerConfiguration conf = s.getServerConfiguration(); - if (addClasses) { - conf.addHttpHandler(new VM(resources), "/bck2brwsr.js"); - conf.addHttpHandler(new Classes(resources), "/classes/"); - } - return s; - } - - private void executeInBrowser() throws InterruptedException, URISyntaxException, IOException { - wait = new CountDownLatch(1); - server = initServer(".", true); - final ServerConfiguration conf = server.getServerConfiguration(); - - class DynamicResourceHandler extends HttpHandler { - private final InvocationContext ic; - 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() { - conf.removeHttpHandler(this); - } - - @Override - public void service(Request request, Response response) throws Exception { - for (Resource r : ic.resources) { - if (r.httpPath.equals(request.getRequestURI())) { - LOG.log(Level.INFO, "Serving HttpResource for {0}", request.getRequestURI()); - response.setContentType(r.httpType); - copyStream(r.httpContent, response.getOutputStream(), null); - } - } - } - } - - conf.addHttpHandler(new Page(resources, - "org/apidesign/bck2brwsr/launcher/harness.xhtml" - ), "/execute"); - - conf.addHttpHandler(new HttpHandler() { - int cnt; - List cases = new ArrayList<>(); - DynamicResourceHandler prev; - @Override - public void service(Request request, Response response) throws Exception { - String id = request.getParameter("request"); - String value = request.getParameter("result"); - - - InvocationContext mi = null; - int caseNmbr = -1; - - if (id != null && value != null) { - LOG.log(Level.INFO, "Received result for case {0} = {1}", new Object[]{id, value}); - value = decodeURL(value); - int indx = Integer.parseInt(id); - cases.get(indx).result(value, null); - if (++indx < cases.size()) { - mi = cases.get(indx); - LOG.log(Level.INFO, "Re-executing case {0}", indx); - caseNmbr = indx; - } - } else { - if (!cases.isEmpty()) { - LOG.info("Re-executing test cases"); - mi = cases.get(0); - caseNmbr = 0; - } - } - - if (prev != null) { - prev.close(); - prev = null; - } - - if (mi == null) { - mi = methods.take(); - caseNmbr = cnt++; - } - if (mi == END) { - response.getWriter().write(""); - wait.countDown(); - cnt = 0; - LOG.log(Level.INFO, "End of data reached. Exiting."); - return; - } - - if (!mi.resources.isEmpty()) { - prev = new DynamicResourceHandler(mi); - } - - cases.add(mi); - final String cn = mi.clazz.getName(); - final String mn = mi.methodName; - LOG.log(Level.INFO, "Request for {0} case. Sending {1}.{2}", new Object[]{caseNmbr, cn, mn}); - response.getWriter().write("{" - + "className: '" + cn + "', " - + "methodName: '" + mn + "', " - + "request: " + caseNmbr - ); - if (mi.html != null) { - response.getWriter().write(", html: '"); - response.getWriter().write(encodeJSON(mi.html)); - response.getWriter().write("'"); - } - response.getWriter().write("}"); - } - }, "/data"); - - this.brwsr = launchServerAndBrwsr(server, "/execute"); - } - - private static String encodeJSON(String in) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < in.length(); i++) { - char ch = in.charAt(i); - if (ch < 32 || ch == '\'' || ch == '"') { - sb.append("\\u"); - String hs = "0000" + Integer.toHexString(ch); - hs = hs.substring(hs.length() - 4); - sb.append(hs); - } else { - sb.append(ch); - } - } - return sb.toString(); - } - - @Override - public void shutdown() throws IOException { - methods.offer(END); - for (;;) { - int prev = methods.size(); - try { - if (wait != null && wait.await(timeOut, TimeUnit.MILLISECONDS)) { - break; - } - } catch (InterruptedException ex) { - throw new IOException(ex); - } - if (prev == methods.size()) { - LOG.log( - Level.WARNING, - "Timeout and no test has been executed meanwhile (at {0}). Giving up.", - methods.size() - ); - break; - } - LOG.log(Level.INFO, - "Timeout, but tests got from {0} to {1}. Trying again.", - new Object[]{prev, methods.size()} - ); - } - stopServerAndBrwsr(server, brwsr); - } - - static void copyStream(InputStream is, OutputStream os, String baseURL, String... params) throws IOException { - for (;;) { - int ch = is.read(); - if (ch == -1) { - break; - } - if (ch == '$' && params.length > 0) { - int cnt = is.read() - '0'; - if (cnt == 'U' - '0') { - os.write(baseURL.getBytes("UTF-8")); - } - if (cnt >= 0 && cnt < params.length) { - os.write(params[cnt].getBytes("UTF-8")); - } - } else { - os.write(ch); - } - } - } - - 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); - LOG.log(Level.INFO, "Showing {0}", uri); - if (cmd == null) { - try { - LOG.log(Level.INFO, "Trying Desktop.browse on {0} {2} by {1}", new Object[] { - System.getProperty("java.vm.name"), - System.getProperty("java.vm.vendor"), - System.getProperty("java.vm.version"), - }); - java.awt.Desktop.getDesktop().browse(uri); - LOG.log(Level.INFO, "Desktop.browse successfully finished"); - return null; - } catch (UnsupportedOperationException ex) { - LOG.log(Level.INFO, "Desktop.browse not supported: {0}", ex.getMessage()); - LOG.log(Level.FINE, null, ex); - } - } - { - String cmdName = cmd == null ? "xdg-open" : cmd; - String[] cmdArr = { - cmdName, uri.toString() - }; - LOG.log(Level.INFO, "Launching {0}", Arrays.toString(cmdArr)); - final Process process = Runtime.getRuntime().exec(cmdArr); - return new Object[] { process, null }; - } - } - - private static String decodeURL(String s) { - for (;;) { - int pos = s.indexOf('%'); - if (pos == -1) { - return s; - } - int i = Integer.parseInt(s.substring(pos + 1, pos + 2), 16); - s = s.substring(0, pos) + (char)i + s.substring(pos + 2); - } - } - - private void stopServerAndBrwsr(HttpServer server, Object[] brwsr) throws IOException { - if (brwsr == null) { - return; - } - Process process = (Process)brwsr[0]; - - server.stop(); - InputStream stdout = process.getInputStream(); - InputStream stderr = process.getErrorStream(); - drain("StdOut", stdout); - drain("StdErr", stderr); - process.destroy(); - int res; - try { - res = process.waitFor(); - } catch (InterruptedException ex) { - throw new IOException(ex); - } - LOG.log(Level.INFO, "Exit code: {0}", res); - - deleteTree((File)brwsr[1]); - } - - private static void drain(String name, InputStream is) throws IOException { - int av = is.available(); - if (av > 0) { - StringBuilder sb = new StringBuilder(); - sb.append("v== ").append(name).append(" ==v\n"); - while (av-- > 0) { - sb.append((char)is.read()); - } - sb.append("\n^== ").append(name).append(" ==^"); - LOG.log(Level.INFO, sb.toString()); - } - } - - private void deleteTree(File file) { - if (file == null) { - return; - } - File[] arr = file.listFiles(); - if (arr != null) { - for (File s : arr) { - deleteTree(s); - } - } - file.delete(); - } - - @Override - public void close() throws IOException { - shutdown(); - } - - private class Res implements Bck2Brwsr.Resources { - @Override - public InputStream get(String resource) throws IOException { - for (ClassLoader l : loaders) { - URL u = null; - Enumeration en = l.getResources(resource); - while (en.hasMoreElements()) { - u = en.nextElement(); - } - if (u != null) { - return u.openStream(); - } - } - throw new IOException("Can't find " + resource); - } - } - - private static class Page extends HttpHandler { - private final String resource; - private final String[] args; - private final Res res; - - public Page(Res res, String resource, String... args) { - this.res = res; - this.resource = resource; - this.args = args.length == 0 ? new String[] { "$0" } : args; - } - - @Override - public void service(Request request, Response response) throws Exception { - String r = resource; - if (r == null) { - r = request.getHttpHandlerPath(); - } - if (r.startsWith("/")) { - r = r.substring(1); - } - String[] replace = {}; - if (r.endsWith(".html")) { - response.setContentType("text/html"); - LOG.info("Content type text/html"); - replace = args; - } - if (r.endsWith(".xhtml")) { - response.setContentType("application/xhtml+xml"); - LOG.info("Content type application/xhtml+xml"); - replace = args; - } - OutputStream os = response.getOutputStream(); - try (InputStream is = res.get(r)) { - copyStream(is, os, request.getRequestURL().toString(), replace); - } catch (IOException ex) { - response.setDetailMessage(ex.getLocalizedMessage()); - response.setError(); - response.setStatus(404); - } - } - } - - private static class VM extends HttpHandler { - private final String bck2brwsr; - - public VM(Res loader) throws IOException { - StringBuilder sb = new StringBuilder(); - Bck2Brwsr.generate(sb, loader); - sb.append( - "(function WrapperVM(global) {" - + " function ldCls(res) {\n" - + " var request = new XMLHttpRequest();\n" - + " request.open('GET', '/classes/' + res, false);\n" - + " request.send();\n" - + " if (request.status !== 200) return null;\n" - + " var arr = eval('(' + request.responseText + ')');\n" - + " return arr;\n" - + " }\n" - + " var prevvm = global.bck2brwsr;\n" - + " global.bck2brwsr = function() {\n" - + " var args = Array.prototype.slice.apply(arguments);\n" - + " args.unshift(ldCls);\n" - + " return prevvm.apply(null, args);\n" - + " };\n" - + "})(this);\n" - ); - this.bck2brwsr = sb.toString(); - } - - @Override - public void service(Request request, Response response) throws Exception { - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/javascript"); - response.getWriter().write(bck2brwsr); - } - } - - private static class Classes extends HttpHandler { - private final Res loader; - - public Classes(Res loader) { - this.loader = loader; - } - - @Override - public void service(Request request, Response response) throws Exception { - String res = request.getHttpHandlerPath(); - if (res.startsWith("/")) { - res = res.substring(1); - } - try (InputStream is = loader.get(res)) { - response.setContentType("text/javascript"); - Writer w = response.getWriter(); - w.append("["); - for (int i = 0;; i++) { - int b = is.read(); - if (b == -1) { - break; - } - if (i > 0) { - w.append(", "); - } - if (i % 20 == 0) { - w.write("\n"); - } - if (b > 127) { - b = b - 256; - } - w.append(Integer.toString(b)); - } - w.append("\n]"); - } catch (IOException ex) { - response.setStatus(HttpStatus.NOT_FOUND_404); - response.setError(); - response.setDetailMessage(ex.getMessage()); - } - } - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,110 +0,0 @@ -/** - * 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; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** Represents individual method invocation, its context and its result. - * - * @author Jaroslav Tulach - */ -public final class InvocationContext { - final CountDownLatch wait = new CountDownLatch(1); - final Class clazz; - final String methodName; - private final Launcher launcher; - private String result; - private Throwable exception; - String html; - final List resources = new ArrayList<>(); - - InvocationContext(Launcher launcher, Class clazz, String methodName) { - this.launcher = launcher; - this.clazz = clazz; - this.methodName = methodName; - } - - /** An HTML fragment to be available for the execution. Useful primarily when - * executing in a browser via {@link Launcher#createBrowser(java.lang.String)}. - * @param html the html fragment - */ - public void setHtmlFragment(String html) { - this.html = html; - } - - /** HTTP resource to be available during execution. An invocation may - * perform an HTTP query and obtain a resource relative to the page. - */ - public void addHttpResource(String relativePath, String mimeType, InputStream content) { - if (relativePath == null || mimeType == null || content == null) { - throw new NullPointerException(); - } - resources.add(new Resource(content, mimeType, relativePath)); - } - - /** Invokes the associated method. - * @return the textual result of the invocation - */ - public String invoke() throws IOException { - launcher.runMethod(this); - return toString(); - } - - /** Obtains textual result of the invocation. - * @return text representing the exception or result value - */ - @Override - public String toString() { - if (exception != null) { - return exception.toString(); - } - return result; - } - - /** - * @param timeOut - * @throws InterruptedException - */ - void await(long timeOut) throws InterruptedException { - wait.await(timeOut, TimeUnit.MILLISECONDS); - } - - void result(String r, Throwable e) { - this.result = r; - this.exception = e; - wait.countDown(); - } - - - static final class Resource { - final InputStream httpContent; - final String httpType; - final String httpPath; - - Resource(InputStream httpContent, String httpType, String httpPath) { - this.httpContent = httpContent; - this.httpType = httpType; - this.httpPath = httpPath; - } - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/JSLauncher.java --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/JSLauncher.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,132 +0,0 @@ -/** - * 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; - -import org.apidesign.bck2brwsr.launcher.impl.Console; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Enumeration; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.script.Invocable; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; -import org.apidesign.vm4brwsr.Bck2Brwsr; - -/** - * Tests execution in Java's internal scripting engine. - */ -final class JSLauncher extends Launcher { - private static final Logger LOG = Logger.getLogger(JSLauncher.class.getName()); - private Set loaders = new LinkedHashSet<>(); - private final Res resources = new Res(); - private Invocable code; - private StringBuilder codeSeq; - private Object console; - - - @Override InvocationContext runMethod(InvocationContext mi) { - loaders.add(mi.clazz.getClassLoader()); - try { - long time = System.currentTimeMillis(); - LOG.log(Level.FINE, "Invoking {0}.{1}", new Object[]{mi.clazz.getName(), mi.methodName}); - String res = code.invokeMethod( - console, - "invoke__Ljava_lang_String_2Ljava_lang_String_2Ljava_lang_String_2", - mi.clazz.getName(), mi.methodName).toString(); - time = System.currentTimeMillis() - time; - LOG.log(Level.FINE, "Resut of {0}.{1} = {2} in {3} ms", new Object[]{mi.clazz.getName(), mi.methodName, res, time}); - mi.result(res, null); - } catch (ScriptException | NoSuchMethodException ex) { - mi.result(null, ex); - } - return mi; - } - - public void addClassLoader(ClassLoader url) { - this.loaders.add(url); - } - - @Override - public void initialize() throws IOException { - try { - initRhino(); - } catch (Exception ex) { - if (ex instanceof IOException) { - throw (IOException)ex; - } - if (ex instanceof RuntimeException) { - throw (RuntimeException)ex; - } - throw new IOException(ex); - } - } - - private void initRhino() throws IOException, ScriptException, NoSuchMethodException { - StringBuilder sb = new StringBuilder(); - Bck2Brwsr.generate(sb, new Res()); - - ScriptEngineManager sem = new ScriptEngineManager(); - ScriptEngine mach = sem.getEngineByExtension("js"); - - sb.append( - "\nvar vm = new bck2brwsr(org.apidesign.bck2brwsr.launcher.impl.Console.read);" - + "\nfunction initVM() { return vm; };" - + "\n"); - - Object res = mach.eval(sb.toString()); - if (!(mach instanceof Invocable)) { - throw new IOException("It is invocable object: " + res); - } - code = (Invocable) mach; - codeSeq = sb; - - Object vm = code.invokeFunction("initVM"); - console = code.invokeMethod(vm, "loadClass", Console.class.getName()); - } - - @Override - public void shutdown() throws IOException { - } - - @Override - public String toString() { - return codeSeq.toString(); - } - - private class Res implements Bck2Brwsr.Resources { - @Override - public InputStream get(String resource) throws IOException { - for (ClassLoader l : loaders) { - URL u = null; - Enumeration en = l.getResources(resource); - while (en.hasMoreElements()) { - u = en.nextElement(); - } - if (u != null) { - return u.openStream(); - } - } - throw new IOException("Can't find " + resource); - } - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -/** - * 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; - -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.net.URLClassLoader; -import org.apidesign.vm4brwsr.Bck2Brwsr; - -/** An abstraction for executing tests in a Bck2Brwsr virtual machine. - * Either in {@linkm Launcher#createJavaScript JavaScript engine}, - * or in {@linkm Launcher#createBrowser external browser}. - * - * @author Jaroslav Tulach - */ -public abstract class Launcher { - - Launcher() { - } - - /** Initializes the launcher. This may mean starting a web browser or - * initializing execution engine. - * @throws IOException if something goes wrong - */ - public abstract void initialize() throws IOException; - - /** Shuts down the launcher. - * @throws IOException if something goes wrong - */ - public abstract void shutdown() throws IOException; - - - /** Builds an invocation context. The context can later be customized - * and {@link InvocationContext#invoke() invoked}. - * - * @param clazz the class to execute method from - * @param method the method to execute - * @return the context pointing to the selected method - */ - public InvocationContext createInvocation(Class clazz, String method) { - return new InvocationContext(this, clazz, method); - } - - - /** Creates launcher that uses internal JavaScript engine (Rhino). - * @return the launcher - */ - public static Launcher createJavaScript() { - final JSLauncher l = new JSLauncher(); - l.addClassLoader(Bck2Brwsr.class.getClassLoader()); - return l; - } - - /** Creates launcher that is using external browser. - * - * @param cmd null to use java.awt.Desktop to show the launcher - * or a string to execute in an external process (with a parameter to the URL) - * @return launcher executing in external browser. - */ - public static Launcher createBrowser(String cmd) { - final Bck2BrwsrLauncher l = new Bck2BrwsrLauncher(cmd); - l.addClassLoader(Bck2Brwsr.class.getClassLoader()); - l.setTimeout(180000); - return l; - } - - /** Starts an HTTP server which provides access to classes and resources - * available in the classes URL and shows a start page - * available as {@link ClassLoader#getResource(java.lang.String)} from the - * provide classloader. Opens a browser with URL showing the start page. - * - * @param classes classloader offering access to classes and resources - * @param startpage page to show in the browser - * @return interface that allows one to stop the server - * @throws IOException if something goes wrong - */ - public static Closeable showURL(URLClassLoader classes, String startpage) throws IOException { - Bck2BrwsrLauncher l = new Bck2BrwsrLauncher(null); - l.addClassLoader(classes); - l.showURL(startpage); - return l; - } - /** Starts an HTTP server which provides access to certain directory. - * The startpage should be relative location inside the root - * driecotry - * Opens a browser with URL showing the start page. - * - * @param directory the root directory on disk - * @praam startpage relative path from the root to the page - * @exception IOException if something goes wrong. - */ - public static Closeable showDir(File directory, String startpage) throws IOException { - Bck2BrwsrLauncher l = new Bck2BrwsrLauncher(null); - l.showDirectory(directory, startpage); - return l; - } - - abstract InvocationContext runMethod(InvocationContext c) throws IOException; -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java --- a/rt/launcher/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,255 +0,0 @@ -/** - * 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.impl; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URL; -import java.util.Enumeration; -import org.apidesign.bck2brwsr.core.JavaScriptBody; - -/** - * - * @author Jaroslav Tulach - */ -public class Console { - private Console() { - } - static { - turnAssetionStatusOn(); - } - - @JavaScriptBody(args = {"id", "attr"}, body = - "return window.document.getElementById(id)[attr].toString();") - private static native Object getAttr(String id, 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 = {}, body = "return; window.close();") - private static native void closeWindow(); - - private static void log(String newText) { - String id = "bck2brwsr.result"; - String attr = "value"; - setAttr(id, attr, getAttr(id, attr) + "\n" + newText); - setAttr(id, "scrollTop", getAttr(id, "scrollHeight")); - } - - public static void execute() throws Exception { - String clazz = (String) getAttr("clazz", "value"); - String method = (String) getAttr("method", "value"); - Object res = invokeMethod(clazz, method); - setAttr("bck2brwsr.result", "value", res); - } - - @JavaScriptBody(args = { "url", "callback", "arr" }, body = "" - + "var request = new XMLHttpRequest();\n" - + "request.open('GET', url, true);\n" - + "request.onreadystatechange = function() {\n" - + " if (this.readyState!==4) return;\n" - + " arr[0] = this.responseText;\n" - + " callback.run__V();\n" - + "};" - + "request.send();" - ) - private static native void loadText(String url, Runnable callback, String[] arr) throws IOException; - - public static void harness(String url) throws IOException { - log("Connecting to " + url); - Request r = new Request(url); - } - - private static class Request implements Runnable { - private final String[] arr = { null }; - private final String url; - - private Request(String url) throws IOException { - this.url = url; - loadText(url, this, arr); - } - - @Override - public void run() { - try { - String data = arr[0]; - log("\nGot \"" + data + "\""); - - if (data == null) { - log("Some error exiting"); - closeWindow(); - return; - } - - if (data.isEmpty()) { - log("No data, exiting"); - closeWindow(); - return; - } - - Case c = Case.parseData(data); - if (c.getHtmlFragment() != null) { - setAttr("bck2brwsr.fragment", "innerHTML", c.getHtmlFragment()); - } - log("Invoking " + c.getClassName() + '.' + c.getMethodName() + " as request: " + c.getRequestId()); - - Object result = invokeMethod(c.getClassName(), c.getMethodName()); - - setAttr("bck2brwsr.fragment", "innerHTML", ""); - log("Result: " + result); - - result = encodeURL("" + result); - - log("Sending back: " + url + "?request=" + c.getRequestId() + "&result=" + result); - String u = url + "?request=" + c.getRequestId() + "&result=" + result; - - loadText(u, this, arr); - - } catch (Exception ex) { - log(ex.getClass().getName() + ":" + ex.getMessage()); - } - } - } - - private static String encodeURL(String r) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < r.length(); i++) { - int ch = r.charAt(i); - if (ch < 32 || ch == '%' || ch == '+') { - sb.append("%").append(("0" + Integer.toHexString(ch)).substring(0, 2)); - } else { - if (ch == 32) { - sb.append("+"); - } else { - sb.append((char)ch); - } - } - } - return sb.toString(); - } - - static String invoke(String clazz, String method) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException { - final Object r = invokeMethod(clazz, method); - return r == null ? "null" : r.toString().toString(); - } - - /** Helper method that inspects the classpath and loads given resource - * (usually a class file). Used while running tests in Rhino. - * - * @param name resource name to find - * @return the array of bytes in the given resource - * @throws IOException I/O in case something goes wrong - */ - public static byte[] read(String name) throws IOException { - URL u = null; - Enumeration en = Console.class.getClassLoader().getResources(name); - while (en.hasMoreElements()) { - u = en.nextElement(); - } - if (u == null) { - throw new IOException("Can't find " + name); - } - try (InputStream is = u.openStream()) { - byte[] arr; - arr = new byte[is.available()]; - int offset = 0; - while (offset < arr.length) { - int len = is.read(arr, offset, arr.length - offset); - if (len == -1) { - throw new IOException("Can't read " + name); - } - offset += len; - } - return arr; - } - } - - private static Object invokeMethod(String clazz, String method) - throws ClassNotFoundException, InvocationTargetException, - SecurityException, IllegalAccessException, IllegalArgumentException, - InstantiationException { - Method found = null; - Class c = Class.forName(clazz); - for (Method m : c.getMethods()) { - if (m.getName().equals(method)) { - found = m; - } - } - Object res; - if (found != null) { - try { - if ((found.getModifiers() & Modifier.STATIC) != 0) { - res = found.invoke(null); - } else { - res = found.invoke(c.newInstance()); - } - } catch (Throwable ex) { - res = ex.getClass().getName() + ":" + ex.getMessage(); - } - } else { - res = "Can't find method " + method + " in " + clazz; - } - return res; - } - - @JavaScriptBody(args = {}, body = "vm.desiredAssertionStatus = true;") - private static void turnAssetionStatusOn() { - } - - private static final class Case { - private final Object data; - - private Case(Object data) { - this.data = data; - } - - public static Case parseData(String s) { - return new Case(toJSON(s)); - } - - public String getMethodName() { - return value("methodName", data); - } - - public String getClassName() { - return value("className", data); - } - - public String getRequestId() { - return value("request", data); - } - - public String getHtmlFragment() { - return value("html", data); - } - - @JavaScriptBody(args = "s", body = "return eval('(' + s + ')');") - private static native Object toJSON(String s); - - @JavaScriptBody(args = {"p", "d"}, body = - "var v = d[p];\n" - + "if (typeof v === 'undefined') return null;\n" - + "return v.toString();" - ) - private static native String value(String p, Object d); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/launcher/src/main/resources/org/apidesign/bck2brwsr/launcher/harness.xhtml --- a/rt/launcher/src/main/resources/org/apidesign/bck2brwsr/launcher/harness.xhtml Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ - - - - - - Bck2Brwsr Harness - - - - - -

Bck2Brwsr Execution Harness

- - - -
- - - - diff -r 8264f07b1f46 -r f73c1a0234fb rt/mojo/pom.xml --- a/rt/mojo/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/mojo/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -1,18 +1,17 @@ - + 4.0.0 org.apidesign.bck2brwsr rt - 0.3-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr - mojo - 0.3-SNAPSHOT + bck2brwsr-maven-plugin + 0.9-SNAPSHOT maven-plugin - Bck2Brwsr Maven Project - http://maven.apache.org + Bck2Brwsr Maven Plugin + http://bck2brwsr.apidesign.org/ @@ -63,7 +62,7 @@ ${project.groupId} vm4brwsr - 0.3-SNAPSHOT + ${project.version} emul.mini @@ -82,5 +81,15 @@ launcher ${project.version} + + ${project.groupId} + launcher.http + ${project.version} + + + ${project.groupId} + launcher.fx + ${project.version} + diff -r 8264f07b1f46 -r f73c1a0234fb rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/BrswrMojo.java --- a/rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/BrswrMojo.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -/** - * 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.mojo; - -import java.io.Closeable; -import org.apache.maven.plugin.AbstractMojo; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import org.apache.maven.artifact.Artifact; -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.project.MavenProject; -import org.apidesign.bck2brwsr.launcher.Launcher; - -/** Executes given HTML page in a browser. */ -@Mojo(name="brwsr", defaultPhase=LifecyclePhase.NONE) -public class BrswrMojo extends AbstractMojo { - public BrswrMojo() { - } - /** Resource to show as initial page */ - @Parameter - private String startpage; - - @Parameter(defaultValue="${project}") - private MavenProject prj; - - /** Root of the class files */ - @Parameter(defaultValue="${project.build.directory}/classes") - private File classes; - - /** Root of all pages, and files, etc. */ - @Parameter - private File directory; - - @Override - public void execute() throws MojoExecutionException { - 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()); - try { - httpServer = Launcher.showURL(url, startpage()); - } catch (Exception ex) { - throw new MojoExecutionException("Can't open " + startpage(), ex); - } - } - System.in.read(); - httpServer.close(); - } catch (IOException ex) { - throw new MojoExecutionException("Can't show the browser", ex); - } - } - - private String startpage() { - return startpage; - } - - private static URLClassLoader buildClassLoader(File root, Collection deps) throws MalformedURLException { - List arr = new ArrayList(); - arr.add(root.toURI().toURL()); - for (Artifact a : deps) { - final File f = a.getFile(); - if (f != null) { - arr.add(f.toURI().toURL()); - } - } - return new URLClassLoader(arr.toArray(new URL[0]), BrswrMojo.class.getClassLoader()); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/BrwsrMojo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/BrwsrMojo.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,121 @@ +/** + * 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.mojo; + +import java.io.Closeable; +import org.apache.maven.plugin.AbstractMojo; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +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", + requiresDependencyResolution = ResolutionScope.RUNTIME, + defaultPhase=LifecyclePhase.NONE +) +public class BrwsrMojo extends AbstractMojo { + public BrwsrMojo() { + } + + /** The identification of a launcher to use. Known values fxbrwsr, + * bck2brwsr, or + * name of an external process to execute. + */ + @Parameter + private String launcher; + + + /** Resource to show as initial page */ + @Parameter + private String startpage; + + @Parameter(defaultValue="${project}") + private MavenProject prj; + + /** Root of the class files */ + @Parameter(defaultValue="${project.build.directory}/classes") + private File classes; + + /** Root of all pages, and files, etc. */ + @Parameter + private File directory; + + @Override + public void execute() throws MojoExecutionException { + if (startpage == null) { + throw new MojoExecutionException("You have to provide a start page"); + } + try { + Closeable httpServer; + if (directory != null) { + URLClassLoader url = buildClassLoader(classes, prj.getArtifacts()); + httpServer = Launcher.showDir(launcher, directory, url, startpage); + } else { + 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); + } + } + System.in.read(); + httpServer.close(); + } catch (IOException ex) { + throw new MojoExecutionException("Can't show the browser", ex); + } + } + + private String startpage() { + return startpage; + } + + private static URLClassLoader buildClassLoader(File root, Collection deps) throws MalformedURLException { + List arr = new ArrayList(); + arr.add(root.toURI().toURL()); + for (Artifact a : deps) { + final File f = a.getFile(); + if (f != null) { + arr.add(f.toURI().toURL()); + } + } + return new URLClassLoader(arr.toArray(new URL[0]), BrwsrMojo.class.getClassLoader()); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/Java2JavaScript.java --- a/rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/Java2JavaScript.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/Java2JavaScript.java Mon Oct 07 14:20:58 2013 +0200 @@ -33,26 +33,43 @@ 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() { } /** Root of the class files */ @Parameter(defaultValue="${project.build.directory}/classes") private File classes; - /** File to generate. Defaults bootjava.js in the first non-empty - package under the classes directory */ + /** JavaScript file to generate */ @Parameter private File javascript; + + /** Additional classes that should be pre-compiled into the javascript + * file. By default compiles all classes found under classes + * directory and their transitive closure. + */ + @Parameter + private List compileclasses; @Parameter(defaultValue="${project}") private MavenProject prj; - - + + /** + * The obfuscation level for the generated JavaScript file. + * + * @since 0.5 + */ + @Parameter(defaultValue="NONE") + private ObfuscationLevel obfuscation; @Override public void execute() throws MojoExecutionException { @@ -60,38 +77,32 @@ throw new MojoExecutionException("Can't find " + classes); } - if (javascript == null) { - javascript = new File(findNonEmptyFolder(classes), "bootjava.js"); - } - List arr = new ArrayList(); long newest = collectAllClasses("", classes, arr); + if (compileclasses != null) { + arr.retainAll(compileclasses); + arr.addAll(compileclasses); + } + if (javascript.lastModified() > newest) { return; } try { - URLClassLoader url = buildClassLoader(classes, prj.getDependencyArtifacts()); + URLClassLoader url = buildClassLoader(classes, prj.getArtifacts()); FileWriter w = new FileWriter(javascript); - Bck2Brwsr.generate(w, url, arr.toArray(new String[0])); + Bck2Brwsr.newCompiler(). + obfuscation(obfuscation). + resources(url). + addRootClasses(arr.toArray(new String[0])). + generate(w); w.close(); } catch (IOException ex) { throw new MojoExecutionException("Can't compile", ex); } } - private static File findNonEmptyFolder(File dir) throws MojoExecutionException { - if (!dir.isDirectory()) { - throw new MojoExecutionException("Not a directory " + dir); - } - File[] arr = dir.listFiles(); - if (arr.length == 1 && arr[0].isDirectory()) { - return findNonEmptyFolder(arr[0]); - } - return dir; - } - private static long collectAllClasses(String prefix, File toCheck, List arr) { File[] files = toCheck.listFiles(); if (files != null) { @@ -104,7 +115,8 @@ } return newest; } else if (toCheck.getName().endsWith(".class")) { - arr.add(prefix.substring(0, prefix.length() - 7)); + final String cls = prefix.substring(0, prefix.length() - 7); + arr.add(cls); return toCheck.lastModified(); } else { return 0L; @@ -115,7 +127,9 @@ List arr = new ArrayList(); arr.add(root.toURI().toURL()); for (Artifact a : deps) { - arr.add(a.getFile().toURI().toURL()); + if (a.getFile() != null) { + arr.add(a.getFile().toURI().toURL()); + } } return new URLClassLoader(arr.toArray(new URL[0]), Java2JavaScript.class.getClassLoader()); } diff -r 8264f07b1f46 -r f73c1a0234fb rt/mojo/src/main/resources/META-INF/maven/archetype-metadata.xml --- a/rt/mojo/src/main/resources/META-INF/maven/archetype-metadata.xml Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ - - - - - - src/main/java - - **/App.java - - - - src/main/resources - - **/*.xhtml - **/*.html - - - - src/test/java - - **/*Test.java - - - - - - nbactions.xml - - - - - - bck2brwsr-assembly.xml - - - - \ No newline at end of file diff -r 8264f07b1f46 -r f73c1a0234fb rt/mojo/src/main/resources/archetype-resources/bck2brwsr-assembly.xml --- a/rt/mojo/src/main/resources/archetype-resources/bck2brwsr-assembly.xml Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ - - - - - bck2brwsr - - zip - - public_html - - - false - runtime - lib - - *:jar - *:rt - - - - false - provided - - *:js - - true - / - - - - - ${project.build.directory}/${project.build.finalName}.jar - / - - - ${project.build.directory}/classes/${package.replace('.','/')}/index.html - / - index.html - - - - \ No newline at end of file diff -r 8264f07b1f46 -r f73c1a0234fb rt/mojo/src/main/resources/archetype-resources/nbactions.xml --- a/rt/mojo/src/main/resources/archetype-resources/nbactions.xml Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ - - - - run - - process-classes - org.apidesign.bck2brwsr:mojo:0.3-SNAPSHOT:brwsr - - - diff -r 8264f07b1f46 -r f73c1a0234fb rt/mojo/src/main/resources/archetype-resources/pom.xml --- a/rt/mojo/src/main/resources/archetype-resources/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ - - - 4.0.0 - - ${groupId} - ${artifactId} - ${version} - jar - - ${artifactId} - - - - java.net - Java.net - https://maven.java.net/content/repositories/snapshots/ - - true - - - - netbeans - NetBeans - http://bits.netbeans.org/maven2/ - - - - - java.net - Local Maven repository of releases - https://maven.java.net/content/repositories/snapshots/ - - true - - - - - - UTF-8 - - - - - org.apidesign.bck2brwsr - mojo - 0.3-SNAPSHOT - - - - brwsr - - - - - ${package.replace('.','/')}/index.html - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.7 - 1.7 - - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - - true - lib/ - - - - - - maven-assembly-plugin - 2.4 - - - distro-assembly - package - - single - - - - bck2brwsr-assembly.xml - - - - - - - - - - - org.apidesign.bck2brwsr - emul - 0.3-SNAPSHOT - rt - - - org.apidesign.bck2brwsr - javaquery.api - 0.3-SNAPSHOT - - - org.testng - testng - 6.5.2 - test - - - org.apidesign.bck2brwsr - vm4brwsr - js - zip - 0.3-SNAPSHOT - provided - - - org.apidesign.bck2brwsr - vmtest - 0.3-SNAPSHOT - test - - - diff -r 8264f07b1f46 -r f73c1a0234fb rt/mojo/src/main/resources/archetype-resources/src/main/java/App.java --- a/rt/mojo/src/main/resources/archetype-resources/src/main/java/App.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -package ${package}; - -import org.apidesign.bck2brwsr.htmlpage.api.*; -import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*; -import org.apidesign.bck2brwsr.htmlpage.api.Page; -import org.apidesign.bck2brwsr.htmlpage.api.Property; -import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; - -/** Edit the index.xhtml file. Use 'id' to name certain HTML elements. - * Use this class to define behavior of the elements. - */ -@Page(xhtml="index.html", className="Index", properties={ - @Property(name="name", type=String.class) -}) -public class App { - static { - Index model = new Index(); - model.setName("World"); - model.applyBindings(); - } - - @On(event = CLICK, id="hello") - static void hello(Index m) { - GraphicsContext g = m.CANVAS.getContext(); - g.clearRect(0, 0, 1000, 1000); - g.setFont("italic 40px Calibri"); - g.fillText(m.getHelloMessage(), 10, 40); - } - - @ComputedProperty - static String helloMessage(String name) { - return "Hello " + name + "!"; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/mojo/src/main/resources/archetype-resources/src/main/resources/index.html --- a/rt/mojo/src/main/resources/archetype-resources/src/main/resources/index.html Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ - - - - - Bck2Brwsr's Hello World - - -

Loading Bck2Brwsr's Hello World...

- Your name: - -

- - -

- - - - - diff -r 8264f07b1f46 -r f73c1a0234fb rt/mojo/src/main/resources/archetype-resources/src/test/java/AppTest.java --- a/rt/mojo/src/main/resources/archetype-resources/src/test/java/AppTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -package ${package}; - -import static org.testng.Assert.*; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -/** Demonstrating POJO testing of HTML page model. Runs in good old HotSpot - * as it does not reference any HTML elements or browser functionality. Just - * operates on the page model. - * - * @author Jaroslav Tulach - */ -public class AppTest { - private Index model; - - - @BeforeMethod - public void initModel() { - model = new Index().applyBindings(); - } - - @Test public void testHelloMessage() { - model.setName("Joe"); - assertEquals(model.getHelloMessage(), "Hello Joe!", "Cleared after pressing +"); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/mojo/src/main/resources/archetype-resources/src/test/java/InconsistencyTest.java --- a/rt/mojo/src/main/resources/archetype-resources/src/test/java/InconsistencyTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -package ${package}; - -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** Bck2brwsr cares about compatibility with real Java. Whatever API is - * supported by bck2brwsr, it needs to behave the same way as when running - * in HotSpot VM. - *

- * There can be bugs, however. To help us fix them, we kindly ask you to - * write an "inconsistency" test. A test that compares behavior of the API - * between real VM and bck2brwsr VM. This class is skeleton of such test. - * - * @author Jaroslav Tulach - */ -public class InconsistencyTest { - /** A method to demonstrate inconsistency between bck2brwsr and HotSpot. - * Make calls to an API that behaves strangely, return some result at - * the end. No need to use any assert. - * - * @return value to compare between HotSpot and bck2brwsr - */ - @Compare - public int checkStringHashCode() throws Exception { - return "Is string hashCode the same?".hashCode(); - } - - /** Factory method that creates a three tests for each method annotated with - * {@link org.apidesign.bck2brwsr.vmtest.Compare}. One executes the code in - * HotSpot, one in Rhino and the last one compares the results. - * - * @see org.apidesign.bck2brwsr.vmtest.VMTest - */ - @Factory - public static Object[] create() { - return VMTest.create(InconsistencyTest.class); - } - -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/mojo/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java --- a/rt/mojo/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -package ${package}; - -import org.apidesign.bck2brwsr.htmlpage.api.OnEvent; -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. - * - * @author Jaroslav Tulach - */ -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( - "

Loading Bck2Brwsr's Hello World...

\n" + - "Your name: \n" + - "\n" + - "

\n" + - " \n" + - "

\n" - ) - @BrwsrTest - public void modifyValueAssertChangeInModel() { - Index m = new Index(); - m.setName("Joe Hacker"); - m.applyBindings(); - assert "Joe Hacker".equals(m.INPUT.getValue()) : "Value is really Joe Hacker: " + m.INPUT.getValue(); - m.INPUT.setValue("Happy Joe"); - m.triggerEvent(m.INPUT, OnEvent.CHANGE); - assert "Happy Joe".equals(m.getName()) : "Name property updated to Happy Joe: " + m.getName(); - } - - @Factory - public static Object[] create() { - return VMTest.create(IntegrationTest.class); - } - -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/pom.xml --- a/rt/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -3,21 +3,19 @@ 4.0.0 org.apidesign.bck2brwsr rt - 0.3-SNAPSHOT + 0.9-SNAPSHOT pom - Bck3Brwsr Runtime + Bck2Brwsr Runtime org.apidesign bck2brwsr - 0.3-SNAPSHOT + 0.9-SNAPSHOT core emul - javap - launcher mojo vm vmtest -
\ No newline at end of file + diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/pom.xml --- a/rt/vm/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -1,15 +1,14 @@ - + 4.0.0 org.apidesign.bck2brwsr rt - 0.3-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr vm4brwsr - 0.3-SNAPSHOT + 0.9-SNAPSHOT jar Virtual Machine for Browser @@ -44,6 +43,7 @@ scm:hg:http://source.apidesign.org/hg/bck2brwsr http://source.apidesign.org/hg/bck2brwsr + HEAD @@ -69,6 +69,13 @@ + org.apache.maven.plugins + maven-javadoc-plugin + + false + + + org.codehaus.mojo exec-maven-plugin 1.2.1 @@ -76,18 +83,24 @@ generate-js process-classes + + java + + -Dskip.if.exists=true + -cp + + org.apidesign.vm4brwsr.Main + --obfuscatelevel + MINIMAL + ${project.build.directory}/bck2brwsr.js + org/apidesign/vm4brwsr/Bck2Brwsr + + - java + exec - - org.apidesign.vm4brwsr.Main - - ${project.build.directory}/bck2brwsr.js - org/apidesign/vm4brwsr/Bck2Brwsr - - maven-assembly-plugin @@ -134,10 +147,16 @@ compile - ${project.groupId} - javap - ${project.version} + com.google.javascript + closure-compiler + r2388 compile + + org.apidesign.html + net.java.html.boot + test + ${net.java.html.version} + diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/Bck2Brwsr.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/Bck2Brwsr.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/Bck2Brwsr.java Mon Oct 07 14:20:58 2013 +0200 @@ -54,46 +54,127 @@ * @author Jaroslav Tulach */ public final class Bck2Brwsr { - private Bck2Brwsr() { + private final ObfuscationLevel level; + private final StringArray classes; + private final Resources res; + + private Bck2Brwsr(ObfuscationLevel level, StringArray classes, Resources resources) { + this.level = level; + this.classes = classes; + this.res = resources; } - /** Generates virtual machine from bytes served by a resources + /** Helper method to generate virtual machine from bytes served by a resources * provider. - * + * * @param out the output to write the generated JavaScript to * @param resources provider of class files to use * @param classes additional classes to include in the generated script * @throws IOException I/O exception can be thrown when something goes wrong */ public static void generate(Appendable out, Resources resources, String... classes) throws IOException { - StringArray arr = StringArray.asList(classes); - arr.add(VM.class.getName().replace('.', '/')); - VM.compile(resources, out, arr); + newCompiler().resources(resources).addRootClasses(classes).generate(out); } - - /** Generates virtual machine from bytes served by a class loader. - * + + /** Helper method to generate virtual machine from bytes served by a class loader. + * * @param out the output to write the generated JavaScript to * @param loader class loader to load needed classes from * @param classes additional classes to include in the generated script * @throws IOException I/O exception can be thrown when something goes wrong */ - public static void generate(Appendable out, final ClassLoader loader, String... classes) throws IOException { - class R implements Resources { - @Override - public InputStream get(String name) throws IOException { - Enumeration en = loader.getResources(name); - URL u = null; - while (en.hasMoreElements()) { - u = en.nextElement(); - } - if (u == null) { - throw new IOException("Can't find " + name); - } - return u.openStream(); + public static void generate(Appendable out, ClassLoader loader, String... classes) throws IOException { + newCompiler().resources(loader).addRootClasses(classes).generate(out); + } + + /** Creates new instance of Bck2Brwsr compiler which is ready to generate + * empty Bck2Brwsr virtual machine. The instance can be further + * configured by calling chain of methods. For example: + *
+     * {@link #createCompiler()}.{@link #resources(org.apidesign.vm4brwsr.Bck2Brwsr.Resources) resources(loader)}.{@link #addRootClasses(java.lang.String[]) addRootClasses("your/Clazz")}.{@link #generate(java.lang.Appendable) generate(out)};
+     * 
+ * + * @return new instance of the Bck2Brwsr compiler + * @since 0.5 + */ + public static Bck2Brwsr newCompiler() { + StringArray arr = StringArray.asList(VM.class.getName().replace('.', '/')); + return new Bck2Brwsr(ObfuscationLevel.NONE, arr, null); + } + + /** Creates new instance of the Bck2Brwsr compiler which inherits + * all values from this instance and adds additional classes + * to the list of those that should be compiled by the {@link #generate(java.lang.Appendable)} + * method. + * + * @param classes the classes to add to the compilation + * @return new instance of the compiler + */ + public Bck2Brwsr addRootClasses(String... classes) { + if (classes.length == 0) { + return this; + } else { + return new Bck2Brwsr(level, this.classes.addAndNew(classes), res); + } + } + + /** Changes the obfuscation level for the compiler by creating new instance + * which inherits all values from this and adjust the level + * of obfuscation. + * + * @param level the new level of obfuscation + * @return new instance of the compiler with changed level of obfuscation + * @since 0.5 + */ + public Bck2Brwsr obfuscation(ObfuscationLevel level) { + return new Bck2Brwsr(level, classes, res); + } + + /** A way to change the provider of additional resources (classes) for the + * compiler. + * + * @param res the implementation of resources provider + * @return new instance of the compiler with all values remaining the same, just + * with different resources provider + * @since 0.5 + */ + public Bck2Brwsr resources(Resources res) { + return new Bck2Brwsr(level, classes, res); + } + + /** A way to change the provider of additional resources (classes) for the + * compiler by specifying classloader to use for loading them. + * + * @param loader class loader to load the resources from + * @return new instance of the compiler with all values being the same, just + * different resources provider + * @since 0.5 + */ + public Bck2Brwsr resources(final ClassLoader loader) { + return resources(new LdrRsrcs(loader)); + } + + /** Generates virtual machine based on previous configuration of the + * compiler. + * + * @param out the output to write the generated JavaScript to + * @since 0.5 + */ + public void generate(Appendable out) throws IOException { + Resources r = res != null ? res : new LdrRsrcs(Bck2Brwsr.class.getClassLoader()); + if (level != ObfuscationLevel.NONE) { + try { + ClosureWrapper.produceTo(out, level, r, classes); + return; + } catch (IOException ex) { + throw ex; + } catch (Throwable ex) { + out.append("/* Failed to obfuscate: " + ex.getMessage() + + " */\n"); } } - generate(out, new R(), classes); + + VM.compile(r, out, classes); } /** Provider of resources (classes and other files). The diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,3461 @@ +/* + * Copyright (c) 2002, 2004, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.apidesign.vm4brwsr; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.core.JavaScriptPrototype; + +/** This is a byte code parser heavily based on original code of JavaP utility. + * As such I decided to keep the original Oracle's GPLv2 header. + * + * @author Jaroslav Tulach + */ +final class ByteCodeParser { + private ByteCodeParser() { + } + /* Signature Characters */ + public static final char SIGC_VOID = 'V'; + public static final String SIG_VOID = "V"; + public static final char SIGC_BOOLEAN = 'Z'; + public static final String SIG_BOOLEAN = "Z"; + public static final char SIGC_BYTE = 'B'; + public static final String SIG_BYTE = "B"; + public static final char SIGC_CHAR = 'C'; + public static final String SIG_CHAR = "C"; + public static final char SIGC_SHORT = 'S'; + public static final String SIG_SHORT = "S"; + public static final char SIGC_INT = 'I'; + public static final String SIG_INT = "I"; + public static final char SIGC_LONG = 'J'; + public static final String SIG_LONG = "J"; + public static final char SIGC_FLOAT = 'F'; + public static final String SIG_FLOAT = "F"; + public static final char SIGC_DOUBLE = 'D'; + public static final String SIG_DOUBLE = "D"; + public static final char SIGC_ARRAY = '['; + public static final String SIG_ARRAY = "["; + public static final char SIGC_CLASS = 'L'; + public static final String SIG_CLASS = "L"; + public static final char SIGC_METHOD = '('; + public static final String SIG_METHOD = "("; + public static final char SIGC_ENDCLASS = ';'; + public static final String SIG_ENDCLASS = ";"; + public static final char SIGC_ENDMETHOD = ')'; + public static final String SIG_ENDMETHOD = ")"; + public static final char SIGC_PACKAGE = '/'; + public static final String SIG_PACKAGE = "/"; + + /* Class File Constants */ + public static final int JAVA_MAGIC = 0xcafebabe; + public static final int JAVA_VERSION = 45; + public static final int JAVA_MINOR_VERSION = 3; + + /* Constant table */ + public static final int CONSTANT_UTF8 = 1; + public static final int CONSTANT_UNICODE = 2; + public static final int CONSTANT_INTEGER = 3; + public static final int CONSTANT_FLOAT = 4; + public static final int CONSTANT_LONG = 5; + public static final int CONSTANT_DOUBLE = 6; + public static final int CONSTANT_CLASS = 7; + public static final int CONSTANT_STRING = 8; + public static final int CONSTANT_FIELD = 9; + public static final int CONSTANT_METHOD = 10; + public static final int CONSTANT_INTERFACEMETHOD = 11; + public static final int CONSTANT_NAMEANDTYPE = 12; + + /* Access Flags */ + public static final int ACC_PUBLIC = 0x00000001; + public static final int ACC_PRIVATE = 0x00000002; + public static final int ACC_PROTECTED = 0x00000004; + public static final int ACC_STATIC = 0x00000008; + public static final int ACC_FINAL = 0x00000010; + public static final int ACC_SYNCHRONIZED = 0x00000020; + public static final int ACC_SUPER = 0x00000020; + public static final int ACC_VOLATILE = 0x00000040; + public static final int ACC_TRANSIENT = 0x00000080; + public static final int ACC_NATIVE = 0x00000100; + public static final int ACC_INTERFACE = 0x00000200; + public static final int ACC_ABSTRACT = 0x00000400; + public static final int ACC_STRICT = 0x00000800; + public static final int ACC_EXPLICIT = 0x00001000; + public static final int ACC_SYNTHETIC = 0x00010000; // actually, this is an attribute + + /* Type codes */ + public static final int T_CLASS = 0x00000002; + public static final int T_BOOLEAN = 0x00000004; + public static final int T_CHAR = 0x00000005; + public static final int T_FLOAT = 0x00000006; + public static final int T_DOUBLE = 0x00000007; + public static final int T_BYTE = 0x00000008; + public static final int T_SHORT = 0x00000009; + public static final int T_INT = 0x0000000a; + public static final int T_LONG = 0x0000000b; + + /* Type codes for StackMap attribute */ + public static final int ITEM_Bogus =0; // an unknown or uninitialized value + public static final int ITEM_Integer =1; // a 32-bit integer + public static final int ITEM_Float =2; // not used + public static final int ITEM_Double =3; // not used + public static final int ITEM_Long =4; // a 64-bit integer + public static final int ITEM_Null =5; // the type of null + public static final int ITEM_InitObject =6; // "this" in constructor + public static final int ITEM_Object =7; // followed by 2-byte index of class name + public static final int ITEM_NewObject =8; // followed by 2-byte ref to "new" + + /* Constants used in StackMapTable attribute */ + public static final int SAME_FRAME_BOUND = 64; + public static final int SAME_LOCALS_1_STACK_ITEM_BOUND = 128; + public static final int SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247; + public static final int SAME_FRAME_EXTENDED = 251; + public static final int FULL_FRAME = 255; + + /* Opcodes */ + public static final int opc_dead = -2; + public static final int opc_label = -1; + public static final int opc_nop = 0; + public static final int opc_aconst_null = 1; + public static final int opc_iconst_m1 = 2; + public static final int opc_iconst_0 = 3; + public static final int opc_iconst_1 = 4; + public static final int opc_iconst_2 = 5; + public static final int opc_iconst_3 = 6; + public static final int opc_iconst_4 = 7; + public static final int opc_iconst_5 = 8; + public static final int opc_lconst_0 = 9; + public static final int opc_lconst_1 = 10; + public static final int opc_fconst_0 = 11; + public static final int opc_fconst_1 = 12; + public static final int opc_fconst_2 = 13; + public static final int opc_dconst_0 = 14; + public static final int opc_dconst_1 = 15; + public static final int opc_bipush = 16; + public static final int opc_sipush = 17; + public static final int opc_ldc = 18; + public static final int opc_ldc_w = 19; + public static final int opc_ldc2_w = 20; + public static final int opc_iload = 21; + public static final int opc_lload = 22; + public static final int opc_fload = 23; + public static final int opc_dload = 24; + public static final int opc_aload = 25; + public static final int opc_iload_0 = 26; + public static final int opc_iload_1 = 27; + public static final int opc_iload_2 = 28; + public static final int opc_iload_3 = 29; + public static final int opc_lload_0 = 30; + public static final int opc_lload_1 = 31; + public static final int opc_lload_2 = 32; + public static final int opc_lload_3 = 33; + public static final int opc_fload_0 = 34; + public static final int opc_fload_1 = 35; + public static final int opc_fload_2 = 36; + public static final int opc_fload_3 = 37; + public static final int opc_dload_0 = 38; + public static final int opc_dload_1 = 39; + public static final int opc_dload_2 = 40; + public static final int opc_dload_3 = 41; + public static final int opc_aload_0 = 42; + public static final int opc_aload_1 = 43; + public static final int opc_aload_2 = 44; + public static final int opc_aload_3 = 45; + public static final int opc_iaload = 46; + public static final int opc_laload = 47; + public static final int opc_faload = 48; + public static final int opc_daload = 49; + public static final int opc_aaload = 50; + public static final int opc_baload = 51; + public static final int opc_caload = 52; + public static final int opc_saload = 53; + public static final int opc_istore = 54; + public static final int opc_lstore = 55; + public static final int opc_fstore = 56; + public static final int opc_dstore = 57; + public static final int opc_astore = 58; + public static final int opc_istore_0 = 59; + public static final int opc_istore_1 = 60; + public static final int opc_istore_2 = 61; + public static final int opc_istore_3 = 62; + public static final int opc_lstore_0 = 63; + public static final int opc_lstore_1 = 64; + public static final int opc_lstore_2 = 65; + public static final int opc_lstore_3 = 66; + public static final int opc_fstore_0 = 67; + public static final int opc_fstore_1 = 68; + public static final int opc_fstore_2 = 69; + public static final int opc_fstore_3 = 70; + public static final int opc_dstore_0 = 71; + public static final int opc_dstore_1 = 72; + public static final int opc_dstore_2 = 73; + public static final int opc_dstore_3 = 74; + public static final int opc_astore_0 = 75; + public static final int opc_astore_1 = 76; + public static final int opc_astore_2 = 77; + public static final int opc_astore_3 = 78; + public static final int opc_iastore = 79; + public static final int opc_lastore = 80; + public static final int opc_fastore = 81; + public static final int opc_dastore = 82; + public static final int opc_aastore = 83; + public static final int opc_bastore = 84; + public static final int opc_castore = 85; + public static final int opc_sastore = 86; + public static final int opc_pop = 87; + public static final int opc_pop2 = 88; + public static final int opc_dup = 89; + public static final int opc_dup_x1 = 90; + public static final int opc_dup_x2 = 91; + public static final int opc_dup2 = 92; + public static final int opc_dup2_x1 = 93; + public static final int opc_dup2_x2 = 94; + public static final int opc_swap = 95; + public static final int opc_iadd = 96; + public static final int opc_ladd = 97; + public static final int opc_fadd = 98; + public static final int opc_dadd = 99; + public static final int opc_isub = 100; + public static final int opc_lsub = 101; + public static final int opc_fsub = 102; + public static final int opc_dsub = 103; + public static final int opc_imul = 104; + public static final int opc_lmul = 105; + public static final int opc_fmul = 106; + public static final int opc_dmul = 107; + public static final int opc_idiv = 108; + public static final int opc_ldiv = 109; + public static final int opc_fdiv = 110; + public static final int opc_ddiv = 111; + public static final int opc_irem = 112; + public static final int opc_lrem = 113; + public static final int opc_frem = 114; + public static final int opc_drem = 115; + public static final int opc_ineg = 116; + public static final int opc_lneg = 117; + public static final int opc_fneg = 118; + public static final int opc_dneg = 119; + public static final int opc_ishl = 120; + public static final int opc_lshl = 121; + public static final int opc_ishr = 122; + public static final int opc_lshr = 123; + public static final int opc_iushr = 124; + public static final int opc_lushr = 125; + public static final int opc_iand = 126; + public static final int opc_land = 127; + public static final int opc_ior = 128; + public static final int opc_lor = 129; + public static final int opc_ixor = 130; + public static final int opc_lxor = 131; + public static final int opc_iinc = 132; + public static final int opc_i2l = 133; + public static final int opc_i2f = 134; + public static final int opc_i2d = 135; + public static final int opc_l2i = 136; + public static final int opc_l2f = 137; + public static final int opc_l2d = 138; + public static final int opc_f2i = 139; + public static final int opc_f2l = 140; + public static final int opc_f2d = 141; + public static final int opc_d2i = 142; + public static final int opc_d2l = 143; + public static final int opc_d2f = 144; + public static final int opc_i2b = 145; + public static final int opc_int2byte = 145; + public static final int opc_i2c = 146; + public static final int opc_int2char = 146; + public static final int opc_i2s = 147; + public static final int opc_int2short = 147; + public static final int opc_lcmp = 148; + public static final int opc_fcmpl = 149; + public static final int opc_fcmpg = 150; + public static final int opc_dcmpl = 151; + public static final int opc_dcmpg = 152; + public static final int opc_ifeq = 153; + public static final int opc_ifne = 154; + public static final int opc_iflt = 155; + public static final int opc_ifge = 156; + public static final int opc_ifgt = 157; + public static final int opc_ifle = 158; + public static final int opc_if_icmpeq = 159; + public static final int opc_if_icmpne = 160; + public static final int opc_if_icmplt = 161; + public static final int opc_if_icmpge = 162; + public static final int opc_if_icmpgt = 163; + public static final int opc_if_icmple = 164; + public static final int opc_if_acmpeq = 165; + public static final int opc_if_acmpne = 166; + public static final int opc_goto = 167; + public static final int opc_jsr = 168; + public static final int opc_ret = 169; + public static final int opc_tableswitch = 170; + public static final int opc_lookupswitch = 171; + public static final int opc_ireturn = 172; + public static final int opc_lreturn = 173; + public static final int opc_freturn = 174; + public static final int opc_dreturn = 175; + public static final int opc_areturn = 176; + public static final int opc_return = 177; + public static final int opc_getstatic = 178; + public static final int opc_putstatic = 179; + public static final int opc_getfield = 180; + public static final int opc_putfield = 181; + public static final int opc_invokevirtual = 182; + public static final int opc_invokenonvirtual = 183; + public static final int opc_invokespecial = 183; + public static final int opc_invokestatic = 184; + public static final int opc_invokeinterface = 185; +// public static final int opc_xxxunusedxxx = 186; + public static final int opc_new = 187; + public static final int opc_newarray = 188; + public static final int opc_anewarray = 189; + public static final int opc_arraylength = 190; + public static final int opc_athrow = 191; + public static final int opc_checkcast = 192; + public static final int opc_instanceof = 193; + public static final int opc_monitorenter = 194; + public static final int opc_monitorexit = 195; + public static final int opc_wide = 196; + public static final int opc_multianewarray = 197; + public static final int opc_ifnull = 198; + public static final int opc_ifnonnull = 199; + public static final int opc_goto_w = 200; + public static final int opc_jsr_w = 201; + /* Pseudo-instructions */ + public static final int opc_bytecode = 203; + public static final int opc_try = 204; + public static final int opc_endtry = 205; + public static final int opc_catch = 206; + public static final int opc_var = 207; + public static final int opc_endvar = 208; + public static final int opc_localsmap = 209; + public static final int opc_stackmap = 210; + /* PicoJava prefixes */ + public static final int opc_nonpriv = 254; + public static final int opc_priv = 255; + + /* Wide instructions */ + public static final int opc_iload_w = (opc_wide<<8)|opc_iload; + public static final int opc_lload_w = (opc_wide<<8)|opc_lload; + public static final int opc_fload_w = (opc_wide<<8)|opc_fload; + public static final int opc_dload_w = (opc_wide<<8)|opc_dload; + public static final int opc_aload_w = (opc_wide<<8)|opc_aload; + public static final int opc_istore_w = (opc_wide<<8)|opc_istore; + public static final int opc_lstore_w = (opc_wide<<8)|opc_lstore; + public static final int opc_fstore_w = (opc_wide<<8)|opc_fstore; + public static final int opc_dstore_w = (opc_wide<<8)|opc_dstore; + public static final int opc_astore_w = (opc_wide<<8)|opc_astore; + public static final int opc_ret_w = (opc_wide<<8)|opc_ret; + public static final int opc_iinc_w = (opc_wide<<8)|opc_iinc; + + /* Opcode Names */ + public static final String opcNamesTab[] = { + "nop", + "aconst_null", + "iconst_m1", + "iconst_0", + "iconst_1", + "iconst_2", + "iconst_3", + "iconst_4", + "iconst_5", + "lconst_0", + "lconst_1", + "fconst_0", + "fconst_1", + "fconst_2", + "dconst_0", + "dconst_1", + "bipush", + "sipush", + "ldc", + "ldc_w", + "ldc2_w", + "iload", + "lload", + "fload", + "dload", + "aload", + "iload_0", + "iload_1", + "iload_2", + "iload_3", + "lload_0", + "lload_1", + "lload_2", + "lload_3", + "fload_0", + "fload_1", + "fload_2", + "fload_3", + "dload_0", + "dload_1", + "dload_2", + "dload_3", + "aload_0", + "aload_1", + "aload_2", + "aload_3", + "iaload", + "laload", + "faload", + "daload", + "aaload", + "baload", + "caload", + "saload", + "istore", + "lstore", + "fstore", + "dstore", + "astore", + "istore_0", + "istore_1", + "istore_2", + "istore_3", + "lstore_0", + "lstore_1", + "lstore_2", + "lstore_3", + "fstore_0", + "fstore_1", + "fstore_2", + "fstore_3", + "dstore_0", + "dstore_1", + "dstore_2", + "dstore_3", + "astore_0", + "astore_1", + "astore_2", + "astore_3", + "iastore", + "lastore", + "fastore", + "dastore", + "aastore", + "bastore", + "castore", + "sastore", + "pop", + "pop2", + "dup", + "dup_x1", + "dup_x2", + "dup2", + "dup2_x1", + "dup2_x2", + "swap", + "iadd", + "ladd", + "fadd", + "dadd", + "isub", + "lsub", + "fsub", + "dsub", + "imul", + "lmul", + "fmul", + "dmul", + "idiv", + "ldiv", + "fdiv", + "ddiv", + "irem", + "lrem", + "frem", + "drem", + "ineg", + "lneg", + "fneg", + "dneg", + "ishl", + "lshl", + "ishr", + "lshr", + "iushr", + "lushr", + "iand", + "land", + "ior", + "lor", + "ixor", + "lxor", + "iinc", + "i2l", + "i2f", + "i2d", + "l2i", + "l2f", + "l2d", + "f2i", + "f2l", + "f2d", + "d2i", + "d2l", + "d2f", + "i2b", + "i2c", + "i2s", + "lcmp", + "fcmpl", + "fcmpg", + "dcmpl", + "dcmpg", + "ifeq", + "ifne", + "iflt", + "ifge", + "ifgt", + "ifle", + "if_icmpeq", + "if_icmpne", + "if_icmplt", + "if_icmpge", + "if_icmpgt", + "if_icmple", + "if_acmpeq", + "if_acmpne", + "goto", + "jsr", + "ret", + "tableswitch", + "lookupswitch", + "ireturn", + "lreturn", + "freturn", + "dreturn", + "areturn", + "return", + "getstatic", + "putstatic", + "getfield", + "putfield", + "invokevirtual", + "invokespecial", // was "invokenonvirtual", + "invokestatic", + "invokeinterface", + "bytecode 186", //"xxxunusedxxx", + "new", + "newarray", + "anewarray", + "arraylength", + "athrow", + "checkcast", + "instanceof", + "monitorenter", + "monitorexit", + null, // "wide", + "multianewarray", + "ifnull", + "ifnonnull", + "goto_w", + "jsr_w", + "bytecode 202", // "breakpoint", + "bytecode", + "try", + "endtry", + "catch", + "var", + "endvar", + "locals_map", + "stack_map" + }; + + /* Opcode Lengths */ + public static final int opcLengthsTab[] = { + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 3, + 2, + 3, + 3, + 2, + 2, + 2, + 2, + 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 2, + 99, + 99, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 5, + 0, + 3, + 2, + 3, + 1, + 1, + 3, + 3, + 1, + 1, + 0, // wide + 4, + 3, + 3, + 5, + 5, + 1, + 1, 0, 0, 0, 0, 0 // pseudo + }; + + /** + * End of input + */ + public static final int EOF = -1; + + /* + * Flags + */ + public static final int F_VERBOSE = 1 << 0; + public static final int F_DUMP = 1 << 1; + public static final int F_WARNINGS = 1 << 2; + public static final int F_DEBUG = 1 << 3; + public static final int F_OPTIMIZE = 1 << 4; + public static final int F_DEPENDENCIES = 1 << 5; + + /* + * Type codes + */ + public static final int TC_BOOLEAN = 0; + public static final int TC_BYTE = 1; + public static final int TC_CHAR = 2; + public static final int TC_SHORT = 3; + public static final int TC_INT = 4; + public static final int TC_LONG = 5; + public static final int TC_FLOAT = 6; + public static final int TC_DOUBLE = 7; + public static final int TC_NULL = 8; + public static final int TC_ARRAY = 9; + public static final int TC_CLASS = 10; + public static final int TC_VOID = 11; + public static final int TC_METHOD = 12; + public static final int TC_ERROR = 13; + + /* + * Type Masks + */ + public static final int TM_NULL = 1 << TC_NULL; + public static final int TM_VOID = 1 << TC_VOID; + public static final int TM_BOOLEAN = 1 << TC_BOOLEAN; + public static final int TM_BYTE = 1 << TC_BYTE; + public static final int TM_CHAR = 1 << TC_CHAR; + public static final int TM_SHORT = 1 << TC_SHORT; + public static final int TM_INT = 1 << TC_INT; + public static final int TM_LONG = 1 << TC_LONG; + public static final int TM_FLOAT = 1 << TC_FLOAT; + public static final int TM_DOUBLE = 1 << TC_DOUBLE; + public static final int TM_ARRAY = 1 << TC_ARRAY; + public static final int TM_CLASS = 1 << TC_CLASS; + public static final int TM_METHOD = 1 << TC_METHOD; + public static final int TM_ERROR = 1 << TC_ERROR; + + public static final int TM_INT32 = TM_BYTE | TM_SHORT | TM_CHAR | TM_INT; + public static final int TM_NUM32 = TM_INT32 | TM_FLOAT; + public static final int TM_NUM64 = TM_LONG | TM_DOUBLE; + public static final int TM_INTEGER = TM_INT32 | TM_LONG; + public static final int TM_REAL = TM_FLOAT | TM_DOUBLE; + public static final int TM_NUMBER = TM_INTEGER | TM_REAL; + public static final int TM_REFERENCE = TM_ARRAY | TM_CLASS | TM_NULL; + + /* + * Class status + */ + public static final int CS_UNDEFINED = 0; + public static final int CS_UNDECIDED = 1; + public static final int CS_BINARY = 2; + public static final int CS_SOURCE = 3; + public static final int CS_PARSED = 4; + public static final int CS_COMPILED = 5; + public static final int CS_NOTFOUND = 6; + + /* + * Attributes + */ + public static final int ATT_ALL = -1; + public static final int ATT_CODE = 1; + + /* + * Number of bits used in file offsets + */ + public static final int OFFSETBITS = 19; + public static final int MAXFILESIZE = (1 << OFFSETBITS) - 1; + public static final int MAXLINENUMBER = (1 << (32 - OFFSETBITS)) - 1; + + /* + * Operators + */ + public final int COMMA = 0; + public final int ASSIGN = 1; + + public final int ASGMUL = 2; + public final int ASGDIV = 3; + public final int ASGREM = 4; + public final int ASGADD = 5; + public final int ASGSUB = 6; + public final int ASGLSHIFT = 7; + public final int ASGRSHIFT = 8; + public final int ASGURSHIFT = 9; + public final int ASGBITAND = 10; + public final int ASGBITOR = 11; + public final int ASGBITXOR = 12; + + public final int COND = 13; + public final int OR = 14; + public final int AND = 15; + public final int BITOR = 16; + public final int BITXOR = 17; + public final int BITAND = 18; + public final int NE = 19; + public final int EQ = 20; + public final int GE = 21; + public final int GT = 22; + public final int LE = 23; + public final int LT = 24; + public final int INSTANCEOF = 25; + public final int LSHIFT = 26; + public final int RSHIFT = 27; + public final int URSHIFT = 28; + public final int ADD = 29; + public final int SUB = 30; + public final int DIV = 31; + public final int REM = 32; + public final int MUL = 33; + public final int CAST = 34; // (x)y + public final int POS = 35; // +x + public final int NEG = 36; // -x + public final int NOT = 37; + public final int BITNOT = 38; + public final int PREINC = 39; // ++x + public final int PREDEC = 40; // --x + public final int NEWARRAY = 41; + public final int NEWINSTANCE = 42; + public final int NEWFROMNAME = 43; + public final int POSTINC = 44; // x++ + public final int POSTDEC = 45; // x-- + public final int FIELD = 46; + public final int METHOD = 47; // x(y) + public final int ARRAYACCESS = 48; // x[y] + public final int NEW = 49; + public final int INC = 50; + public final int DEC = 51; + + public final int CONVERT = 55; // implicit conversion + public final int EXPR = 56; // (x) + public final int ARRAY = 57; // {x, y, ...} + public final int GOTO = 58; + + /* + * Value tokens + */ + public final int IDENT = 60; + public final int BOOLEANVAL = 61; + public final int BYTEVAL = 62; + public final int CHARVAL = 63; + public final int SHORTVAL = 64; + public final int INTVAL = 65; + public final int LONGVAL = 66; + public final int FLOATVAL = 67; + public final int DOUBLEVAL = 68; + public final int STRINGVAL = 69; + + /* + * Type keywords + */ + public final int BYTE = 70; + public final int CHAR = 71; + public final int SHORT = 72; + public final int INT = 73; + public final int LONG = 74; + public final int FLOAT = 75; + public final int DOUBLE = 76; + public final int VOID = 77; + public final int BOOLEAN = 78; + + /* + * Expression keywords + */ + public final int TRUE = 80; + public final int FALSE = 81; + public final int THIS = 82; + public final int SUPER = 83; + public final int NULL = 84; + + /* + * Statement keywords + */ + public final int IF = 90; + public final int ELSE = 91; + public final int FOR = 92; + public final int WHILE = 93; + public final int DO = 94; + public final int SWITCH = 95; + public final int CASE = 96; + public final int DEFAULT = 97; + public final int BREAK = 98; + public final int CONTINUE = 99; + public final int RETURN = 100; + public final int TRY = 101; + public final int CATCH = 102; + public final int FINALLY = 103; + public final int THROW = 104; + public final int STAT = 105; + public final int EXPRESSION = 106; + public final int DECLARATION = 107; + public final int VARDECLARATION = 108; + + /* + * Declaration keywords + */ + public final int IMPORT = 110; + public final int CLASS = 111; + public final int EXTENDS = 112; + public final int IMPLEMENTS = 113; + public final int INTERFACE = 114; + public final int PACKAGE = 115; + + /* + * Modifier keywords + */ + public final int PRIVATE = 120; + public final int PUBLIC = 121; + public final int PROTECTED = 122; + public final int CONST = 123; + public final int STATIC = 124; + public final int TRANSIENT = 125; + public final int SYNCHRONIZED = 126; + public final int NATIVE = 127; + public final int FINAL = 128; + public final int VOLATILE = 129; + public final int ABSTRACT = 130; + public final int STRICT = 165; + + /* + * Punctuation + */ + public final int SEMICOLON = 135; + public final int COLON = 136; + public final int QUESTIONMARK = 137; + public final int LBRACE = 138; + public final int RBRACE = 139; + public final int LPAREN = 140; + public final int RPAREN = 141; + public final int LSQBRACKET = 142; + public final int RSQBRACKET = 143; + public final int THROWS = 144; + + /* + * Special tokens + */ + public final int ERROR = 145; // an error + public final int COMMENT = 146; // not used anymore. + public final int TYPE = 147; + public final int LENGTH = 148; + public final int INLINERETURN = 149; + public final int INLINEMETHOD = 150; + public final int INLINENEWINSTANCE = 151; + + /* + * Added for jasm + */ + public final int METHODREF = 152; + public final int FIELDREF = 153; + public final int STACK = 154; + public final int LOCAL = 155; + public final int CPINDEX = 156; + public final int CPNAME = 157; + public final int SIGN = 158; + public final int BITS = 159; + public final int INF = 160; + public final int NAN = 161; + public final int INNERCLASS = 162; + public final int OF = 163; + public final int SYNTHETIC = 164; +// last used=165; + + /* + * Operator precedence + */ + public static final int opPrecedence[] = { + 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 12, 13, 14, 15, 16, 17, 18, + 18, 19, 19, 19, 19, 19, 20, 20, 20, 21, + 21, 22, 22, 22, 23, 24, 24, 24, 24, 24, + 24, 25, 25, 26, 26, 26, 26, 26, 26 + }; + + /* + * Operator names + */ + public static final String opNames[] = { + ",", "=", "*=", "/=", "%=", + "+=", "-=", "<<=", ">>=", "<<<=", + "&=", "|=", "^=", "?:", "||", + "&&", "|", "^", "&", "!=", + "==", ">=", ">", "<=", "<", + "instanceof", "<<", ">>", "<<<", "+", + "-", "/", "%", "*", "cast", + "+", "-", "!", "~", "++", + "--", "new", "new", "new", "++", + "--", "field", "method", "[]", "new", + "++", "--", null, null, null, + + "convert", "expr", "array", "goto", null, + + "Identifier", "Boolean", "Byte", "Char", "Short", + "Integer", "Long", "Float", "Double", "String", + + "byte", "char", "short", "int", "long", + "float", "double", "void", "boolean", null, + + "true", "false", "this", "super", "null", + null, null, null, null, null, + + "if", "else", "for", "while", "do", + "switch", "case", "default", "break", "continue", + "return", "try", "catch", "finally", "throw", + "stat", "expression", "declaration", "declaration", null, + + "import", "class", "extends", "implements", "interface", + "package", null, null, null, null, + + "private", "public", "protected", "const", "static", + "transient", "synchronized", "native", "final", "volatile", + "abstract", null, null, null, null, + + ";", ":", "?", "{", "}", + "(", ")", "[", "]", "throws", + "error", "comment", "type", "length", "inline-return", + "inline-method", "inline-new", + "method", "field", "stack", "locals", "CPINDEX", "CPName", "SIGN", + "bits", "INF", "NaN", "InnerClass", "of", "synthetic" + }; + + static class AnnotationParser { + + private final boolean textual; + private final boolean iterateArray; + + protected AnnotationParser(boolean textual, boolean iterateArray) { + this.textual = textual; + this.iterateArray = iterateArray; + } + + protected void visitAnnotationStart(String type, boolean top) throws IOException { + } + + protected void visitAnnotationEnd(String type, boolean top) throws IOException { + } + + protected void visitValueStart(String attrName, char type) throws IOException { + } + + protected void visitValueEnd(String attrName, char type) throws IOException { + } + + protected void visitAttr( + String annoType, String attr, String attrType, String value) throws IOException { + } + + protected void visitEnumAttr( + String annoType, String attr, String attrType, String value) throws IOException { + visitAttr(annoType, attr, attrType, value); + } + + /** + * Initialize the parsing with constant pool from + * cd. + * + * @param attr the attribute defining annotations + * @param cd constant pool + * @throws IOException in case I/O fails + */ + public final void parse(byte[] attr, ClassData cd) throws IOException { + ByteArrayInputStream is = new ByteArrayInputStream(attr); + DataInputStream dis = new DataInputStream(is); + try { + read(dis, cd); + } finally { + is.close(); + } + } + + private void read(DataInputStream dis, ClassData cd) throws IOException { + int cnt = dis.readUnsignedShort(); + for (int i = 0; i < cnt; i++) { + readAnno(dis, cd, true); + } + } + + private void readAnno(DataInputStream dis, ClassData cd, boolean top) throws IOException { + int type = dis.readUnsignedShort(); + String typeName = cd.StringValue(type); + visitAnnotationStart(typeName, top); + int cnt = dis.readUnsignedShort(); + for (int i = 0; i < cnt; i++) { + String attrName = cd.StringValue(dis.readUnsignedShort()); + readValue(dis, cd, typeName, attrName); + } + visitAnnotationEnd(typeName, top); + if (cnt == 0) { + visitAttr(typeName, null, null, null); + } + } + + private void readValue( + DataInputStream dis, ClassData cd, String typeName, String attrName) throws IOException { + char type = (char) dis.readByte(); + visitValueStart(attrName, type); + if (type == '@') { + readAnno(dis, cd, false); + } else if ("CFJZsSIDB".indexOf(type) >= 0) { // NOI18N + int primitive = dis.readUnsignedShort(); + String val = cd.stringValue(primitive, textual); + String attrType; + if (type == 's') { + attrType = "Ljava_lang_String_2"; + if (textual) { + val = '"' + val + '"'; + } + } else { + attrType = "" + type; + } + visitAttr(typeName, attrName, attrType, val); + } else if (type == 'c') { + int cls = dis.readUnsignedShort(); + } else if (type == '[') { + int cnt = dis.readUnsignedShort(); + for (int i = 0; i < cnt; i++) { + readValue(dis, cd, typeName, iterateArray ? attrName : null); + } + } else if (type == 'e') { + int enumT = dis.readUnsignedShort(); + String attrType = cd.stringValue(enumT, textual); + int enumN = dis.readUnsignedShort(); + String val = cd.stringValue(enumN, textual); + visitEnumAttr(typeName, attrName, attrType, val); + } else { + throw new IOException("Unknown type " + type); + } + visitValueEnd(attrName, type); + } + } + + /** + * Reads and stores attribute information. + * + * @author Sucheta Dambalkar (Adopted code from jdis) + */ + private static class AttrData { + + ClassData cls; + int name_cpx; + int datalen; + byte data[]; + + public AttrData(ClassData cls) { + this.cls = cls; + } + + /** + * Reads unknown attribute. + */ + public void read(int name_cpx, DataInputStream in) throws IOException { + this.name_cpx = name_cpx; + datalen = in.readInt(); + data = new byte[datalen]; + in.readFully(data); + } + + /** + * Reads just the name of known attribute. + */ + public void read(int name_cpx) { + this.name_cpx = name_cpx; + } + + /** + * Returns attribute name. + */ + public String getAttrName() { + return cls.getString(name_cpx); + } + + /** + * Returns attribute data. + */ + public byte[] getData() { + return data; + } + } + + /** + * Stores constant pool entry information with one field. + * + * @author Sucheta Dambalkar (Adopted code from jdis) + */ + private static class CPX { + + int cpx; + + CPX(int cpx) { + this.cpx = cpx; + } + } + + /** + * Stores constant pool entry information with two fields. + * + * @author Sucheta Dambalkar (Adopted code from jdis) + */ + private static class CPX2 { + + int cpx1, cpx2; + + CPX2(int cpx1, int cpx2) { + this.cpx1 = cpx1; + this.cpx2 = cpx2; + } + } + + /** + * Central data repository of the Java Disassembler. Stores all the + * information in java class file. + * + * @author Sucheta Dambalkar (Adopted code from jdis) + */ + static final class ClassData { + + private int magic; + private int minor_version; + private int major_version; + private int cpool_count; + private Object cpool[]; + private int access; + private int this_class = 0; + private int super_class; + private int interfaces_count; + private int[] interfaces = new int[0]; + private int fields_count; + private FieldData[] fields; + private int methods_count; + private MethodData[] methods; + private InnerClassData[] innerClasses; + private int attributes_count; + private AttrData[] attrs; + private String classname; + private String superclassname; + private int source_cpx = 0; + private byte tags[]; + private Hashtable indexHashAscii = new Hashtable(); + private String pkgPrefix = ""; + private int pkgPrefixLen = 0; + + /** + * Read classfile to disassemble. + */ + public ClassData(InputStream infile) throws IOException { + this.read(new DataInputStream(infile)); + } + + /** + * Reads and stores class file information. + */ + public void read(DataInputStream in) throws IOException { + // Read the header + magic = in.readInt(); + if (magic != JAVA_MAGIC) { + throw new ClassFormatError("wrong magic: " + + toHex(magic) + ", expected " + + toHex(JAVA_MAGIC)); + } + minor_version = in.readShort(); + major_version = in.readShort(); + if (major_version != JAVA_VERSION) { + } + + // Read the constant pool + readCP(in); + access = in.readUnsignedShort(); + this_class = in.readUnsignedShort(); + super_class = in.readUnsignedShort(); + + //Read interfaces. + interfaces_count = in.readUnsignedShort(); + if (interfaces_count > 0) { + interfaces = new int[interfaces_count]; + } + for (int i = 0; i < interfaces_count; i++) { + interfaces[i] = in.readShort(); + } + + // Read the fields + readFields(in); + + // Read the methods + readMethods(in); + + // Read the attributes + attributes_count = in.readUnsignedShort(); + attrs = new AttrData[attributes_count]; + for (int k = 0; k < attributes_count; k++) { + int name_cpx = in.readUnsignedShort(); + if (getTag(name_cpx) == CONSTANT_UTF8 + && getString(name_cpx).equals("SourceFile")) { + if (in.readInt() != 2) { + throw new ClassFormatError("invalid attr length"); + } + source_cpx = in.readUnsignedShort(); + AttrData attr = new AttrData(this); + attr.read(name_cpx); + attrs[k] = attr; + + } else if (getTag(name_cpx) == CONSTANT_UTF8 + && getString(name_cpx).equals("InnerClasses")) { + int length = in.readInt(); + int num = in.readUnsignedShort(); + if (2 + num * 8 != length) { + throw new ClassFormatError("invalid attr length"); + } + innerClasses = new InnerClassData[num]; + for (int j = 0; j < num; j++) { + InnerClassData innerClass = new InnerClassData(this); + innerClass.read(in); + innerClasses[j] = innerClass; + } + AttrData attr = new AttrData(this); + attr.read(name_cpx); + attrs[k] = attr; + } else { + AttrData attr = new AttrData(this); + attr.read(name_cpx, in); + attrs[k] = attr; + } + } + in.close(); + } // end ClassData.read() + + /** + * Reads and stores constant pool info. + */ + void readCP(DataInputStream in) throws IOException { + cpool_count = in.readUnsignedShort(); + tags = new byte[cpool_count]; + cpool = new Object[cpool_count]; + for (int i = 1; i < cpool_count; i++) { + byte tag = in.readByte(); + + switch (tags[i] = tag) { + case CONSTANT_UTF8: + String str = in.readUTF(); + indexHashAscii.put(cpool[i] = str, new Integer(i)); + break; + case CONSTANT_INTEGER: + cpool[i] = new Integer(in.readInt()); + break; + case CONSTANT_FLOAT: + cpool[i] = new Float(in.readFloat()); + break; + case CONSTANT_LONG: + cpool[i++] = new Long(in.readLong()); + break; + case CONSTANT_DOUBLE: + cpool[i++] = new Double(in.readDouble()); + break; + case CONSTANT_CLASS: + case CONSTANT_STRING: + cpool[i] = new CPX(in.readUnsignedShort()); + break; + + case CONSTANT_FIELD: + case CONSTANT_METHOD: + case CONSTANT_INTERFACEMETHOD: + case CONSTANT_NAMEANDTYPE: + cpool[i] = new CPX2(in.readUnsignedShort(), in.readUnsignedShort()); + break; + + case 0: + default: + throw new ClassFormatError("invalid constant type: " + (int) tags[i]); + } + } + } + + /** + * Reads and strores field info. + */ + protected void readFields(DataInputStream in) throws IOException { + int fields_count = in.readUnsignedShort(); + fields = new FieldData[fields_count]; + for (int k = 0; k < fields_count; k++) { + FieldData field = new FieldData(this); + field.read(in); + fields[k] = field; + } + } + + /** + * Reads and strores Method info. + */ + protected void readMethods(DataInputStream in) throws IOException { + int methods_count = in.readUnsignedShort(); + methods = new MethodData[methods_count]; + for (int k = 0; k < methods_count; k++) { + MethodData method = new MethodData(this); + method.read(in); + methods[k] = method; + } + } + + /** + * get a string + */ + public String getString(int n) { + if (n == 0) { + return null; + } else { + return (String) cpool[n]; + } + } + + /** + * get the type of constant given an index + */ + public byte getTag(int n) { + try { + return tags[n]; + } catch (ArrayIndexOutOfBoundsException e) { + return (byte) 100; + } + } + static final String hexString = "0123456789ABCDEF"; + public static char hexTable[] = hexString.toCharArray(); + + static String toHex(long val, int width) { + StringBuffer s = new StringBuffer(); + for (int i = width - 1; i >= 0; i--) { + s.append(hexTable[((int) (val >> (4 * i))) & 0xF]); + } + return "0x" + s.toString(); + } + + static String toHex(long val) { + int width; + for (width = 16; width > 0; width--) { + if ((val >> (width - 1) * 4) != 0) { + break; + } + } + return toHex(val, width); + } + + static String toHex(int val) { + int width; + for (width = 8; width > 0; width--) { + if ((val >> (width - 1) * 4) != 0) { + break; + } + } + return toHex(val, width); + } + + /** + * Returns the name of this class. + */ + public String getClassName() { + String res = null; + if (this_class == 0) { + return res; + } + int tcpx; + try { + if (tags[this_class] != CONSTANT_CLASS) { + return res; //" "; + } + tcpx = ((CPX) cpool[this_class]).cpx; + } catch (ArrayIndexOutOfBoundsException e) { + return res; // "#"+cpx+"// invalid constant pool index"; + } catch (Throwable e) { + return res; // "#"+cpx+"// ERROR IN DISASSEMBLER"; + } + + try { + return (String) (cpool[tcpx]); + } catch (ArrayIndexOutOfBoundsException e) { + return res; // "class #"+scpx+"// invalid constant pool index"; + } catch (ClassCastException e) { + return res; // "class #"+scpx+"// invalid constant pool reference"; + } catch (Throwable e) { + return res; // "#"+cpx+"// ERROR IN DISASSEMBLER"; + } + + } + + /** + * Returns the name of class at perticular index. + */ + public String getClassName(int cpx) { + String res = "#" + cpx; + if (cpx == 0) { + return res; + } + int scpx; + try { + if (tags[cpx] != CONSTANT_CLASS) { + return res; //" "; + } + scpx = ((CPX) cpool[cpx]).cpx; + } catch (ArrayIndexOutOfBoundsException e) { + return res; // "#"+cpx+"// invalid constant pool index"; + } catch (Throwable e) { + return res; // "#"+cpx+"// ERROR IN DISASSEMBLER"; + } + res = "#" + scpx; + try { + return (String) (cpool[scpx]); + } catch (ArrayIndexOutOfBoundsException e) { + return res; // "class #"+scpx+"// invalid constant pool index"; + } catch (ClassCastException e) { + return res; // "class #"+scpx+"// invalid constant pool reference"; + } catch (Throwable e) { + return res; // "#"+cpx+"// ERROR IN DISASSEMBLER"; + } + } + + public int getAccessFlags() { + return access; + } + + /** + * Returns true if it is a class + */ + public boolean isClass() { + if ((access & ACC_INTERFACE) == 0) { + return true; + } + return false; + } + + /** + * Returns true if it is a interface. + */ + public boolean isInterface() { + if ((access & ACC_INTERFACE) != 0) { + return true; + } + return false; + } + + /** + * Returns true if this member is public, false otherwise. + */ + public boolean isPublic() { + return (access & ACC_PUBLIC) != 0; + } + + /** + * Returns the access of this class or interface. + */ + public String[] getAccess() { + Vector v = new Vector(); + if ((access & ACC_PUBLIC) != 0) { + v.addElement("public"); + } + if ((access & ACC_FINAL) != 0) { + v.addElement("final"); + } + if ((access & ACC_ABSTRACT) != 0) { + v.addElement("abstract"); + } + String[] accflags = new String[v.size()]; + v.copyInto(accflags); + return accflags; + } + + /** + * Returns list of innerclasses. + */ + public InnerClassData[] getInnerClasses() { + return innerClasses; + } + + /** + * Returns list of attributes. + */ + final AttrData[] getAttributes() { + return attrs; + } + + public byte[] findAnnotationData(boolean classRetention) { + String n = classRetention + ? "RuntimeInvisibleAnnotations" : // NOI18N + "RuntimeVisibleAnnotations"; // NOI18N + return findAttr(n, attrs); + } + + /** + * Returns true if superbit is set. + */ + public boolean isSuperSet() { + if ((access & ACC_SUPER) != 0) { + return true; + } + return false; + } + + /** + * Returns super class name. + */ + public String getSuperClassName() { + String res = null; + if (super_class == 0) { + return res; + } + int scpx; + try { + if (tags[super_class] != CONSTANT_CLASS) { + return res; //" "; + } + scpx = ((CPX) cpool[super_class]).cpx; + } catch (ArrayIndexOutOfBoundsException e) { + return res; // "#"+cpx+"// invalid constant pool index"; + } catch (Throwable e) { + return res; // "#"+cpx+"// ERROR IN DISASSEMBLER"; + } + + try { + return (String) (cpool[scpx]); + } catch (ArrayIndexOutOfBoundsException e) { + return res; // "class #"+scpx+"// invalid constant pool index"; + } catch (ClassCastException e) { + return res; // "class #"+scpx+"// invalid constant pool reference"; + } catch (Throwable e) { + return res; // "#"+cpx+"// ERROR IN DISASSEMBLER"; + } + } + + /** + * Returns list of super interfaces. + */ + public String[] getSuperInterfaces() { + String interfacenames[] = new String[interfaces.length]; + int interfacecpx = -1; + for (int i = 0; i < interfaces.length; i++) { + interfacecpx = ((CPX) cpool[interfaces[i]]).cpx; + interfacenames[i] = (String) (cpool[interfacecpx]); + } + return interfacenames; + } + + /** + * Returns string at prticular constant pool index. + */ + public String getStringValue(int cpoolx) { + try { + return ((String) cpool[cpoolx]); + } catch (ArrayIndexOutOfBoundsException e) { + return "//invalid constant pool index:" + cpoolx; + } catch (ClassCastException e) { + return "//invalid constant pool ref:" + cpoolx; + } + } + + /** + * Returns list of field info. + */ + public FieldData[] getFields() { + return fields; + } + + /** + * Returns list of method info. + */ + public MethodData[] getMethods() { + return methods; + } + + /** + * Returns constant pool entry at that index. + */ + public CPX2 getCpoolEntry(int cpx) { + return ((CPX2) (cpool[cpx])); + } + + public Object getCpoolEntryobj(int cpx) { + return (cpool[cpx]); + } + + /** + * Returns index of this class. + */ + public int getthis_cpx() { + return this_class; + } + + /** + * Returns string at that index. + */ + public String StringValue(int cpx) { + return stringValue(cpx, false); + } + + public String stringValue(int cpx, boolean textual) { + return stringValue(cpx, textual, null); + } + + public String stringValue(int cpx, String[] classRefs) { + return stringValue(cpx, true, classRefs); + } + + private String stringValue(int cpx, boolean textual, String[] refs) { + if (cpx == 0) { + return "#0"; + } + int tag; + Object x; + String suffix = ""; + try { + tag = tags[cpx]; + x = cpool[cpx]; + } catch (IndexOutOfBoundsException e) { + return ""; + } + + if (x == null) { + return ""; + } + switch (tag) { + case CONSTANT_UTF8: { + if (!textual) { + return (String) x; + } + StringBuilder sb = new StringBuilder(); + String s = (String) x; + for (int k = 0; k < s.length(); k++) { + char c = s.charAt(k); + switch (c) { + case '\\': + sb.append('\\').append('\\'); + break; + case '\t': + sb.append('\\').append('t'); + break; + case '\n': + sb.append('\\').append('n'); + break; + case '\r': + sb.append('\\').append('r'); + break; + case '\"': + sb.append('\\').append('\"'); + break; + default: + sb.append(c); + } + } + return sb.toString(); + } + case CONSTANT_DOUBLE: { + Double d = (Double) x; + String sd = d.toString(); + if (textual) { + return sd; + } + return sd + "d"; + } + case CONSTANT_FLOAT: { + Float f = (Float) x; + String sf = (f).toString(); + if (textual) { + return sf; + } + return sf + "f"; + } + case CONSTANT_LONG: { + Long ln = (Long) x; + if (textual) { + return ln.toString(); + } + return ln.toString() + 'l'; + } + case CONSTANT_INTEGER: { + Integer in = (Integer) x; + return in.toString(); + } + case CONSTANT_CLASS: + String jn = getClassName(cpx); + if (textual) { + if (refs != null) { + refs[0] = jn; + } + return jn; + } + return javaName(jn); + case CONSTANT_STRING: + String sv = stringValue(((CPX) x).cpx, textual); + if (textual) { + return '"' + sv + '"'; + } else { + return sv; + } + case CONSTANT_FIELD: + case CONSTANT_METHOD: + case CONSTANT_INTERFACEMETHOD: + //return getShortClassName(((CPX2)x).cpx1)+"."+StringValue(((CPX2)x).cpx2); + return javaName(getClassName(((CPX2) x).cpx1)) + "." + StringValue(((CPX2) x).cpx2); + + case CONSTANT_NAMEANDTYPE: + return getName(((CPX2) x).cpx1) + ":" + StringValue(((CPX2) x).cpx2); + default: + return "UnknownTag"; //TBD + } + } + + /** + * Returns resolved java type name. + */ + public String javaName(String name) { + if (name == null) { + return "null"; + } + int len = name.length(); + if (len == 0) { + return "\"\""; + } + int cc = '/'; + fullname: + { // xxx/yyy/zzz + int cp; + for (int k = 0; k < len; k += Character.charCount(cp)) { + cp = name.codePointAt(k); + if (cc == '/') { + if (!isJavaIdentifierStart(cp)) { + break fullname; + } + } else if (cp != '/') { + if (!isJavaIdentifierPart(cp)) { + break fullname; + } + } + cc = cp; + } + return name; + } + return "\"" + name + "\""; + } + + public String getName(int cpx) { + String res; + try { + return javaName((String) cpool[cpx]); //.replace('/','.'); + } catch (ArrayIndexOutOfBoundsException e) { + return ""; + } catch (ClassCastException e) { + return ""; + } + } + + /** + * Returns unqualified class name. + */ + public String getShortClassName(int cpx) { + String classname = javaName(getClassName(cpx)); + pkgPrefixLen = classname.lastIndexOf("/") + 1; + if (pkgPrefixLen != 0) { + pkgPrefix = classname.substring(0, pkgPrefixLen); + if (classname.startsWith(pkgPrefix)) { + return classname.substring(pkgPrefixLen); + } + } + return classname; + } + + /** + * Returns source file name. + */ + public String getSourceName() { + return getName(source_cpx); + } + + /** + * Returns package name. + */ + public String getPkgName() { + String classname = getClassName(this_class); + pkgPrefixLen = classname.lastIndexOf("/") + 1; + if (pkgPrefixLen != 0) { + pkgPrefix = classname.substring(0, pkgPrefixLen); + return ("package " + pkgPrefix.substring(0, pkgPrefixLen - 1) + ";\n"); + } else { + return null; + } + } + + /** + * Returns total constant pool entry count. + */ + public int getCpoolCount() { + return cpool_count; + } + + /** + * Returns minor version of class file. + */ + public int getMinor_version() { + return minor_version; + } + + /** + * Returns major version of class file. + */ + public int getMajor_version() { + return major_version; + } + + private boolean isJavaIdentifierStart(int cp) { + return ('a' <= cp && cp <= 'z') || ('A' <= cp && cp <= 'Z'); + } + + private boolean isJavaIdentifierPart(int cp) { + return isJavaIdentifierStart(cp) || ('0' <= cp && cp <= '9'); + } + + public String[] getNameAndType(int indx) { + return getNameAndType(indx, 0, new String[2]); + } + + private String[] getNameAndType(int indx, int at, String[] arr) { + CPX2 c2 = getCpoolEntry(indx); + arr[at] = StringValue(c2.cpx1); + arr[at + 1] = StringValue(c2.cpx2); + return arr; + } + + public String[] getFieldInfoName(int indx) { + CPX2 c2 = getCpoolEntry(indx); + String[] arr = new String[3]; + arr[0] = getClassName(c2.cpx1); + return getNameAndType(c2.cpx2, 1, arr); + } + + static byte[] findAttr(String n, AttrData[] attrs) { + for (AttrData ad : attrs) { + if (n.equals(ad.getAttrName())) { + return ad.getData(); + } + } + return null; + } + } + + /** + * Strores field data informastion. + * + * @author Sucheta Dambalkar (Adopted code from jdis) + */ + static class FieldData { + + ClassData cls; + int access; + int name_index; + int descriptor_index; + int attributes_count; + int value_cpx = 0; + boolean isSynthetic = false; + boolean isDeprecated = false; + Vector attrs; + + public FieldData(ClassData cls) { + this.cls = cls; + } + + /** + * Read and store field info. + */ + public void read(DataInputStream in) throws IOException { + access = in.readUnsignedShort(); + name_index = in.readUnsignedShort(); + descriptor_index = in.readUnsignedShort(); + // Read the attributes + int attributes_count = in.readUnsignedShort(); + attrs = new Vector(attributes_count); + for (int i = 0; i < attributes_count; i++) { + int attr_name_index = in.readUnsignedShort(); + if (cls.getTag(attr_name_index) != CONSTANT_UTF8) { + continue; + } + String attr_name = cls.getString(attr_name_index); + if (attr_name.equals("ConstantValue")) { + if (in.readInt() != 2) { + throw new ClassFormatError("invalid ConstantValue attr length"); + } + value_cpx = in.readUnsignedShort(); + AttrData attr = new AttrData(cls); + attr.read(attr_name_index); + attrs.addElement(attr); + } else if (attr_name.equals("Synthetic")) { + if (in.readInt() != 0) { + throw new ClassFormatError("invalid Synthetic attr length"); + } + isSynthetic = true; + AttrData attr = new AttrData(cls); + attr.read(attr_name_index); + attrs.addElement(attr); + } else if (attr_name.equals("Deprecated")) { + if (in.readInt() != 0) { + throw new ClassFormatError("invalid Synthetic attr length"); + } + isDeprecated = true; + AttrData attr = new AttrData(cls); + attr.read(attr_name_index); + attrs.addElement(attr); + } else { + AttrData attr = new AttrData(cls); + attr.read(attr_name_index, in); + attrs.addElement(attr); + } + } + + } // end read + + public boolean isStatic() { + return (access & ACC_STATIC) != 0; + } + + /** + * Returns access of a field. + */ + public String[] getAccess() { + Vector v = new Vector(); + if ((access & ACC_PUBLIC) != 0) { + v.addElement("public"); + } + if ((access & ACC_PRIVATE) != 0) { + v.addElement("private"); + } + if ((access & ACC_PROTECTED) != 0) { + v.addElement("protected"); + } + if ((access & ACC_STATIC) != 0) { + v.addElement("static"); + } + if ((access & ACC_FINAL) != 0) { + v.addElement("final"); + } + if ((access & ACC_VOLATILE) != 0) { + v.addElement("volatile"); + } + if ((access & ACC_TRANSIENT) != 0) { + v.addElement("transient"); + } + String[] accflags = new String[v.size()]; + v.copyInto(accflags); + return accflags; + } + + /** + * Returns name of a field. + */ + public String getName() { + return cls.getStringValue(name_index); + } + + /** + * Returns internal signature of a field + */ + public String getInternalSig() { + return cls.getStringValue(descriptor_index); + } + + /** + * Returns true if field is synthetic. + */ + public boolean isSynthetic() { + return isSynthetic; + } + + /** + * Returns true if field is deprecated. + */ + public boolean isDeprecated() { + return isDeprecated; + } + + /** + * Returns index of constant value in cpool. + */ + public int getConstantValueIndex() { + return (value_cpx); + } + + /** + * Returns list of attributes of field. + */ + public Vector getAttributes() { + return attrs; + } + + public byte[] findAnnotationData(boolean classRetention) { + String n = classRetention + ? "RuntimeInvisibleAnnotations" : // NOI18N + "RuntimeVisibleAnnotations"; // NOI18N + AttrData[] arr = new AttrData[attrs.size()]; + attrs.copyInto(arr); + return ClassData.findAttr(n, arr); + } + } + + /** + * A JavaScript optimized replacement for Hashtable. + * + * @author Jaroslav Tulach + */ + private static final class Hashtable { + + private Object[] keys; + private Object[] values; + + Hashtable(int i) { + this(); + } + + Hashtable(int i, double d) { + this(); + } + + Hashtable() { + } + + synchronized void put(Object key, Object val) { + int[] where = {-1, -1}; + Object found = get(key, where); + if (where[0] != -1) { + // key exists + values[where[0]] = val; + } else { + if (where[1] != -1) { + // null found + keys[where[1]] = key; + values[where[1]] = val; + } else { + if (keys == null) { + keys = new Object[11]; + values = new Object[11]; + keys[0] = key; + values[0] = val; + } else { + Object[] newKeys = new Object[keys.length * 2]; + Object[] newValues = new Object[values.length * 2]; + for (int i = 0; i < keys.length; i++) { + newKeys[i] = keys[i]; + newValues[i] = values[i]; + } + newKeys[keys.length] = key; + newValues[keys.length] = val; + keys = newKeys; + values = newValues; + } + } + } + } + + Object get(Object key) { + return get(key, null); + } + + private synchronized Object get(Object key, int[] foundAndNull) { + if (keys == null) { + return null; + } + for (int i = 0; i < keys.length; i++) { + if (keys[i] == null) { + if (foundAndNull != null) { + foundAndNull[1] = i; + } + } else if (keys[i].equals(key)) { + if (foundAndNull != null) { + foundAndNull[0] = i; + } + return values[i]; + } + } + return null; + } + } + + /** + * Strores InnerClass data informastion. + * + * @author Sucheta Dambalkar (Adopted code from jdis) + */ + private static class InnerClassData { + + ClassData cls; + int inner_class_info_index, outer_class_info_index, inner_name_index, access; + + public InnerClassData(ClassData cls) { + this.cls = cls; + + } + + /** + * Read Innerclass attribute data. + */ + public void read(DataInputStream in) throws IOException { + inner_class_info_index = in.readUnsignedShort(); + outer_class_info_index = in.readUnsignedShort(); + inner_name_index = in.readUnsignedShort(); + access = in.readUnsignedShort(); + } // end read + + /** + * Returns the access of this class or interface. + */ + public String[] getAccess() { + Vector v = new Vector(); + if ((access & ACC_PUBLIC) != 0) { + v.addElement("public"); + } + if ((access & ACC_FINAL) != 0) { + v.addElement("final"); + } + if ((access & ACC_ABSTRACT) != 0) { + v.addElement("abstract"); + } + String[] accflags = new String[v.size()]; + v.copyInto(accflags); + return accflags; + } + } // end InnerClassData + + /** + * Strores LineNumberTable data information. + * + * @author Sucheta Dambalkar (Adopted code from jdis) + */ + private static class LineNumData { + + short start_pc, line_number; + + public LineNumData() { + } + + /** + * Read LineNumberTable attribute. + */ + public LineNumData(DataInputStream in) throws IOException { + start_pc = in.readShort(); + line_number = in.readShort(); + + } + } + + /** + * Strores LocalVariableTable data information. + * + * @author Sucheta Dambalkar (Adopted code from jdis) + */ + private static class LocVarData { + + short start_pc, length, name_cpx, sig_cpx, slot; + + public LocVarData() { + } + + /** + * Read LocalVariableTable attribute. + */ + public LocVarData(DataInputStream in) throws IOException { + start_pc = in.readShort(); + length = in.readShort(); + name_cpx = in.readShort(); + sig_cpx = in.readShort(); + slot = in.readShort(); + + } + } + /** + * Strores method data informastion. + * + * @author Sucheta Dambalkar (Adopted code from jdis) + */ + static class MethodData { + + ClassData cls; + int access; + int name_index; + int descriptor_index; + int attributes_count; + byte[] code; + Vector exception_table = new Vector(0); + Vector lin_num_tb = new Vector(0); + Vector loc_var_tb = new Vector(0); + StackMapTableData[] stackMapTable; + StackMapData[] stackMap; + int[] exc_index_table = null; + Vector attrs = new Vector(0); + Vector code_attrs = new Vector(0); + int max_stack, max_locals; + boolean isSynthetic = false; + boolean isDeprecated = false; + + public MethodData(ClassData cls) { + this.cls = cls; + } + + /** + * Read method info. + */ + public void read(DataInputStream in) throws IOException { + access = in.readUnsignedShort(); + name_index = in.readUnsignedShort(); + descriptor_index = in.readUnsignedShort(); + int attributes_count = in.readUnsignedShort(); + for (int i = 0; i < attributes_count; i++) { + int attr_name_index = in.readUnsignedShort(); + + readAttr: + { + if (cls.getTag(attr_name_index) == CONSTANT_UTF8) { + String attr_name = cls.getString(attr_name_index); + if (attr_name.equals("Code")) { + readCode(in); + AttrData attr = new AttrData(cls); + attr.read(attr_name_index); + attrs.addElement(attr); + break readAttr; + } else if (attr_name.equals("Exceptions")) { + readExceptions(in); + AttrData attr = new AttrData(cls); + attr.read(attr_name_index); + attrs.addElement(attr); + break readAttr; + } else if (attr_name.equals("Synthetic")) { + if (in.readInt() != 0) { + throw new ClassFormatError("invalid Synthetic attr length"); + } + isSynthetic = true; + AttrData attr = new AttrData(cls); + attr.read(attr_name_index); + attrs.addElement(attr); + break readAttr; + } else if (attr_name.equals("Deprecated")) { + if (in.readInt() != 0) { + throw new ClassFormatError("invalid Synthetic attr length"); + } + isDeprecated = true; + AttrData attr = new AttrData(cls); + attr.read(attr_name_index); + attrs.addElement(attr); + break readAttr; + } + } + AttrData attr = new AttrData(cls); + attr.read(attr_name_index, in); + attrs.addElement(attr); + } + } + } + + /** + * Read code attribute info. + */ + public void readCode(DataInputStream in) throws IOException { + + int attr_length = in.readInt(); + max_stack = in.readUnsignedShort(); + max_locals = in.readUnsignedShort(); + int codelen = in.readInt(); + + code = new byte[codelen]; + int totalread = 0; + while (totalread < codelen) { + totalread += in.read(code, totalread, codelen - totalread); + } + // in.read(code, 0, codelen); + int clen = 0; + readExceptionTable(in); + int code_attributes_count = in.readUnsignedShort(); + + for (int k = 0; k < code_attributes_count; k++) { + int table_name_index = in.readUnsignedShort(); + int table_name_tag = cls.getTag(table_name_index); + AttrData attr = new AttrData(cls); + if (table_name_tag == CONSTANT_UTF8) { + String table_name_tstr = cls.getString(table_name_index); + if (table_name_tstr.equals("LineNumberTable")) { + readLineNumTable(in); + attr.read(table_name_index); + } else if (table_name_tstr.equals("LocalVariableTable")) { + readLocVarTable(in); + attr.read(table_name_index); + } else if (table_name_tstr.equals("StackMapTable")) { + readStackMapTable(in); + attr.read(table_name_index); + } else if (table_name_tstr.equals("StackMap")) { + readStackMap(in); + attr.read(table_name_index); + } else { + attr.read(table_name_index, in); + } + code_attrs.addElement(attr); + continue; + } + + attr.read(table_name_index, in); + code_attrs.addElement(attr); + } + } + + /** + * Read exception table info. + */ + void readExceptionTable(DataInputStream in) throws IOException { + int exception_table_len = in.readUnsignedShort(); + exception_table = new Vector(exception_table_len); + for (int l = 0; l < exception_table_len; l++) { + exception_table.addElement(new TrapData(in, l)); + } + } + + /** + * Read LineNumberTable attribute info. + */ + void readLineNumTable(DataInputStream in) throws IOException { + int attr_len = in.readInt(); // attr_length + int lin_num_tb_len = in.readUnsignedShort(); + lin_num_tb = new Vector(lin_num_tb_len); + for (int l = 0; l < lin_num_tb_len; l++) { + lin_num_tb.addElement(new LineNumData(in)); + } + } + + /** + * Read LocalVariableTable attribute info. + */ + void readLocVarTable(DataInputStream in) throws IOException { + int attr_len = in.readInt(); // attr_length + int loc_var_tb_len = in.readUnsignedShort(); + loc_var_tb = new Vector(loc_var_tb_len); + for (int l = 0; l < loc_var_tb_len; l++) { + loc_var_tb.addElement(new LocVarData(in)); + } + } + + /** + * Read Exception attribute info. + */ + public void readExceptions(DataInputStream in) throws IOException { + int attr_len = in.readInt(); // attr_length in prog + int num_exceptions = in.readUnsignedShort(); + exc_index_table = new int[num_exceptions]; + for (int l = 0; l < num_exceptions; l++) { + int exc = in.readShort(); + exc_index_table[l] = exc; + } + } + + /** + * Read StackMapTable attribute info. + */ + void readStackMapTable(DataInputStream in) throws IOException { + int attr_len = in.readInt(); //attr_length + int stack_map_tb_len = in.readUnsignedShort(); + stackMapTable = new StackMapTableData[stack_map_tb_len]; + for (int i = 0; i < stack_map_tb_len; i++) { + stackMapTable[i] = StackMapTableData.getInstance(in, this); + } + } + + /** + * Read StackMap attribute info. + */ + void readStackMap(DataInputStream in) throws IOException { + int attr_len = in.readInt(); //attr_length + int stack_map_len = in.readUnsignedShort(); + stackMap = new StackMapData[stack_map_len]; + for (int i = 0; i < stack_map_len; i++) { + stackMap[i] = new StackMapData(in, this); + } + } + + /** + * Return access of the method. + */ + public int getAccess() { + return access; + } + + /** + * Return name of the method. + */ + public String getName() { + return cls.getStringValue(name_index); + } + + /** + * Return internal siganature of the method. + */ + public String getInternalSig() { + return cls.getStringValue(descriptor_index); + } + + /** + * Return code attribute data of a method. + */ + public byte[] getCode() { + return code; + } + + /** + * Return LineNumberTable size. + */ + public int getnumlines() { + return lin_num_tb.size(); + } + + /** + * Return LineNumberTable + */ + public Vector getlin_num_tb() { + return lin_num_tb; + } + + /** + * Return LocalVariableTable size. + */ + public int getloc_var_tbsize() { + return loc_var_tb.size(); + } + + /** + * Return LocalVariableTable. + */ + public Vector getloc_var_tb() { + return loc_var_tb; + } + + /** + * Return StackMap. + */ + public StackMapData[] getStackMap() { + return stackMap; + } + + /** + * Return StackMapTable. + */ + public StackMapTableData[] getStackMapTable() { + return stackMapTable; + } + + public StackMapIterator createStackMapIterator() { + return new StackMapIterator(this); + } + + /** + * Return true if method is static + */ + public boolean isStatic() { + if ((access & ACC_STATIC) != 0) { + return true; + } + return false; + } + + /** + * Return max depth of operand stack. + */ + public int getMaxStack() { + return max_stack; + } + + /** + * Return number of local variables. + */ + public int getMaxLocals() { + return max_locals; + } + + /** + * Return exception index table in Exception attribute. + */ + public int[] get_exc_index_table() { + return exc_index_table; + } + + /** + * Return exception table in code attributre. + */ + public TrapDataIterator getTrapDataIterator() { + return new TrapDataIterator(exception_table); + } + + /** + * Return method attributes. + */ + public Vector getAttributes() { + return attrs; + } + + /** + * Return code attributes. + */ + public Vector getCodeAttributes() { + return code_attrs; + } + + /** + * Return true if method id synthetic. + */ + public boolean isSynthetic() { + return isSynthetic; + } + + /** + * Return true if method is deprecated. + */ + public boolean isDeprecated() { + return isDeprecated; + } + + public byte[] findAnnotationData(boolean classRetention) { + String n = classRetention + ? "RuntimeInvisibleAnnotations" : // NOI18N + "RuntimeVisibleAnnotations"; // NOI18N + AttrData[] arr = new AttrData[attrs.size()]; + attrs.copyInto(arr); + return ClassData.findAttr(n, arr); + } + + public boolean isConstructor() { + return "".equals(getName()); + } + } + + /* represents one entry of StackMap attribute + */ + private static class StackMapData { + + final int offset; + final int[] locals; + final int[] stack; + + StackMapData(int offset, int[] locals, int[] stack) { + this.offset = offset; + this.locals = locals; + this.stack = stack; + } + + StackMapData(DataInputStream in, MethodData method) throws IOException { + offset = in.readUnsignedShort(); + int local_size = in.readUnsignedShort(); + locals = readTypeArray(in, local_size, method); + int stack_size = in.readUnsignedShort(); + stack = readTypeArray(in, stack_size, method); + } + + static final int[] readTypeArray(DataInputStream in, int length, MethodData method) throws IOException { + int[] types = new int[length]; + for (int i = 0; i < length; i++) { + types[i] = readType(in, method); + } + return types; + } + + static final int readType(DataInputStream in, MethodData method) throws IOException { + int type = in.readUnsignedByte(); + if (type == ITEM_Object || type == ITEM_NewObject) { + type = type | (in.readUnsignedShort() << 8); + } + return type; + } + } + + static final class StackMapIterator { + + private final StackMapTableData[] stackMapTable; + private final TypeArray argTypes; + private final TypeArray localTypes; + private final TypeArray stackTypes; + private int nextFrameIndex; + private int lastFrameByteCodeOffset; + private int byteCodeOffset; + + StackMapIterator(final MethodData methodData) { + this(methodData.getStackMapTable(), + methodData.getInternalSig(), + methodData.isStatic()); + } + + StackMapIterator(final StackMapTableData[] stackMapTable, + final String methodSignature, + final boolean isStaticMethod) { + this.stackMapTable = (stackMapTable != null) + ? stackMapTable + : new StackMapTableData[0]; + + argTypes = getArgTypes(methodSignature, isStaticMethod); + localTypes = new TypeArray(); + stackTypes = new TypeArray(); + + localTypes.addAll(argTypes); + + lastFrameByteCodeOffset = -1; + advanceBy(0); + } + + public String getFrameAsString() { + return (nextFrameIndex == 0) + ? StackMapTableData.toString("INITIAL", 0, null, null) + : stackMapTable[nextFrameIndex - 1].toString(); + } + + public int getFrameIndex() { + return nextFrameIndex; + } + + public TypeArray getFrameStack() { + return stackTypes; + } + + public TypeArray getFrameLocals() { + return localTypes; + } + + public TypeArray getArguments() { + return argTypes; + } + + public void advanceBy(final int numByteCodes) { + if (numByteCodes < 0) { + throw new IllegalStateException("Forward only iterator"); + } + + byteCodeOffset += numByteCodes; + while ((nextFrameIndex < stackMapTable.length) + && ((byteCodeOffset - lastFrameByteCodeOffset) + >= (stackMapTable[nextFrameIndex].offsetDelta + + 1))) { + final StackMapTableData nextFrame = stackMapTable[nextFrameIndex]; + + lastFrameByteCodeOffset += nextFrame.offsetDelta + 1; + nextFrame.applyTo(localTypes, stackTypes); + + ++nextFrameIndex; + } + } + + public void advanceTo(final int nextByteCodeOffset) { + advanceBy(nextByteCodeOffset - byteCodeOffset); + } + + private static TypeArray getArgTypes(final String methodSignature, + final boolean isStaticMethod) { + final TypeArray argTypes = new TypeArray(); + + if (!isStaticMethod) { + argTypes.add(ITEM_Object); + } + + if (methodSignature.charAt(0) != '(') { + throw new IllegalArgumentException("Invalid method signature"); + } + + final int length = methodSignature.length(); + boolean skipType = false; + int argType; + for (int i = 1; i < length; ++i) { + switch (methodSignature.charAt(i)) { + case 'B': + case 'C': + case 'S': + case 'Z': + case 'I': + argType = ITEM_Integer; + break; + case 'J': + argType = ITEM_Long; + break; + case 'F': + argType = ITEM_Float; + break; + case 'D': + argType = ITEM_Double; + break; + case 'L': { + i = methodSignature.indexOf(';', i + 1); + if (i == -1) { + throw new IllegalArgumentException( + "Invalid method signature"); + } + argType = ITEM_Object; + break; + } + case ')': + // not interested in the return value type + return argTypes; + case '[': + if (!skipType) { + argTypes.add(ITEM_Object); + skipType = true; + } + continue; + + default: + throw new IllegalArgumentException( + "Invalid method signature"); + } + + if (!skipType) { + argTypes.add(argType); + } else { + skipType = false; + } + } + + return argTypes; + } + } + /* represents one entry of StackMapTable attribute + */ + + private static abstract class StackMapTableData { + + final int frameType; + int offsetDelta; + + StackMapTableData(int frameType) { + this.frameType = frameType; + } + + abstract void applyTo(TypeArray localTypes, TypeArray stackTypes); + + protected static String toString( + final String frameType, + final int offset, + final int[] localTypes, + final int[] stackTypes) { + final StringBuilder sb = new StringBuilder(frameType); + + sb.append("(off: +").append(offset); + if (localTypes != null) { + sb.append(", locals: "); + appendTypes(sb, localTypes); + } + if (stackTypes != null) { + sb.append(", stack: "); + appendTypes(sb, stackTypes); + } + sb.append(')'); + + return sb.toString(); + } + + private static void appendTypes(final StringBuilder sb, final int[] types) { + sb.append('['); + if (types.length > 0) { + sb.append(TypeArray.typeString(types[0])); + for (int i = 1; i < types.length; ++i) { + sb.append(", "); + sb.append(TypeArray.typeString(types[i])); + } + } + sb.append(']'); + } + + private static class SameFrame extends StackMapTableData { + + SameFrame(int frameType, int offsetDelta) { + super(frameType); + this.offsetDelta = offsetDelta; + } + + @Override + void applyTo(TypeArray localTypes, TypeArray stackTypes) { + stackTypes.clear(); + } + + @Override + public String toString() { + return toString("SAME" + ((frameType == SAME_FRAME_EXTENDED) + ? "_FRAME_EXTENDED" : ""), + offsetDelta, + null, null); + } + } + + private static class SameLocals1StackItem extends StackMapTableData { + + final int[] stack; + + SameLocals1StackItem(int frameType, int offsetDelta, int[] stack) { + super(frameType); + this.offsetDelta = offsetDelta; + this.stack = stack; + } + + @Override + void applyTo(TypeArray localTypes, TypeArray stackTypes) { + stackTypes.setAll(stack); + } + + @Override + public String toString() { + return toString( + "SAME_LOCALS_1_STACK_ITEM" + + ((frameType == SAME_LOCALS_1_STACK_ITEM_EXTENDED) + ? "_EXTENDED" : ""), + offsetDelta, + null, stack); + } + } + + private static class ChopFrame extends StackMapTableData { + + ChopFrame(int frameType, int offsetDelta) { + super(frameType); + this.offsetDelta = offsetDelta; + } + + @Override + void applyTo(TypeArray localTypes, TypeArray stackTypes) { + localTypes.setSize(localTypes.getSize() + - (SAME_FRAME_EXTENDED - frameType)); + stackTypes.clear(); + } + + @Override + public String toString() { + return toString("CHOP", offsetDelta, null, null); + } + } + + private static class AppendFrame extends StackMapTableData { + + final int[] locals; + + AppendFrame(int frameType, int offsetDelta, int[] locals) { + super(frameType); + this.offsetDelta = offsetDelta; + this.locals = locals; + } + + @Override + void applyTo(TypeArray localTypes, TypeArray stackTypes) { + localTypes.addAll(locals); + stackTypes.clear(); + } + + @Override + public String toString() { + return toString("APPEND", offsetDelta, locals, null); + } + } + + private static class FullFrame extends StackMapTableData { + + final int[] locals; + final int[] stack; + + FullFrame(int offsetDelta, int[] locals, int[] stack) { + super(FULL_FRAME); + this.offsetDelta = offsetDelta; + this.locals = locals; + this.stack = stack; + } + + @Override + void applyTo(TypeArray localTypes, TypeArray stackTypes) { + localTypes.setAll(locals); + stackTypes.setAll(stack); + } + + @Override + public String toString() { + return toString("FULL", offsetDelta, locals, stack); + } + } + + static StackMapTableData getInstance(DataInputStream in, MethodData method) + throws IOException { + int frameType = in.readUnsignedByte(); + + if (frameType < SAME_FRAME_BOUND) { + // same_frame + return new SameFrame(frameType, frameType); + } else if (SAME_FRAME_BOUND <= frameType && frameType < SAME_LOCALS_1_STACK_ITEM_BOUND) { + // same_locals_1_stack_item_frame + // read additional single stack element + return new SameLocals1StackItem(frameType, + (frameType - SAME_FRAME_BOUND), + StackMapData.readTypeArray(in, 1, method)); + } else if (frameType == SAME_LOCALS_1_STACK_ITEM_EXTENDED) { + // same_locals_1_stack_item_extended + return new SameLocals1StackItem(frameType, + in.readUnsignedShort(), + StackMapData.readTypeArray(in, 1, method)); + } else if (SAME_LOCALS_1_STACK_ITEM_EXTENDED < frameType && frameType < SAME_FRAME_EXTENDED) { + // chop_frame or same_frame_extended + return new ChopFrame(frameType, in.readUnsignedShort()); + } else if (frameType == SAME_FRAME_EXTENDED) { + // chop_frame or same_frame_extended + return new SameFrame(frameType, in.readUnsignedShort()); + } else if (SAME_FRAME_EXTENDED < frameType && frameType < FULL_FRAME) { + // append_frame + return new AppendFrame(frameType, in.readUnsignedShort(), + StackMapData.readTypeArray(in, frameType - SAME_FRAME_EXTENDED, method)); + } else if (frameType == FULL_FRAME) { + // full_frame + int offsetDelta = in.readUnsignedShort(); + int locals_size = in.readUnsignedShort(); + int[] locals = StackMapData.readTypeArray(in, locals_size, method); + int stack_size = in.readUnsignedShort(); + int[] stack = StackMapData.readTypeArray(in, stack_size, method); + return new FullFrame(offsetDelta, locals, stack); + } else { + throw new ClassFormatError("unrecognized frame_type in StackMapTable"); + } + } + } + + /** + * Stores exception table data in code attribute. + * + * @author Sucheta Dambalkar (Adopted code from jdis) + */ + static final class TrapData { + + public final short start_pc; + public final short end_pc; + public final short handler_pc; + public final short catch_cpx; + final int num; + + /** + * Read and store exception table data in code attribute. + */ + TrapData(DataInputStream in, int num) throws IOException { + this.num = num; + start_pc = in.readShort(); + end_pc = in.readShort(); + handler_pc = in.readShort(); + catch_cpx = in.readShort(); + } + + /** + * returns recommended identifier + */ + public String ident() { + return "t" + num; + } + } + /** + * + * @author Jaroslav Tulach + */ + static final class TrapDataIterator { + + private final Hashtable exStart = new Hashtable(); + private final Hashtable exStop = new Hashtable(); + private TrapData[] current = new TrapData[10]; + private int currentCount; + + TrapDataIterator(Vector exceptionTable) { + for (int i = 0; i < exceptionTable.size(); i++) { + final TrapData td = (TrapData) exceptionTable.elementAt(i); + put(exStart, td.start_pc, td); + put(exStop, td.end_pc, td); + } + } + + private static void put(Hashtable h, short key, TrapData td) { + Short s = Short.valueOf((short) key); + Vector v = (Vector) h.get(s); + if (v == null) { + v = new Vector(1); + h.put(s, v); + } + v.add(td); + } + + private boolean processAll(Hashtable h, Short key, boolean add) { + boolean change = false; + Vector v = (Vector) h.get(key); + if (v != null) { + int s = v.size(); + for (int i = 0; i < s; i++) { + TrapData td = (TrapData) v.elementAt(i); + if (add) { + add(td); + change = true; + } else { + remove(td); + change = true; + } + } + } + return change; + } + + public boolean advanceTo(int i) { + Short s = Short.valueOf((short) i); + boolean ch1 = processAll(exStart, s, true); + boolean ch2 = processAll(exStop, s, false); + return ch1 || ch2; + } + + public boolean useTry() { + return currentCount > 0; + } + + public TrapData[] current() { + TrapData[] copy = new TrapData[currentCount]; + for (int i = 0; i < currentCount; i++) { + copy[i] = current[i]; + } + return copy; + } + + private void add(TrapData e) { + if (currentCount == current.length) { + TrapData[] data = new TrapData[currentCount * 2]; + for (int i = 0; i < currentCount; i++) { + data[i] = current[i]; + } + current = data; + } + current[currentCount++] = e; + } + + private void remove(TrapData e) { + if (currentCount == 0) { + return; + } + int from = 0; + while (from < currentCount) { + if (e == current[from++]) { + break; + } + } + while (from < currentCount) { + current[from - 1] = current[from]; + current[from] = null; + from++; + } + currentCount--; + } + } + static final class TypeArray { + + private static final int CAPACITY_INCREMENT = 16; + private int[] types; + private int size; + + public TypeArray() { + } + + public TypeArray(final TypeArray initialTypes) { + setAll(initialTypes); + } + + public void add(final int newType) { + ensureCapacity(size + 1); + types[size++] = newType; + } + + public void addAll(final TypeArray newTypes) { + addAll(newTypes.types, 0, newTypes.size); + } + + public void addAll(final int[] newTypes) { + addAll(newTypes, 0, newTypes.length); + } + + public void addAll(final int[] newTypes, + final int offset, + final int count) { + if (count > 0) { + ensureCapacity(size + count); + arraycopy(newTypes, offset, types, size, count); + size += count; + } + } + + public void set(final int index, final int newType) { + types[index] = newType; + } + + public void setAll(final TypeArray newTypes) { + setAll(newTypes.types, 0, newTypes.size); + } + + public void setAll(final int[] newTypes) { + setAll(newTypes, 0, newTypes.length); + } + + public void setAll(final int[] newTypes, + final int offset, + final int count) { + if (count > 0) { + ensureCapacity(count); + arraycopy(newTypes, offset, types, 0, count); + size = count; + } else { + clear(); + } + } + + public void setSize(final int newSize) { + if (size != newSize) { + ensureCapacity(newSize); + + for (int i = size; i < newSize; ++i) { + types[i] = 0; + } + size = newSize; + } + } + + public void clear() { + size = 0; + } + + public int getSize() { + return size; + } + + public int get(final int index) { + return types[index]; + } + + public static String typeString(final int type) { + switch (type & 0xff) { + case ITEM_Bogus: + return "_top_"; + case ITEM_Integer: + return "_int_"; + case ITEM_Float: + return "_float_"; + case ITEM_Double: + return "_double_"; + case ITEM_Long: + return "_long_"; + case ITEM_Null: + return "_null_"; + case ITEM_InitObject: // UninitializedThis + return "_init_"; + case ITEM_Object: + return "_object_"; + case ITEM_NewObject: // Uninitialized + return "_new_"; + default: + throw new IllegalArgumentException("Unknown type"); + } + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("["); + if (size > 0) { + sb.append(typeString(types[0])); + for (int i = 1; i < size; ++i) { + sb.append(", "); + sb.append(typeString(types[i])); + } + } + + return sb.append(']').toString(); + } + + private void ensureCapacity(final int minCapacity) { + if ((minCapacity == 0) + || (types != null) && (minCapacity <= types.length)) { + return; + } + + final int newCapacity = + ((minCapacity + CAPACITY_INCREMENT - 1) / CAPACITY_INCREMENT) + * CAPACITY_INCREMENT; + final int[] newTypes = new int[newCapacity]; + + if (size > 0) { + arraycopy(types, 0, newTypes, 0, size); + } + + types = newTypes; + } + + // no System.arraycopy + private void arraycopy(final int[] src, final int srcPos, + final int[] dest, final int destPos, + final int length) { + for (int i = 0; i < length; ++i) { + dest[destPos + i] = src[srcPos + i]; + } + } + } + /** + * A JavaScript ready replacement for java.util.Vector + * + * @author Jaroslav Tulach + */ + @JavaScriptPrototype(prototype = "new Array") + private static final class Vector { + + private Object[] arr; + + Vector() { + } + + Vector(int i) { + } + + void add(Object objectType) { + addElement(objectType); + } + + @JavaScriptBody(args = {"obj"}, body = + "this.push(obj);") + void addElement(Object obj) { + final int s = size(); + setSize(s + 1); + setElementAt(obj, s); + } + + @JavaScriptBody(args = {}, body = + "return this.length;") + int size() { + return arr == null ? 0 : arr.length; + } + + @JavaScriptBody(args = {"newArr"}, body = + "for (var i = 0; i < this.length; i++) {\n" + + " newArr[i] = this[i];\n" + + "}\n") + void copyInto(Object[] newArr) { + if (arr == null) { + return; + } + int min = Math.min(newArr.length, arr.length); + for (int i = 0; i < min; i++) { + newArr[i] = arr[i]; + } + } + + @JavaScriptBody(args = {"index"}, body = + "return this[index];") + Object elementAt(int index) { + return arr[index]; + } + + private void setSize(int len) { + Object[] newArr = new Object[len]; + copyInto(newArr); + arr = newArr; + } + + @JavaScriptBody(args = {"val", "index"}, body = + "this[index] = val;") + void setElementAt(Object val, int index) { + arr[index] = val; + } + } + +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Mon Oct 07 14:20:58 2013 +0200 @@ -19,15 +19,7 @@ import java.io.IOException; import java.io.InputStream; -import org.apidesign.bck2brwsr.core.JavaScriptBody; -import org.apidesign.javap.AnnotationParser; -import org.apidesign.javap.ClassData; -import org.apidesign.javap.FieldData; -import org.apidesign.javap.MethodData; -import org.apidesign.javap.StackMapIterator; -import static org.apidesign.javap.RuntimeConstants.*; -import org.apidesign.javap.TrapData; -import org.apidesign.javap.TrapDataIterator; +import static org.apidesign.vm4brwsr.ByteCodeParser.*; /** Translator of the code inside class files to JavaScript. * @@ -36,9 +28,16 @@ abstract class ByteCodeToJavaScript { private ClassData jc; final Appendable out; + final ObfuscationDelegate obfuscationDelegate; protected ByteCodeToJavaScript(Appendable out) { + this(out, ObfuscationDelegate.NULL); + } + + protected ByteCodeToJavaScript( + Appendable out, ObfuscationDelegate obfuscationDelegate) { this.out = out; + this.obfuscationDelegate = obfuscationDelegate; } /* Collects additional required resources. @@ -66,7 +65,9 @@ /* protected */ String accessClass(String classOperation) { return classOperation; } - + + abstract String getVMObject(); + /** Prints out a debug message. * * @param msg the message @@ -95,14 +96,34 @@ ); } byte[] arrData = jc.findAnnotationData(true); - String[] arr = findAnnotation(arrData, jc, - "org.apidesign.bck2brwsr.core.ExtraJavaScript", - "resource", "processByteCode" - ); - if (arr != null) { - requireScript(arr[0]); - if ("0".equals(arr[1])) { - return null; + { + 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; + } + } + } + { + 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, @@ -112,7 +133,8 @@ StringArray toInitilize = new StringArray(); final String className = className(jc); out.append("\n\n").append(assignClass(className)); - out.append("function CLS() {"); + out.append("function ").append(className).append("() {"); + out.append("\n var CLS = ").append(className).append(';'); out.append("\n if (!CLS.$class) {"); if (proto == null) { String sc = jc.getSuperClassName(); // with _ @@ -132,6 +154,10 @@ for (FieldData v : jc.getFields()) { if (v.isStatic()) { out.append("\n CLS.").append(v.getName()).append(initField(v)); + out.append("\n c._").append(v.getName()).append(" = function (v) {") + .append(" if (arguments.length == 1) CLS.").append(v.getName()) + .append(" = v; return CLS."). + append(v.getName()).append("; };"); } else { out.append("\n c._").append(v.getName()).append(" = function (v) {") .append(" if (arguments.length == 1) this.fld_"). @@ -140,6 +166,8 @@ append(className).append('_').append(v.getName()) .append("; };"); } + + obfuscationDelegate.exportField(out, "c", "_" + v.getName(), v); } for (MethodData m : jc.getMethods()) { byte[] onlyArr = m.findAnnotationData(true); @@ -154,34 +182,44 @@ } continue; } - String prefix; + String destObject; String mn; + out.append("\n "); if (m.isStatic()) { - prefix = "\n c."; - mn = generateStaticMethod(prefix, m, toInitilize); + destObject = "c"; + mn = generateStaticMethod(destObject, m, toInitilize); } else { if (m.isConstructor()) { - prefix = "\n CLS."; - mn = generateInstanceMethod(prefix, m); + destObject = "CLS"; + mn = generateInstanceMethod(destObject, m); } else { - prefix = "\n c."; - mn = generateInstanceMethod(prefix, m); + destObject = "c"; + mn = generateInstanceMethod(destObject, m); } } + obfuscationDelegate.exportMethod(out, destObject, mn, m); byte[] runAnno = m.findAnnotationData(false); if (runAnno != null) { - out.append(prefix).append(mn).append(".anno = {"); + out.append("\n ").append(destObject).append(".").append(mn).append(".anno = {"); generateAnno(jc, out, runAnno); out.append("\n };"); } - out.append(prefix).append(mn).append(".access = " + m.getAccess()).append(";"); - out.append(prefix).append(mn).append(".cls = CLS;"); + out.append("\n ").append(destObject).append(".").append(mn).append(".access = " + m.getAccess()).append(";"); + out.append("\n ").append(destObject).append(".").append(mn).append(".cls = CLS;"); } out.append("\n c.constructor = CLS;"); - out.append("\n c.$instOf_").append(className).append(" = true;"); + out.append("\n function fillInstOf(x) {"); + String instOfName = "$instOf_" + className; + out.append("\n x.").append(instOfName).append(" = true;"); for (String superInterface : jc.getSuperInterfaces()) { - out.append("\n c.$instOf_").append(superInterface.replace('/', '_')).append(" = true;"); + 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); out.append("\n CLS.$class = 'temp';"); out.append("\n CLS.$class = "); out.append(accessClass("java_lang_Class(true);")); @@ -195,6 +233,9 @@ generateAnno(jc, out, classAnno); out.append("\n };"); } + for (String init : toInitilize.toArray()) { + out.append("\n ").append(init).append("();"); + } out.append("\n }"); out.append("\n if (arguments.length === 0) {"); out.append("\n if (!(this instanceof CLS)) {"); @@ -223,14 +264,17 @@ out.append("\n }"); out.append("\n return arguments[0] ? new CLS() : CLS.prototype;"); out.append("\n};"); - StringBuilder sb = new StringBuilder(); - for (String init : toInitilize.toArray()) { - sb.append("\n").append(init).append("();"); - } - return sb.toString(); + + obfuscationDelegate.exportClass(out, getVMObject(), className, jc); + +// StringBuilder sb = new StringBuilder(); +// for (String init : toInitilize.toArray()) { +// sb.append("\n").append(init).append("();"); +// } + return ""; } - private String generateStaticMethod(String prefix, MethodData m, StringArray toInitilize) throws IOException { - String jsb = javaScriptBody(prefix, m, true); + private String generateStaticMethod(String destObject, MethodData m, StringArray toInitilize) throws IOException { + String jsb = javaScriptBody(destObject, m, true); if (jsb != null) { return jsb; } @@ -238,28 +282,28 @@ if (mn.equals("class__V")) { toInitilize.add(accessClass(className(jc)) + "(false)." + mn); } - generateMethod(prefix, mn, m); + generateMethod(destObject, mn, m); return mn; } - private String generateInstanceMethod(String prefix, MethodData m) throws IOException { - String jsb = javaScriptBody(prefix, m, false); + private String generateInstanceMethod(String destObject, MethodData m) throws IOException { + String jsb = javaScriptBody(destObject, m, false); if (jsb != null) { return jsb; } final String mn = findMethodName(m, new StringBuilder()); - generateMethod(prefix, mn, m); + generateMethod(destObject, mn, m); return mn; } - private void generateMethod(String prefix, String name, MethodData m) + private void generateMethod(String destObject, String name, MethodData m) throws IOException { final StackMapIterator stackMapIterator = m.createStackMapIterator(); TrapDataIterator trap = m.getTrapDataIterator(); final LocalsMapper lmapper = new LocalsMapper(stackMapIterator.getArguments()); - out.append(prefix).append(name).append(" = function("); + out.append(destObject).append(".").append(name).append(" = function("); lmapper.outputArguments(out, m.isStatic()); out.append(") {").append("\n"); @@ -282,22 +326,36 @@ TrapData[] previousTrap = null; boolean wide = false; - out.append("\n var gt = 0;\n for(;;) switch(gt) {\n"); + out.append("\n var gt = 0;\n"); + int openBraces = 0; + int topMostLabel = 0; for (int i = 0; i < byteCodes.length; i++) { int prev = i; stackMapIterator.advanceTo(i); boolean changeInCatch = trap.advanceTo(i); if (changeInCatch || lastStackFrame != stackMapIterator.getFrameIndex()) { if (previousTrap != null) { - generateCatch(previousTrap); + generateCatch(previousTrap, i, topMostLabel); previousTrap = null; } } if (lastStackFrame != stackMapIterator.getFrameIndex()) { + if (i != 0) { + out.append(" }\n"); + } + if (openBraces > 64) { + for (int c = 0; c < 64; c++) { + out.append("break;}\n"); + } + openBraces = 1; + topMostLabel = i; + } + lastStackFrame = stackMapIterator.getFrameIndex(); lmapper.syncWithFrameLocals(stackMapIterator.getFrameLocals()); smapper.syncWithFrameStack(stackMapIterator.getFrameStack()); - out.append(" case " + i).append(": "); + out.append(" X_" + i).append(": for (;;) { IF: if (gt <= " + i + ") {\n"); + openBraces++; changeInCatch = true; } else { debug(" /* " + i + " */ "); @@ -769,7 +827,7 @@ } case opc_ldc_w: case opc_ldc2_w: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); i += 2; String v = encodeConstant(indx); int type = VarType.fromConstantType(jc.getTag(indx)); @@ -800,133 +858,104 @@ break; case opc_if_acmpeq: i = generateIf(byteCodes, i, smapper.popA(), smapper.popA(), - "==="); + "===", topMostLabel); break; case opc_if_acmpne: i = generateIf(byteCodes, i, smapper.popA(), smapper.popA(), - "!="); + "!==", topMostLabel); break; case opc_if_icmpeq: i = generateIf(byteCodes, i, smapper.popI(), smapper.popI(), - "=="); + "==", topMostLabel); break; case opc_ifeq: { - int indx = i + readIntArg(byteCodes, i); - emit(out, "if (@1 == 0) { gt = @2; continue; }", - smapper.popI(), Integer.toString(indx)); + int indx = i + readShortArg(byteCodes, i); + emitIf(out, "if (@1 == 0) ", + smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifne: { - int indx = i + readIntArg(byteCodes, i); - emit(out, "if (@1 != 0) { gt = @2; continue; }", - smapper.popI(), Integer.toString(indx)); + int indx = i + readShortArg(byteCodes, i); + emitIf(out, "if (@1 != 0) ", + smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_iflt: { - int indx = i + readIntArg(byteCodes, i); - emit(out, "if (@1 < 0) { gt = @2; continue; }", - smapper.popI(), Integer.toString(indx)); + int indx = i + readShortArg(byteCodes, i); + emitIf(out, "if (@1 < 0) ", + smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifle: { - int indx = i + readIntArg(byteCodes, i); - emit(out, "if (@1 <= 0) { gt = @2; continue; }", - smapper.popI(), Integer.toString(indx)); + int indx = i + readShortArg(byteCodes, i); + emitIf(out, "if (@1 <= 0) ", + smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifgt: { - int indx = i + readIntArg(byteCodes, i); - emit(out, "if (@1 > 0) { gt = @2; continue; }", - smapper.popI(), Integer.toString(indx)); + int indx = i + readShortArg(byteCodes, i); + emitIf(out, "if (@1 > 0) ", + smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifge: { - int indx = i + readIntArg(byteCodes, i); - emit(out, "if (@1 >= 0) { gt = @2; continue; }", - smapper.popI(), Integer.toString(indx)); + int indx = i + readShortArg(byteCodes, i); + emitIf(out, "if (@1 >= 0) ", + smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifnonnull: { - int indx = i + readIntArg(byteCodes, i); - emit(out, "if (@1 !== null) { gt = @2; continue; }", - smapper.popA(), Integer.toString(indx)); + int indx = i + readShortArg(byteCodes, i); + emitIf(out, "if (@1 !== null) ", + smapper.popA(), i, indx, topMostLabel); i += 2; break; } case opc_ifnull: { - int indx = i + readIntArg(byteCodes, i); - emit(out, "if (@1 === null) { gt = @2; continue; }", - smapper.popA(), Integer.toString(indx)); + int indx = i + readShortArg(byteCodes, i); + emitIf(out, "if (@1 === null) ", + smapper.popA(), i, indx, topMostLabel); i += 2; break; } case opc_if_icmpne: i = generateIf(byteCodes, i, smapper.popI(), smapper.popI(), - "!="); + "!=", topMostLabel); break; case opc_if_icmplt: i = generateIf(byteCodes, i, smapper.popI(), smapper.popI(), - "<"); + "<", topMostLabel); break; case opc_if_icmple: i = generateIf(byteCodes, i, smapper.popI(), smapper.popI(), - "<="); + "<=", topMostLabel); break; case opc_if_icmpgt: i = generateIf(byteCodes, i, smapper.popI(), smapper.popI(), - ">"); + ">", topMostLabel); break; case opc_if_icmpge: i = generateIf(byteCodes, i, smapper.popI(), smapper.popI(), - ">="); + ">=", topMostLabel); break; case opc_goto: { - int indx = i + readIntArg(byteCodes, i); - emit(out, "gt = @1; continue;", Integer.toString(indx)); + int indx = i + readShortArg(byteCodes, i); + goTo(out, i, indx, topMostLabel); i += 2; break; } case opc_lookupswitch: { - int table = i / 4 * 4 + 4; - int dflt = i + readInt4(byteCodes, table); - table += 4; - int n = readInt4(byteCodes, table); - table += 4; - out.append("switch (").append(smapper.popI()).append(") {\n"); - while (n-- > 0) { - int cnstnt = readInt4(byteCodes, table); - table += 4; - int offset = i + readInt4(byteCodes, table); - table += 4; - out.append(" case " + cnstnt).append(": gt = " + offset).append("; continue;\n"); - } - out.append(" default: gt = " + dflt).append("; continue;\n}"); - i = table - 1; + i = generateLookupSwitch(i, byteCodes, smapper, topMostLabel); break; } case opc_tableswitch: { - int table = i / 4 * 4 + 4; - int dflt = i + readInt4(byteCodes, table); - table += 4; - int low = readInt4(byteCodes, table); - table += 4; - int high = readInt4(byteCodes, table); - table += 4; - out.append("switch (").append(smapper.popI()).append(") {\n"); - while (low <= high) { - int offset = i + readInt4(byteCodes, table); - table += 4; - out.append(" case " + low).append(": gt = " + offset).append("; continue;\n"); - low++; - } - out.append(" default: gt = " + dflt).append("; continue;\n}"); - i = table - 1; + i = generateTableSwitch(i, byteCodes, smapper, topMostLabel); break; } case opc_invokeinterface: { @@ -943,7 +972,7 @@ i = invokeStaticMethod(byteCodes, i, smapper, true); break; case opc_new: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); String ci = jc.getClassName(indx); emit(out, "var @1 = new @2;", smapper.pushA(), accessClass(ci.replace('/', '_'))); @@ -953,50 +982,18 @@ } case opc_newarray: int atype = readUByte(byteCodes, ++i); - String jvmType; - switch (atype) { - case 4: jvmType = "[Z"; break; - case 5: jvmType = "[C"; break; - case 6: jvmType = "[F"; break; - case 7: jvmType = "[D"; break; - case 8: jvmType = "[B"; break; - case 9: jvmType = "[S"; break; - case 10: jvmType = "[I"; break; - case 11: jvmType = "[J"; break; - default: throw new IllegalStateException("Array type: " + atype); - } - emit(out, "var @2 = Array.prototype.newArray__Ljava_lang_Object_2ZLjava_lang_String_2I(true, '@3', @1);", - smapper.popI(), smapper.pushA(), jvmType); + generateNewArray(atype, smapper); break; case opc_anewarray: { - int type = readIntArg(byteCodes, i); + int type = readUShortArg(byteCodes, i); i += 2; - String typeName = jc.getClassName(type); - if (typeName.startsWith("[")) { - typeName = "[" + typeName; - } else { - typeName = "[L" + typeName + ";"; - } - emit(out, "var @2 = Array.prototype.newArray__Ljava_lang_Object_2ZLjava_lang_String_2I(false, '@3', @1);", - smapper.popI(), smapper.pushA(), typeName); + generateANewArray(type, smapper); break; } case opc_multianewarray: { - int type = readIntArg(byteCodes, i); + int type = readUShortArg(byteCodes, i); i += 2; - String typeName = jc.getClassName(type); - int dim = readUByte(byteCodes, ++i); - StringBuilder dims = new StringBuilder(); - dims.append('['); - for (int d = 0; d < dim; d++) { - if (d != 0) { - dims.append(","); - } - dims.append(smapper.popI()); - } - dims.append(']'); - emit(out, "var @2 = Array.prototype.multiNewArray__Ljava_lang_Object_2Ljava_lang_String_2_3II('@3', @1, 0);", - dims.toString(), smapper.pushA(), typeName); + i = generateMultiANewArray(type, byteCodes, i, smapper); break; } case opc_arraylength: @@ -1210,11 +1207,11 @@ case opc_sipush: emit(out, "var @1 = @2;", smapper.pushI(), - Integer.toString(readIntArg(byteCodes, i))); + Integer.toString(readShortArg(byteCodes, i))); i += 2; break; case opc_getfield: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); String[] fi = jc.getFieldInfoName(indx); final int type = VarType.fromFieldType(fi[2].charAt(0)); final String mangleClass = mangleSig(fi[0]); @@ -1227,7 +1224,7 @@ break; } case opc_putfield: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); String[] fi = jc.getFieldInfoName(indx); final int type = VarType.fromFieldType(fi[2].charAt(0)); final String mangleClass = mangleSig(fi[0]); @@ -1241,10 +1238,10 @@ break; } case opc_getstatic: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); String[] fi = jc.getFieldInfoName(indx); final int type = VarType.fromFieldType(fi[2].charAt(0)); - emit(out, "var @1 = @2(false).constructor.@3;", + emit(out, "var @1 = @2(false)._@3();", smapper.pushT(type), accessClass(fi[0].replace('/', '_')), fi[1]); i += 2; @@ -1252,10 +1249,10 @@ break; } case opc_putstatic: { - int indx = readIntArg(byteCodes, i); + int indx = readUShortArg(byteCodes, i); String[] fi = jc.getFieldInfoName(indx); final int type = VarType.fromFieldType(fi[2].charAt(0)); - emit(out, "@1(false).constructor.@2 = @3;", + emit(out, "@1(false)._@2(@3);", accessClass(fi[0].replace('/', '_')), fi[1], smapper.popT(type)); i += 2; @@ -1263,33 +1260,14 @@ break; } case opc_checkcast: { - int indx = readIntArg(byteCodes, i); - final String type = jc.getClassName(indx); - if (!type.startsWith("[")) { - emit(out, - "if (@1 !== null && !@1.$instOf_@2) throw {};", - smapper.getA(0), type.replace('/', '_')); - } else { - emit(out, "vm.java_lang_Class(false).forName__Ljava_lang_Class_2Ljava_lang_String_2('@2').cast__Ljava_lang_Object_2Ljava_lang_Object_2(@1);", - smapper.getA(0), type - ); - } + int indx = readUShortArg(byteCodes, i); + generateCheckcast(indx, smapper); i += 2; break; } case opc_instanceof: { - int indx = readIntArg(byteCodes, i); - final String type = jc.getClassName(indx); - if (!type.startsWith("[")) { - emit(out, "var @2 = @1 != null && @1.$instOf_@3 ? 1 : 0;", - smapper.popA(), smapper.pushI(), - type.replace('/', '_')); - } else { - emit(out, "var @2 = vm.java_lang_Class(false).forName__Ljava_lang_Class_2Ljava_lang_String_2('@3').isInstance__ZLjava_lang_Object_2(@1);", - smapper.popA(), smapper.pushI(), - type - ); - } + int indx = readUShortArg(byteCodes, i); + generateInstanceOf(indx, smapper); i += 2; break; } @@ -1325,37 +1303,29 @@ } } if (debug(" //")) { - for (int j = prev; j <= i; j++) { - out.append(" "); - final int cc = readUByte(byteCodes, j); - out.append(Integer.toString(cc)); - } + generateByteCodeComment(prev, i, byteCodes); } out.append("\n"); } if (previousTrap != null) { - generateCatch(previousTrap); + generateCatch(previousTrap, byteCodes.length, topMostLabel); } - out.append(" }\n"); - out.append("};"); + out.append("\n }\n"); + while (openBraces-- > 0) { + out.append('}'); + } + out.append("\n};"); } - private int generateIf(byte[] byteCodes, int i, - final Variable v2, final Variable v1, - final String test) throws IOException { - int indx = i + readIntArg(byteCodes, i); + private int generateIf(byte[] byteCodes, int i, final Variable v2, final Variable v1, final String test, int topMostLabel) throws IOException { + int indx = i + readShortArg(byteCodes, i); out.append("if (").append(v1) .append(' ').append(test).append(' ') - .append(v2).append(") { gt = " + indx) - .append("; continue; }"); + .append(v2).append(") "); + goTo(out, i, indx, topMostLabel); return i + 2; } - - private int readIntArg(byte[] byteCodes, int offsetInstruction) { - final int indxHi = byteCodes[offsetInstruction + 1] << 8; - final int indxLo = byteCodes[offsetInstruction + 2]; - return (indxHi & 0xffffff00) | (indxLo & 0xff); - } + private int readInt4(byte[] byteCodes, int offset) { final int d = byteCodes[offset + 0] << 24; final int c = byteCodes[offset + 1] << 16; @@ -1363,18 +1333,25 @@ final int a = byteCodes[offset + 3]; return (d & 0xff000000) | (c & 0xff0000) | (b & 0xff00) | (a & 0xff); } - private int readUByte(byte[] byteCodes, int offset) { + private static int readUByte(byte[] byteCodes, int offset) { return byteCodes[offset] & 0xff; } - private int readUShort(byte[] byteCodes, int offset) { + private static int readUShort(byte[] byteCodes, int offset) { return ((byteCodes[offset] & 0xff) << 8) | (byteCodes[offset + 1] & 0xff); } + private static int readUShortArg(byte[] byteCodes, int offsetInstruction) { + return readUShort(byteCodes, offsetInstruction + 1); + } - private int readShort(byte[] byteCodes, int offset) { - return (byteCodes[offset] << 8) - | (byteCodes[offset + 1] & 0xff); + private static int readShort(byte[] byteCodes, int offset) { + int signed = byteCodes[offset]; + byte b0 = (byte)signed; + return (b0 << 8) | (byteCodes[offset + 1] & 0xff); + } + private static int readShortArg(byte[] byteCodes, int offsetInstruction) { + return readShort(byteCodes, offsetInstruction + 1); } private static void countArgs(String descriptor, char[] returnType, StringBuilder sig, StringBuilder cnt) { @@ -1502,7 +1479,7 @@ private int invokeStaticMethod(byte[] byteCodes, int i, final StackMapper mapper, boolean isStatic) throws IOException { - int methodIndex = readIntArg(byteCodes, i); + int methodIndex = readUShortArg(byteCodes, i); String[] mi = jc.getFieldInfoName(methodIndex); char[] returnType = { 'V' }; StringBuilder cnt = new StringBuilder(); @@ -1547,7 +1524,7 @@ } private int invokeVirtualMethod(byte[] byteCodes, int i, final StackMapper mapper) throws IOException { - int methodIndex = readIntArg(byteCodes, i); + int methodIndex = readUShortArg(byteCodes, i); String[] mi = jc.getFieldInfoName(methodIndex); char[] returnType = { 'V' }; StringBuilder cnt = new StringBuilder(); @@ -1614,12 +1591,13 @@ return s; } - private String javaScriptBody(String prefix, MethodData m, boolean isStatic) throws IOException { + private String javaScriptBody(String destObject, MethodData m, boolean isStatic) throws IOException { byte[] arr = m.findAnnotationData(true); if (arr == null) { 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); @@ -1628,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) { @@ -1640,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(); @@ -1649,7 +1639,7 @@ } StringBuilder cnt = new StringBuilder(); final String mn = findMethodName(m, cnt); - out.append(prefix).append(mn); + out.append(destObject).append(".").append(mn); out.append(" = function("); String space = ""; int index = 0; @@ -1659,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('/', '_'); @@ -1822,7 +1923,7 @@ out.append(format, processed, length); } - private void generateCatch(TrapData[] traps) throws IOException { + private void generateCatch(TrapData[] traps, int current, int topMostLabel) throws IOException { out.append("} catch (e) {\n"); int finallyPC = -1; for (TrapData e : traps) { @@ -1832,19 +1933,11 @@ if (e.catch_cpx != 0) { //not finally final String classInternalName = jc.getClassName(e.catch_cpx); addReference(classInternalName); - if ("java/lang/Throwable".equals(classInternalName)) { - out.append("if (e.$instOf_java_lang_Throwable) {"); - out.append(" var stA0 = e;"); - out.append("} else {"); - out.append(" var stA0 = vm.java_lang_Throwable(true);"); - out.append(" vm.java_lang_Throwable.cons__VLjava_lang_String_2.call(stA0, e.toString());"); - out.append("}"); - out.append("gt=" + e.handler_pc + "; continue;"); - } else { - out.append("if (e.$instOf_" + classInternalName.replace('/', '_') + ") {"); - out.append("gt=" + e.handler_pc + "; var stA0 = e; continue;"); - out.append("}\n"); - } + out.append("e = vm.java_lang_Throwable(false).bck2BrwsrCnvrt(e);"); + out.append("if (e.$instOf_" + classInternalName.replace('/', '_') + ") {"); + out.append("var stA0 = e;"); + goTo(out, current, e.handler_pc, topMostLabel); + out.append("}\n"); } else { finallyPC = e.handler_pc; } @@ -1852,8 +1945,152 @@ if (finallyPC == -1) { out.append("throw e;"); } else { - out.append("gt=" + finallyPC + "; var stA0 = e; continue;"); + out.append("var stA0 = e;"); + goTo(out, current, finallyPC, topMostLabel); } out.append("\n}"); } + + private static void goTo(Appendable out, int current, int to, int canBack) throws IOException { + if (to < current) { + if (canBack < to) { + out.append("{ gt = 0; continue X_" + to + "; }"); + } else { + out.append("{ gt = " + to + "; continue X_0; }"); + } + } else { + out.append("{ gt = " + to + "; break IF; }"); + } + } + + private static void emitIf( + Appendable out, String pattern, Variable param, + int current, int to, int canBack + ) throws IOException { + emit(out, pattern, param); + goTo(out, current, to, canBack); + } + + private void generateNewArray(int atype, final StackMapper smapper) throws IOException, IllegalStateException { + String jvmType; + switch (atype) { + case 4: jvmType = "[Z"; break; + case 5: jvmType = "[C"; break; + case 6: jvmType = "[F"; break; + case 7: jvmType = "[D"; break; + case 8: jvmType = "[B"; break; + case 9: jvmType = "[S"; break; + case 10: jvmType = "[I"; break; + case 11: jvmType = "[J"; break; + default: throw new IllegalStateException("Array type: " + atype); + } + emit(out, "var @2 = Array.prototype.newArray__Ljava_lang_Object_2ZLjava_lang_String_2I(true, '@3', @1);", + smapper.popI(), smapper.pushA(), jvmType); + } + + private void generateANewArray(int type, final StackMapper smapper) throws IOException { + String typeName = jc.getClassName(type); + if (typeName.startsWith("[")) { + typeName = "[" + typeName; + } else { + typeName = "[L" + typeName + ";"; + } + emit(out, "var @2 = Array.prototype.newArray__Ljava_lang_Object_2ZLjava_lang_String_2I(false, '@3', @1);", + smapper.popI(), smapper.pushA(), typeName); + } + + private int generateMultiANewArray(int type, final byte[] byteCodes, int i, final StackMapper smapper) throws IOException { + String typeName = jc.getClassName(type); + int dim = readUByte(byteCodes, ++i); + StringBuilder dims = new StringBuilder(); + dims.append('['); + for (int d = 0; d < dim; d++) { + if (d != 0) { + dims.insert(1, ","); + } + dims.insert(1, smapper.popI()); + } + dims.append(']'); + emit(out, "var @2 = Array.prototype.multiNewArray__Ljava_lang_Object_2Ljava_lang_String_2_3II('@3', @1, 0);", + dims.toString(), smapper.pushA(), typeName); + return i; + } + + private int generateTableSwitch(int i, final byte[] byteCodes, final StackMapper smapper, int topMostLabel) throws IOException { + int table = i / 4 * 4 + 4; + int dflt = i + readInt4(byteCodes, table); + table += 4; + int low = readInt4(byteCodes, table); + table += 4; + int high = readInt4(byteCodes, table); + table += 4; + out.append("switch (").append(smapper.popI()).append(") {\n"); + while (low <= high) { + int offset = i + readInt4(byteCodes, table); + table += 4; + out.append(" case " + low).append(":"); goTo(out, i, offset, topMostLabel); out.append('\n'); + low++; + } + out.append(" default: "); + goTo(out, i, dflt, topMostLabel); + out.append("\n}"); + i = table - 1; + return i; + } + + private int generateLookupSwitch(int i, final byte[] byteCodes, final StackMapper smapper, int topMostLabel) throws IOException { + int table = i / 4 * 4 + 4; + int dflt = i + readInt4(byteCodes, table); + table += 4; + int n = readInt4(byteCodes, table); + table += 4; + out.append("switch (").append(smapper.popI()).append(") {\n"); + while (n-- > 0) { + int cnstnt = readInt4(byteCodes, table); + table += 4; + int offset = i + readInt4(byteCodes, table); + table += 4; + out.append(" case " + cnstnt).append(": "); goTo(out, i, offset, topMostLabel); out.append('\n'); + } + out.append(" default: "); + goTo(out, i, dflt, topMostLabel); + out.append("\n}"); + i = table - 1; + return i; + } + + private void generateInstanceOf(int indx, final StackMapper smapper) throws IOException { + final String type = jc.getClassName(indx); + if (!type.startsWith("[")) { + emit(out, "var @2 = @1 != null && @1.$instOf_@3 ? 1 : 0;", + smapper.popA(), smapper.pushI(), + type.replace('/', '_')); + } else { + emit(out, "var @2 = vm.java_lang_Class(false).forName__Ljava_lang_Class_2Ljava_lang_String_2('@3').isInstance__ZLjava_lang_Object_2(@1);", + smapper.popA(), smapper.pushI(), + type + ); + } + } + + private void generateCheckcast(int indx, final StackMapper smapper) throws IOException { + final String type = jc.getClassName(indx); + if (!type.startsWith("[")) { + emit(out, + "if (@1 !== null && !@1.$instOf_@2) throw vm.java_lang_ClassCastException(true);", + smapper.getA(0), type.replace('/', '_')); + } else { + emit(out, "vm.java_lang_Class(false).forName__Ljava_lang_Class_2Ljava_lang_String_2('@2').cast__Ljava_lang_Object_2Ljava_lang_Object_2(@1);", + smapper.getA(0), type + ); + } + } + + private void generateByteCodeComment(int prev, int i, final byte[] byteCodes) throws IOException { + for (int j = prev; j <= i; j++) { + out.append(" "); + final int cc = readUByte(byteCodes, j); + out.append(Integer.toString(cc)); + } + } } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/ClosureWrapper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/ClosureWrapper.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,334 @@ +/** + * 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 com.google.javascript.jscomp.CommandLineRunner; +import com.google.javascript.jscomp.SourceFile; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apidesign.bck2brwsr.core.ExtraJavaScript; +import org.apidesign.vm4brwsr.ByteCodeParser.ClassData; +import org.apidesign.vm4brwsr.ByteCodeParser.FieldData; +import org.apidesign.vm4brwsr.ByteCodeParser.MethodData; + +/** + * + * @author Jaroslav Tulach + */ +@ExtraJavaScript(processByteCode = false, resource="") +final class ClosureWrapper extends CommandLineRunner { + private static final String[] ARGS = { "--compilation_level", "SIMPLE_OPTIMIZATIONS", "--js", "bck2brwsr-raw.js" /*, "--debug", "--formatting", "PRETTY_PRINT" */ }; + + private final ClosuresObfuscationDelegate obfuscationDelegate; + private final Bck2Brwsr.Resources res; + private final StringArray classes; + + private String compiledCode; + private String externsCode; + + private ClosureWrapper(Appendable out, + String compilationLevel, + ClosuresObfuscationDelegate obfuscationDelegate, + Bck2Brwsr.Resources res, StringArray classes) { + super( + generateArguments(compilationLevel), + new PrintStream(new APS(out)), System.err + ); + this.obfuscationDelegate = obfuscationDelegate; + this.res = res; + this.classes = classes; + } + + @Override + protected List createInputs(List files, boolean allowStdIn) throws FlagUsageException, IOException { + if (files.size() != 1 || !"bck2brwsr-raw.js".equals(files.get(0))) { + throw new IOException("Unexpected files: " + files); + } + return Collections.nCopies( + 1, + SourceFile.fromGenerator( + "bck2brwsr-raw.js", + new SourceFile.Generator() { + @Override + public String getCode() { + return getCompiledCode(); + } + })); + } + + + @Override + protected List createExterns() + throws FlagUsageException, IOException { + final List externsFiles = + new ArrayList(super.createExterns()); + + externsFiles.add( + SourceFile.fromGenerator( + "bck2brwsr_externs.js", + new SourceFile.Generator() { + @Override + public String getCode() { + return getExternsCode(); + } + })); + return externsFiles; + } + + private String getCompiledCode() { + if (compiledCode == null) { + StringBuilder sb = new StringBuilder(); + try { + VM.compile(res, sb, classes, obfuscationDelegate); + compiledCode = sb.toString(); + } catch (IOException ex) { + compiledCode = ex.getMessage(); + } + } + return compiledCode; + } + + private String getExternsCode() { + if (externsCode == null) { + // need compiled code at this point + getCompiledCode(); + + final StringBuilder sb = new StringBuilder("function RAW() {};\n"); + for (final String extern: obfuscationDelegate.getExterns()) { + sb.append("RAW.prototype.").append(extern).append(";\n"); + } + externsCode = sb.toString(); + } + return externsCode; + } + + private static final class APS extends OutputStream { + private final Appendable out; + + public APS(Appendable out) { + this.out = out; + } + @Override + public void write(int b) throws IOException { + out.append((char)b); + } + } + + private static String[] generateArguments(String compilationLevel) { + String[] finalArgs = ARGS.clone(); + finalArgs[1] = compilationLevel; + + return finalArgs; + } + + static int produceTo(Appendable w, ObfuscationLevel obfuscationLevel, Bck2Brwsr.Resources resources, StringArray arr) throws IOException { + ClosureWrapper cw = create(w, obfuscationLevel, resources, arr); + try { + return cw.doRun(); + } catch (FlagUsageException ex) { + throw new IOException(ex); + } + } + + private static ClosureWrapper create(Appendable w, + ObfuscationLevel obfuscationLevel, + Bck2Brwsr.Resources resources, + StringArray arr) { + switch (obfuscationLevel) { + case MINIMAL: + return new ClosureWrapper(w, "SIMPLE_OPTIMIZATIONS", + new SimpleObfuscationDelegate(), + resources, arr); +/* + case MEDIUM: + return new ClosureWrapper(w, "ADVANCED_OPTIMIZATIONS", + new MediumObfuscationDelegate(), + resources, arr); +*/ + case FULL: + return new ClosureWrapper(w, "ADVANCED_OPTIMIZATIONS", + new FullObfuscationDelegate(), + resources, arr); + default: + throw new IllegalArgumentException( + "Unsupported level: " + obfuscationLevel); + } + } + + private static abstract class ClosuresObfuscationDelegate + extends ObfuscationDelegate { + public abstract Collection getExterns(); + } + + private static final class SimpleObfuscationDelegate + extends ClosuresObfuscationDelegate { + @Override + public void exportJSProperty(Appendable out, + String destObject, + String propertyName) throws IOException { + } + + @Override + public void exportClass(Appendable out, + String destObject, + String mangledName, + ClassData classData) throws IOException { + } + + @Override + public void exportMethod(Appendable out, + String destObject, + String mangledName, + MethodData methodData) throws IOException { + } + + @Override + public void exportField(Appendable out, + String destObject, + String mangledName, + FieldData fieldData) throws IOException { + } + + @Override + public Collection getExterns() { + return Collections.EMPTY_LIST; + } + } + + private static abstract class AdvancedObfuscationDelegate + extends ClosuresObfuscationDelegate { + private static final String[] INITIAL_EXTERNS = { + "bck2brwsr", + "$class", + "anno", + "array", + "access", + "cls", + "vm", + "loadClass", + "loadBytes", + "jvmName", + "primitive", + "superclass", + "cnstr", + "add32", + "sub32", + "mul32", + "neg32", + "toInt8", + "toInt16", + "next32", + "high32", + "toInt32", + "toFP", + "toLong", + "toExactString", + "add64", + "sub64", + "mul64", + "and64", + "or64", + "xor64", + "shl64", + "shr64", + "ushr64", + "compare64", + "neg64", + "div32", + "mod32", + "div64", + "mod64", + "at", + "getClass__Ljava_lang_Class_2", + "clone__Ljava_lang_Object_2" + }; + + private final Collection externs; + + protected AdvancedObfuscationDelegate() { + externs = new ArrayList(Arrays.asList(INITIAL_EXTERNS)); + } + + @Override + public void exportClass(Appendable out, + String destObject, + String mangledName, + ClassData classData) throws IOException { + exportJSProperty(out, destObject, mangledName); + } + + @Override + public void exportMethod(Appendable out, + String destObject, + String mangledName, + MethodData methodData) throws IOException { + if ((methodData.access & ByteCodeParser.ACC_PRIVATE) == 0) { + exportJSProperty(out, destObject, mangledName); + } + } + + @Override + public void exportField(Appendable out, + String destObject, + String mangledName, + FieldData fieldData) throws IOException { + if ((fieldData.access & ByteCodeParser.ACC_PRIVATE) == 0) { + exportJSProperty(out, destObject, mangledName); + } + } + + @Override + public Collection getExterns() { + return externs; + } + + protected void addExtern(String extern) { + externs.add(extern); + } + } + + private static final class MediumObfuscationDelegate + extends AdvancedObfuscationDelegate { + @Override + public void exportJSProperty(Appendable out, + String destObject, + String propertyName) { + addExtern(propertyName); + } + } + + private static final class FullObfuscationDelegate + extends AdvancedObfuscationDelegate { + @Override + public void exportJSProperty(Appendable out, + String destObject, + String propertyName) throws IOException { + out.append("\n").append(destObject).append("['") + .append(propertyName) + .append("'] = ") + .append(destObject).append(".").append(propertyName) + .append(";\n"); + } + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/LdrRsrcs.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/LdrRsrcs.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,48 @@ +/** + * 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 java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +/** Implementation of Resources that delegates to some class loader. + * + * @author Jaroslav Tulach + */ +final class LdrRsrcs implements Bck2Brwsr.Resources { + private final ClassLoader loader; + + LdrRsrcs(ClassLoader loader) { + this.loader = loader; + } + + @Override + public InputStream get(String name) throws IOException { + Enumeration en = loader.getResources(name); + URL u = null; + while (en.hasMoreElements()) { + u = en.nextElement(); + } + if (u == null) { + throw new IOException("Can't find " + name); + } + return u.openStream(); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/LocalsMapper.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/LocalsMapper.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/LocalsMapper.java Mon Oct 07 14:20:58 2013 +0200 @@ -18,8 +18,7 @@ package org.apidesign.vm4brwsr; import java.io.IOException; -import org.apidesign.javap.RuntimeConstants; -import org.apidesign.javap.TypeArray; +import org.apidesign.vm4brwsr.ByteCodeParser.TypeArray; final class LocalsMapper { private final TypeArray argTypeRecords; @@ -113,7 +112,7 @@ final int srcSize = stackMapTypes.getSize(); for (int i = 0, dstIndex = 0; i < srcSize; ++i) { final int smType = stackMapTypes.get(i); - if (smType == RuntimeConstants.ITEM_Bogus) { + if (smType == ByteCodeParser.ITEM_Bogus) { ++dstIndex; continue; } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/Main.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/Main.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/Main.java Mon Oct 07 14:20:58 2013 +0200 @@ -18,6 +18,7 @@ package org.apidesign.vm4brwsr; import java.io.BufferedWriter; +import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; @@ -31,20 +32,69 @@ private Main() {} public static void main(String... args) throws IOException { + final String obfuscate = "--obfuscatelevel"; + if (args.length < 2) { System.err.println("Bck2Brwsr Translator from Java(tm) to JavaScript, (c) Jaroslav Tulach 2012"); - System.err.println("Usage: java -cp ... -jar ... java/lang/Class org/your/App ..."); - return; + System.err.println("Usage: java -cp ... -jar ... ["); + System.err.print(obfuscate); + System.err.print(" ["); + boolean first = true; + for (ObfuscationLevel l : ObfuscationLevel.values()) { + if (!first) { + System.err.print('|'); + } + System.err.print(l.name()); + first = false; + } + + System.err.println("] java/lang/Class org/your/App ..."); + System.exit(9); } - Writer w = new BufferedWriter(new FileWriter(args[0])); - StringArray classes = StringArray.asList(args); - classes.delete(0); - try { - Bck2Brwsr.generate(w, Main.class.getClassLoader(), - classes.toArray()); - } finally { - w.close(); + ObfuscationLevel obfLevel = ObfuscationLevel.NONE; + StringArray classes = new StringArray(); + String generateTo = null; + for (int i = 0; i < args.length; i++) { + if (obfuscate.equals(args[i])) { // NOI18N + i++; + try { + obfLevel = ObfuscationLevel.valueOf(args[i]); + } catch (Exception e) { + System.err.print(obfuscate); + System.err.print(" parameter needs to be followed by one of "); + boolean first = true; + for (ObfuscationLevel l : ObfuscationLevel.values()) { + if (!first) { + System.err.print(", "); + } + System.err.print(l.name()); + first = false; + } + System.err.println(); + System.exit(1); + } + continue; + } + if (generateTo == null) { + generateTo = args[i]; + } else { + classes = classes.addAndNew(args[i]); + } + } + + File gt = new File(generateTo); + if (Boolean.getBoolean("skip.if.exists") && gt.isFile()) { + System.err.println("Skipping as " + gt + " exists."); + System.exit(0); + } + + try (Writer w = new BufferedWriter(new FileWriter(gt))) { + Bck2Brwsr.newCompiler(). + obfuscation(obfLevel). + addRootClasses(classes.toArray()). + resources(Main.class.getClassLoader()). + generate(w); } } } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/ObfuscationDelegate.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/ObfuscationDelegate.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,75 @@ +/** + * 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 java.io.IOException; +import org.apidesign.vm4brwsr.ByteCodeParser.ClassData; +import org.apidesign.vm4brwsr.ByteCodeParser.FieldData; +import org.apidesign.vm4brwsr.ByteCodeParser.MethodData; + +abstract class ObfuscationDelegate { + static ObfuscationDelegate NULL = + new ObfuscationDelegate() { + @Override + public void exportJSProperty(Appendable out, + String destObject, + String propertyName) + throws IOException { + } + + @Override + public void exportClass(Appendable out, + String destObject, + String mangledName, + ClassData classData) + throws IOException { + } + + @Override + public void exportMethod(Appendable out, + String destObject, + String mangledName, + MethodData methodData) + throws IOException { + } + + @Override + public void exportField(Appendable out, + String destObject, + String mangledName, + FieldData fieldData) + throws IOException { + } + }; + + public abstract void exportJSProperty( + Appendable out, String destObject, String propertyName) + throws IOException; + + public abstract void exportClass( + Appendable out, String destObject, String mangledName, + ClassData classData) throws IOException; + + public abstract void exportMethod( + Appendable out, String destObject, String mangledName, + MethodData methodData) throws IOException; + + public abstract void exportField( + Appendable out, String destObject, String mangledName, + FieldData fieldData) throws IOException; +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/ObfuscationLevel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/ObfuscationLevel.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,41 @@ +/** + * 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; + +/** + * Defines obfuscation level of produced JavaScript files. + * + * @since 0.5 + */ +public enum ObfuscationLevel { + /** Generated JavaScript is (sort of) human readable. Useful for debugging. + * Dynamic capabilities of the virtual machine work on all classes. + */ + NONE, + /** White spaces are removed. Names of external symbols remain unchanged. + * Dynamic capabilities of the virtual machine work on all classes. + */ + MINIMAL, +// temporarily commented out before merge. not well defined yet: +// MEDIUM, + /** Aggressive obfuscation of everything. Compact, unreadable "one-liner". + * One cannot load classes dynamically. Useful mostly for static compilation + * of self contained application. + */ + FULL +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/StackMapper.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/StackMapper.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/StackMapper.java Mon Oct 07 14:20:58 2013 +0200 @@ -17,7 +17,7 @@ */ package org.apidesign.vm4brwsr; -import org.apidesign.javap.TypeArray; +import org.apidesign.vm4brwsr.ByteCodeParser.TypeArray; final class StackMapper { private final TypeArray stackTypeIndexPairs; diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/StringArray.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/StringArray.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/StringArray.java Mon Oct 07 14:20:58 2013 +0200 @@ -43,6 +43,25 @@ } arr[arr.length - 1] = s; } + + StringArray addAndNew(String... values) { + int j; + String[] tmp; + if (arr == null) { + tmp = new String[values.length]; + j = 0; + } else { + tmp = new String[arr.length + values.length]; + for (int i = 0; i < arr.length; i++) { + tmp[i] = arr[i]; + } + j = arr.length; + } + for (int i = 0; i < values.length;) { + tmp[j++] = values[i++]; + } + return new StringArray(tmp); + } public String[] toArray() { return arr == null ? new String[0] : arr; @@ -93,5 +112,4 @@ } return -1; } - } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/VM.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/VM.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/VM.java Mon Oct 07 14:20:58 2013 +0200 @@ -28,7 +28,11 @@ public VM(Appendable out) { super(out); } - + + private VM(Appendable out, ObfuscationDelegate obfuscationDelegate) { + super(out, obfuscationDelegate); + } + static { // uses VMLazy to load dynamic classes boolean assertsOn = false; @@ -47,6 +51,12 @@ static void compile(Bck2Brwsr.Resources l, Appendable out, StringArray names) throws IOException { new VM(out).doCompile(l, names); } + + static void compile(Bck2Brwsr.Resources l, Appendable out, StringArray names, + ObfuscationDelegate obfuscationDelegate) throws IOException { + new VM(out, obfuscationDelegate).doCompile(l, names); + } + protected void doCompile(Bck2Brwsr.Resources l, StringArray names) throws IOException { out.append("(function VM(global) {var fillInVMSkeleton = function(vm) {"); StringArray processed = new StringArray(); @@ -110,7 +120,10 @@ for (String ic : toInit.toArray()) { int indx = processed.indexOf(ic); if (indx >= 0) { - out.append(initCode.toArray()[indx]).append("\n"); + final String theCode = initCode.toArray()[indx]; + if (!theCode.isEmpty()) { + out.append(theCode).append("\n"); + } initCode.toArray()[indx] = ""; } } @@ -227,4 +240,9 @@ String accessClass(String className) { return "vm." + className; } + + @Override + String getVMObject() { + return "vm"; + } } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/VMLazy.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/VMLazy.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/VMLazy.java Mon Oct 07 14:20:58 2013 +0200 @@ -56,7 +56,7 @@ throw new ClassNotFoundException(name); } // beingDefined(loader, name); - StringBuilder out = new StringBuilder(); + StringBuilder out = new StringBuilder(65535); out.append("var loader = arguments[0];\n"); out.append("var vm = loader.vm;\n"); int prelude = out.length(); @@ -130,6 +130,14 @@ @Override protected void requireScript(String resourcePath) throws IOException { + if (!resourcePath.startsWith("/")) { + resourcePath = "/" + resourcePath; + } + String code = readCode(resourcePath); + applyCode(lazy.loader, null, code, false); + } + + private String readCode(String resourcePath) throws IOException { InputStream is = getClass().getResourceAsStream(resourcePath); StringBuilder sb = new StringBuilder(); for (;;) { @@ -139,7 +147,7 @@ } sb.append((char)ch); } - applyCode(lazy.loader, null, sb.toString(), false); + return sb.toString(); } @Override @@ -151,5 +159,10 @@ String accessClass(String classOperation) { return "vm." + classOperation; } + + @Override + String getVMObject() { + return "vm"; + } } } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/main/java/org/apidesign/vm4brwsr/VarType.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/VarType.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/VarType.java Mon Oct 07 14:20:58 2013 +0200 @@ -17,8 +17,6 @@ */ package org.apidesign.vm4brwsr; -import org.apidesign.javap.RuntimeConstants; - final class VarType { public static final int INTEGER = 0; public static final int LONG = 1; @@ -37,21 +35,21 @@ public static int fromStackMapType(final int smType) { switch (smType & 0xff) { - case RuntimeConstants.ITEM_Integer: + case ByteCodeParser.ITEM_Integer: return VarType.INTEGER; - case RuntimeConstants.ITEM_Float: + case ByteCodeParser.ITEM_Float: return VarType.FLOAT; - case RuntimeConstants.ITEM_Double: + case ByteCodeParser.ITEM_Double: return VarType.DOUBLE; - case RuntimeConstants.ITEM_Long: + case ByteCodeParser.ITEM_Long: return VarType.LONG; - case RuntimeConstants.ITEM_Null: - case RuntimeConstants.ITEM_InitObject: - case RuntimeConstants.ITEM_Object: - case RuntimeConstants.ITEM_NewObject: + case ByteCodeParser.ITEM_Null: + case ByteCodeParser.ITEM_InitObject: + case ByteCodeParser.ITEM_Object: + case ByteCodeParser.ITEM_NewObject: return VarType.REFERENCE; - case RuntimeConstants.ITEM_Bogus: + case ByteCodeParser.ITEM_Bogus: /* unclear how to handle for now */ default: throw new IllegalStateException("Unhandled stack map type"); @@ -60,25 +58,25 @@ public static int fromConstantType(final byte constantTag) { switch (constantTag) { - case RuntimeConstants.CONSTANT_INTEGER: + case ByteCodeParser.CONSTANT_INTEGER: return VarType.INTEGER; - case RuntimeConstants.CONSTANT_FLOAT: + case ByteCodeParser.CONSTANT_FLOAT: return VarType.FLOAT; - case RuntimeConstants.CONSTANT_LONG: + case ByteCodeParser.CONSTANT_LONG: return VarType.LONG; - case RuntimeConstants.CONSTANT_DOUBLE: + case ByteCodeParser.CONSTANT_DOUBLE: return VarType.DOUBLE; - case RuntimeConstants.CONSTANT_CLASS: - case RuntimeConstants.CONSTANT_UTF8: - case RuntimeConstants.CONSTANT_UNICODE: - case RuntimeConstants.CONSTANT_STRING: + case ByteCodeParser.CONSTANT_CLASS: + case ByteCodeParser.CONSTANT_UTF8: + case ByteCodeParser.CONSTANT_UNICODE: + case ByteCodeParser.CONSTANT_STRING: return VarType.REFERENCE; - case RuntimeConstants.CONSTANT_FIELD: - case RuntimeConstants.CONSTANT_METHOD: - case RuntimeConstants.CONSTANT_INTERFACEMETHOD: - case RuntimeConstants.CONSTANT_NAMEANDTYPE: + case ByteCodeParser.CONSTANT_FIELD: + case ByteCodeParser.CONSTANT_METHOD: + case ByteCodeParser.CONSTANT_INTERFACEMETHOD: + case ByteCodeParser.CONSTANT_NAMEANDTYPE: /* unclear how to handle for now */ default: throw new IllegalStateException("Unhandled constant tag"); diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/Array.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/Array.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/Array.java Mon Oct 07 14:20:58 2013 +0200 @@ -132,4 +132,8 @@ arraycopy(arr()[0][0].chars, 0, arr, 0, 1); return arr[0]; } + + public static int multiLen() { + return new int[1][0].length; + } } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/ArrayTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/ArrayTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/ArrayTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -18,6 +18,7 @@ package org.apidesign.vm4brwsr; import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -74,6 +75,9 @@ @Test public void verifyInstanceOfArray() throws Exception { assertExec("Returns 'false'", Array.class, "instanceOfArray__ZLjava_lang_Object_2", Double.valueOf(0), "non-array"); } + @Test public void verifyMultiLen() throws Exception { + assertExec("Multi len is one", Array.class, "multiLen__I", Double.valueOf(1)); + } private static TestVM code; @@ -81,6 +85,10 @@ public void compileTheCode() throws Exception { code = TestVM.compileClass("org/apidesign/vm4brwsr/Array"); } + @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 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -19,6 +19,7 @@ import org.testng.annotations.Test; import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; /** @@ -190,6 +191,10 @@ public void compileTheCode() throws Exception { code = TestVM.compileClass("org/apidesign/vm4brwsr/Classes"); } + @AfterClass + public static void releaseTheCode() { + code = null; + } private void assertExec( String msg, Class clazz, String method, Object expRes, Object... args @@ -203,4 +208,24 @@ ); } + @Test public void valueOfEnum() throws Exception { + assertExec("can get value of enum", Classes.class, + "valueEnum__Ljava_lang_String_2Ljava_lang_String_2", + "TWO", "TWO" + ); + } + + @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 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java Mon Oct 07 14:20:58 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; @@ -230,4 +231,23 @@ return Application.class.isAssignableFrom(MyApplication.class); } + 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 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/ExceptionsTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/ExceptionsTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/ExceptionsTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -19,6 +19,7 @@ import javax.script.ScriptException; import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -108,6 +109,10 @@ public void compileTheCode() throws Exception { code = TestVM.compileClass("org/apidesign/vm4brwsr/Exceptions"); } + @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 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/InstanceTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/InstanceTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/InstanceTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -17,6 +17,7 @@ */ package org.apidesign.vm4brwsr; +import org.testng.annotations.AfterClass; import org.testng.annotations.Test; import org.testng.annotations.BeforeClass; @@ -156,6 +157,10 @@ public void compileTheCode() throws Exception { code = TestVM.compileClass(startCompilationWith()); } + @AfterClass + public static void releaseTheCode() { + code = null; + } private void assertExec( String msg, Class clazz, String method, Object expRes, Object... args diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/NumberTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/NumberTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/NumberTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -18,6 +18,7 @@ package org.apidesign.vm4brwsr; import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -184,13 +185,36 @@ Double.valueOf(Long.MAX_VALUE / 5), 9 ); } + @Test public void valueOfLongCharA() throws Exception { + assertExec("Can we call JavaScripts valueOf on Character?", + Numbers.class, "seven__DI", + Double.valueOf('A'), 65 + ); + } + + @Test public void valueOfLongBooleanTrue() throws Exception { + assertExec("Can we call JavaScripts valueOf on Boolean?", + Numbers.class, "bseven__ZI", + true, 31 + ); + } + @Test public void valueOfLongBooleanFalse() throws Exception { + assertExec("Can we call JavaScripts valueOf on Boolean?", + Numbers.class, "bseven__ZI", + false, 30 + ); + } private static TestVM code; @BeforeClass - public void compileTheCode() throws Exception { + public static void compileTheCode() throws Exception { code = TestVM.compileClass("org/apidesign/vm4brwsr/Numbers"); } + @AfterClass + public static void releaseTheCode() { + code = null; + } private static void assertExec( String msg, Class clazz, String method, Object expRes, Object... args) throws Exception diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/Numbers.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/Numbers.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/Numbers.java Mon Oct 07 14:20:58 2013 +0200 @@ -78,13 +78,26 @@ case 4: return sevenNew().byteValue(); case 8: return valueOf(Double.valueOf(7.0)); case 9: return valueOf(Long.valueOf(Long.MAX_VALUE / 5)); + case 30: return valueOf(Boolean.FALSE); + case 31: return valueOf(Boolean.TRUE); + case 65: return valueOf(Character.valueOf('A')); + default: throw new IllegalStateException(); + } + } + static boolean bseven(int todo) { + switch (todo) { + case 30: return bvalueOf(Boolean.FALSE); + case 31: return bvalueOf(Boolean.TRUE); default: throw new IllegalStateException(); } } @JavaScriptBody(args = {}, body = "return 7;") private static native Number sevenNew(); + + @JavaScriptBody(args = { "o" }, body = "return o.valueOf();") + private static native double valueOf(Object o); @JavaScriptBody(args = { "o" }, body = "return o.valueOf();") - private static native double valueOf(Object o); + private static native boolean bvalueOf(Object o); } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/StaticMethodTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/StaticMethodTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/StaticMethodTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -18,6 +18,7 @@ package org.apidesign.vm4brwsr; import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -324,10 +325,14 @@ private static TestVM code; @BeforeClass - public void compileTheCode() throws Exception { + public static void compileTheCode() throws Exception { StringBuilder sb = new StringBuilder(); code = TestVM.compileClass(sb, "org/apidesign/vm4brwsr/StaticMethod"); } + @AfterClass + public static void releaseTheCode() { + code = null; + } private void assertExec( String msg, Class clazz, String method, diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/StaticUseSub.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/StaticUseSub.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,24 @@ +/** + * 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; + +public class StaticUseSub extends StaticUse { + public static String getNonNull() { + return NON_NULL.getClass().getName(); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/StaticUseSubTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/StaticUseSubTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -0,0 +1,48 @@ +/** + * 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 org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public class StaticUseSubTest { + private static TestVM code; + + @BeforeClass + public static void compileTheCode() throws Exception { + StringBuilder sb = new StringBuilder(); + code = TestVM.compileClass(sb, "org/apidesign/vm4brwsr/StaticUseSub"); + } + @AfterClass + public static void releaseTheCode() { + code = null; + } + + @Test public void getInheritedStaticField() throws Exception { + code.assertExec( + "Obtains non-null", StaticUseSub.class, + "getNonNull__Ljava_lang_String_2", + "java.lang.Object" + ); + } +} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/StringSample.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringSample.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringSample.java Mon Oct 07 14:20:58 2013 +0200 @@ -18,6 +18,7 @@ package org.apidesign.vm4brwsr; import java.io.UnsupportedEncodingException; +import org.apidesign.bck2brwsr.core.JavaScriptBody; /** * @@ -129,4 +130,20 @@ public String toString() { return HELLO + cnt; } + + @JavaScriptBody(args = {}, body = "return [1, 2];") + private static native Object crtarr(); + @JavaScriptBody(args = { "o" }, body = "return o.toString();") + private static native String toStrng(Object o); + + public static String toStringArray(boolean fakeArr, boolean toString) { + final Object arr = fakeArr ? crtarr() : new Object[2]; + final String whole = toString ? arr.toString() : toStrng(arr); + int zav = whole.indexOf('@'); + if (zav <= 0) { + zav = whole.length(); + } + return whole.substring(0, zav).toString().toString(); + } + } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/StringTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -19,6 +19,7 @@ import org.testng.annotations.Test; import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; /** @@ -193,15 +194,47 @@ } + @Test public void toStringOnJSArray() throws Exception { + String exp = StringSample.toStringArray(false, true); + + assertExec( + "Treated as Java Object array", + StringSample.class, "toStringArray__Ljava_lang_String_2ZZ", + exp, true, true + ); + } + + @Test public void toStringOnRealArray() throws Exception { + String exp = StringSample.toStringArray(false, true); + + assertExec( + "Is Java Object array", + StringSample.class, "toStringArray__Ljava_lang_String_2ZZ", + exp, false, true + ); + } + + @Test public void valueOfOnJSArray() throws Exception { + assertExec( + "Treated as classical JavaScript array", + StringSample.class, "toStringArray__Ljava_lang_String_2ZZ", + "1,2", true, false + ); + } + private static TestVM code; @BeforeClass - public void compileTheCode() throws Exception { + public static void compileTheCode() throws Exception { code = TestVM.compileClass( "org/apidesign/vm4brwsr/StringSample", "java/lang/String" ); } + @AfterClass + public static void releaseTheCode() { + code = null; + } private static void assertExec(String msg, Class clazz, String method, Object expRes, Object... args diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/SystemTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/SystemTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/SystemTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -19,6 +19,7 @@ import org.testng.annotations.BeforeClass; import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; import org.testng.annotations.Test; /** @@ -51,6 +52,10 @@ code = TestVM.compileClass( "org/apidesign/bck2brwsr/emul/lang/System"); } + @AfterClass + public static void releaseTheCode() { + code = null; + } } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/TestVM.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/TestVM.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/TestVM.java Mon Oct 07 14:20:58 2013 +0200 @@ -51,7 +51,7 @@ ret = code.invokeMethod(bck2brwsr, "loadClass", clazz.getName()); ret = code.invokeMethod(ret, method, args); } catch (ScriptException ex) { - fail("Execution failed in " + dumpJS(codeSeq), ex); + fail("Execution failed in " + dumpJS(codeSeq) + ": " + ex.getMessage(), ex); } catch (NoSuchMethodException ex) { fail("Cannot find method in " + dumpJS(codeSeq), ex); } @@ -68,9 +68,9 @@ ret = code.invokeMethod(ret, "toFP"); ret = code.invokeFunction("Number", ret); } catch (ScriptException ex) { - fail("Conversion to number failed in " + dumpJS(codeSeq), ex); + fail("Conversion to number failed in " + dumpJS(codeSeq) + ": " + ex.getMessage(), ex); } catch (NoSuchMethodException ex) { - fail("Cannot find global Number(x) function in " + dumpJS(codeSeq), ex); + fail("Cannot find global Number(x) function in " + dumpJS(codeSeq) + ": " + ex.getMessage(), ex); } } return ret; @@ -115,7 +115,7 @@ if (sb.length() > 2000) { sb = dumpJS(sb); } - fail("Could not evaluate:\n" + sb, ex); + fail("Could not evaluate:" + ex.getClass() + ":" + ex.getMessage() + "\n" + sb, ex); return null; } } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/VMLazyTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/VMLazyTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/VMLazyTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -22,6 +22,7 @@ import javax.script.ScriptException; import org.testng.annotations.BeforeClass; import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; import org.testng.annotations.Test; /** Implements loading class by class. @@ -32,7 +33,7 @@ private static TestVM code; @BeforeClass - public void compileTheCode() throws Exception { + public static void compileTheCode() throws Exception { StringBuilder sb = new StringBuilder(); sb.append("\nvar data = {};"); sb.append("\nfunction test(clazz, method) {"); @@ -51,6 +52,10 @@ ); arr[0].getContext().setAttribute("loader", new BytesLoader(), ScriptContext.ENGINE_SCOPE); } + @AfterClass + public static void releaseTheCode() { + code = null; + } @Test public void invokeStaticMethod() throws Exception { assertExec("Trying to get -1", "test", Double.valueOf(-1), diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/VMinVM.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/VMinVM.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/VMinVM.java Mon Oct 07 14:20:58 2013 +0200 @@ -43,4 +43,9 @@ @Override protected void requireScript(String resourcePath) { } + + @Override + String getVMObject() { + return "global"; + } } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/java/org/apidesign/vm4brwsr/VMinVMTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/VMinVMTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/VMinVMTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -21,6 +21,7 @@ import java.io.FileWriter; import java.io.IOException; import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -39,12 +40,24 @@ compareCode("org/apidesign/vm4brwsr/Classes.class"); } + @Test public void compareGeneratedCodeForToolkitClass() throws Exception { + String genCode = compareCode("org/apidesign/vm4brwsr/Bck2BrwsrToolkit.class"); + int indx = genCode.indexOf("gt = 65604"); + if (indx >= 0) { + fail("Goto to an invalid label:\n...." + genCode.substring(indx - 30, indx + 30) + "...."); + } + } + @BeforeClass - public void compileTheCode() throws Exception { + public static void compileTheCode() throws Exception { code = TestVM.compileClass("org/apidesign/vm4brwsr/VMinVM"); } + @AfterClass + public static void releaseTheCode() { + code = null; + } - private void compareCode(final String nm) throws Exception, IOException { + private String compareCode(final String nm) throws Exception, IOException { byte[] arr = BytesLoader.readClass(nm); String ret1 = VMinVM.toJavaScript(arr); @@ -83,5 +96,7 @@ msg.append(code.toString()); fail(msg.toString()); } + + return ret1; } } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vm/src/test/resources/org/apidesign/vm4brwsr/Bck2BrwsrToolkit.class Binary file rt/vm/src/test/resources/org/apidesign/vm4brwsr/Bck2BrwsrToolkit.class has changed diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/pom.xml --- a/rt/vmtest/pom.xml Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vmtest/pom.xml Mon Oct 07 14:20:58 2013 +0200 @@ -1,15 +1,14 @@ - + 4.0.0 org.apidesign.bck2brwsr rt - 0.3-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr vmtest - 0.3-SNAPSHOT + 0.9-SNAPSHOT VM Testing APIs http://bck2brwsr.apidesign.org @@ -24,6 +23,14 @@ 1.7 + + org.apache.maven.plugins + maven-javadoc-plugin + + org.apidesign.bck2brwsr.vmtest.impl + false + + @@ -43,18 +50,6 @@ ${project.groupId} - vm4brwsr - ${project.version} - jar - - - ${project.groupId} - emul.mini - ${project.version} - test - - - ${project.groupId} launcher ${project.version} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/BrwsrTest.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/BrwsrTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/BrwsrTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -29,6 +29,10 @@ * The browser to is by default executed via {@link java.awt.Desktop#browse(java.net.URI)}, * but one can change that by specifying -Dvmtest.brwsrs=firefox,google-chrome * property. + *

+ * If the annotated method throws {@link InterruptedException}, it will return + * the processing to the browser and after 100ms, called again. This is useful + * for testing asynchronous communication, etc. * * @author Jaroslav Tulach */ diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Http.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Http.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/Http.java Mon Oct 07 14:20:58 2013 +0200 @@ -23,8 +23,7 @@ import java.lang.annotation.Target; /** - * Exposes HTTP page or pages to the running {@link BrwsrTest}, so it can access under - * the relative path. + * Exposes an {@link Resource HTTP page} or a set of {@link #value() pages} to the running {@link BrwsrTest}. * * @author Jaroslav Tulach */ @@ -34,8 +33,10 @@ /** Set of pages to make available */ public Resource[] value(); - /** Exposes an HTTP page to the running {@link BrwsrTest}, so it can access - * under the relative path. + /** Describes single HTTP page to the running {@link BrwsrTest}, so it can be + * accessed under the specified {@link #path() relative path}. The page + * content can either be specified inline via {@link #content()} or as + * an external {@link #resource() resource}. * * @author Jaroslav Tulach */ @@ -44,7 +45,7 @@ public @interface Resource { /** path on the server that the test can use to access the exposed resource */ String path(); - /** the content of the HttpResource */ + /** the content of the Http.Resource */ String content(); /** resource relative to the class that should be used instead of content. * Leave content equal to empty string. @@ -52,5 +53,10 @@ String resource() default ""; /** mime type of the resource */ String mimeType(); + /** query parameters. Can be referenced from the {@link #content} as + * $0, $1, etc. The values will be extracted + * from URL parameters of the request. + */ + String[] parameters() default {}; } } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/VMTest.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/VMTest.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/VMTest.java Mon Oct 07 14:20:58 2013 +0200 @@ -17,27 +17,123 @@ */ package org.apidesign.bck2brwsr.vmtest; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.apidesign.bck2brwsr.launcher.Launcher; import org.apidesign.bck2brwsr.vmtest.impl.CompareCase; import org.testng.annotations.Factory; -/** A TestNG {@link Factory} that seeks for {@link Compare} annotations - * in provided class and builds set of tests that compare the computations - * in real as well as JavaScript virtual machines. Use as:

+/** A TestNG {@link Factory} that seeks for {@link Compare} and {@link BrwsrTest} annotations
+ * in provided class and builds set of tests that verify the functionality of Bck2Brwsr 
+ * based system. Use as:
+ * 
  * {@code @}{@link Factory} public static create() {
- *   return @{link VMTest}.{@link #create(YourClass.class);
+ *   return @{link VMTest}.{@link #create(java.lang.Class) create}(YourClass.class);
  * }
- * + * where YourClass contains methods annotated with + * {@link Compare} and {@link BrwsrTest} annotations. + * * @author Jaroslav Tulach */ public final class VMTest { - /** Inspects clazz and for each {@lik Compare} method creates - * instances of tests. Each instance runs the test in different virtual + private final List classes = new ArrayList<>(); + private final List launcher = new ArrayList<>(); + private Class annotation = BrwsrTest.class; + + private VMTest() { + } + + /** Inspects clazz and for each method annotated by + * {@link Compare} or {@link BrwsrTest} creates + * instances of tests. + *

+ * Each {@link Compare} instance runs the test in different virtual * machine and at the end they compare the results. + *

+ * Each {@link BrwsrTest} annotated method is executed once in {@link Launcher started + * browser}. * - * @param clazz the class to inspect + * @param clazz the class (or classes) to inspect * @return the set of created tests */ - public static Object[] create(Class clazz) { - return CompareCase.create(clazz); + public static Object[] create(Class clazz) { + return newTests().withClasses(clazz).build(); + } + + /** Creates new builder for test execution. Continue with methods + * like {@link #withClasses(java.lang.Class[])} or {@link #withLaunchers(java.lang.String[])}. + * Finish the process by calling {@link #build()}. + * + * @return new instance of a builder + * @since 0.7 + */ + public static VMTest newTests() { + return new VMTest(); + } + + /** Adds class (or classes) to the test execution. The classes are inspected + * to contain methods annotated by + * {@link Compare} or {@link BrwsrTest}. Appropriate set of TestNG test + * cases is then created. + *

+ * Each {@link Compare} instance runs the test in different virtual + * machine and at the end they compare the results. + *

+ * Each {@link BrwsrTest} annotated method is executed once in {@link Launcher started + * browser}. + * + * @param classes one or more classes to inspect + * @since 0.7 + */ + public final VMTest withClasses(Class... classes) { + this.classes.addAll(Arrays.asList(classes)); + return this; + } + + /** Adds list of launchers that should be used to execute tests defined + * by {@link Compare} and {@link BrwsrTest} annotations. This value + * can be overrided by using vmtest.brwsrs property. + * List of supported launchers is available in the documentation of + * {@link Launcher}. + * + * @param launcher names of one or more launchers to use for the execution + * of tests + * @since 0.7 + */ + public final VMTest withLaunchers(String... launcher) { + 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 + * @since 0.7 + */ + public final Object[] build() { + return CompareCase.create( + launcher.toArray(new String[0]), + classes.toArray(new Class[0]), + annotation + ); } } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/Bck2BrwsrCase.java Mon Oct 07 14:20:58 2013 +0200 @@ -65,17 +65,17 @@ for (Http.Resource r : http) { if (!r.content().isEmpty()) { InputStream is = new ByteArrayInputStream(r.content().getBytes("UTF-8")); - c.addHttpResource(r.path(), r.mimeType(), is); + c.addHttpResource(r.path(), r.mimeType(), r.parameters(), is); } else { InputStream is = m.getDeclaringClass().getResourceAsStream(r.resource()); - c.addHttpResource(r.path(), r.mimeType(), is); + c.addHttpResource(r.path(), r.mimeType(), r.parameters(), is); } } } String res = c.invoke(); value = res; if (fail) { - int idx = res.indexOf(':'); + int idx = res == null ? -1 : res.indexOf(':'); if (idx >= 0) { Class thrwbl = null; try { diff -r 8264f07b1f46 -r f73c1a0234fb 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 Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java Mon Oct 07 14:20:58 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,26 +54,25 @@ * @param clazz the class to inspect * @return the set of created tests */ - public static Object[] create(Class clazz) { - Method[] arr = clazz.getMethods(); + public static Object[] create(String[] brwsr, Class[] classes, Class brwsrTest) { List ret = new ArrayList<>(); final LaunchSetup l = LaunchSetup.INSTANCE; ret.add(l); - String[] brwsr; { String p = System.getProperty("vmtest.brwsrs"); if (p != null) { brwsr = p.split(","); - } else { - brwsr = new String[0]; } } - for (Method m : arr) { - registerCompareCases(m, l, ret, brwsr); - registerBrwsrCases(m, l, ret, brwsr); + for (Class clazz : classes) { + Method[] arr = clazz.getMethods(); + for (Method m : arr) { + registerCompareCases(m, l, ret, brwsr); + registerBrwsrCases(brwsrTest, m, l, ret, brwsr); + } } return ret.toArray(); } @@ -83,10 +83,34 @@ @Test(dependsOnGroups = "run") public void compareResults() throws Throwable { Object v1 = first.value; Object v2 = second.value; - if (v1 != null) { - v1 = v1.toString(); + if (v1 instanceof Integer || v1 instanceof Long || v1 instanceof Byte || v1 instanceof Short) { + try { + v1 = Long.parseLong(v1.toString()); + } catch (NumberFormatException nfe) { + v1 = "Can't parse " + v1.toString(); + } + try { + v2 = Long.parseLong(v2.toString()); + } catch (NumberFormatException nfe) { + v2 = "Can't parse " + v2.toString(); + } + } else if (v1 instanceof Number) { + try { + v1 = Double.parseDouble(v1.toString()); + } catch (NumberFormatException nfe) { + v1 = "Can't parse " + v1.toString(); + } + try { + v2 = Double.parseDouble(v2.toString()); + } catch (NumberFormatException nfe) { + v2 = "Can't parse " + v2.toString(); + } } else { - v1 = "null"; + if (v1 != null) { + v1 = v1.toString(); + } else { + v1 = "null"; + } } try { Assert.assertEquals(v2, v1, "Comparing results"); @@ -126,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; } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/LaunchSetup.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/LaunchSetup.java Wed Feb 27 17:50:47 2013 +0100 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/LaunchSetup.java Mon Oct 07 14:20:58 2013 +0200 @@ -42,7 +42,14 @@ } private synchronized Launcher js(boolean create) { if (js == null && create) { - js = Launcher.createJavaScript(); + final String p = System.getProperty("vmtest.js", "script"); // NOI18N + switch (p) { + case "brwsr": js = Launcher.createBrowser(null); break; // NOI18N + case "script": js = Launcher.createJavaScript(); break; // NOI18N + default: throw new IllegalArgumentException( + "Unknown value of vmtest.js property: " + p + ); + } } return js; } diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/AssertionTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/AssertionTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -/** - * 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.tck; - -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class AssertionTest { - - @Compare public Object checkAssert() throws ClassNotFoundException { - try { - assert false : "Is assertion status on?"; - return null; - } catch (AssertionError ex) { - return ex.getClass().getName(); - } - } - - @Factory - public static Object[] create() { - return VMTest.create(AssertionTest.class); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/BrwsrCheckTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/BrwsrCheckTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * 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.tck; - -import org.apidesign.bck2brwsr.core.JavaScriptBody; -import org.apidesign.bck2brwsr.vmtest.BrwsrTest; -import org.apidesign.bck2brwsr.vmtest.HtmlFragment; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class BrwsrCheckTest { - - @BrwsrTest public void assertWindowObjectIsDefined() { - assert window() != null : "No window object found!"; - } - - - - - @HtmlFragment("

\n" - + "Hello!\n" - + "

\n") - @BrwsrTest public void accessProvidedFragment() { - assert getElementById("hello") != null : "Element with 'hello' ID found"; - } - - @Factory - public static Object[] create() { - return VMTest.create(BrwsrCheckTest.class); - } - - - @JavaScriptBody(args = {}, body = "return window;") - private static native Object window(); - - @JavaScriptBody(args = { "id" }, body = "return window.document.getElementById(id);") - private static native Object getElementById(String id); -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ByteArithmeticTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ByteArithmeticTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,102 +0,0 @@ -/** - * 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.tck; - -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class ByteArithmeticTest { - - private static byte add(byte x, byte y) { - return (byte)(x + y); - } - - private static byte sub(byte x, byte y) { - return (byte)(x - y); - } - - private static byte mul(byte x, byte y) { - return (byte)(x * y); - } - - private static byte div(byte x, byte y) { - return (byte)(x / y); - } - - private static byte mod(byte x, byte y) { - return (byte)(x % y); - } - - @Compare public byte conversion() { - return (byte)123456; - } - - @Compare public byte addOverflow() { - return add(Byte.MAX_VALUE, (byte)1); - } - - @Compare public byte subUnderflow() { - return sub(Byte.MIN_VALUE, (byte)1); - } - - @Compare public byte addMaxByteAndMaxByte() { - return add(Byte.MAX_VALUE, Byte.MAX_VALUE); - } - - @Compare public byte subMinByteAndMinByte() { - return sub(Byte.MIN_VALUE, Byte.MIN_VALUE); - } - - @Compare public byte multiplyMaxByte() { - return mul(Byte.MAX_VALUE, (byte)2); - } - - @Compare public byte multiplyMaxByteAndMaxByte() { - return mul(Byte.MAX_VALUE, Byte.MAX_VALUE); - } - - @Compare public byte multiplyMinByte() { - return mul(Byte.MIN_VALUE, (byte)2); - } - - @Compare public byte multiplyMinByteAndMinByte() { - return mul(Byte.MIN_VALUE, Byte.MIN_VALUE); - } - - @Compare public byte multiplyPrecision() { - return mul((byte)17638, (byte)1103); - } - - @Compare public byte division() { - return div((byte)1, (byte)2); - } - - @Compare public byte divisionReminder() { - return mod((byte)1, (byte)2); - } - - @Factory - public static Object[] create() { - return VMTest.create(ByteArithmeticTest.class); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CloneTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CloneTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -/** - * 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.tck; - -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class CloneTest { - private int value; - - @Compare - public Object notSupported() throws CloneNotSupportedException { - return this.clone(); - } - - @Compare public String sameClass() throws CloneNotSupportedException { - return new Clnbl().clone().getClass().getName(); - } - - @Compare public boolean differentInstance() throws CloneNotSupportedException { - Clnbl orig = new Clnbl(); - return orig == orig.clone(); - } - - @Compare public int sameReference() throws CloneNotSupportedException { - CloneTest self = this; - Clnbl orig = new Clnbl(); - self.value = 33; - orig.ref = self; - return ((Clnbl)orig.clone()).ref.value; - } - - @Compare public int sameValue() throws CloneNotSupportedException { - Clnbl orig = new Clnbl(); - orig.value = 10; - return ((Clnbl)orig.clone()).value; - } - - @Factory - public static Object[] create() { - return VMTest.create(CloneTest.class); - } - - public static final class Clnbl implements Cloneable { - public CloneTest ref; - private int value; - - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); - } - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CompareByteArrayTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CompareByteArrayTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -/** - * 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.tck; - -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class CompareByteArrayTest { - @Compare public int byteArraySum() { - byte[] arr = createArray(); - return sumByteArr(arr); - } - - @Compare public int countZeros() { - int zeros = 0; - for (Byte b : createArray()) { - if (b == 0) { - zeros++; - } - } - return zeros; - } - - private static int sumByteArr(byte[] arr) { - int sum = 0; - for (int i = 0; i < arr.length; i++) { - sum += arr[i]; - } - return sum; - } - - @Compare public String noOutOfBounds() { - return atIndex(1); - } - - @Compare public String outOfBounds() { - return atIndex(5); - } - - @Compare public String outOfBoundsMinus() { - return atIndex(-1); - } - - @Compare public String toOfBounds() { - return toIndex(5); - } - - @Compare public String toOfBoundsMinus() { - return toIndex(-1); - } - - private static final int[] arr = { 0, 1, 2 }; - public static String atIndex(int at) { - return "at@" + arr[at]; - } - public static String toIndex(int at) { - arr[at] = 10; - return "ok"; - } - - - @Factory - public static Object[] create() { - return VMTest.create(CompareByteArrayTest.class); - } - - private byte[] createArray() { - byte[] arr = new byte[10]; - arr[5] = 3; - arr[7] = 8; - return arr; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CompareHashTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CompareHashTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/** - * 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.tck; - -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class CompareHashTest { - @Compare public int hashOfString() { - return "Ahoj".hashCode(); - } - - @Compare public int hashRemainsYieldsZero() { - Object o = new Object(); - return o.hashCode() - o.hashCode(); - } - - @Compare public int initializeInStatic() { - return StaticUse.NON_NULL.hashCode() - StaticUse.NON_NULL.hashCode(); - } - - @Compare public int hashOfInt() { - return Integer.valueOf(Integer.MAX_VALUE).hashCode(); - } - - @Factory - public static Object[] create() { - return VMTest.create(CompareHashTest.class); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CompareIntArrayTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CompareIntArrayTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -/** - * 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.tck; - -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class CompareIntArrayTest { - @Compare public int integerArraySum() { - int[] arr = createArray(); - return sumIntArr(arr); - } - - @Compare public int countZeros() { - int zeros = 0; - for (Integer i : createArray()) { - if (i == 0) { - zeros++; - } - } - return zeros; - } - - private static int sumIntArr(int[] arr) { - int sum = 0; - for (int i = 0; i < arr.length; i++) { - sum += arr[i]; - } - return sum; - } - - @Factory - public static Object[] create() { - return VMTest.create(CompareIntArrayTest.class); - } - - private int[] createArray() { - int[] arr = new int[10]; - arr[5] = 3; - arr[7] = 8; - return arr; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CompareStringsTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/CompareStringsTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,162 +0,0 @@ -/** - * 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.tck; - -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URL; -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class CompareStringsTest { - @Compare public String firstChar() { - return "" + ("Hello".toCharArray()[0]); - } - - @Compare public String classCast() { - Object o = firstChar(); - return String.class.cast(o); - } - - @Compare public String classCastThrown() { - Object o = null; - return String.class.cast(o); - } - - @Compare public boolean equalToNull() { - return "Ahoj".equals(null); - } - - @Compare public int highByteLenght() { - byte[] arr= { 77,97,110,105,102,101,115,116,45,86,101,114,115,105,111,110 }; - return new String(arr, 0).length(); - } - - @Compare public String highByte() { - byte[] arr= { 77,97,110,105,102,101,115,116,45,86,101,114,115,105,111,110 }; - StringBuilder sb = new StringBuilder(); - sb.append("pref:"); - sb.append(new String(arr, 0)); - return sb.toString(); - } - - @Compare public static Object compareURLs() throws MalformedURLException { - return new URL("http://apidesign.org:8080/wiki/").toExternalForm().toString(); - } - - @Compare public String deleteLastTwoCharacters() { - StringBuilder sb = new StringBuilder(); - sb.append("453.0"); - if (sb.toString().endsWith(".0")) { - final int l = sb.length(); - sb.delete(l - 2, l); - } - return sb.toString().toString(); - } - - @Compare public String nameOfStringClass() throws Exception { - return Class.forName("java.lang.String").getName(); - } - @Compare public String nameOfArrayClass() throws Exception { - return Class.forName("org.apidesign.bck2brwsr.tck.CompareHashTest").getName(); - } - - @Compare public String lowerHello() { - return "HeLlO".toLowerCase(); - } - - @Compare public String lowerA() { - return String.valueOf(Character.toLowerCase('A')).toString(); - } - @Compare public String upperHello() { - return "hello".toUpperCase(); - } - - @Compare public String upperA() { - return String.valueOf(Character.toUpperCase('a')).toString(); - } - - @Compare public boolean matchRegExp() throws Exception { - return "58038503".matches("\\d*"); - } - - @Compare public boolean doesNotMatchRegExp() throws Exception { - return "58038503GH".matches("\\d*"); - } - - @Compare public boolean doesNotMatchRegExpFully() throws Exception { - return "Hello".matches("Hell"); - } - - @Compare public String emptyCharArray() { - char[] arr = new char[10]; - return new String(arr); - } - - @Compare public String variousCharacterTests() throws Exception { - StringBuilder sb = new StringBuilder(); - - sb.append(Character.isUpperCase('a')); - sb.append(Character.isUpperCase('A')); - sb.append(Character.isLowerCase('a')); - sb.append(Character.isLowerCase('A')); - - sb.append(Character.isLetter('A')); - sb.append(Character.isLetterOrDigit('9')); - sb.append(Character.isLetterOrDigit('A')); - sb.append(Character.isLetter('0')); - - return sb.toString().toString(); - } - - @Compare - public String nullFieldInitialized() { - NullField nf = new NullField(); - return ("" + nf.name).toString(); - } - @Compare - public String toUTFString() throws UnsupportedEncodingException { - byte[] arr = { - (byte) -59, (byte) -67, (byte) 108, (byte) 117, (byte) -59, (byte) -91, - (byte) 111, (byte) 117, (byte) -60, (byte) -115, (byte) 107, (byte) -61, - (byte) -67, (byte) 32, (byte) 107, (byte) -59, (byte) -81, (byte) -59, - (byte) -120 - }; - return new String(arr, "utf-8"); - } - - @Compare - public int stringToBytesLenght() throws UnsupportedEncodingException { - return "\u017dlu\u0165ou\u010dk\u00fd k\u016f\u0148".getBytes("utf8").length; - } - - @Factory - public static Object[] create() { - return VMTest.create(CompareStringsTest.class); - } - - private static final class NullField { - - String name; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/HttpResourceTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/HttpResourceTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,106 +0,0 @@ -/** - * 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.tck; - -import java.io.InputStream; -import java.net.URL; -import org.apidesign.bck2brwsr.core.JavaScriptBody; -import org.apidesign.bck2brwsr.vmtest.BrwsrTest; -import org.apidesign.bck2brwsr.vmtest.Http; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class HttpResourceTest { - - @Http({ - @Http.Resource(path = "/xhr", content = "Hello Brwsr!", mimeType = "text/plain") - }) - @BrwsrTest - - - public String testReadContentViaXHR() throws Exception { - String msg = read("/xhr"); - assert "Hello Brwsr!".equals(msg) : "The message was " + msg; - return msg; - } - - @Http({ - @Http.Resource(path = "/url", content = "Hello via URL!", mimeType = "text/plain") - }) - @BrwsrTest - public String testReadContentViaURL() throws Exception { - URL url = new URL("http:/url"); - String msg = (String) url.getContent(); - assert "Hello via URL!".equals(msg) : "The message was " + msg; - return msg; - } - @Http({ - @Http.Resource(path = "/url", content = "Hello via URL!", mimeType = "text/plain") - }) - @BrwsrTest - public String testReadContentViaURLWithStringParam() throws Exception { - URL url = new URL("http:/url"); - String msg = (String) url.getContent(new Class[] { String.class }); - assert "Hello via URL!".equals(msg) : "The message was " + msg; - return msg; - } - - @Http({ - @Http.Resource(path = "/bytes", content = "", resource = "0xfe", mimeType = "x-application/binary") - }) - @BrwsrTest - public void testReadByte() throws Exception { - URL url = new URL("http:/bytes"); - final Object res = url.getContent(new Class[] { byte[].class }); - assert res instanceof byte[] : "Expecting byte[]: " + res; - byte[] arr = (byte[]) res; - assert arr.length == 1 : "One byte " + arr.length; - assert arr[0] == 0xfe : "It is 0xfe: " + Integer.toHexString(arr[0]); - } - - @Http({ - @Http.Resource(path = "/bytes", content = "", resource = "0xfe", mimeType = "x-application/binary") - }) - @BrwsrTest - public void testReadByteViaInputStream() throws Exception { - URL url = new URL("http:/bytes"); - InputStream is = url.openStream(); - byte[] arr = new byte[10]; - int len = is.read(arr); - assert len == 1 : "One byte " + len; - assert arr[0] == 0xfe : "It is 0xfe: " + Integer.toHexString(arr[0]); - } - - @JavaScriptBody(args = { "url" }, body = - "var req = new XMLHttpRequest();\n" - + "req.open('GET', url, false);\n" - + "req.send();\n" - + "return req.responseText;" - ) - private static native String read(String url); - - - @Factory - public static Object[] create() { - return VMTest.create(HttpResourceTest.class); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/InheritanceA.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/InheritanceA.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -/** - * 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.tck; - -/** - * - * @author Jaroslav Tulach - */ -public class InheritanceA { - private String name; - - public void setA(String n) { - this.name = n; - } - - public String getA() { - return name; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/InheritanceB.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/InheritanceB.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -/** - * 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.tck; - -/** - * - * @author Jaroslav Tulach - */ -public class InheritanceB extends InheritanceA { - private String name; - - public void setB(String n) { - this.name = n; - } - - public String getB() { - return name; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/InheritanceTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/InheritanceTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -/** - * 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.tck; - -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class InheritanceTest { - - @Compare public String checkFieldsIndependent() throws ClassNotFoundException { - InheritanceB ib = new InheritanceB(); - ib.setA("A"); - ib.setB("B"); - return "A: " + ib.getA() + " B: " + ib.getB(); - } - - @Factory - public static Object[] create() { - return VMTest.create(InheritanceTest.class); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/IntegerArithmeticTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/IntegerArithmeticTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,166 +0,0 @@ -/** - * 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.tck; - -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class IntegerArithmeticTest { - - private static int add(int x, int y) { - return x + y; - } - - private static int sub(int x, int y) { - return x - y; - } - - private static int mul(int x, int y) { - return x * y; - } - - private static int div(int x, int y) { - return x / y; - } - - private static int mod(int x, int y) { - return x % y; - } - - private static int neg(int x) { - return (-x); - } - - private static float fadd(float x, float y) { - return x + y; - } - - private static double dadd(double x, double y) { - return x + y; - } - - @Compare public int addOverflow() { - return add(Integer.MAX_VALUE, 1); - } - - @Compare public int subUnderflow() { - return sub(Integer.MIN_VALUE, 1); - } - - @Compare public int addMaxIntAndMaxInt() { - return add(Integer.MAX_VALUE, Integer.MAX_VALUE); - } - - @Compare public int subMinIntAndMinInt() { - return sub(Integer.MIN_VALUE, Integer.MIN_VALUE); - } - - @Compare public int multiplyMaxInt() { - return mul(Integer.MAX_VALUE, 2); - } - - @Compare public int multiplyMaxIntAndMaxInt() { - return mul(Integer.MAX_VALUE, Integer.MAX_VALUE); - } - - @Compare public int multiplyMinInt() { - return mul(Integer.MIN_VALUE, 2); - } - - @Compare public int multiplyMinIntAndMinInt() { - return mul(Integer.MIN_VALUE, Integer.MIN_VALUE); - } - - @Compare public int multiplyPrecision() { - return mul(119106029, 1103515245); - } - - @Compare public int division() { - return div(1, 2); - } - - @Compare public int divisionReminder() { - return mod(1, 2); - } - - @Compare public int negativeDivision() { - return div(-7, 3); - } - - @Compare public int negativeDivisionReminder() { - return mod(-7, 3); - } - - @Compare public int conversionFromFloat() { - return (int) fadd(-2, -0.6f); - } - - @Compare public int conversionFromDouble() { - return (int) dadd(-2, -0.6); - } - - @Compare public boolean divByZeroThrowsArithmeticException() { - try { - div(1, 0); - return false; - } catch (final ArithmeticException e) { - return true; - } - } - - @Compare public boolean modByZeroThrowsArithmeticException() { - try { - mod(1, 0); - return false; - } catch (final ArithmeticException e) { - return true; - } - } - - @Compare public int negate() { - return neg(123456); - } - - @Compare public int negateMaxInt() { - return neg(Integer.MAX_VALUE); - } - - @Compare public int negateMinInt() { - return neg(Integer.MIN_VALUE); - } - - @Compare public int sumTwoDimensions() { - int[][] matrix = createMatrix(4, 3); - matrix[0][0] += 10; - return matrix[0][0]; - } - - static int[][] createMatrix(int x, int y) { - return new int[x][y]; - } - - @Factory - public static Object[] create() { - return VMTest.create(IntegerArithmeticTest.class); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/LongArithmeticTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/LongArithmeticTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,376 +0,0 @@ -/** - * 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.tck; - -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class LongArithmeticTest { - - private static long add(long x, long y) { - return (x + y); - } - - private static long sub(long x, long y) { - return (x - y); - } - - private static long mul(long x, long y) { - return (x * y); - } - - private static long div(long x, long y) { - return (x / y); - } - - private static long mod(long x, long y) { - return (x % y); - } - - private static long neg(long x) { - return (-x); - } - - private static long shl(long x, int b) { - return (x << b); - } - - private static long shr(long x, int b) { - return (x >> b); - } - - private static long ushr(long x, int b) { - return (x >>> b); - } - - private static long and(long x, long y) { - return (x & y); - } - - private static long or(long x, long y) { - return (x | y); - } - - private static long xor(long x, long y) { - return (x ^ y); - } - - private static float fadd(float x, float y) { - return x + y; - } - - private static double dadd(double x, double y) { - return x + y; - } - - public static int compare(long x, long y, int zero) { - final int xyResult = compareL(x, y, zero); - final int yxResult = compareL(y, x, zero); - - return ((xyResult + yxResult) == 0) ? xyResult : -2; - } - - private static int compareL(long x, long y, int zero) { - int result = -2; - int trueCount = 0; - - x += zero; - if (x == y) { - result = 0; - ++trueCount; - } - - x += zero; - if (x < y) { - result = -1; - ++trueCount; - } - - x += zero; - if (x > y) { - result = 1; - ++trueCount; - } - - return (trueCount == 1) ? result : -2; - } - - @Compare public long conversion() { - return Long.MAX_VALUE; - } - - @Compare public long negate1() { - return neg(0x00fa37d7763e0ca1l); - } - - @Compare public long negate2() { - return neg(0x80fa37d7763e0ca1l); - } - - @Compare public long negate3() { - return neg(0xfffffffffffffeddl); - } - - @Compare public long addOverflow() { - return add(Long.MAX_VALUE, 1l); - } - - @Compare public long subUnderflow() { - return sub(Long.MIN_VALUE, 1l); - } - - @Compare public long addMaxLongAndMaxLong() { - return add(Long.MAX_VALUE, Long.MAX_VALUE); - } - - @Compare public long subMinLongAndMinLong() { - return sub(Long.MIN_VALUE, Long.MIN_VALUE); - } - - @Compare public long subMinLongAndMaxLong() { - return sub(Long.MIN_VALUE, Long.MAX_VALUE); - } - - @Compare public long multiplyMaxLong() { - return mul(Long.MAX_VALUE, 2l); - } - - @Compare public long multiplyMaxLongAndMaxLong() { - return mul(Long.MAX_VALUE, Long.MAX_VALUE); - } - - @Compare public long multiplyMinLong() { - return mul(Long.MIN_VALUE, 2l); - } - - @Compare public long multiplyMinLongAndMinLong() { - return mul(Long.MIN_VALUE, Long.MIN_VALUE); - } - - @Compare public long multiplyPrecision() { - return mul(0x00fa37d7763e0ca1l, 0xa7b3432fff00123el); - } - - @Compare public long divideSmallPositiveNumbers() { - return div(0xabcdef, 0x123); - } - - @Compare public long divideSmallNegativeNumbers() { - return div(-0xabcdef, -0x123); - } - - @Compare public long divideSmallMixedNumbers() { - return div(0xabcdef, -0x123); - } - - @Compare public long dividePositiveNumbersOneDigitDenom() { - return div(0xabcdef0102ffffl, 0x654); - } - - @Compare public long divideNegativeNumbersOneDigitDenom() { - return div(-0xabcdef0102ffffl, -0x654); - } - - @Compare public long divideMixedNumbersOneDigitDenom() { - return div(-0xabcdef0102ffffl, 0x654); - } - - @Compare public long dividePositiveNumbersMultiDigitDenom() { - return div(0x7ffefc003322aabbl, 0x89ab1000l); - } - - @Compare public long divideNegativeNumbersMultiDigitDenom() { - return div(-0x7ffefc003322aabbl, -0x123489ab1001l); - } - - @Compare public long divideMixedNumbersMultiDigitDenom() { - return div(0x7ffefc003322aabbl, -0x38f49b0b7574e36l); - } - - @Compare public long divideWithOverflow() { - return div(0x8000fffe0000l, 0x8000ffffl); - } - - @Compare public long divideWithCorrection() { - return div(0x7fff800000000000l, 0x800000000001l); - } - - @Compare public long moduloSmallPositiveNumbers() { - return mod(0xabcdef, 0x123); - } - - @Compare public long moduloSmallNegativeNumbers() { - return mod(-0xabcdef, -0x123); - } - - @Compare public long moduloSmallMixedNumbers() { - return mod(0xabcdef, -0x123); - } - - @Compare public long moduloPositiveNumbersOneDigitDenom() { - return mod(0xabcdef0102ffffl, 0x654); - } - - @Compare public long moduloNegativeNumbersOneDigitDenom() { - return mod(-0xabcdef0102ffffl, -0x654); - } - - @Compare public long moduloMixedNumbersOneDigitDenom() { - return mod(-0xabcdef0102ffffl, 0x654); - } - - @Compare public long moduloPositiveNumbersMultiDigitDenom() { - return mod(0x7ffefc003322aabbl, 0x89ab1000l); - } - - @Compare public long moduloNegativeNumbersMultiDigitDenom() { - return mod(-0x7ffefc003322aabbl, -0x123489ab1001l); - } - - @Compare public long moduloMixedNumbersMultiDigitDenom() { - return mod(0x7ffefc003322aabbl, -0x38f49b0b7574e36l); - } - - @Compare public long moduloWithOverflow() { - return mod(0x8000fffe0000l, 0x8000ffffl); - } - - @Compare public long moduloWithCorrection() { - return mod(0x7fff800000000000l, 0x800000000001l); - } - - @Compare public long conversionFromFloatPositive() { - return (long) fadd(2, 0.6f); - } - - @Compare public long conversionFromFloatNegative() { - return (long) fadd(-2, -0.6f); - } - - @Compare public long conversionFromDoublePositive() { - return (long) dadd(0x20ffff0000L, 0.6); - } - - @Compare public long conversionFromDoubleNegative() { - return (long) dadd(-0x20ffff0000L, -0.6); - } - - @Compare public boolean divByZeroThrowsArithmeticException() { - try { - div(1, 0); - return false; - } catch (final ArithmeticException e) { - return true; - } - } - - @Compare public boolean modByZeroThrowsArithmeticException() { - try { - mod(1, 0); - return false; - } catch (final ArithmeticException e) { - return true; - } - } - - @Compare public long shiftL1() { - return shl(0x00fa37d7763e0ca1l, 5); - } - - @Compare public long shiftL2() { - return shl(0x00fa37d7763e0ca1l, 32); - } - - @Compare public long shiftL3() { - return shl(0x00fa37d7763e0ca1l, 45); - } - - @Compare public long shiftR1() { - return shr(0x00fa37d7763e0ca1l, 5); - } - - @Compare public long shiftR2() { - return shr(0x00fa37d7763e0ca1l, 32); - } - - @Compare public long shiftR3() { - return shr(0x00fa37d7763e0ca1l, 45); - } - - @Compare public long uShiftR1() { - return ushr(0x00fa37d7763e0ca1l, 5); - } - - @Compare public long uShiftR2() { - return ushr(0x00fa37d7763e0ca1l, 45); - } - - @Compare public long uShiftR3() { - return ushr(0xf0fa37d7763e0ca1l, 5); - } - - @Compare public long uShiftR4() { - return ushr(0xf0fa37d7763e0ca1l, 45); - } - - @Compare public long and1() { - return and(0x00fa37d7763e0ca1l, 0xa7b3432fff00123el); - } - - @Compare public long or1() { - return or(0x00fa37d7763e0ca1l, 0xa7b3432fff00123el); - } - - @Compare public long xor1() { - return xor(0x00fa37d7763e0ca1l, 0xa7b3432fff00123el); - } - - @Compare public long xor2() { - return xor(0x00fa37d7763e0ca1l, 0x00000000ff00123el); - } - - @Compare public long xor3() { - return xor(0x00000000763e0ca1l, 0x00000000ff00123el); - } - - @Compare public int compareSameNumbers() { - return compare(0x0000000000000000l, 0x0000000000000000l, 0); - } - - @Compare public int comparePositiveNumbers() { - return compare(0x0000000000200000l, 0x0000000010000000l, 0); - } - - @Compare public int compareNegativeNumbers() { - return compare(0xffffffffffffffffl, 0xffffffff00000000l, 0); - } - - @Compare public int compareMixedNumbers() { - return compare(0x8000000000000000l, 0x7fffffffffffffffl, 0); - } - - @Factory - public static Object[] create() { - return VMTest.create(LongArithmeticTest.class); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionArrayTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionArrayTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ -/** - * 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.tck; - -import java.lang.reflect.Array; -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class ReflectionArrayTest { - @Compare public int lengthOfStringArray() { - String[] arr = (String[]) Array.newInstance(String.class, 10); - return arr.length; - } - - @Compare public int reflectiveLengthOfStringArray() { - Object arr = Array.newInstance(String.class, 10); - return Array.getLength(arr); - } - - @Compare public int reflectiveLengthOneNonArray() { - Object arr = "non-array"; - return Array.getLength(arr); - } - - @Compare public String compTypeOfStringArray() { - String[] arr = (String[]) Array.newInstance(String.class, 10); - return arr.getClass().getComponentType().getName(); - } - - @Compare public Object negativeArrayExcp() { - return Array.newInstance(String.class, -5); - } - - @Compare public int lengthOfIntArray() { - int[] arr = (int[]) Array.newInstance(Integer.TYPE, 10); - return arr.length; - } - - @Compare public int reflectiveLengthOfIntArray() { - Object arr = Array.newInstance(Integer.TYPE, 10); - return Array.getLength(arr); - } - - @Compare public String compTypeOfIntArray() { - int[] arr = (int[]) Array.newInstance(int.class, 10); - return arr.getClass().getComponentType().getName(); - } - - @Compare public Object intNegativeArrayExcp() { - return Array.newInstance(int.class, -5); - } - - @Compare public Integer verifyAutobox() { - int[] arr = (int[]) Array.newInstance(int.class, 5); - return (Integer) Array.get(arr, 0); - } - @Compare public String verifyObjectArray() { - String[] arr = (String[]) Array.newInstance(String.class, 5); - Array.set(arr, 0, "Hello"); - return (String) Array.get(arr, 0); - } - @Compare public int verifyInt() { - int[] arr = (int[]) Array.newInstance(int.class, 5); - return Array.getInt(arr, 0); - } - @Compare public long verifyConvertToLong() { - int[] arr = (int[]) Array.newInstance(int.class, 5); - return Array.getLong(arr, 0); - } - - @Compare public Object verifySetIntToObject() { - try { - Object[] arr = (Object[]) Array.newInstance(Object.class, 5); - Array.setInt(arr, 0, 10); - return Array.get(arr, 0); - } catch (Exception exception) { - return exception.getClass().getName(); - } - } - @Compare public long verifySetShort() { - int[] arr = (int[]) Array.newInstance(int.class, 5); - Array.setShort(arr, 0, (short)10); - return Array.getLong(arr, 0); - } - @Compare public long verifyCantSetLong() { - int[] arr = (int[]) Array.newInstance(int.class, 5); - Array.setLong(arr, 0, 10); - return Array.getLong(arr, 0); - } - @Compare public float verifyLongToFloat() { - Object arr = Array.newInstance(float.class, 5); - Array.setLong(arr, 0, 10); - return Array.getFloat(arr, 0); - } - - @Compare public double verifyConvertToDouble() { - int[] arr = (int[]) Array.newInstance(int.class, 5); - return Array.getDouble(arr, 0); - } - - @Compare public int multiIntArray() { - int[][][] arr = (int[][][]) Array.newInstance(int.class, 3, 3, 3); - return arr[0][1][2] + 5 + arr[2][2][0]; - } - - @Compare public String multiIntArrayCompType() { - return Array.newInstance(int.class, 3, 3, 3).getClass().getName(); - } - - - @Factory - public static Object[] create() { - return VMTest.create(ReflectionArrayTest.class); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,242 +0,0 @@ -/** - * 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.tck; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import org.apidesign.bck2brwsr.core.JavaScriptBody; -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class ReflectionTest { - @Compare public boolean nonNullThis() { - return this == null; - } - - @Compare public String intType() { - return Integer.TYPE.toString(); - } - - @Compare public String voidType() throws Exception { - return void.class.toString(); - } - - @Compare public String longClass() { - return long.class.toString(); - } - - @Compare public boolean isRunnableInterface() { - return Runnable.class.isInterface(); - } - - @Compare public String isRunnableHasRunMethod() throws NoSuchMethodException { - return Runnable.class.getMethod("run").getName(); - } - - @Compare public String namesOfMethods() { - StringBuilder sb = new StringBuilder(); - String[] arr = new String[20]; - int i = 0; - for (Method m : StaticUse.class.getMethods()) { - arr[i++] = m.getName(); - } - for (String s : sort(arr, i)) { - sb.append(s).append("\n"); - } - return sb.toString(); - } - - @Compare public String namesOfDeclaringClassesOfMethods() { - StringBuilder sb = new StringBuilder(); - String[] arr = new String[20]; - int i = 0; - for (Method m : StaticUse.class.getMethods()) { - arr[i++] = m.getName() + "@" + m.getDeclaringClass().getName(); - } - for (String s : sort(arr, i)) { - sb.append(s).append("\n"); - } - return sb.toString(); - } - - @Compare public String cannotCallNonStaticMethodWithNull() throws Exception { - StaticUse.class.getMethod("instanceMethod").invoke(null); - return "should not happen"; - } - - @Compare public Object voidReturnType() throws Exception { - return StaticUse.class.getMethod("instanceMethod").getReturnType(); - } - - @Retention(RetentionPolicy.RUNTIME) - @interface Ann { - } - - @Compare public String annoClass() throws Exception { - Retention r = Ann.class.getAnnotation(Retention.class); - assert r != null : "Annotation is present"; - assert r.value() == RetentionPolicy.RUNTIME : "Policy value is OK: " + r.value(); - return r.annotationType().getName(); - } - - @Compare public boolean isAnnotation() { - return Ann.class.isAnnotation(); - } - @Compare public boolean isNotAnnotation() { - return String.class.isAnnotation(); - } - @Compare public boolean isNotAnnotationEnum() { - return E.class.isAnnotation(); - } - enum E { A, B }; - @Compare public boolean isEnum() { - return E.A.getClass().isEnum(); - } - - @Compare public boolean isNotEnum() { - return "".getClass().isEnum(); - } - - @Compare public String newInstanceFails() throws InstantiationException { - try { - return "success: " + StaticUse.class.newInstance(); - } catch (IllegalAccessException ex) { - return ex.getClass().getName(); - } - } - - @Compare public String paramTypes() throws Exception { - Method plus = StaticUse.class.getMethod("plus", int.class, Integer.TYPE); - final Class[] pt = plus.getParameterTypes(); - return pt[0].getName(); - } - @Compare public String paramTypesNotFound() throws Exception { - return StaticUse.class.getMethod("plus", int.class, double.class).toString(); - } - @Compare public int methodWithArgs() throws Exception { - Method plus = StaticUse.class.getMethod("plus", int.class, Integer.TYPE); - return (Integer)plus.invoke(null, 2, 3); - } - - @Compare public String classGetNameForByte() { - return byte.class.getName(); - } - @Compare public String classGetNameForBaseObject() { - return newObject().getClass().getName(); - } - @Compare public String classGetNameForJavaObject() { - return new Object().getClass().getName(); - } - @Compare public String classGetNameForObjectArray() { - return (new Object[3]).getClass().getName(); - } - @Compare public String classGetNameForSimpleIntArray() { - return (new int[3]).getClass().getName(); - } - @Compare public boolean sameClassGetNameForSimpleCharArray() { - return (new char[3]).getClass() == (new char[34]).getClass(); - } - @Compare public String classGetNameForMultiIntArray() { - return (new int[3][4][5][6][7][8][9]).getClass().getName(); - } - @Compare public String classGetNameForMultiIntArrayInner() { - final int[][][][][][][] arr = new int[3][4][5][6][7][8][9]; - int[][][][][][] subarr = arr[0]; - int[][][][][] subsubarr = subarr[0]; - return subsubarr.getClass().getName(); - } - @Compare public String classGetNameForMultiStringArray() { - return (new String[3][4][5][6][7][8][9]).getClass().getName(); - } - - @Compare public String classForByte() throws Exception { - return Class.forName("[Z").getName(); - } - - @Compare public String classForUnknownArray() { - try { - return Class.forName("[W").getName(); - } catch (Exception ex) { - return ex.getClass().getName(); - } - } - - @Compare public String classForUnknownDeepArray() { - try { - return Class.forName("[[[[[W").getName(); - } catch (Exception ex) { - return ex.getClass().getName(); - } - } - - @Compare public String componentGetNameForObjectArray() { - return (new Object[3]).getClass().getComponentType().getName(); - } - @Compare public boolean sameComponentGetNameForObjectArray() { - return (new Object[3]).getClass().getComponentType() == Object.class; - } - @Compare public String componentGetNameForSimpleIntArray() { - return (new int[3]).getClass().getComponentType().getName(); - } - @Compare public String componentGetNameForMultiIntArray() { - return (new int[3][4][5][6][7][8][9]).getClass().getComponentType().getName(); - } - @Compare public String componentGetNameForMultiStringArray() { - Class c = (new String[3][4][5][6][7][8][9]).getClass(); - StringBuilder sb = new StringBuilder(); - for (;;) { - sb.append(c.getName()).append("\n"); - c = c.getComponentType(); - if (c == null) { - break; - } - } - return sb.toString(); - } - - @Compare public boolean isArray() { - return new Object[0].getClass().isArray(); - } - - @JavaScriptBody(args = { "arr", "len" }, body="var a = arr.slice(0, len); a.sort(); return a;") - private static String[] sort(String[] arr, int len) { - List list = Arrays.asList(arr).subList(0, len); - Collections.sort(list); - return list.toArray(new String[0]); - } - - @JavaScriptBody(args = {}, body = "return new Object();") - private static Object newObject() { - return new Object(); - } - - @Factory - public static Object[] create() { - return VMTest.create(ReflectionTest.class); - } - -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ResourcesTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ResourcesTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -/** - * 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.tck; - -import java.io.InputStream; -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class ResourcesTest { - - @Compare public String readResourceAsStream() throws Exception { - InputStream is = getClass().getResourceAsStream("Resources.txt"); - byte[] b = new byte[30]; - int len = is.read(b); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < len; i++) { - sb.append((char)b[i]); - } - return sb.toString(); - } - - @Factory public static Object[] create() { - return VMTest.create(ResourcesTest.class); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ShortArithmeticTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/ShortArithmeticTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,102 +0,0 @@ -/** - * 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.tck; - -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class ShortArithmeticTest { - - private static short add(short x, short y) { - return (short)(x + y); - } - - private static short sub(short x, short y) { - return (short)(x - y); - } - - private static short mul(short x, short y) { - return (short)(x * y); - } - - private static short div(short x, short y) { - return (short)(x / y); - } - - private static short mod(short x, short y) { - return (short)(x % y); - } - - @Compare public short conversion() { - return (short)123456; - } - - @Compare public short addOverflow() { - return add(Short.MAX_VALUE, (short)1); - } - - @Compare public short subUnderflow() { - return sub(Short.MIN_VALUE, (short)1); - } - - @Compare public short addMaxShortAndMaxShort() { - return add(Short.MAX_VALUE, Short.MAX_VALUE); - } - - @Compare public short subMinShortAndMinShort() { - return sub(Short.MIN_VALUE, Short.MIN_VALUE); - } - - @Compare public short multiplyMaxShort() { - return mul(Short.MAX_VALUE, (short)2); - } - - @Compare public short multiplyMaxShortAndMaxShort() { - return mul(Short.MAX_VALUE, Short.MAX_VALUE); - } - - @Compare public short multiplyMinShort() { - return mul(Short.MIN_VALUE, (short)2); - } - - @Compare public short multiplyMinShortAndMinShort() { - return mul(Short.MIN_VALUE, Short.MIN_VALUE); - } - - @Compare public short multiplyPrecision() { - return mul((short)17638, (short)1103); - } - - @Compare public short division() { - return div((short)1, (short)2); - } - - @Compare public short divisionReminder() { - return mod((short)1, (short)2); - } - - @Factory - public static Object[] create() { - return VMTest.create(ShortArithmeticTest.class); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/StaticUse.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/tck/StaticUse.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -/** - * 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.tck; - -class StaticUse { - public static final Object NON_NULL = new Object(); - private StaticUse() { - } - - public void instanceMethod() { - } - - public static int plus(int a, int b) { - return a + b; - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/CRC32Test.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/CRC32Test.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -/** - * 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.vmtest.impl; - -import java.io.UnsupportedEncodingException; -import java.util.zip.CRC32; -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -public class CRC32Test { - - @Compare public long crc1() throws UnsupportedEncodingException { - CRC32 crc = new CRC32(); - crc.update("Hello World!".getBytes("UTF-8")); - return crc.getValue(); - } - - @Factory public static Object[] create() { - return VMTest.create(CRC32Test.class); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/ZipEntryTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/ZipEntryTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -/** - * 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.vmtest.impl; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import org.apidesign.bck2brwsr.emul.zip.FastJar; -import org.testng.annotations.Test; -import static org.testng.Assert.*; - -/** - * - * @author Jaroslav Tulach - */ -@GenerateZip(name = "five.zip", contents = { - "1.txt", "one", - "2.txt", "duo", - "3.txt", "three", - "4.txt", "four", - "5.txt", "five" -}) -public class ZipEntryTest { - @Test - public void readEntriesEffectively() throws IOException { - InputStream is = ZipEntryTest.class.getResourceAsStream("five.zip"); - byte[] arr = new byte[is.available()]; - int len = is.read(arr); - assertEquals(len, arr.length, "Read fully"); - - FastJar fj = new FastJar(arr); - FastJar.Entry[] entrs = fj.list(); - - assertEquals(5, entrs.length, "Five entries"); - - for (int i = 1; i <= 5; i++) { - FastJar.Entry en = entrs[i - 1]; - assertEquals(en.name, i + ".txt"); -// assertEquals(cis.cnt, 0, "Content of the file should be skipped, not read"); - } - - assertContent("three", fj.getInputStream(entrs[3 - 1]), "read OK"); - assertContent("five", fj.getInputStream(entrs[5 - 1]), "read OK"); - } - - private static void assertContent(String exp, InputStream is, String msg) throws IOException { - byte[] arr = new byte[512]; - int len = is.read(arr); - String s = new String(arr, 0, len); - assertEquals(exp, s, msg); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/ZipFileTest.java --- a/rt/vmtest/src/test/java/org/apidesign/bck2brwsr/vmtest/impl/ZipFileTest.java Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -/** - * 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.vmtest.impl; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Objects; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import org.apidesign.bck2brwsr.core.JavaScriptBody; -import org.apidesign.bck2brwsr.vmtest.BrwsrTest; -import org.apidesign.bck2brwsr.vmtest.Compare; -import org.apidesign.bck2brwsr.vmtest.Http; -import org.apidesign.bck2brwsr.vmtest.VMTest; -import org.testng.annotations.Factory; - -/** - * - * @author Jaroslav Tulach - */ -@GenerateZip(name = "readAnEntry.zip", contents = { - "my/main/file.txt", "Hello World!" -}) -public class ZipFileTest { - - @Compare public String readAnEntry() throws IOException { - InputStream is = ZipFileTest.class.getResourceAsStream("readAnEntry.zip"); - ZipInputStream zip = new ZipInputStream(is); - ZipEntry entry = zip.getNextEntry(); - assertEquals(entry.getName(), "my/main/file.txt", "Correct entry"); - - byte[] arr = new byte[4096]; - int len = zip.read(arr); - - assertEquals(zip.getNextEntry(), null, "No next entry"); - - final String ret = new String(arr, 0, len, "UTF-8"); - return ret; - } - - @JavaScriptBody(args = { "res", "path" }, body = - "var myvm = bck2brwsr.apply(null, path);\n" - + "var cls = myvm.loadClass('java.lang.String');\n" - + "return cls.getClass__Ljava_lang_Class_2().getResourceAsStream__Ljava_io_InputStream_2Ljava_lang_String_2(res);\n" - ) - private static native Object loadVMResource(String res, String...path); - - @Http({ - @Http.Resource(path = "/readAnEntry.jar", mimeType = "x-application/zip", content = "", resource="readAnEntry.zip") - }) - @BrwsrTest public void canVmLoadResourceFromZip() throws IOException { - Object res = loadVMResource("/my/main/file.txt", "/readAnEntry.jar"); - assert res instanceof InputStream : "Got array of bytes: " + res; - InputStream is = (InputStream)res; - - byte[] arr = new byte[4096]; - int len = is.read(arr); - - final String ret = new String(arr, 0, len, "UTF-8"); - - assertEquals(ret, "Hello World!", "Can read the bytes"); - } - - @GenerateZip(name = "cpattr.zip", contents = { - "META-INF/MANIFEST.MF", "Manifest-Version: 1.0\n" - + "Created-By: hand\n" - + "Class-Path: realJar.jar\n\n\n" - }) - @Http({ - @Http.Resource(path = "/readComplexEntry.jar", mimeType = "x-application/zip", content = "", resource="cpattr.zip"), - @Http.Resource(path = "/realJar.jar", mimeType = "x-application/zip", content = "", resource="readAnEntry.zip"), - }) - @BrwsrTest public void understandsClassPathAttr() throws IOException { - Object res = loadVMResource("/my/main/file.txt", "/readComplexEntry.jar"); - assert res instanceof InputStream : "Got array of bytes: " + res; - InputStream is = (InputStream)res; - - byte[] arr = new byte[4096]; - int len = is.read(arr); - - final String ret = new String(arr, 0, len, "UTF-8"); - - assertEquals(ret, "Hello World!", "Can read the bytes from secondary JAR"); - } - - private static void assertEquals(Object real, Object exp, String msg) { - assert Objects.equals(exp, real) : msg + " exp: " + exp + " real: " + real; - } - - @Factory public static Object[] create() { - return VMTest.create(ZipFileTest.class); - } -} diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/resources/org/apidesign/bck2brwsr/tck/0xfe --- a/rt/vmtest/src/test/resources/org/apidesign/bck2brwsr/tck/0xfe Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -þ \ No newline at end of file diff -r 8264f07b1f46 -r f73c1a0234fb rt/vmtest/src/test/resources/org/apidesign/bck2brwsr/tck/Resources.txt --- a/rt/vmtest/src/test/resources/org/apidesign/bck2brwsr/tck/Resources.txt Wed Feb 27 17:50:47 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -Ahoj