# HG changeset patch # User Jaroslav Tulach # Date 1398863050 -7200 # Node ID ba912ef24b278c2b6e8950be74efe810a70394e5 # Parent e995e8d392407e449ac0556ed749c07e7a11025f# Parent 5171ac3b4232fecda835dfcecb8a1420438cab4f Merging from default branch and resolving conflicts. mvn install -DskipTests passes OK. diff -r e995e8d39240 -r ba912ef24b27 .hgtags --- a/.hgtags Tue Apr 29 15:25:58 2014 +0200 +++ b/.hgtags Wed Apr 30 15:04:10 2014 +0200 @@ -10,3 +10,5 @@ 623816269b75e53fffb4b19960df7040a3c20056 release-0.7 23572dc719bd630817d11eaabdee4565f63ef8e1 release-0.7.1 56abd247f421febd8b2c5e59d666968692e11555 release-0.7.2 +a83e16b8b825399bb21461e578c32d86982e4ed3 release-0.8 +1b30bba2b38db83c7a232039cfd2eaf7e0e25f3f release-0.8.1 diff -r e995e8d39240 -r ba912ef24b27 benchmarks/matrix-multiplication/pom.xml --- a/benchmarks/matrix-multiplication/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/benchmarks/matrix-multiplication/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,12 +4,12 @@ org.apidesign.bck2brwsr matrix.multiplication - 0.8-SNAPSHOT + 0.9-SNAPSHOT jar benchmarks org.apidesign.bck2brwsr - 0.8-SNAPSHOT + 0.9-SNAPSHOT Matrix multiplication @@ -74,7 +74,7 @@ org.apidesign.bck2brwsr emul.mini - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.testng @@ -91,13 +91,13 @@ org.apidesign.bck2brwsr vmtest - 0.8-SNAPSHOT + 0.9-SNAPSHOT test org.apidesign.bck2brwsr launcher.http - 0.8-SNAPSHOT + 0.9-SNAPSHOT test diff -r e995e8d39240 -r ba912ef24b27 benchmarks/pom.xml --- a/benchmarks/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/benchmarks/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,11 +4,11 @@ bck2brwsr org.apidesign - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr benchmarks - 0.8-SNAPSHOT + 0.9-SNAPSHOT pom Performance benchmarks diff -r e995e8d39240 -r ba912ef24b27 dew/nbactions.xml --- a/dew/nbactions.xml Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ - - - - - run - - process-classes - org.codehaus.mojo:exec-maven-plugin:1.2.1:exec - - - - debug - - process-classes - org.codehaus.mojo:exec-maven-plugin:1.2.1:exec - - - -Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=${jpda.address} -classpath %classpath org.apidesign.bck2brwsr.dew.Dew - java - true - - - - profile - - process-classes - org.codehaus.mojo:exec-maven-plugin:1.2.1:exec - - - ${profiler.args} -classpath %classpath org.apidesign.bck2brwsr.dew.Dew - ${profiler.java} - - - diff -r e995e8d39240 -r ba912ef24b27 dew/pom.xml --- a/dew/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,91 +0,0 @@ - - - 4.0.0 - - org.apidesign - bck2brwsr - 0.8-SNAPSHOT - - org.apidesign.bck2brwsr - dew - 0.8-SNAPSHOT - Development Environment for Web - http://maven.apache.org - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.7 - 1.7 - - - - org.codehaus.mojo - exec-maven-plugin - 1.2.1 - - - - exec - - - - - java - - -classpath - - org.apidesign.bck2brwsr.dew.Dew - - - - - org.apache.maven.plugins - maven-deploy-plugin - 2.7 - - true - - - - - - UTF-8 - - - - org.glassfish.grizzly - grizzly-http-server - 2.2.19 - - - ${project.groupId} - vm4brwsr - ${project.version} - - - org.json - json - 20090211 - - - org.testng - testng - test - - - junit - junit - - - - - ${project.groupId} - javaquery.api - ${project.version} - - - diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/java/org/apidesign/bck2brwsr/dew/Compile.java --- a/dew/src/main/java/org/apidesign/bck2brwsr/dew/Compile.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,203 +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.dew; - -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 e995e8d39240 -r ba912ef24b27 dew/src/main/java/org/apidesign/bck2brwsr/dew/Dew.java --- a/dew/src/main/java/org/apidesign/bck2brwsr/dew/Dew.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,138 +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.dew; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.util.List; -import java.util.Locale; -import javax.tools.Diagnostic; -import javax.tools.JavaFileObject; -import org.apidesign.vm4brwsr.Bck2Brwsr; -import org.glassfish.grizzly.http.Method; -import org.glassfish.grizzly.http.server.HttpHandler; -import org.glassfish.grizzly.http.server.HttpServer; -import org.glassfish.grizzly.http.server.Request; -import org.glassfish.grizzly.http.server.Response; -import org.glassfish.grizzly.http.util.HttpStatus; -import org.json.JSONArray; -import org.json.JSONObject; -import org.json.JSONTokener; - -/** - * - * @author phrebejk - */ -final class Dew extends HttpHandler implements Bck2Brwsr.Resources { - private Compile data; - - public static void main(String... args) throws Exception { - DewLauncher l = new DewLauncher(null); - l.addClassLoader(DewLauncher.class.getClassLoader()); - final Dew dew = new Dew(); - HttpServer s = l.initServer(dew); - s.getServerConfiguration().addHttpHandler(dew, "/dew/"); - l.launchServerAndBrwsr(s, "/dew/"); - System.in.read(); - } - - @Override - public void service(Request request, Response response) throws Exception { - - if ( request.getMethod() == Method.POST ) { - InputStream is = request.getInputStream(); - JSONTokener tok = new JSONTokener(new InputStreamReader(is)); - JSONObject obj = new JSONObject(tok); - String tmpHtml = obj.getString("html"); - String tmpJava = obj.getString("java"); - - Compile res = Compile.create(tmpHtml, tmpJava); - List> err = res.getErrors(); - if (err.isEmpty()) { - data = res; - response.getOutputStream().write("[]".getBytes()); - response.setStatus(HttpStatus.OK_200); - } else { - - JSONArray errors = new JSONArray(); - - for (Diagnostic d : err) { - JSONObject e = new JSONObject(); - e.put("col", d.getColumnNumber()); - e.put("line", d.getLineNumber()); - e.put("kind", d.getKind().toString()); - e.put("msg", d.getMessage(Locale.ENGLISH)); - errors.put(e); - } - - errors.write(response.getWriter()); - response.setStatus(HttpStatus.PRECONDITION_FAILED_412); - } - - return; - } - - String r = request.getHttpHandlerPath(); - if (r == null || r.equals("/")) { - r = "index.html"; - } - if (r.equals("/result.html")) { - response.setContentType("text/html"); - if (data != null) { - response.getOutputBuffer().write(data.getHtml()); - } - response.setStatus(HttpStatus.OK_200); - return; - } - - if (r.startsWith("/")) { - r = r.substring(1); - } - - if (r.endsWith(".html") || r.endsWith(".xhtml")) { - response.setContentType("text/html"); - } - OutputStream os = response.getOutputStream(); - try (InputStream is = Dew.class.getResourceAsStream(r) ) { - copyStream(is, os, request.getRequestURL().toString() ); - } catch (IOException ex) { - response.setDetailMessage(ex.getLocalizedMessage()); - response.setError(); - response.setStatus(404); - } - } - - static void copyStream(InputStream is, OutputStream os, String baseURL) throws IOException { - for (;;) { - int ch = is.read(); - if (ch == -1) { - break; - } - os.write(ch); - } - } - - @Override - public InputStream get(String r) throws IOException { - byte[] arr = data == null ? null : data.get(r); - return arr == null ? null : new ByteArrayInputStream(arr); - } -} diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/java/org/apidesign/bck2brwsr/dew/DewLauncher.java --- a/dew/src/main/java/org/apidesign/bck2brwsr/dew/DewLauncher.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,203 +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.dew; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Writer; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; -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; - -/** - * Lightweight server to launch dew - the Development Environment for Web. - */ -final class DewLauncher { - private static final Logger LOG = Logger.getLogger(DewLauncher.class.getName()); - private Set loaders = new LinkedHashSet<>(); - private Set xRes = new LinkedHashSet<>(); - private final Res resources = new Res(); - private final String cmd; - - public DewLauncher(String cmd) { - this.cmd = cmd; - } - - public void addClassLoader(ClassLoader url) { - this.loaders.add(url); - } - - final HttpServer initServer(Bck2Brwsr.Resources... extraResources) throws IOException { - xRes.addAll(Arrays.asList(extraResources)); - - HttpServer s = HttpServer.createSimpleServer(".", new PortRange(8080, 65535)); - - final ServerConfiguration conf = s.getServerConfiguration(); - conf.addHttpHandler(new VM(resources), "/bck2brwsr.js"); - conf.addHttpHandler(new VMInit(), "/vm.js"); - conf.addHttpHandler(new Classes(resources), "/classes/"); - return s; - } - - final 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 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(); - } - } - for (Bck2Brwsr.Resources r : xRes) { - InputStream is = r.get(resource); - if (is != null) { - return is; - } - } - throw new IOException("Can't find " + resource); - } - } - - private static class VM extends HttpHandler { - private final String bck2brwsr; - - public VM(Res loader) throws IOException { - StringBuilder sb = new StringBuilder(); - Bck2Brwsr.generate(sb, loader); - 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 VMInit extends HttpHandler { - public VMInit() { - } - - @Override - public void service(Request request, Response response) throws Exception { - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/javascript"); - response.getWriter().append( - "function ldCls(res) {\n" - + " var request = new XMLHttpRequest();\n" - + " request.open('GET', '/classes/' + res, false);\n" - + " request.send();\n" - + " var arr = eval('(' + request.responseText + ')');\n" - + " return arr;\n" - + "}\n" - + "var vm = new bck2brwsr(ldCls);\n"); - } - } - - 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.setError(); - response.setDetailMessage(ex.getMessage()); - } - } - } -} diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/css/app.css --- a/dew/src/main/resources/org/apidesign/bck2brwsr/dew/css/app.css Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/* app css stylesheet */ -.code-editor, .mono-font, .CodeMirror { - font-family: "Inconsolata","Monaco","Consolas","Andale Mono","Bitstream Vera Sans Mono","Courier New",Courier,monospace; - font-size: 13px; - line-height: 15px; -} - -.CodeMirror { - border: 1px solid #d9edf7; - height: 300px; -} - -.CodeMirror-scroll { - overflow-y: auto; - overflow-x: auto; -} - -.error-hover:hover { - text-decoration: underline; - cursor: pointer; -} - -.ic-html5 { - display: inline-block; - height: 20px; - width: 20px; - vertical-align: text-bottom; - background-repeat: no-repeat; - background-image: url("../img/html5.png"); -} - -.ic-java { - display: inline-block; - height: 20px; - width: 20px; - vertical-align: text-bottom; - background-repeat: no-repeat; - background-image: url("../img/java.png"); - -} - -.issues { - width: 16px; -} - -.issue { - height: 16px; - width: 16px; - vertical-align: middle; - background-repeat: no-repeat; - background-image: url("../img/error.png"); - /* color: #822; */ -} - diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/css/bootstrap-combined.min.css --- a/dew/src/main/resources/org/apidesign/bck2brwsr/dew/css/bootstrap-combined.min.css Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -/*! - * Bootstrap v2.2.2 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover{color:#808080}.text-warning{color:#c09853}a.text-warning:hover{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover{color:#2d6987}.text-success{color:#468847}a.text-success:hover{color:#356635}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:25px}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{margin-bottom:5px;font-size:0;white-space:nowrap}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover td,.table-hover tbody tr:hover th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success td{background-color:#dff0d8}.table tbody tr.error td{background-color:#f2dede}.table tbody tr.warning td{background-color:#fcf8e3}.table tbody tr.info td{background-color:#d9edf7}.table-hover tbody tr.success:hover td{background-color:#d0e9c6}.table-hover tbody tr.error:hover td{background-color:#ebcccc}.table-hover tbody tr.warning:hover td{background-color:#faf2cc}.table-hover tbody tr.info:hover td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu li>a:hover,.dropdown-menu li>a:focus,.dropdown-submenu:hover>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .disabled>a,.dropdown-menu .disabled>a:hover{color:#999}.dropdown-menu .disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #bbb;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#a2a2a2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn{border-color:#c5c5c5;border-color:rgba(0,0,0,0.15) rgba(0,0,0,0.15) rgba(0,0,0,0.25)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-mini .caret,.btn-small .caret,.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret{border-top-color:#555;border-bottom-color:#555}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{margin-top:-3px}.tooltip.right{margin-left:3px}.tooltip.bottom{margin-top:3px}.tooltip.left{margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;width:236px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media .pull-left{margin-right:10px}.media .pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} -/*! - * Bootstrap Responsive v2.2.2 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */@-ms-viewport{width:device-width}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/img/error.png Binary file dew/src/main/resources/org/apidesign/bck2brwsr/dew/img/error.png has changed diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/img/glyphicons-halflings-white.png Binary file dew/src/main/resources/org/apidesign/bck2brwsr/dew/img/glyphicons-halflings-white.png has changed diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/img/glyphicons-halflings.png Binary file dew/src/main/resources/org/apidesign/bck2brwsr/dew/img/glyphicons-halflings.png has changed diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/img/html5.png Binary file dew/src/main/resources/org/apidesign/bck2brwsr/dew/img/html5.png has changed diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/img/java.png Binary file dew/src/main/resources/org/apidesign/bck2brwsr/dew/img/java.png has changed diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/index.html --- a/dew/src/main/resources/org/apidesign/bck2brwsr/dew/index.html Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ - - - - - Back2Browser - DEW - - - - - - - - - - - -
- -
 
- -
-
- - -
HTML5
-
- -
- {{doc.modelError.toString()}} -
-
-
- -
- - -
Java
-
- -
- {{doc.modelError.toString()}} -
-
-
- -
- - - - - - - - -
{{e.line}}:{{e.col}}{{e.msg}}
- - -
 
- - - - - - - -
- - - - - - - - diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/js/angular/angular.min.js --- a/dew/src/main/resources/org/apidesign/bck2brwsr/dew/js/angular/angular.min.js Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,159 +0,0 @@ -/* - AngularJS v1.0.3 - (c) 2010-2012 Google, Inc. http://angularjs.org - License: MIT -*/ -(function(U,ca,p){'use strict';function m(b,a,c){var d;if(b)if(N(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==m)b.forEach(a,c);else if(L(b)&&wa(b.length))for(d=0;d=0&&b.splice(c,1);return a}function V(b,a){if(oa(b)||b&&b.$evalAsync&&b.$watch)throw B("Can't copy Window or Scope");if(a){if(b=== -a)throw B("Can't copy equivalent objects or arrays");if(J(b)){for(;a.length;)a.pop();for(var c=0;c2?ia.call(arguments,2):[];return N(a)&&!(a instanceof RegExp)?c.length? -function(){return arguments.length?a.apply(b,c.concat(ia.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function ic(b,a){var c=a;/^\$+/.test(b)?c=p:oa(a)?c="$WINDOW":a&&ca===a?c="$DOCUMENT":a&&a.$evalAsync&&a.$watch&&(c="$SCOPE");return c}function da(b,a){return JSON.stringify(b,ic,a?" ":null)}function nb(b){return F(b)?JSON.parse(b):b}function Wa(b){b&&b.length!==0?(b=E(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1; -return b}function pa(b){b=u(b).clone();try{b.html("")}catch(a){}return u("
").append(b).html().match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+E(b)})}function Xa(b){var a={},c,d;m((b||"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]),a[d]=v(c[1])?decodeURIComponent(c[1]):!0)});return a}function ob(b){var a=[];m(b,function(b,d){a.push(Ya(d,!0)+(b===!0?"":"="+Ya(b,!0)))});return a.length?a.join("&"):""}function Za(b){return Ya(b,!0).replace(/%26/gi,"&").replace(/%3D/gi, -"=").replace(/%2B/gi,"+")}function Ya(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(a?null:/%20/g,"+")}function jc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,i=["ng:app","ng-app","x-ng-app","data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;m(i,function(a){i[a]=!0;c(ca.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(m(b.querySelectorAll("."+a),c),m(b.querySelectorAll("."+a+"\\:"),c),m(b.querySelectorAll("["+ -a+"]"),c))});m(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):m(a.attributes,function(b){if(!e&&i[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])}function pb(b,a){b=u(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");var c=qb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,i){a.$apply(function(){b.data("$injector",i);c(b)(a)})}]);return c}function $a(b,a){a=a||"_";return b.replace(kc, -function(b,d){return(d?a:"")+b.toLowerCase()})}function qa(b,a,c){if(!b)throw new B("Argument '"+(a||"?")+"' is "+(c||"required"));return b}function ra(b,a,c){c&&J(b)&&(b=b[b.length-1]);qa(N(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function lc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c, -d,e){return function(){b[e||"push"]([c,d,arguments]);return j}}if(!e)throw B("No module: "+d);var b=[],c=[],k=a("$injector","invoke"),j={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:k,run:function(a){c.push(a); -return this}};g&&k(g);return j})}})}function rb(b){return b.replace(mc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(nc,"Moz$1")}function ab(b,a){function c(){var e;for(var b=[this],c=a,i,f,h,k,j,l;b.length;){i=b.shift();f=0;for(h=i.length;f 
"+b;a.removeChild(a.firstChild);bb(this,a.childNodes);this.remove()}else bb(this,b)}function cb(b){return b.cloneNode(!0)}function sa(b){sb(b);for(var a=0,b=b.childNodes||[];a-1}function vb(b,a){a&&m(a.split(" "),function(a){b.className= -R((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+R(a)+" "," "))})}function wb(b,a){a&&m(a.split(" "),function(a){if(!Ca(b,a))b.className=R(b.className+" "+R(a))})}function bb(b,a){if(a)for(var a=!a.nodeName&&v(a.length)&&!oa(a)?a:[a],c=0;c4096&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!"),W.length>20&&c.warn("Cookie '"+a+"' possibly not set or overflowed because too many cookies were already set ("+W.length+ -" > 20 )")}else{if(h.cookie!==y){y=h.cookie;d=y.split("; ");W={};for(f=0;f0&&(W[unescape(e.substring(0,k))]=unescape(e.substring(k+1)))}return W}};f.defer=function(a,b){var c;n++;c=l(function(){delete r[c];e(a)},b||0);r[c]=!0;return c};f.defer.cancel=function(a){return r[a]?(delete r[a],o(a),e(D),!0):!1}}function wc(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new vc(b,d,a,c)}]}function xc(){this.$get=function(){function b(b, -d){function e(a){if(a!=l){if(o){if(o==a)o=a.n}else o=a;g(a.n,a.p);g(a,l);l=a;l.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw B("cacheId "+b+" taken");var i=0,f=x({},d,{id:b}),h={},k=d&&d.capacity||Number.MAX_VALUE,j={},l=null,o=null;return a[b]={put:function(a,b){var c=j[a]||(j[a]={key:a});e(c);t(b)||(a in h||i++,h[a]=b,i>k&&this.remove(o.key))},get:function(a){var b=j[a];if(b)return e(b),h[a]},remove:function(a){var b=j[a];if(b){if(b==l)l=b.p;if(b==o)o=b.n;g(b.n,b.p);delete j[a]; -delete h[a];i--}},removeAll:function(){h={};i=0;j={};l=o=null},destroy:function(){j=f=h=null;delete a[b]},info:function(){return x({},f,{size:i})}}}var a={};b.info=function(){var b={};m(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function yc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Bb(b){var a={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: "; -this.directive=function f(d,e){F(d)?(qa(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];m(a[d],function(a){try{var f=b.invoke(a);if(N(f))f={compile:I(f)};else if(!f.compile&&f.link)f.compile=I(f.link);f.priority=f.priority||0;f.name=f.name||d;f.require=f.require||f.controller&&f.name;f.restrict=f.restrict||"A";e.push(f)}catch(k){c(k)}});return e}])),a[d].push(e)):m(d,mb(f));return this};this.$get=["$injector","$interpolate","$exceptionHandler", -"$http","$templateCache","$parse","$controller","$rootScope",function(b,h,k,j,l,o,r,n){function w(a,b,c){a instanceof u||(a=u(a));m(a,function(b,c){b.nodeType==3&&(a[c]=u(b).wrap("").parent()[0])});var d=s(a,b,a,c);return function(b,c){qa(b,"scope");var e=c?ua.clone.call(a):a;e.data("$scope",b);q(e,"ng-scope");c&&c(e,b);d&&d(b,e,e);return e}}function q(a,b){try{a.addClass(b)}catch(c){}}function s(a,b,c,d){function e(a,c,d,k){for(var g,h,j,n,o,l=0,r=0,q=f.length;lz.priority)break;if(Y=z.scope)M("isolated scope",C,z,y),L(Y)&&(q(y,"ng-isolate-scope"),C=z),q(y,"ng-scope"),s=s||z;H=z.name;if(Y=z.controller)t=t||{},M("'"+H+"' controller",t[H],z,y),t[H]=z;if(Y=z.transclude)M("transclusion",D,z,y),D=z,n=z.priority,Y=="element"?(X=u(b),y=c.$$element=u("<\!-- "+H+": "+c[H]+" --\>"),b=y[0],Ga(e,u(X[0]),b),v=w(X,d,n)):(X=u(cb(b)).contents(),y.html(""),v=w(X,d));if(Y=z.template)if(M("template",A,z,y),A=z,Y=Ha(Y),z.replace){X=u("
"+R(Y)+"
").contents(); -b=X[0];if(X.length!=1||b.nodeType!==1)throw new B(g+Y);Ga(e,y,b);H={$attr:{}};a=a.concat(O(b,a.splice(E+1,a.length-(E+1)),H));K(c,H);G=a.length}else y.html(Y);if(z.templateUrl)M("template",A,z,y),A=z,j=W(a.splice(E,a.length-E),j,y,c,e,z.replace,v),G=a.length;else if(z.compile)try{x=z.compile(y,c,v),N(x)?f(null,x):x&&f(x.pre,x.post)}catch(I){k(I,pa(y))}if(z.terminal)j.terminal=!0,n=Math.max(n,z.priority)}j.scope=s&&s.scope;j.transclude=D&&v;return j}function A(d,e,g,h){var j=!1;if(a.hasOwnProperty(e))for(var n, -e=b.get(e+c),o=0,l=e.length;on.priority)&&n.restrict.indexOf(g)!=-1)d.push(n),j=!0}catch(r){k(r)}return j}function K(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;m(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});m(b,function(b,f){f=="class"?(q(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):f=="style"?e.attr("style",e.attr("style")+";"+b):f.charAt(0)!="$"&&!a.hasOwnProperty(f)&&(a[f]=b,d[f]=c[f])})}function W(a,b,c,d,e, -f,k){var h=[],n,o,r=c[0],q=a.shift(),w=x({},q,{controller:null,templateUrl:null,transclude:null,scope:null});c.html("");j.get(q.templateUrl,{cache:l}).success(function(j){var l,q,j=Ha(j);if(f){q=u("
"+R(j)+"
").contents();l=q[0];if(q.length!=1||l.nodeType!==1)throw new B(g+j);j={$attr:{}};Ga(e,c,l);O(l,a,j);K(d,j)}else l=r,c.html(j);a.unshift(w);n=C(a,c,d,k);for(o=s(c.contents(),k);h.length;){var ba=h.pop(),j=h.pop();q=h.pop();var y=h.pop(),m=l;q!==r&&(m=cb(l),Ga(j,u(q),m));n(function(){b(o, -y,m,e,ba)},y,m,e,ba)}h=null}).error(function(a,b,c,d){throw B("Failed to load template: "+d.url);});return function(a,c,d,e,f){h?(h.push(c),h.push(d),h.push(e),h.push(f)):n(function(){b(o,c,d,e,f)},c,d,e,f)}}function y(a,b){return b.priority-a.priority}function M(a,b,c,d){if(b)throw B("Multiple directives ["+b.name+", "+c.name+"] asking for "+a+" on: "+pa(d));}function H(a,b){var c=h(b,!0);c&&a.push({priority:0,compile:I(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);q(d.data("$binding", -e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function X(a,b,c,d){var e=h(c,!0);e&&b.push({priority:100,compile:I(function(a,b,c){b=c.$$observers||(c.$$observers={});d==="class"&&(e=h(c[d],!0));c[d]=p;(b[d]||(b[d]=[])).$$inter=!0;(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,function(a){c.$set(d,a)})})})}function Ga(a,b,c){var d=b[0],e=d.parentNode,f,g;if(a){f=0;for(g=a.length;f0){var e=M[0],f=e.text;if(f==a||f==b||f==c||f==d||!a&&!b&&!c&&!d)return e}return!1}function f(b,c,d,f){return(b=i(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),M.shift(),b):!1}function h(a){f(a)||e("is unexpected, expecting ["+a+"]",i())}function k(a,b){return function(c,d){return a(c,d,b)}}function j(a,b,c){return function(d,e){return b(d,e,a,c)}}function l(){for(var a=[];;)if(M.length>0&&!i("}",")",";","]")&&a.push(v()),!f(";"))return a.length==1?a[0]:function(b,c){for(var d, -e=0;e","<=",">="))a=j(a,b.fn,q());return a}function s(){for(var a=m(),b;b=f("*","/","%");)a=j(a,b.fn,m());return a}function m(){var a;return f("+")?C():(a=f("-"))?j(W,a.fn,m()):(a=f("!"))?k(a.fn,m()):C()}function C(){var a;if(f("("))a=v(),h(")");else if(f("["))a=A();else if(f("{"))a=K();else{var b=f();(a=b.fn)||e("not a primary expression",b)}for(var c;b=f("(","[",".");)b.text==="("?(a=u(a,c),c=null):b.text==="["?(c=a,a=ea(a)):b.text==="."?(c=a,a=t(a)):e("IMPOSSIBLE"); -return a}function A(){var a=[];if(g().text!="]"){do a.push(H());while(f(","))}h("]");return function(b,c){for(var d=[],e=0;e1;d++){var e=a.shift(),g= -b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]=c}function fb(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,i=0;i7),hasEvent:function(c){if(c=="input"&&aa==9)return!1;if(t(a[c])){var e=b.document.createElement("div");a[c]="on"+c in e}return a[c]},csp:!1}}]}function Uc(){this.$get=I(U)}function Mb(b){var a={},c,d,e;if(!b)return a;m(b.split("\n"),function(b){e=b.indexOf(":");c=E(R(b.substr(0, -e)));d=R(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function Nb(b){var a=L(b)?b:p;return function(c){a||(a=Mb(b));return c?a[E(c)]||null:a}}function Ob(b,a,c){if(N(c))return c(b,a);m(c,function(c){b=c(b,a)});return b}function Vc(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d=this.defaults={transformResponse:[function(d){F(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=nb(d,!0)));return d}],transformRequest:[function(a){return L(a)&&Sa.apply(a)!=="[object File]"?da(a):a}], -headers:{common:{Accept:"application/json, text/plain, */*","X-Requested-With":"XMLHttpRequest"},post:{"Content-Type":"application/json;charset=utf-8"},put:{"Content-Type":"application/json;charset=utf-8"}}},e=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,h,k,j){function l(a){function c(a){var b=x({},a,{data:Ob(a.data,a.headers,f)});return 200<=a.status&&a.status<300?b:k.reject(b)}a.method=la(a.method);var e=a.transformRequest|| -d.transformRequest,f=a.transformResponse||d.transformResponse,h=d.headers,h=x({"X-XSRF-TOKEN":b.cookies()["XSRF-TOKEN"]},h.common,h[E(a.method)],a.headers),e=Ob(a.data,Nb(h),e),g;t(a.data)&&delete h["Content-Type"];g=o(a,e,h);g=g.then(c,c);m(w,function(a){g=a(g)});g.success=function(b){g.then(function(c){b(c.data,c.status,c.headers,a)});return g};g.error=function(b){g.then(null,function(c){b(c.data,c.status,c.headers,a)});return g};return g}function o(b,c,d){function e(a,b,c){m&&(200<=a&&a<300?m.put(w, -[a,b,Mb(c)]):m.remove(w));f(b,a,c);h.$apply()}function f(a,c,d){c=Math.max(c,0);(200<=c&&c<300?j.resolve:j.reject)({data:a,status:c,headers:Nb(d),config:b})}function i(){var a=za(l.pendingRequests,b);a!==-1&&l.pendingRequests.splice(a,1)}var j=k.defer(),o=j.promise,m,p,w=r(b.url,b.params);l.pendingRequests.push(b);o.then(i,i);b.cache&&b.method=="GET"&&(m=L(b.cache)?b.cache:n);if(m)if(p=m.get(w))if(p.then)return p.then(i,i),p;else J(p)?f(p[1],p[0],V(p[2])):f(p,200,{});else m.put(w,o);p||a(b.method, -w,c,e,d,b.timeout,b.withCredentials);return o}function r(a,b){if(!b)return a;var c=[];ec(b,function(a,b){a==null||a==p||(L(a)&&(a=da(a)),c.push(encodeURIComponent(b)+"="+encodeURIComponent(a)))});return a+(a.indexOf("?")==-1?"?":"&")+c.join("&")}var n=c("$http"),w=[];m(e,function(a){w.push(F(a)?j.get(a):j.invoke(a))});l.pendingRequests=[];(function(a){m(arguments,function(a){l[a]=function(b,c){return l(x(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){m(arguments,function(a){l[a]= -function(b,c,d){return l(x(d||{},{method:a,url:b,data:c}))}})})("post","put");l.defaults=d;return l}]}function Wc(){this.$get=["$browser","$window","$document",function(b,a,c){return Xc(b,Yc,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(":",""))}]}function Xc(b,a,c,d,e,g){function i(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;aa?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror= -d;e.body.appendChild(c)}return function(e,h,k,j,l,o,r){function n(a,c,d,e){c=(h.match(Fb)||["",g])[1]=="file"?d?200:404:c;a(c==1223?204:c,d,e);b.$$completeOutstandingRequest(D)}b.$$incOutstandingRequestCount();h=h||b.url();if(E(e)=="jsonp"){var p="_"+(d.counter++).toString(36);d[p]=function(a){d[p].data=a};i(h.replace("JSON_CALLBACK","angular.callbacks."+p),function(){d[p].data?n(j,200,d[p].data):n(j,-2);delete d[p]})}else{var q=new a;q.open(e,h,!0);m(l,function(a,b){a&&q.setRequestHeader(b,a)}); -var s;q.onreadystatechange=function(){q.readyState==4&&n(j,s||q.status,q.responseText,q.getAllResponseHeaders())};if(r)q.withCredentials=!0;q.send(k||"");o>0&&c(function(){s=-1;q.abort()},o)}}}function Zc(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"}, -DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a", -shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function $c(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,h){var k=c.defer(),j=k.promise,l=v(h)&&!h,f=a.defer(function(){try{k.resolve(e())}catch(a){k.reject(a),d(a)}l||b.$apply()},f),h=function(){delete g[j.$$timeoutId]};j.$$timeoutId=f;g[f]=k;j.then(h,h);return j}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)): -!1};return e}]}function Pb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",Qb);a("date",Rb);a("filter",ad);a("json",bd);a("limitTo",cd);a("lowercase",dd);a("number",Sb);a("orderBy",Tb);a("uppercase",ed)}function ad(){return function(b,a){if(!(b instanceof Array))return b;var c=[];c.check=function(a){for(var b=0;b-1;case "object":for(var c in a)if(c.charAt(0)!=="$"&&d(a[c],b))return!0;return!1;case "array":for(c=0;ce+1?i="0":(f=i,k=!0)}if(!k){i=(i.split(Vb)[1]||"").length;t(e)&&(e=Math.min(Math.max(a.minFrac,i),a.maxFrac));var i=Math.pow(10,e),b=Math.round(b*i)/i,b=(""+b).split(Vb),i=b[0],b=b[1]||"",k=0,j=a.lgSize,l=a.gSize;if(i.length>=j+l)for(var k=i.length-j,o=0;o0||e>-c)e+=c;e===0&&c==-12&&(e=12);return ib(e,a,d)}}function La(b,a){return function(c,d){var e=c["get"+b](),g=la(a?"SHORT"+b:b);return d[g][e]}}function Rb(b){function a(a){var b; -if(b=a.match(c)){var a=new Date(0),g=0,i=0;b[9]&&(g=G(b[9]+b[10]),i=G(b[9]+b[11]));a.setUTCFullYear(G(b[1]),G(b[2])-1,G(b[3]));a.setUTCHours(G(b[4]||0)-g,G(b[5]||0)-i,G(b[6]||0),G(b[7]||0))}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e){var g="",i=[],f,h,e=e||"mediumDate",e=b.DATETIME_FORMATS[e]||e;F(c)&&(c=fd.test(c)?G(c):a(c));wa(c)&&(c=new Date(c));if(!na(c))return c;for(;e;)(h=gd.exec(e))?(i=i.concat(ia.call(h, -1)),e=i.pop()):(i.push(e),e=null);m(i,function(a){f=hd[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function bd(){return function(b){return da(b,!0)}}function cd(){return function(b,a){if(!(b instanceof Array))return b;var a=G(a),c=[],d,e;if(!b||!(b instanceof Array))return c;a>b.length?a=b.length:a<-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;dl?(d.$setValidity("maxlength", -!1),p):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(c);d.$formatters.push(c)}}function jb(b,a){b="ngClass"+b;return S(function(c,d,e){function g(b,d){if(a===!0||c.$index%2===a)d&&b!==d&&i(d),f(b)}function i(a){L(a)&&!J(a)&&(a=Ta(a,function(a,b){if(a)return b}));d.removeClass(J(a)?a.join(" "):a)}function f(a){L(a)&&!J(a)&&(a=Ta(a,function(a,b){if(a)return b}));a&&d.addClass(J(a)?a.join(" "):a)}c.$watch(e[b],g,!0);e.$observe("class",function(){var a=c.$eval(e[b]);g(a,a)});b!=="ngClass"&&c.$watch("$index", -function(d,g){var j=d%2;j!==g%2&&(j==a?f(c.$eval(e[b])):i(c.$eval(e[b])))})})}var E=function(b){return F(b)?b.toLowerCase():b},la=function(b){return F(b)?b.toUpperCase():b},B=U.Error,aa=G((/msie (\d+)/.exec(E(navigator.userAgent))||[])[1]),u,ja,ia=[].slice,Ra=[].push,Sa=Object.prototype.toString,Yb=U.angular||(U.angular={}),ta,Cb,Z=["0","0","0"];D.$inject=[];ma.$inject=[];Cb=aa<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?la(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName? -b.nodeName:b[0].nodeName};var kc=/[A-Z]/g,id={full:"1.0.3",major:1,minor:0,dot:3,codeName:"bouncy-thunder"},Ba=Q.cache={},Aa=Q.expando="ng-"+(new Date).getTime(),oc=1,Zb=U.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},db=U.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},mc=/([\:\-\_]+(.))/g,nc=/^moz([A-Z])/,ua=Q.prototype={ready:function(b){function a(){c||(c=!0,b())} -var c=!1;this.bind("DOMContentLoaded",a);Q(U).bind("load",a)},toString:function(){var b=[];m(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?u(this[b]):u(this[this.length+b])},length:0,push:Ra,sort:[].sort,splice:[].splice},Ea={};m("multiple,selected,checked,disabled,readOnly,required".split(","),function(b){Ea[E(b)]=b});var zb={};m("input,select,option,textarea,button,form".split(","),function(b){zb[la(b)]=!0});m({data:ub,inheritedData:Da,scope:function(b){return Da(b, -"$scope")},controller:xb,injector:function(b){return Da(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Ca,css:function(b,a,c){a=rb(a);if(v(c))b.style[a]=c;else{var d;aa<=8&&(d=b.currentStyle&&b.currentStyle[a],d===""&&(d="auto"));d=d||b.style[a];aa<=8&&(d=d===""?p:d);return d}},attr:function(b,a,c){var d=E(a);if(Ea[d])if(v(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||D).specified?d:p;else if(v(c))b.setAttribute(a, -c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?p:b},prop:function(b,a,c){if(v(c))b[a]=c;else return b[a]},text:x(aa<9?function(b,a){if(b.nodeType==1){if(t(a))return b.innerText;b.innerText=a}else{if(t(a))return b.nodeValue;b.nodeValue=a}}:function(b,a){if(t(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(t(a))return b.value;b.value=a},html:function(b,a){if(t(a))return b.innerHTML;for(var c=0,d=b.childNodes;c":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a, -c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Lc={n:"\n",f:"\u000c",r:"\r",t:"\t",v:"\u000b","'":"'",'"':'"'},hb={},Yc=U.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw new B("This browser does not support XMLHttpRequest."); -};Pb.$inject=["$provide"];Qb.$inject=["$locale"];Sb.$inject=["$locale"];var Vb=".",hd={yyyy:P("FullYear",4),yy:P("FullYear",2,0,!0),y:P("FullYear",1),MMMM:La("Month"),MMM:La("Month",!0),MM:P("Month",2,1),M:P("Month",1,1),dd:P("Date",2),d:P("Date",1),HH:P("Hours",2),H:P("Hours",1),hh:P("Hours",2,-12),h:P("Hours",1,-12),mm:P("Minutes",2),m:P("Minutes",1),ss:P("Seconds",2),s:P("Seconds",1),EEEE:La("Day"),EEE:La("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=a.getTimezoneOffset(); -return ib(a/60,2)+ib(Math.abs(a%60),2)}},gd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,fd=/^\d+$/;Rb.$inject=["$locale"];var dd=I(E),ed=I(la);Tb.$inject=["$parse"];var jd=I({restrict:"E",compile:function(a,c){c.href||c.$set("href","");return function(a,c){c.bind("click",function(a){if(!c.attr("href"))return a.preventDefault(),!1})}}}),kb={};m(Ea,function(a,c){var d=fa("ng-"+c);kb[d]=function(){return{priority:100,compile:function(){return function(a,g,i){a.$watch(i[d], -function(a){i.$set(c,!!a)})}}}}});m(["src","href"],function(a){var c=fa("ng-"+a);kb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),aa&&e.prop(a,c))})}}}});var Oa={$addControl:D,$removeControl:D,$setValidity:D,$setDirty:D};Wb.$inject=["$element","$attrs","$scope"];var Ra=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E",controller:Wb,compile:function(){return{pre:function(a,d,i,f){if(!i.action){var h=function(a){a.preventDefault? -a.preventDefault():a.returnValue=!1};Zb(d[0],"submit",h);d.bind("$destroy",function(){c(function(){db(d[0],"submit",h)},0,!1)})}var k=d.parent().controller("form"),j=i.name||i.ngForm;j&&(a[j]=f);k&&d.bind("$destroy",function(){k.$removeControl(f);j&&(a[j]=p);x(f,Oa)})}}}};return a?x(V(d),{restrict:"EAC"}):d}]},kd=Ra(),ld=Ra(!0),md=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,nd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,od=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/, -ac={text:Qa,number:function(a,c,d,e,g,i){Qa(a,c,d,e,g,i);e.$parsers.push(function(a){var c=T(a);return c||od.test(a)?(e.$setValidity("number",!0),a===""?null:c?a:parseFloat(a)):(e.$setValidity("number",!1),p)});e.$formatters.push(function(a){return T(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!T(a)&&ah?(e.$setValidity("max", -!1),p):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return T(a)||wa(a)?(e.$setValidity("number",!0),a):(e.$setValidity("number",!1),p)})},url:function(a,c,d,e,g,i){Qa(a,c,d,e,g,i);a=function(a){return T(a)||md.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,i){Qa(a,c,d,e,g,i);a=function(a){return T(a)||nd.test(a)?(e.$setValidity("email",!0),a):(e.$setValidity("email", -!1),p)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){t(d.name)&&c.attr("name",xa());c.bind("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,i=d.ngFalseValue;F(g)||(g=!0);F(i)||(i=!1);c.bind("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a=== -g});e.$parsers.push(function(a){return a?g:i})},hidden:D,button:D,submit:D,reset:D},bc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,i){i&&(ac[E(g.type)]||ac.text)(d,e,g,i,c,a)}}}],Na="ng-valid",Ma="ng-invalid",Pa="ng-pristine",Xb="ng-dirty",pd=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,g){function i(a,c){c=c?"-"+$a(c,"-"):"";e.removeClass((a?Ma:Na)+c).addClass((a?Na:Ma)+c)}this.$modelValue=this.$viewValue=Number.NaN; -this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var f=g(d.ngModel),h=f.assign;if(!h)throw B(Db+d.ngModel+" ("+pa(e)+")");this.$render=D;var k=e.inheritedData("$formController")||Oa,j=0,l=this.$error={};e.addClass(Pa);i(!0);this.$setValidity=function(a,c){if(l[a]!==!c){if(c){if(l[a]&&j--,!j)i(!0),this.$valid=!0,this.$invalid=!1}else i(!1),this.$invalid=!0,this.$valid=!1,j++;l[a]=!c;i(c,a);k.$setValidity(a, -c,this)}};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=!1,e.removeClass(Pa).addClass(Xb),k.$setDirty();m(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,h(a,d),m(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};var o=this;a.$watch(function(){var c=f(a);if(o.$modelValue!==c){var d=o.$formatters,e=d.length;for(o.$modelValue=c;e--;)c=d[e](c);if(o.$viewValue!==c)o.$viewValue=c,o.$render()}})}],qd=function(){return{require:["ngModel", -"^?form"],controller:pd,link:function(a,c,d,e){var g=e[0],i=e[1]||Oa;i.$addControl(g);c.bind("$destroy",function(){i.$removeControl(g)})}}},rd=I({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),cc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&(T(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g); -d.$observe("required",function(){g(e.$viewValue)})}}}},sd=function(){return{require:"ngModel",link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&m(a.split(g),function(a){a&&c.push(R(a))});return c});e.$formatters.push(function(a){return J(a)?a.join(", "):p})}}},td=/^(true|false|\d+)$/,ud=function(){return{priority:100,compile:function(a,c){return td.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a, -c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},vd=S(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==p?"":a)})}),wd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],xd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe, -function(a){c.html(a||"")})}}],yd=jb("",!0),zd=jb("Odd",0),Ad=jb("Even",1),Bd=S({compile:function(a,c){c.$set("ngCloak",p);a.removeClass("ng-cloak")}}),Cd=[function(){return{scope:!0,controller:"@"}}],Dd=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],dc={};m("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave".split(" "),function(a){var c=fa("ng-"+a);dc[c]=["$parse",function(d){return function(e,g,i){var f=d(i[c]);g.bind(E(a),function(a){e.$apply(function(){f(e, -{$event:a})})})}}]});var Ed=S(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),Fd=["$http","$templateCache","$anchorScroll","$compile",function(a,c,d,e){return{restrict:"ECA",terminal:!0,compile:function(g,i){var f=i.ngInclude||i.src,h=i.onload||"",k=i.autoscroll;return function(g,i){var o=0,m,n=function(){m&&(m.$destroy(),m=null);i.html("")};g.$watch(f,function(f){var p=++o;f?a.get(f,{cache:c}).success(function(a){p===o&&(m&&m.$destroy(),m=g.$new(),i.html(a),e(i.contents())(m), -v(k)&&(!k||g.$eval(k))&&d(),m.$emit("$includeContentLoaded"),g.$eval(h))}).error(function(){p===o&&n()}):n()})}}}}],Gd=S({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Hd=S({terminal:!0,priority:1E3}),Id=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,i){var f=i.count,h=g.attr(i.$attr.when),k=i.offset||0,j=e.$eval(h),l={},o=c.startSymbol(),r=c.endSymbol();m(j,function(a,e){l[e]=c(a.replace(d,o+f+"-"+k+r))});e.$watch(function(){var c= -parseFloat(e.$eval(f));return isNaN(c)?"":(j[c]||(c=a.pluralCat(c-k)),l[c](e,g,!0))},function(a){g.text(a)})}}}],Jd=S({transclude:"element",priority:1E3,terminal:!0,compile:function(a,c,d){return function(a,c,i){var f=i.ngRepeat,i=f.match(/^\s*(.+)\s+in\s+(.*)\s*$/),h,k,j;if(!i)throw B("Expected ngRepeat in form of '_item_ in _collection_' but got '"+f+"'.");f=i[1];h=i[2];i=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!i)throw B("'item' in 'item in collection' should be identifier or (key, value) but got '"+ -f+"'.");k=i[3]||i[1];j=i[2];var l=new eb;a.$watch(function(a){var e,f,i=a.$eval(h),m=gc(i,!0),p,u=new eb,C,A,v,t,y=c;if(J(i))v=i||[];else{v=[];for(C in i)i.hasOwnProperty(C)&&C.charAt(0)!="$"&&v.push(C);v.sort()}e=0;for(f=v.length;ex;)t.pop().element.remove()}for(;v.length>w;)v.pop()[0].element.remove()}var i;if(!(i=w.match(d)))throw B("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '"+w+"'.");var j=c(i[2]||i[1]),k=i[4]|| -i[6],l=i[5],m=c(i[3]||""),o=c(i[2]?i[1]:k),r=c(i[7]),v=[[{element:f,label:""}]];q&&(a(q)(e),q.removeClass("ng-scope"),q.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=r(e)||[],d={},h,i,j,m,q,s;if(n){i=[];m=0;for(s=v.length;m@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}'); diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/js/app.js --- a/dew/src/main/resources/org/apidesign/bck2brwsr/dew/js/app.js Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,224 +0,0 @@ -// 'use strict'; - -// Declare app level module which depends on filters, and services -angular.module('bck2brwsr', []). - directive('uiCodemirror', ['$timeout', function($timeout) { - 'use strict'; - - var events = ["cursorActivity", "viewportChange", "gutterClick", "focus", "blur", "scroll", "update"]; - return { - restrict: 'A', - require: 'ngModel', - link: function(scope, elm, attrs, ngModel) { - var options, opts, onChange, deferCodeMirror, codeMirror, timeoutId, val; - - if (elm[0].type !== 'textarea') { - throw new Error('uiCodemirror3 can only be applied to a textarea element'); - } - - options = /* uiConfig.codemirror || */ {}; - opts = angular.extend({}, options, scope.$eval(attrs.uiCodemirror)); - - onChange = function(instance, changeObj) { - val = instance.getValue(); - $timeout.cancel(timeoutId); - timeoutId = $timeout(function() { - ngModel.$setViewValue(val); - }, 500); - }; - - deferCodeMirror = function() { - codeMirror = CodeMirror.fromTextArea(elm[0], opts); - elm[0].codeMirror = codeMirror; - // codeMirror.on("change", onChange(opts.onChange)); - codeMirror.on("change", onChange); - - for (var i = 0, n = events.length, aEvent; i < n; ++i) { - aEvent = opts["on" + events[i].charAt(0).toUpperCase() + events[i].slice(1)]; - if (aEvent === void 0) - continue; - if (typeof aEvent !== "function") - continue; - - var bound = _.bind( aEvent, scope ); - - codeMirror.on(events[i], bound); - } - - // CodeMirror expects a string, so make sure it gets one. - // This does not change the model. - ngModel.$formatters.push(function(value) { - if (angular.isUndefined(value) || value === null) { - return ''; - } - else if (angular.isObject(value) || angular.isArray(value)) { - throw new Error('ui-codemirror cannot use an object or an array as a model'); - } - return value; - }); - - // Override the ngModelController $render method, which is what gets called when the model is updated. - // This takes care of the synchronizing the codeMirror element with the underlying model, in the case that it is changed by something else. - ngModel.$render = function() { - codeMirror.setValue(ngModel.$viewValue); - }; - - }; - - $timeout(deferCodeMirror); - - } - }; -}]); - -function DevCtrl( $scope, $http ) { - var templateHtml = -"\n" + -" \n" + -" \n" + -" * 0 \n" + -" = 0\n" + -"
\n" + -" \n" + -" " + -"
\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -"\n" + -" \n" + -" \n" + -""; - var templateJava = -"package bck2brwsr.demo;\n" + -"import org.apidesign.bck2brwsr.htmlpage.api.*;\n" + -"import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*;\n" + -"\n" + -"@Page(xhtml=\"index.html\", className=\"Index\", properties={\n" + -" @Property(name=\"value\", type=int.class)\n" + -"})\n" + -"class YourFirstHTML5PageInRealLanguage {\n" + -" static { new Index().applyBindings(); }\n" + -" @On(event=CLICK, id=\"dupl\") static void duplicateValue(Index m) {\n" + -" m.setValue(m.getValue() * 2);\n" + -" }\n" + -" @On(event=CLICK, id=\"clear\") static void zeroTheValue(Index m) {\n" + -" m.setValue(0);;\n" + -" }\n" + -" @ComputedProperty static int powerValue(int value) {\n" + -" return value * value;\n" + -" }\n" + -"}"; - - - $scope.makeMarker = function( editor, line ) { - var marker = document.createElement("div"); - marker.innerHTML = " "; - marker.className = "issue"; - - var info = editor.lineInfo(line); - editor.setGutterMarker(line, "issues", info.markers ? null : marker); - - return marker; - }; - - - // Returns a function, that, as long as it continues to be invoked, will not - // be triggered. The function will be called after it stops being called for - // N milliseconds. If `immediate` is passed, trigger the function on the - // leading edge, instead of the trailing. - $scope.debounce = function(func, wait, immediate) { - var timeout, result; - return function() { - var context = this, args = arguments; - var later = function() { - timeout = null; - if (!immediate) result = func.apply(context, args); - }; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) result = func.apply(context, args); - return result; - }; - }; - - $scope.reload = function() { - $scope.errors = null; - var frame = document.getElementById("result"); - frame.src = "result.html"; - frame.contentDocument.location.reload(true); - frame.contentWindow.location.reload(); - document.getElementById("editorJava").codeMirror.clearGutter("issues"); - }; - - $scope.fail = function( data ) { - $scope.errors = eval( data ); - var editor = document.getElementById("editorJava").codeMirror; - editor.clearGutter( "issues" ); - - for( var i = 0; i < $scope.errors.length; i ++ ) { - $scope.makeMarker( editor, $scope.errors[i].line - 1 ); - } - - }; - - $scope.post = function() { - return $http({url: ".", - method: "POST", - //headers: this.headers, - data: { html : $scope.html, java : $scope.java} - }).success( $scope.reload ).error( $scope.fail ); - }; - - $scope.errorClass = function( kind ) { - switch( kind ) { - case "ERROR" : - return "error"; - default : - return "warning"; - } - }; - - $scope.gotoError = function( line, col ) { - var editor = document.getElementById("editorJava").codeMirror; - editor.setCursor({ line: line - 1, ch : col - 1 }); - editor.focus(); - }; - - $scope.tab = "html"; - $scope.html= templateHtml; - $scope.java = templateJava; - - $scope.$watch( "html", $scope.debounce( $scope.post, 2000 ) ); - $scope.$watch( "java", $scope.debounce( $scope.post, 2000 ) ); - $scope.post(); - -} diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/js/codemirror/codemirror.css --- a/dew/src/main/resources/org/apidesign/bck2brwsr/dew/js/codemirror/codemirror.css Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,239 +0,0 @@ -/* BASICS */ - -.CodeMirror { - /* Set height, width, borders, and global font properties here */ - font-family: monospace; - height: 300px; -} -.CodeMirror-scroll { - /* Set scrolling behaviour here */ - overflow: auto; -} - -/* PADDING */ - -.CodeMirror-lines { - padding: 4px 0; /* Vertical padding around content */ -} -.CodeMirror pre { - padding: 0 4px; /* Horizontal padding of content */ -} - -.CodeMirror-scrollbar-filler { - background-color: white; /* The little square between H and V scrollbars */ -} - -/* GUTTER */ - -.CodeMirror-gutters { - border-right: 1px solid #ddd; - background-color: #f7f7f7; -} -.CodeMirror-linenumbers {} -.CodeMirror-linenumber { - padding: 0 3px 0 5px; - min-width: 20px; - text-align: right; - color: #999; -} - -/* CURSOR */ - -.CodeMirror pre.CodeMirror-cursor { - border-left: 1px solid black; -} -/* Shown when moving in bi-directional text */ -.CodeMirror pre.CodeMirror-secondarycursor { - border-left: 1px solid silver; -} -.cm-keymap-fat-cursor pre.CodeMirror-cursor { - width: auto; - border: 0; - background: transparent; - background: rgba(0, 200, 0, .4); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800); -} -/* Kludge to turn off filter in ie9+, which also accepts rgba */ -.cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) { - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} -/* Can style cursor different in overwrite (non-insert) mode */ -.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {} - -/* DEFAULT THEME */ - -.cm-s-default .cm-keyword {color: #708;} -.cm-s-default .cm-atom {color: #219;} -.cm-s-default .cm-number {color: #164;} -.cm-s-default .cm-def {color: #00f;} -.cm-s-default .cm-variable {color: black;} -.cm-s-default .cm-variable-2 {color: #05a;} -.cm-s-default .cm-variable-3 {color: #085;} -.cm-s-default .cm-property {color: black;} -.cm-s-default .cm-operator {color: black;} -.cm-s-default .cm-comment {color: #a50;} -.cm-s-default .cm-string {color: #a11;} -.cm-s-default .cm-string-2 {color: #f50;} -.cm-s-default .cm-meta {color: #555;} -.cm-s-default .cm-error {color: #f00;} -.cm-s-default .cm-qualifier {color: #555;} -.cm-s-default .cm-builtin {color: #30a;} -.cm-s-default .cm-bracket {color: #997;} -.cm-s-default .cm-tag {color: #170;} -.cm-s-default .cm-attribute {color: #00c;} -.cm-s-default .cm-header {color: blue;} -.cm-s-default .cm-quote {color: #090;} -.cm-s-default .cm-hr {color: #999;} -.cm-s-default .cm-link {color: #00c;} - -.cm-negative {color: #d44;} -.cm-positive {color: #292;} -.cm-header, .cm-strong {font-weight: bold;} -.cm-em {font-style: italic;} -.cm-emstrong {font-style: italic; font-weight: bold;} -.cm-link {text-decoration: underline;} - -.cm-invalidchar {color: #f00;} - -div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} -div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} - -/* STOP */ - -/* The rest of this file contains styles related to the mechanics of - the editor. You probably shouldn't touch them. */ - -.CodeMirror { - line-height: 1; - position: relative; - overflow: hidden; -} - -.CodeMirror-scroll { - /* 30px is the magic margin used to hide the element's real scrollbars */ - /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */ - margin-bottom: -30px; margin-right: -30px; - padding-bottom: 30px; padding-right: 30px; - height: 100%; - outline: none; /* Prevent dragging from highlighting the element */ - position: relative; -} -.CodeMirror-sizer { - position: relative; -} - -/* The fake, visible scrollbars. Used to force redraw during scrolling - before actuall scrolling happens, thus preventing shaking and - flickering artifacts. */ -.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler { - position: absolute; - z-index: 6; - display: none; -} -.CodeMirror-vscrollbar { - right: 0; top: 0; - overflow-x: hidden; - overflow-y: scroll; -} -.CodeMirror-hscrollbar { - bottom: 0; left: 0; - overflow-y: hidden; - overflow-x: scroll; -} -.CodeMirror-scrollbar-filler { - right: 0; bottom: 0; - z-index: 6; -} - -.CodeMirror-gutters { - position: absolute; left: 0; top: 0; - height: 100%; - z-index: 3; -} -.CodeMirror-gutter { - height: 100%; - display: inline-block; - /* Hack to make IE7 behave */ - *zoom:1; - *display:inline; -} -.CodeMirror-gutter-elt { - position: absolute; - cursor: default; - z-index: 4; -} - -.CodeMirror-lines { - cursor: text; -} -.CodeMirror pre { - /* Reset some styles that the rest of the page might have set */ - -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0; - border-width: 0; - background: transparent; - font-family: inherit; - font-size: inherit; - margin: 0; - white-space: pre; - word-wrap: normal; - line-height: inherit; - color: inherit; - z-index: 2; - position: relative; - overflow: visible; -} -.CodeMirror-wrap pre { - word-wrap: break-word; - white-space: pre-wrap; - word-break: normal; -} -.CodeMirror-linebackground { - position: absolute; - left: 0; right: 0; top: 0; bottom: 0; - z-index: 0; -} - -.CodeMirror-linewidget { - position: relative; - z-index: 2; -} - -.CodeMirror-wrap .CodeMirror-scroll { - overflow-x: hidden; -} - -.CodeMirror-measure { - position: absolute; - width: 100%; height: 0px; - overflow: hidden; - visibility: hidden; -} -.CodeMirror-measure pre { position: static; } - -.CodeMirror pre.CodeMirror-cursor { - position: absolute; - visibility: hidden; - border-right: none; - width: 0; -} -.CodeMirror-focused pre.CodeMirror-cursor { - visibility: visible; -} - -.CodeMirror-selected { background: #d9d9d9; } -.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } - -.CodeMirror-searching { - background: #ffa; - background: rgba(255, 255, 0, .4); -} - -/* IE7 hack to prevent it from returning funny offsetTops on the spans */ -.CodeMirror span { *vertical-align: text-bottom; } - -@media print { - /* Hide the cursor when printing */ - .CodeMirror pre.CodeMirror-cursor { - visibility: hidden; - } -} diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/js/codemirror/codemirror.js --- a/dew/src/main/resources/org/apidesign/bck2brwsr/dew/js/codemirror/codemirror.js Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4553 +0,0 @@ -// CodeMirror version 3.0 -// -// CodeMirror is the only global var we claim -window.CodeMirror = (function() { - "use strict"; - - // BROWSER SNIFFING - - // Crude, but necessary to handle a number of hard-to-feature-detect - // bugs and behavior differences. - var gecko = /gecko\/\d/i.test(navigator.userAgent); - var ie = /MSIE \d/.test(navigator.userAgent); - var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent); - var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); - var webkit = /WebKit\//.test(navigator.userAgent); - var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); - var chrome = /Chrome\//.test(navigator.userAgent); - var opera = /Opera\//.test(navigator.userAgent); - var safari = /Apple Computer/.test(navigator.vendor); - var khtml = /KHTML\//.test(navigator.userAgent); - var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent); - var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); - var phantom = /PhantomJS/.test(navigator.userAgent); - - var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); - // This is woefully incomplete. Suggestions for alternative methods welcome. - var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent); - var mac = ios || /Mac/.test(navigator.platform); - - // Optimize some code when these features are not used - var sawReadOnlySpans = false, sawCollapsedSpans = false; - - // CONSTRUCTOR - - function CodeMirror(place, options) { - if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); - - this.options = options = options || {}; - // Determine effective options based on given values and defaults. - for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt)) - options[opt] = defaults[opt]; - setGuttersForLineNumbers(options); - - var display = this.display = makeDisplay(place); - display.wrapper.CodeMirror = this; - updateGutters(this); - if (options.autofocus && !mobile) focusInput(this); - - this.view = makeView(new BranchChunk([new LeafChunk([makeLine("", null, textHeight(display))])])); - this.nextOpId = 0; - loadMode(this); - themeChanged(this); - if (options.lineWrapping) - this.display.wrapper.className += " CodeMirror-wrap"; - - // Initialize the content. - this.setValue(options.value || ""); - // Override magic textarea content restore that IE sometimes does - // on our hidden textarea on reload - if (ie) setTimeout(bind(resetInput, this, true), 20); - this.view.history = makeHistory(); - - registerEventHandlers(this); - // IE throws unspecified error in certain cases, when - // trying to access activeElement before onload - var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { } - if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20); - else onBlur(this); - - operation(this, function() { - for (var opt in optionHandlers) - if (optionHandlers.propertyIsEnumerable(opt)) - optionHandlers[opt](this, options[opt], Init); - for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); - })(); - } - - // DISPLAY CONSTRUCTOR - - function makeDisplay(place) { - var d = {}; - var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;"); - input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); - // Wraps and hides input textarea - d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); - // The actual fake scrollbars. - d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar"); - d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar"); - d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); - // DIVs containing the selection and the actual code - d.lineDiv = elt("div"); - d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); - // Blinky cursor, and element used to ensure cursor fits at the end of a line - d.cursor = elt("pre", "\u00a0", "CodeMirror-cursor"); - // Secondary cursor, shown when on a 'jump' in bi-directional text - d.otherCursor = elt("pre", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"); - // Used to measure text size - d.measure = elt("div", null, "CodeMirror-measure"); - // Wraps everything that needs to exist inside the vertically-padded coordinate system - d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor], - null, "position: relative; outline: none"); - // Moved around its parent to cover visible view - d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); - // Set to the height of the text, causes scrolling - d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); - // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers - d.heightForcer = elt("div", "\u00a0", null, "position: absolute; height: " + scrollerCutOff + "px"); - // Will contain the gutters, if any - d.gutters = elt("div", null, "CodeMirror-gutters"); - d.lineGutter = null; - // Helper element to properly size the gutter backgrounds - var scrollerInner = elt("div", [d.sizer, d.heightForcer, d.gutters], null, "position: relative; min-height: 100%"); - // Provides scrolling - d.scroller = elt("div", [scrollerInner], "CodeMirror-scroll"); - d.scroller.setAttribute("tabIndex", "-1"); - // The element in which the editor lives. - d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV, - d.scrollbarFiller, d.scroller], "CodeMirror"); - // Work around IE7 z-index bug - if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } - if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper); - - // Needed to hide big blue blinking cursor on Mobile Safari - if (ios) input.style.width = "0px"; - if (!webkit) d.scroller.draggable = true; - // Needed to handle Tab key in KHTML - if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; } - // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). - else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px"; - - // Current visible range (may be bigger than the view window). - d.viewOffset = d.showingFrom = d.showingTo = d.lastSizeC = 0; - - // Used to only resize the line number gutter when necessary (when - // the amount of lines crosses a boundary that makes its width change) - d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; - // See readInput and resetInput - d.prevInput = ""; - // Set to true when a non-horizontal-scrolling widget is added. As - // an optimization, widget aligning is skipped when d is false. - d.alignWidgets = false; - // Flag that indicates whether we currently expect input to appear - // (after some event like 'keypress' or 'input') and are polling - // intensively. - d.pollingFast = false; - // Self-resetting timeout for the poller - d.poll = new Delayed(); - // True when a drag from the editor is active - d.draggingText = false; - - d.cachedCharWidth = d.cachedTextHeight = null; - d.measureLineCache = []; - d.measureLineCachePos = 0; - - // Tracks when resetInput has punted to just putting a short - // string instead of the (large) selection. - d.inaccurateSelection = false; - - // Used to adjust overwrite behaviour when a paste has been - // detected - d.pasteIncoming = false; - - return d; - } - - // VIEW CONSTRUCTOR - - function makeView(doc) { - var selPos = {line: 0, ch: 0}; - return { - doc: doc, - // frontier is the point up to which the content has been parsed, - frontier: 0, highlight: new Delayed(), - sel: {from: selPos, to: selPos, head: selPos, anchor: selPos, shift: false, extend: false}, - scrollTop: 0, scrollLeft: 0, - overwrite: false, focused: false, - // Tracks the maximum line length so that - // the horizontal scrollbar can be kept - // static when scrolling. - maxLine: getLine(doc, 0), - maxLineLength: 0, - maxLineChanged: false, - suppressEdits: false, - goalColumn: null, - cantEdit: false, - keyMaps: [] - }; - } - - // STATE UPDATES - - // Used to get the editor into a consistent state again when options change. - - function loadMode(cm) { - var doc = cm.view.doc; - cm.view.mode = CodeMirror.getMode(cm.options, cm.options.mode); - doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); - cm.view.frontier = 0; - startWorker(cm, 100); - } - - function wrappingChanged(cm) { - var doc = cm.view.doc, th = textHeight(cm.display); - if (cm.options.lineWrapping) { - cm.display.wrapper.className += " CodeMirror-wrap"; - var perLine = cm.display.scroller.clientWidth / charWidth(cm.display) - 3; - doc.iter(0, doc.size, function(line) { - if (line.height == 0) return; - var guess = Math.ceil(line.text.length / perLine) || 1; - if (guess != 1) updateLineHeight(line, guess * th); - }); - cm.display.sizer.style.minWidth = ""; - } else { - cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", ""); - computeMaxLength(cm.view); - doc.iter(0, doc.size, function(line) { - if (line.height != 0) updateLineHeight(line, th); - }); - } - regChange(cm, 0, doc.size); - clearCaches(cm); - setTimeout(function(){updateScrollbars(cm.display, cm.view.doc.height);}, 100); - } - - function keyMapChanged(cm) { - var style = keyMap[cm.options.keyMap].style; - cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + - (style ? " cm-keymap-" + style : ""); - } - - function themeChanged(cm) { - cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + - cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); - clearCaches(cm); - } - - function guttersChanged(cm) { - updateGutters(cm); - updateDisplay(cm, true); - } - - function updateGutters(cm) { - var gutters = cm.display.gutters, specs = cm.options.gutters; - removeChildren(gutters); - for (var i = 0; i < specs.length; ++i) { - var gutterClass = specs[i]; - var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); - if (gutterClass == "CodeMirror-linenumbers") { - cm.display.lineGutter = gElt; - gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; - } - } - gutters.style.display = i ? "" : "none"; - } - - function lineLength(doc, line) { - if (line.height == 0) return 0; - var len = line.text.length, merged, cur = line; - while (merged = collapsedSpanAtStart(cur)) { - var found = merged.find(); - cur = getLine(doc, found.from.line); - len += found.from.ch - found.to.ch; - } - cur = line; - while (merged = collapsedSpanAtEnd(cur)) { - var found = merged.find(); - len -= cur.text.length - found.from.ch; - cur = getLine(doc, found.to.line); - len += cur.text.length - found.to.ch; - } - return len; - } - - function computeMaxLength(view) { - view.maxLine = getLine(view.doc, 0); - view.maxLineLength = lineLength(view.doc, view.maxLine); - view.maxLineChanged = true; - view.doc.iter(1, view.doc.size, function(line) { - var len = lineLength(view.doc, line); - if (len > view.maxLineLength) { - view.maxLineLength = len; - view.maxLine = line; - } - }); - } - - // Make sure the gutters options contains the element - // "CodeMirror-linenumbers" when the lineNumbers option is true. - function setGuttersForLineNumbers(options) { - var found = false; - for (var i = 0; i < options.gutters.length; ++i) { - if (options.gutters[i] == "CodeMirror-linenumbers") { - if (options.lineNumbers) found = true; - else options.gutters.splice(i--, 1); - } - } - if (!found && options.lineNumbers) - options.gutters.push("CodeMirror-linenumbers"); - } - - // SCROLLBARS - - // Re-synchronize the fake scrollbars with the actual size of the - // content. Optionally force a scrollTop. - function updateScrollbars(d /* display */, docHeight) { - var totalHeight = docHeight + 2 * paddingTop(d); - d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px"; - var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight); - var needsH = d.scroller.scrollWidth > d.scroller.clientWidth; - var needsV = scrollHeight > d.scroller.clientHeight; - if (needsV) { - d.scrollbarV.style.display = "block"; - d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0"; - d.scrollbarV.firstChild.style.height = - (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px"; - } else d.scrollbarV.style.display = ""; - if (needsH) { - d.scrollbarH.style.display = "block"; - d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0"; - d.scrollbarH.firstChild.style.width = - (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px"; - } else d.scrollbarH.style.display = ""; - if (needsH && needsV) { - d.scrollbarFiller.style.display = "block"; - d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px"; - } else d.scrollbarFiller.style.display = ""; - - if (mac_geLion && scrollbarWidth(d.measure) === 0) - d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px"; - } - - function visibleLines(display, doc, viewPort) { - var top = display.scroller.scrollTop, height = display.wrapper.clientHeight; - if (typeof viewPort == "number") top = viewPort; - else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;} - top = Math.floor(top - paddingTop(display)); - var bottom = Math.ceil(top + height); - return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)}; - } - - // LINE NUMBERS - - function alignHorizontally(cm) { - var display = cm.display; - if (!display.alignWidgets && !display.gutters.firstChild) return; - var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.view.scrollLeft; - var gutterW = display.gutters.offsetWidth, l = comp + "px"; - for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) { - for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l; - } - display.gutters.style.left = (comp + gutterW) + "px"; - } - - function maybeUpdateLineNumberWidth(cm) { - if (!cm.options.lineNumbers) return false; - var doc = cm.view.doc, last = lineNumberFor(cm.options, doc.size - 1), display = cm.display; - if (last.length != display.lineNumChars) { - var test = display.measure.appendChild(elt("div", [elt("div", last)], - "CodeMirror-linenumber CodeMirror-gutter-elt")); - var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; - display.lineGutter.style.width = ""; - display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding); - display.lineNumWidth = display.lineNumInnerWidth + padding; - display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; - display.lineGutter.style.width = display.lineNumWidth + "px"; - return true; - } - return false; - } - - function lineNumberFor(options, i) { - return String(options.lineNumberFormatter(i + options.firstLineNumber)); - } - function compensateForHScroll(display) { - return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left; - } - - // DISPLAY DRAWING - - function updateDisplay(cm, changes, viewPort) { - var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo; - var updated = updateDisplayInner(cm, changes, viewPort); - if (updated) { - signalLater(cm, cm, "update", cm); - if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo) - signalLater(cm, cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo); - } - updateSelection(cm); - updateScrollbars(cm.display, cm.view.doc.height); - - return updated; - } - - // Uses a set of changes plus the current scroll position to - // determine which DOM updates have to be made, and makes the - // updates. - function updateDisplayInner(cm, changes, viewPort) { - var display = cm.display, doc = cm.view.doc; - if (!display.wrapper.clientWidth) { - display.showingFrom = display.showingTo = display.viewOffset = 0; - return; - } - - // Compute the new visible window - // If scrollTop is specified, use that to determine which lines - // to render instead of the current scrollbar position. - var visible = visibleLines(display, doc, viewPort); - // Bail out if the visible area is already rendered and nothing changed. - if (changes !== true && changes.length == 0 && - visible.from > display.showingFrom && visible.to < display.showingTo) - return; - - if (changes && maybeUpdateLineNumberWidth(cm)) - changes = true; - display.sizer.style.marginLeft = display.scrollbarH.style.left = display.gutters.offsetWidth + "px"; - - // When merged lines are present, the line that needs to be - // redrawn might not be the one that was changed. - if (changes !== true && sawCollapsedSpans) - for (var i = 0; i < changes.length; ++i) { - var ch = changes[i], merged; - while (merged = collapsedSpanAtStart(getLine(doc, ch.from))) { - var from = merged.find().from.line; - if (ch.diff) ch.diff -= ch.from - from; - ch.from = from; - } - } - - // Used to determine which lines need their line numbers updated - var positionsChangedFrom = changes === true ? 0 : Infinity; - if (cm.options.lineNumbers && changes && changes !== true) - for (var i = 0; i < changes.length; ++i) - if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; } - - var from = Math.max(visible.from - cm.options.viewportMargin, 0); - var to = Math.min(doc.size, visible.to + cm.options.viewportMargin); - if (display.showingFrom < from && from - display.showingFrom < 20) from = display.showingFrom; - if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(doc.size, display.showingTo); - if (sawCollapsedSpans) { - from = lineNo(visualLine(doc, getLine(doc, from))); - while (to < doc.size && lineIsHidden(getLine(doc, to))) ++to; - } - - // Create a range of theoretically intact lines, and punch holes - // in that using the change info. - var intact = changes === true ? [] : - computeIntact([{from: display.showingFrom, to: display.showingTo}], changes); - // Clip off the parts that won't be visible - var intactLines = 0; - for (var i = 0; i < intact.length; ++i) { - var range = intact[i]; - if (range.from < from) range.from = from; - if (range.to > to) range.to = to; - if (range.from >= range.to) intact.splice(i--, 1); - else intactLines += range.to - range.from; - } - if (intactLines == to - from && from == display.showingFrom && to == display.showingTo) - return; - intact.sort(function(a, b) {return a.from - b.from;}); - - if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none"; - patchDisplay(cm, from, to, intact, positionsChangedFrom); - display.lineDiv.style.display = ""; - - var different = from != display.showingFrom || to != display.showingTo || - display.lastSizeC != display.wrapper.clientHeight; - // This is just a bogus formula that detects when the editor is - // resized or the font size changes. - if (different) display.lastSizeC = display.wrapper.clientHeight; - display.showingFrom = from; display.showingTo = to; - startWorker(cm, 100); - - var prevBottom = display.lineDiv.offsetTop; - for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) { - if (ie_lt8) { - var bot = node.offsetTop + node.offsetHeight; - height = bot - prevBottom; - prevBottom = bot; - } else { - var box = node.getBoundingClientRect(); - height = box.bottom - box.top; - } - var diff = node.lineObj.height - height; - if (height < 2) height = textHeight(display); - if (diff > .001 || diff < -.001) - updateLineHeight(node.lineObj, height); - } - display.viewOffset = heightAtLine(cm, getLine(doc, from)); - // Position the mover div to align with the current virtual scroll position - display.mover.style.top = display.viewOffset + "px"; - return true; - } - - function computeIntact(intact, changes) { - for (var i = 0, l = changes.length || 0; i < l; ++i) { - var change = changes[i], intact2 = [], diff = change.diff || 0; - for (var j = 0, l2 = intact.length; j < l2; ++j) { - var range = intact[j]; - if (change.to <= range.from && change.diff) { - intact2.push({from: range.from + diff, to: range.to + diff}); - } else if (change.to <= range.from || change.from >= range.to) { - intact2.push(range); - } else { - if (change.from > range.from) - intact2.push({from: range.from, to: change.from}); - if (change.to < range.to) - intact2.push({from: change.to + diff, to: range.to + diff}); - } - } - intact = intact2; - } - return intact; - } - - function getDimensions(cm) { - var d = cm.display, left = {}, width = {}; - for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { - left[cm.options.gutters[i]] = n.offsetLeft; - width[cm.options.gutters[i]] = n.offsetWidth; - } - return {fixedPos: compensateForHScroll(d), - gutterTotalWidth: d.gutters.offsetWidth, - gutterLeft: left, - gutterWidth: width, - wrapperWidth: d.wrapper.clientWidth}; - } - - function patchDisplay(cm, from, to, intact, updateNumbersFrom) { - var dims = getDimensions(cm); - var display = cm.display, lineNumbers = cm.options.lineNumbers; - // IE does bad things to nodes when .innerHTML = "" is used on a parent - // we still need widgets and markers intact to add back to the new content later - if (!intact.length && !ie && (!webkit || !cm.display.currentWheelTarget)) - removeChildren(display.lineDiv); - var container = display.lineDiv, cur = container.firstChild; - - function rm(node) { - var next = node.nextSibling; - if (webkit && mac && cm.display.currentWheelTarget == node) { - node.style.display = "none"; - node.lineObj = null; - } else { - container.removeChild(node); - } - return next; - } - - var nextIntact = intact.shift(), lineNo = from; - cm.view.doc.iter(from, to, function(line) { - if (nextIntact && nextIntact.to == lineNo) nextIntact = intact.shift(); - if (lineIsHidden(line)) { - if (line.height != 0) updateLineHeight(line, 0); - } else if (nextIntact && nextIntact.from <= lineNo && nextIntact.to > lineNo) { - // This line is intact. Skip to the actual node. Update its - // line number if needed. - while (cur.lineObj != line) cur = rm(cur); - if (lineNumbers && updateNumbersFrom <= lineNo && cur.lineNumber) - setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineNo)); - cur = cur.nextSibling; - } else { - // This line needs to be generated. - var lineNode = buildLineElement(cm, line, lineNo, dims); - container.insertBefore(lineNode, cur); - lineNode.lineObj = line; - } - ++lineNo; - }); - while (cur) cur = rm(cur); - } - - function buildLineElement(cm, line, lineNo, dims) { - var lineElement = lineContent(cm, line); - var markers = line.gutterMarkers, display = cm.display; - - if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && - (!line.widgets || !line.widgets.length)) return lineElement; - - // Lines with gutter elements or a background class need - // to be wrapped again, and have the extra elements added - // to the wrapper div - - var wrap = elt("div", null, line.wrapClass, "position: relative"); - if (cm.options.lineNumbers || markers) { - var gutterWrap = wrap.appendChild(elt("div", null, null, "position: absolute; left: " + - dims.fixedPos + "px")); - wrap.alignable = [gutterWrap]; - if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) - wrap.lineNumber = gutterWrap.appendChild( - elt("div", lineNumberFor(cm.options, lineNo), - "CodeMirror-linenumber CodeMirror-gutter-elt", - "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " - + display.lineNumInnerWidth + "px")); - if (markers) - for (var k = 0; k < cm.options.gutters.length; ++k) { - var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; - if (found) - gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + - dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); - } - } - // Kludge to make sure the styled element lies behind the selection (by z-index) - if (line.bgClass) - wrap.appendChild(elt("div", "\u00a0", line.bgClass + " CodeMirror-linebackground")); - wrap.appendChild(lineElement); - if (line.widgets) - for (var i = 0, ws = line.widgets; i < ws.length; ++i) { - var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); - node.widget = widget; - if (widget.noHScroll) { - (wrap.alignable || (wrap.alignable = [])).push(node); - var width = dims.wrapperWidth; - node.style.left = dims.fixedPos + "px"; - if (!widget.coverGutter) { - width -= dims.gutterTotalWidth; - node.style.paddingLeft = dims.gutterTotalWidth + "px"; - } - node.style.width = width + "px"; - } - if (widget.coverGutter) { - node.style.zIndex = 5; - node.style.position = "relative"; - if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; - } - if (widget.above) - wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement); - else - wrap.appendChild(node); - } - - if (ie_lt8) wrap.style.zIndex = 2; - return wrap; - } - - // SELECTION / CURSOR - - function updateSelection(cm) { - var display = cm.display; - var collapsed = posEq(cm.view.sel.from, cm.view.sel.to); - if (collapsed || cm.options.showCursorWhenSelecting) - updateSelectionCursor(cm); - else - display.cursor.style.display = display.otherCursor.style.display = "none"; - if (!collapsed) - updateSelectionRange(cm); - else - display.selectionDiv.style.display = "none"; - - // Move the hidden textarea near the cursor to prevent scrolling artifacts - var headPos = cursorCoords(cm, cm.view.sel.head, "div"); - var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); - display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, - headPos.top + lineOff.top - wrapOff.top)) + "px"; - display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, - headPos.left + lineOff.left - wrapOff.left)) + "px"; - } - - // No selection, plain cursor - function updateSelectionCursor(cm) { - var display = cm.display, pos = cursorCoords(cm, cm.view.sel.head, "div"); - display.cursor.style.left = pos.left + "px"; - display.cursor.style.top = pos.top + "px"; - display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; - display.cursor.style.display = ""; - - if (pos.other) { - display.otherCursor.style.display = ""; - display.otherCursor.style.left = pos.other.left + "px"; - display.otherCursor.style.top = pos.other.top + "px"; - display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; - } else { display.otherCursor.style.display = "none"; } - } - - // Highlight selection - function updateSelectionRange(cm) { - var display = cm.display, doc = cm.view.doc, sel = cm.view.sel; - var fragment = document.createDocumentFragment(); - var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display); - - function add(left, top, width, bottom) { - if (top < 0) top = 0; - fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + - "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) + - "px; height: " + (bottom - top) + "px")); - } - - function drawForLine(line, fromArg, toArg, retTop) { - var lineObj = getLine(doc, line); - var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity; - function coords(ch) { - return charCoords(cm, {line: line, ch: ch}, "div", lineObj); - } - - iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { - var leftPos = coords(dir == "rtl" ? to - 1 : from); - var rightPos = coords(dir == "rtl" ? from : to - 1); - var left = leftPos.left, right = rightPos.right; - if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part - add(left, leftPos.top, null, leftPos.bottom); - left = pl; - if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); - } - if (toArg == null && to == lineLen) right = clientWidth; - if (fromArg == null && from == 0) left = pl; - rVal = retTop ? Math.min(rightPos.top, rVal) : Math.max(rightPos.bottom, rVal); - if (left < pl + 1) left = pl; - add(left, rightPos.top, right - left, rightPos.bottom); - }); - return rVal; - } - - if (sel.from.line == sel.to.line) { - drawForLine(sel.from.line, sel.from.ch, sel.to.ch); - } else { - var fromObj = getLine(doc, sel.from.line); - var cur = fromObj, merged, path = [sel.from.line, sel.from.ch], singleLine; - while (merged = collapsedSpanAtEnd(cur)) { - var found = merged.find(); - path.push(found.from.ch, found.to.line, found.to.ch); - if (found.to.line == sel.to.line) { - path.push(sel.to.ch); - singleLine = true; - break; - } - cur = getLine(doc, found.to.line); - } - - // This is a single, merged line - if (singleLine) { - for (var i = 0; i < path.length; i += 3) - drawForLine(path[i], path[i+1], path[i+2]); - } else { - var middleTop, middleBot, toObj = getLine(doc, sel.to.line); - if (sel.from.ch) - // Draw the first line of selection. - middleTop = drawForLine(sel.from.line, sel.from.ch, null, false); - else - // Simply include it in the middle block. - middleTop = heightAtLine(cm, fromObj) - display.viewOffset; - - if (!sel.to.ch) - middleBot = heightAtLine(cm, toObj) - display.viewOffset; - else - middleBot = drawForLine(sel.to.line, collapsedSpanAtStart(toObj) ? null : 0, sel.to.ch, true); - - if (middleTop < middleBot) add(pl, middleTop, null, middleBot); - } - } - - removeChildrenAndAdd(display.selectionDiv, fragment); - display.selectionDiv.style.display = ""; - } - - // Cursor-blinking - function restartBlink(cm) { - var display = cm.display; - clearInterval(display.blinker); - var on = true; - display.cursor.style.visibility = display.otherCursor.style.visibility = ""; - display.blinker = setInterval(function() { - if (!display.cursor.offsetHeight) return; - display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden"; - }, cm.options.cursorBlinkRate); - } - - // HIGHLIGHT WORKER - - function startWorker(cm, time) { - if (cm.view.frontier < cm.display.showingTo) - cm.view.highlight.set(time, bind(highlightWorker, cm)); - } - - function highlightWorker(cm) { - var view = cm.view, doc = view.doc; - if (view.frontier >= cm.display.showingTo) return; - var end = +new Date + cm.options.workTime; - var state = copyState(view.mode, getStateBefore(cm, view.frontier)); - var changed = [], prevChange; - doc.iter(view.frontier, Math.min(doc.size, cm.display.showingTo + 500), function(line) { - if (view.frontier >= cm.display.showingFrom) { // Visible - if (highlightLine(cm, line, state) && view.frontier >= cm.display.showingFrom) { - if (prevChange && prevChange.end == view.frontier) prevChange.end++; - else changed.push(prevChange = {start: view.frontier, end: view.frontier + 1}); - } - line.stateAfter = copyState(view.mode, state); - } else { - processLine(cm, line, state); - line.stateAfter = view.frontier % 5 == 0 ? copyState(view.mode, state) : null; - } - ++view.frontier; - if (+new Date > end) { - startWorker(cm, cm.options.workDelay); - return true; - } - }); - if (changed.length) - operation(cm, function() { - for (var i = 0; i < changed.length; ++i) - regChange(this, changed[i].start, changed[i].end); - })(); - } - - // Finds the line to start with when starting a parse. Tries to - // find a line with a stateAfter, so that it can start with a - // valid state. If that fails, it returns the line with the - // smallest indentation, which tends to need the least context to - // parse correctly. - function findStartLine(cm, n) { - var minindent, minline, doc = cm.view.doc; - for (var search = n, lim = n - 100; search > lim; --search) { - if (search == 0) return 0; - var line = getLine(doc, search-1); - if (line.stateAfter) return search; - var indented = countColumn(line.text, null, cm.options.tabSize); - if (minline == null || minindent > indented) { - minline = search - 1; - minindent = indented; - } - } - return minline; - } - - function getStateBefore(cm, n) { - var view = cm.view; - var pos = findStartLine(cm, n), state = pos && getLine(view.doc, pos-1).stateAfter; - if (!state) state = startState(view.mode); - else state = copyState(view.mode, state); - view.doc.iter(pos, n, function(line) { - processLine(cm, line, state); - var save = pos == n - 1 || pos % 5 == 0 || pos >= view.showingFrom && pos < view.showingTo; - line.stateAfter = save ? copyState(view.mode, state) : null; - ++pos; - }); - return state; - } - - // POSITION MEASUREMENT - - function paddingTop(display) {return display.lineSpace.offsetTop;} - function paddingLeft(display) { - var e = removeChildrenAndAdd(display.measure, elt("pre")).appendChild(elt("span", "x")); - return e.offsetLeft; - } - - function measureChar(cm, line, ch, data) { - var data = data || measureLine(cm, line), dir = -1; - for (var pos = ch;; pos += dir) { - var r = data[pos]; - if (r) break; - if (dir < 0 && pos == 0) dir = 1; - } - return {left: pos < ch ? r.right : r.left, - right: pos > ch ? r.left : r.right, - top: r.top, bottom: r.bottom}; - } - - function measureLine(cm, line) { - // First look in the cache - var display = cm.display, cache = cm.display.measureLineCache; - for (var i = 0; i < cache.length; ++i) { - var memo = cache[i]; - if (memo.text == line.text && memo.markedSpans == line.markedSpans && - display.scroller.clientWidth == memo.width) - return memo.measure; - } - - var measure = measureLineInner(cm, line); - // Store result in the cache - var memo = {text: line.text, width: display.scroller.clientWidth, - markedSpans: line.markedSpans, measure: measure}; - if (cache.length == 16) cache[++display.measureLineCachePos % 16] = memo; - else cache.push(memo); - return measure; - } - - function measureLineInner(cm, line) { - var display = cm.display, measure = emptyArray(line.text.length); - var pre = lineContent(cm, line, measure); - - // IE does not cache element positions of inline elements between - // calls to getBoundingClientRect. This makes the loop below, - // which gathers the positions of all the characters on the line, - // do an amount of layout work quadratic to the number of - // characters. When line wrapping is off, we try to improve things - // by first subdividing the line into a bunch of inline blocks, so - // that IE can reuse most of the layout information from caches - // for those blocks. This does interfere with line wrapping, so it - // doesn't work when wrapping is on, but in that case the - // situation is slightly better, since IE does cache line-wrapping - // information and only recomputes per-line. - if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) { - var fragment = document.createDocumentFragment(); - var chunk = 10, n = pre.childNodes.length; - for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) { - var wrap = elt("div", null, null, "display: inline-block"); - for (var j = 0; j < chunk && n; ++j) { - wrap.appendChild(pre.firstChild); - --n; - } - fragment.appendChild(wrap); - } - pre.appendChild(fragment); - } - - removeChildrenAndAdd(display.measure, pre); - - var outer = display.lineDiv.getBoundingClientRect(); - var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight; - for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) { - var size = cur.getBoundingClientRect(); - var top = Math.max(0, size.top - outer.top), bot = Math.min(size.bottom - outer.top, maxBot); - for (var j = 0; j < vranges.length; j += 2) { - var rtop = vranges[j], rbot = vranges[j+1]; - if (rtop > bot || rbot < top) continue; - if (rtop <= top && rbot >= bot || - top <= rtop && bot >= rbot || - Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) { - vranges[j] = Math.min(top, rtop); - vranges[j+1] = Math.max(bot, rbot); - break; - } - } - if (j == vranges.length) vranges.push(top, bot); - data[i] = {left: size.left - outer.left, right: size.right - outer.left, top: j}; - } - for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) { - var vr = cur.top; - cur.top = vranges[vr]; cur.bottom = vranges[vr+1]; - } - return data; - } - - function clearCaches(cm) { - cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0; - cm.display.cachedCharWidth = cm.display.cachedTextHeight = null; - cm.view.maxLineChanged = true; - } - - // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page" - function intoCoordSystem(cm, lineObj, rect, context) { - if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { - var size = lineObj.widgets[i].node.offsetHeight; - rect.top += size; rect.bottom += size; - } - if (context == "line") return rect; - if (!context) context = "local"; - var yOff = heightAtLine(cm, lineObj); - if (context != "local") yOff -= cm.display.viewOffset; - if (context == "page") { - var lOff = cm.display.lineSpace.getBoundingClientRect(); - yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop); - var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft); - rect.left += xOff; rect.right += xOff; - } - rect.top += yOff; rect.bottom += yOff; - return rect; - } - - function charCoords(cm, pos, context, lineObj) { - if (!lineObj) lineObj = getLine(cm.view.doc, pos.line); - return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context); - } - - function cursorCoords(cm, pos, context, lineObj, measurement) { - lineObj = lineObj || getLine(cm.view.doc, pos.line); - if (!measurement) measurement = measureLine(cm, lineObj); - function get(ch, right) { - var m = measureChar(cm, lineObj, ch, measurement); - if (right) m.left = m.right; else m.right = m.left; - return intoCoordSystem(cm, lineObj, m, context); - } - var order = getOrder(lineObj), ch = pos.ch; - if (!order) return get(ch); - var main, other, linedir = order[0].level; - for (var i = 0; i < order.length; ++i) { - var part = order[i], rtl = part.level % 2, nb, here; - if (part.from < ch && part.to > ch) return get(ch, rtl); - var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to; - if (left == ch) { - // Opera and IE return bogus offsets and widths for edges - // where the direction flips, but only for the side with the - // lower level. So we try to use the side with the higher - // level. - if (i && part.level < (nb = order[i-1]).level) here = get(nb.level % 2 ? nb.from : nb.to - 1, true); - else here = get(rtl && part.from != part.to ? ch - 1 : ch); - if (rtl == linedir) main = here; else other = here; - } else if (right == ch) { - var nb = i < order.length - 1 && order[i+1]; - if (!rtl && nb && nb.from == nb.to) continue; - if (nb && part.level < nb.level) here = get(nb.level % 2 ? nb.to - 1 : nb.from); - else here = get(rtl ? ch : ch - 1, true); - if (rtl == linedir) main = here; else other = here; - } - } - if (linedir && !ch) other = get(order[0].to - 1); - if (!main) return other; - if (other) main.other = other; - return main; - } - - // Coords must be lineSpace-local - function coordsChar(cm, x, y) { - var doc = cm.view.doc; - y += cm.display.viewOffset; - if (y < 0) return {line: 0, ch: 0, outside: true}; - var lineNo = lineAtHeight(doc, y); - if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc, doc.size - 1).text.length}; - if (x < 0) x = 0; - - for (;;) { - var lineObj = getLine(doc, lineNo); - var found = coordsCharInner(cm, lineObj, lineNo, x, y); - var merged = collapsedSpanAtEnd(lineObj); - if (merged && found.ch == lineRight(lineObj)) - lineNo = merged.find().to.line; - else - return found; - } - } - - function coordsCharInner(cm, lineObj, lineNo, x, y) { - var innerOff = y - heightAtLine(cm, lineObj); - var wrongLine = false, cWidth = cm.display.wrapper.clientWidth; - var measurement = measureLine(cm, lineObj); - - function getX(ch) { - var sp = cursorCoords(cm, {line: lineNo, ch: ch}, "line", - lineObj, measurement); - wrongLine = true; - if (innerOff > sp.bottom) return Math.max(0, sp.left - cWidth); - else if (innerOff < sp.top) return sp.left + cWidth; - else wrongLine = false; - return sp.left; - } - - var bidi = getOrder(lineObj), dist = lineObj.text.length; - var from = lineLeft(lineObj), to = lineRight(lineObj); - var fromX = paddingLeft(cm.display), toX = getX(to); - - if (x > toX) return {line: lineNo, ch: to, outside: wrongLine}; - // Do a binary search between these bounds. - for (;;) { - if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { - var after = x - fromX < toX - x, ch = after ? from : to; - while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch; - return {line: lineNo, ch: ch, after: after, outside: wrongLine}; - } - var step = Math.ceil(dist / 2), middle = from + step; - if (bidi) { - middle = from; - for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); - } - var middleX = getX(middle); - if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; dist -= step;} - else {from = middle; fromX = middleX; dist = step;} - } - } - - var measureText; - function textHeight(display) { - if (display.cachedTextHeight != null) return display.cachedTextHeight; - if (measureText == null) { - measureText = elt("pre"); - // Measure a bunch of lines, for browsers that compute - // fractional heights. - for (var i = 0; i < 49; ++i) { - measureText.appendChild(document.createTextNode("x")); - measureText.appendChild(elt("br")); - } - measureText.appendChild(document.createTextNode("x")); - } - removeChildrenAndAdd(display.measure, measureText); - var height = measureText.offsetHeight / 50; - if (height > 3) display.cachedTextHeight = height; - removeChildren(display.measure); - return height || 1; - } - - function charWidth(display) { - if (display.cachedCharWidth != null) return display.cachedCharWidth; - var anchor = elt("span", "x"); - var pre = elt("pre", [anchor]); - removeChildrenAndAdd(display.measure, pre); - var width = anchor.offsetWidth; - if (width > 2) display.cachedCharWidth = width; - return width || 10; - } - - // OPERATIONS - - // Operations are used to wrap changes in such a way that each - // change won't have to update the cursor and display (which would - // be awkward, slow, and error-prone), but instead updates are - // batched and then all combined and executed at once. - - function startOperation(cm) { - if (cm.curOp) ++cm.curOp.depth; - else cm.curOp = { - // Nested operations delay update until the outermost one - // finishes. - depth: 1, - // An array of ranges of lines that have to be updated. See - // updateDisplay. - changes: [], - delayedCallbacks: [], - updateInput: null, - userSelChange: null, - textChanged: null, - selectionChanged: false, - updateMaxLine: false, - id: ++cm.nextOpId - }; - } - - function endOperation(cm) { - var op = cm.curOp; - if (--op.depth) return; - cm.curOp = null; - var view = cm.view, display = cm.display; - if (op.updateMaxLine) computeMaxLength(view); - if (view.maxLineChanged && !cm.options.lineWrapping) { - var width = measureChar(cm, view.maxLine, view.maxLine.text.length).right; - display.sizer.style.minWidth = (width + 3 + scrollerCutOff) + "px"; - view.maxLineChanged = false; - } - var newScrollPos, updated; - if (op.selectionChanged) { - var coords = cursorCoords(cm, view.sel.head); - newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom); - } - if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null) - updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop); - if (!updated && op.selectionChanged) updateSelection(cm); - if (newScrollPos) scrollCursorIntoView(cm); - if (op.selectionChanged) restartBlink(cm); - - if (view.focused && op.updateInput) - resetInput(cm, op.userSelChange); - - if (op.textChanged) - signal(cm, "change", cm, op.textChanged); - if (op.selectionChanged) signal(cm, "cursorActivity", cm); - for (var i = 0; i < op.delayedCallbacks.length; ++i) op.delayedCallbacks[i](cm); - } - - // Wraps a function in an operation. Returns the wrapped function. - function operation(cm1, f) { - return function() { - var cm = cm1 || this; - startOperation(cm); - try {var result = f.apply(cm, arguments);} - finally {endOperation(cm);} - return result; - }; - } - - function regChange(cm, from, to, lendiff) { - cm.curOp.changes.push({from: from, to: to, diff: lendiff}); - } - - // INPUT HANDLING - - function slowPoll(cm) { - if (cm.view.pollingFast) return; - cm.display.poll.set(cm.options.pollInterval, function() { - readInput(cm); - if (cm.view.focused) slowPoll(cm); - }); - } - - function fastPoll(cm) { - var missed = false; - cm.display.pollingFast = true; - function p() { - var changed = readInput(cm); - if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);} - else {cm.display.pollingFast = false; slowPoll(cm);} - } - cm.display.poll.set(20, p); - } - - // prevInput is a hack to work with IME. If we reset the textarea - // on every change, that breaks IME. So we look for changes - // compared to the previous content instead. (Modern browsers have - // events that indicate IME taking place, but these are not widely - // supported or compatible enough yet to rely on.) - function readInput(cm) { - var input = cm.display.input, prevInput = cm.display.prevInput, view = cm.view, sel = view.sel; - if (!view.focused || hasSelection(input) || isReadOnly(cm)) return false; - var text = input.value; - if (text == prevInput && posEq(sel.from, sel.to)) return false; - startOperation(cm); - view.sel.shift = false; - var same = 0, l = Math.min(prevInput.length, text.length); - while (same < l && prevInput[same] == text[same]) ++same; - var from = sel.from, to = sel.to; - if (same < prevInput.length) - from = {line: from.line, ch: from.ch - (prevInput.length - same)}; - else if (view.overwrite && posEq(from, to) && !cm.display.pasteIncoming) - to = {line: to.line, ch: Math.min(getLine(cm.view.doc, to.line).text.length, to.ch + (text.length - same))}; - var updateInput = cm.curOp.updateInput; - updateDoc(cm, from, to, splitLines(text.slice(same)), "end", - cm.display.pasteIncoming ? "paste" : "input", {from: from, to: to}); - cm.curOp.updateInput = updateInput; - if (text.length > 1000) input.value = cm.display.prevInput = ""; - else cm.display.prevInput = text; - endOperation(cm); - cm.display.pasteIncoming = false; - return true; - } - - function resetInput(cm, user) { - var view = cm.view, minimal, selected; - if (!posEq(view.sel.from, view.sel.to)) { - cm.display.prevInput = ""; - minimal = hasCopyEvent && - (view.sel.to.line - view.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000); - if (minimal) cm.display.input.value = "-"; - else cm.display.input.value = selected || cm.getSelection(); - if (view.focused) selectInput(cm.display.input); - } else if (user) cm.display.prevInput = cm.display.input.value = ""; - cm.display.inaccurateSelection = minimal; - } - - function focusInput(cm) { - if (cm.options.readOnly != "nocursor" && (ie || document.activeElement != cm.display.input)) - cm.display.input.focus(); - } - - function isReadOnly(cm) { - return cm.options.readOnly || cm.view.cantEdit; - } - - // EVENT HANDLERS - - function registerEventHandlers(cm) { - var d = cm.display; - on(d.scroller, "mousedown", operation(cm, onMouseDown)); - on(d.scroller, "dblclick", operation(cm, e_preventDefault)); - on(d.lineSpace, "selectstart", function(e) { - if (!mouseEventInWidget(d, e)) e_preventDefault(e); - }); - // Gecko browsers fire contextmenu *after* opening the menu, at - // which point we can't mess with it anymore. Context menu is - // handled in onMouseDown for Gecko. - if (!gecko) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); - - on(d.scroller, "scroll", function() { - setScrollTop(cm, d.scroller.scrollTop); - setScrollLeft(cm, d.scroller.scrollLeft, true); - signal(cm, "scroll", cm); - }); - on(d.scrollbarV, "scroll", function() { - setScrollTop(cm, d.scrollbarV.scrollTop); - }); - on(d.scrollbarH, "scroll", function() { - setScrollLeft(cm, d.scrollbarH.scrollLeft); - }); - - on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); - on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); - - function reFocus() { if (cm.view.focused) setTimeout(bind(focusInput, cm), 0); } - on(d.scrollbarH, "mousedown", reFocus); - on(d.scrollbarV, "mousedown", reFocus); - // Prevent wrapper from ever scrolling - on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); - on(window, "resize", function resizeHandler() { - // Might be a text scaling operation, clear size caches. - d.cachedCharWidth = d.cachedTextHeight = null; - clearCaches(cm); - if (d.wrapper.parentNode) updateDisplay(cm, true); - else off(window, "resize", resizeHandler); - }); - - on(d.input, "keyup", operation(cm, function(e) { - if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; - if (e_prop(e, "keyCode") == 16) cm.view.sel.shift = false; - })); - on(d.input, "input", bind(fastPoll, cm)); - on(d.input, "keydown", operation(cm, onKeyDown)); - on(d.input, "keypress", operation(cm, onKeyPress)); - on(d.input, "focus", bind(onFocus, cm)); - on(d.input, "blur", bind(onBlur, cm)); - - function drag_(e) { - if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return; - e_stop(e); - } - if (cm.options.dragDrop) { - on(d.scroller, "dragstart", function(e){onDragStart(cm, e);}); - on(d.scroller, "dragenter", drag_); - on(d.scroller, "dragover", drag_); - on(d.scroller, "drop", operation(cm, onDrop)); - } - on(d.scroller, "paste", function(){focusInput(cm); fastPoll(cm);}); - on(d.input, "paste", function() { - d.pasteIncoming = true; - fastPoll(cm); - }); - - function prepareCopy() { - if (d.inaccurateSelection) { - d.prevInput = ""; - d.inaccurateSelection = false; - d.input.value = cm.getSelection(); - selectInput(d.input); - } - } - on(d.input, "cut", prepareCopy); - on(d.input, "copy", prepareCopy); - - // Needed to handle Tab key in KHTML - if (khtml) on(d.sizer, "mouseup", function() { - if (document.activeElement == d.input) d.input.blur(); - focusInput(cm); - }); - } - - function mouseEventInWidget(display, e) { - for (var n = e_target(e); n != display.wrapper; n = n.parentNode) - if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) || - n.parentNode == display.sizer && n != display.mover) return true; - } - - function posFromMouse(cm, e, liberal) { - var display = cm.display; - if (!liberal) { - var target = e_target(e); - if (target == display.scrollbarH || target == display.scrollbarH.firstChild || - target == display.scrollbarV || target == display.scrollbarV.firstChild || - target == display.scrollbarFiller) return null; - } - var x, y, space = display.lineSpace.getBoundingClientRect(); - // Fails unpredictably on IE[67] when mouse is dragged around quickly. - try { x = e.clientX; y = e.clientY; } catch (e) { return null; } - return coordsChar(cm, x - space.left, y - space.top); - } - - var lastClick, lastDoubleClick; - function onMouseDown(e) { - var cm = this, display = cm.display, view = cm.view, sel = view.sel, doc = view.doc; - sel.shift = e_prop(e, "shiftKey"); - - if (mouseEventInWidget(display, e)) { - if (!webkit) { - display.scroller.draggable = false; - setTimeout(function(){display.scroller.draggable = true;}, 100); - } - return; - } - if (clickInGutter(cm, e)) return; - var start = posFromMouse(cm, e); - - switch (e_button(e)) { - case 3: - if (gecko) onContextMenu.call(cm, cm, e); - return; - case 2: - if (start) extendSelection(cm, start); - setTimeout(bind(focusInput, cm), 20); - e_preventDefault(e); - return; - } - // For button 1, if it was clicked inside the editor - // (posFromMouse returning non-null), we have to adjust the - // selection. - if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;} - - if (!view.focused) onFocus(cm); - - var now = +new Date, type = "single"; - if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { - type = "triple"; - e_preventDefault(e); - setTimeout(bind(focusInput, cm), 20); - selectLine(cm, start.line); - } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { - type = "double"; - lastDoubleClick = {time: now, pos: start}; - e_preventDefault(e); - var word = findWordAt(getLine(doc, start.line).text, start); - extendSelection(cm, word.from, word.to); - } else { lastClick = {time: now, pos: start}; } - - var last = start; - if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) && - !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") { - var dragEnd = operation(cm, function(e2) { - if (webkit) display.scroller.draggable = false; - view.draggingText = false; - off(document, "mouseup", dragEnd); - off(display.scroller, "drop", dragEnd); - if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { - e_preventDefault(e2); - extendSelection(cm, start); - focusInput(cm); - } - }); - // Let the drag handler handle this. - if (webkit) display.scroller.draggable = true; - view.draggingText = dragEnd; - // IE's approach to draggable - if (display.scroller.dragDrop) display.scroller.dragDrop(); - on(document, "mouseup", dragEnd); - on(display.scroller, "drop", dragEnd); - return; - } - e_preventDefault(e); - if (type == "single") extendSelection(cm, clipPos(doc, start)); - - var startstart = sel.from, startend = sel.to; - - function doSelect(cur) { - if (type == "single") { - extendSelection(cm, clipPos(doc, start), cur); - return; - } - - startstart = clipPos(doc, startstart); - startend = clipPos(doc, startend); - if (type == "double") { - var word = findWordAt(getLine(doc, cur.line).text, cur); - if (posLess(cur, startstart)) extendSelection(cm, word.from, startend); - else extendSelection(cm, startstart, word.to); - } else if (type == "triple") { - if (posLess(cur, startstart)) extendSelection(cm, startend, clipPos(doc, {line: cur.line, ch: 0})); - else extendSelection(cm, startstart, clipPos(doc, {line: cur.line + 1, ch: 0})); - } - } - - var editorSize = display.wrapper.getBoundingClientRect(); - // Used to ensure timeout re-tries don't fire when another extend - // happened in the meantime (clearTimeout isn't reliable -- at - // least on Chrome, the timeouts still happen even when cleared, - // if the clear happens after their scheduled firing time). - var counter = 0; - - function extend(e) { - var curCount = ++counter; - var cur = posFromMouse(cm, e, true); - if (!cur) return; - if (!posEq(cur, last)) { - if (!view.focused) onFocus(cm); - last = cur; - doSelect(cur); - var visible = visibleLines(display, doc); - if (cur.line >= visible.to || cur.line < visible.from) - setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); - } else { - var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; - if (outside) setTimeout(operation(cm, function() { - if (counter != curCount) return; - display.scroller.scrollTop += outside; - extend(e); - }), 50); - } - } - - function done(e) { - counter = Infinity; - var cur = posFromMouse(cm, e); - if (cur) doSelect(cur); - e_preventDefault(e); - focusInput(cm); - off(document, "mousemove", move); - off(document, "mouseup", up); - } - - var move = operation(cm, function(e) { - if (!ie && !e_button(e)) done(e); - else extend(e); - }); - var up = operation(cm, done); - on(document, "mousemove", move); - on(document, "mouseup", up); - } - - function onDrop(e) { - var cm = this; - if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return; - e_preventDefault(e); - var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; - if (!pos || isReadOnly(cm)) return; - if (files && files.length && window.FileReader && window.File) { - var n = files.length, text = Array(n), read = 0; - var loadFile = function(file, i) { - var reader = new FileReader; - reader.onload = function() { - text[i] = reader.result; - if (++read == n) { - pos = clipPos(cm.view.doc, pos); - operation(cm, function() { - var end = replaceRange(cm, text.join(""), pos, pos, "paste"); - setSelection(cm, pos, end); - })(); - } - }; - reader.readAsText(file); - }; - for (var i = 0; i < n; ++i) loadFile(files[i], i); - } else { - // Don't do a replace if the drop happened inside of the selected text. - if (cm.view.draggingText && !(posLess(pos, cm.view.sel.from) || posLess(cm.view.sel.to, pos))) { - cm.view.draggingText(e); - if (ie) setTimeout(bind(focusInput, cm), 50); - return; - } - try { - var text = e.dataTransfer.getData("Text"); - if (text) { - var curFrom = cm.view.sel.from, curTo = cm.view.sel.to; - setSelection(cm, pos, pos); - if (cm.view.draggingText) replaceRange(cm, "", curFrom, curTo, "paste"); - cm.replaceSelection(text, null, "paste"); - focusInput(cm); - onFocus(cm); - } - } - catch(e){} - } - } - - function clickInGutter(cm, e) { - var display = cm.display; - try { var mX = e.clientX, mY = e.clientY; } - catch(e) { return false; } - - if (mX >= Math.floor(display.gutters.getBoundingClientRect().right)) return false; - e_preventDefault(e); - if (!hasHandler(cm, "gutterClick")) return true; - - var lineBox = display.lineDiv.getBoundingClientRect(); - if (mY > lineBox.bottom) return true; - mY -= lineBox.top - display.viewOffset; - - for (var i = 0; i < cm.options.gutters.length; ++i) { - var g = display.gutters.childNodes[i]; - if (g && g.getBoundingClientRect().right >= mX) { - var line = lineAtHeight(cm.view.doc, mY); - var gutter = cm.options.gutters[i]; - signalLater(cm, cm, "gutterClick", cm, line, gutter, e); - break; - } - } - return true; - } - - function onDragStart(cm, e) { - var txt = cm.getSelection(); - e.dataTransfer.setData("Text", txt); - - // Use dummy image instead of default browsers image. - // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. - if (e.dataTransfer.setDragImage && !safari) - e.dataTransfer.setDragImage(elt('img'), 0, 0); - } - - function setScrollTop(cm, val) { - if (Math.abs(cm.view.scrollTop - val) < 2) return; - cm.view.scrollTop = val; - if (!gecko) updateDisplay(cm, [], val); - if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; - if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val; - if (gecko) updateDisplay(cm, []); - } - function setScrollLeft(cm, val, isScroller) { - if (isScroller ? val == cm.view.scrollLeft : Math.abs(cm.view.scrollLeft - val) < 2) return; - cm.view.scrollLeft = val; - alignHorizontally(cm); - if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; - if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val; - } - - // Since the delta values reported on mouse wheel events are - // unstandardized between browsers and even browser versions, and - // generally horribly unpredictable, this code starts by measuring - // the scroll effect that the first few mouse wheel events have, - // and, from that, detects the way it can convert deltas to pixel - // offsets afterwards. - // - // The reason we want to know the amount a wheel event will scroll - // is that it gives us a chance to update the display before the - // actual scrolling happens, reducing flickering. - - var wheelSamples = 0, wheelDX, wheelDY, wheelStartX, wheelStartY, wheelPixelsPerUnit = null; - // Fill in a browser-detected starting value on browsers where we - // know one. These don't have to be accurate -- the result of them - // being wrong would just be a slight flicker on the first wheel - // scroll (if it is large enough). - if (ie) wheelPixelsPerUnit = -.53; - else if (gecko) wheelPixelsPerUnit = 15; - else if (chrome) wheelPixelsPerUnit = -.7; - else if (safari) wheelPixelsPerUnit = -1/3; - - function onScrollWheel(cm, e) { - var dx = e.wheelDeltaX, dy = e.wheelDeltaY; - if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; - if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; - else if (dy == null) dy = e.wheelDelta; - - // Webkit browsers on OS X abort momentum scrolls when the target - // of the scroll event is removed from the scrollable element. - // This hack (see related code in patchDisplay) makes sure the - // element is kept around. - if (dy && mac && webkit) { - for (var cur = e.target; cur != scroll; cur = cur.parentNode) { - if (cur.lineObj) { - cm.display.currentWheelTarget = cur; - break; - } - } - } - - var scroll = cm.display.scroller; - // On some browsers, horizontal scrolling will cause redraws to - // happen before the gutter has been realigned, causing it to - // wriggle around in a most unseemly way. When we have an - // estimated pixels/delta value, we just handle horizontal - // scrolling entirely here. It'll be slightly off from native, but - // better than glitching out. - if (dx && !gecko && !opera && wheelPixelsPerUnit != null) { - if (dy) - setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); - setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); - e_preventDefault(e); - wheelStartX = null; // Abort measurement, if in progress - return; - } - - if (dy && wheelPixelsPerUnit != null) { - var pixels = dy * wheelPixelsPerUnit; - var top = cm.view.scrollTop, bot = top + cm.display.wrapper.clientHeight; - if (pixels < 0) top = Math.max(0, top + pixels - 50); - else bot = Math.min(cm.view.doc.height, bot + pixels + 50); - updateDisplay(cm, [], {top: top, bottom: bot}); - } - - if (wheelSamples < 20) { - if (wheelStartX == null) { - wheelStartX = scroll.scrollLeft; wheelStartY = scroll.scrollTop; - wheelDX = dx; wheelDY = dy; - setTimeout(function() { - if (wheelStartX == null) return; - var movedX = scroll.scrollLeft - wheelStartX; - var movedY = scroll.scrollTop - wheelStartY; - var sample = (movedY && wheelDY && movedY / wheelDY) || - (movedX && wheelDX && movedX / wheelDX); - wheelStartX = wheelStartY = null; - if (!sample) return; - wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); - ++wheelSamples; - }, 200); - } else { - wheelDX += dx; wheelDY += dy; - } - } - } - - function doHandleBinding(cm, bound, dropShift) { - if (typeof bound == "string") { - bound = commands[bound]; - if (!bound) return false; - } - // Ensure previous input has been read, so that the handler sees a - // consistent view of the document - if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false; - var view = cm.view, prevShift = view.sel.shift; - try { - if (isReadOnly(cm)) view.suppressEdits = true; - if (dropShift) view.sel.shift = false; - bound(cm); - } catch(e) { - if (e != Pass) throw e; - return false; - } finally { - view.sel.shift = prevShift; - view.suppressEdits = false; - } - return true; - } - - function allKeyMaps(cm) { - var maps = cm.view.keyMaps.slice(0); - maps.push(cm.options.keyMap); - if (cm.options.extraKeys) maps.unshift(cm.options.extraKeys); - return maps; - } - - var maybeTransition; - function handleKeyBinding(cm, e) { - // Handle auto keymap transitions - var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto; - clearTimeout(maybeTransition); - if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { - if (getKeyMap(cm.options.keyMap) == startMap) - cm.options.keyMap = (next.call ? next.call(null, cm) : next); - }, 50); - - var name = keyNames[e_prop(e, "keyCode")], handled = false; - var flipCtrlCmd = mac && (opera || qtwebkit); - if (name == null || e.altGraphKey) return false; - if (e_prop(e, "altKey")) name = "Alt-" + name; - if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name; - if (e_prop(e, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name; - - var stopped = false; - function stop() { stopped = true; } - var keymaps = allKeyMaps(cm); - - if (e_prop(e, "shiftKey")) { - handled = lookupKey("Shift-" + name, keymaps, - function(b) {return doHandleBinding(cm, b, true);}, stop) - || lookupKey(name, keymaps, function(b) { - if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(cm, b); - }, stop); - } else { - handled = lookupKey(name, keymaps, - function(b) { return doHandleBinding(cm, b); }, stop); - } - if (stopped) handled = false; - if (handled) { - e_preventDefault(e); - restartBlink(cm); - if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } - } - return handled; - } - - function handleCharBinding(cm, e, ch) { - var handled = lookupKey("'" + ch + "'", allKeyMaps(cm), - function(b) { return doHandleBinding(cm, b, true); }); - if (handled) { - e_preventDefault(e); - restartBlink(cm); - } - return handled; - } - - var lastStoppedKey = null; - function onKeyDown(e) { - var cm = this; - if (!cm.view.focused) onFocus(cm); - if (ie && e.keyCode == 27) { e.returnValue = false; } - if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; - var code = e_prop(e, "keyCode"); - // IE does strange things with escape. - cm.view.sel.shift = code == 16 || e_prop(e, "shiftKey"); - // First give onKeyEvent option a chance to handle this. - var handled = handleKeyBinding(cm, e); - if (opera) { - lastStoppedKey = handled ? code : null; - // Opera has no cut event... we try to at least catch the key combo - if (!handled && code == 88 && !hasCopyEvent && e_prop(e, mac ? "metaKey" : "ctrlKey")) - cm.replaceSelection(""); - } - } - - function onKeyPress(e) { - var cm = this; - if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; - var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); - if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} - if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; - var ch = String.fromCharCode(charCode == null ? keyCode : charCode); - if (this.options.electricChars && this.view.mode.electricChars && - this.options.smartIndent && !isReadOnly(this) && - this.view.mode.electricChars.indexOf(ch) > -1) - setTimeout(operation(cm, function() {indentLine(cm, cm.view.sel.to.line, "smart");}), 75); - if (handleCharBinding(cm, e, ch)) return; - fastPoll(cm); - } - - function onFocus(cm) { - if (cm.options.readOnly == "nocursor") return; - if (!cm.view.focused) { - signal(cm, "focus", cm); - cm.view.focused = true; - if (cm.display.scroller.className.search(/\bCodeMirror-focused\b/) == -1) - cm.display.scroller.className += " CodeMirror-focused"; - resetInput(cm, true); - } - slowPoll(cm); - restartBlink(cm); - } - function onBlur(cm) { - if (cm.view.focused) { - signal(cm, "blur", cm); - cm.view.focused = false; - cm.display.scroller.className = cm.display.scroller.className.replace(" CodeMirror-focused", ""); - } - clearInterval(cm.display.blinker); - setTimeout(function() {if (!cm.view.focused) cm.view.sel.shift = false;}, 150); - } - - var detectingSelectAll; - function onContextMenu(cm, e) { - var display = cm.display, sel = cm.view.sel; - var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; - if (!pos || opera) return; // Opera is difficult. - if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) - operation(cm, setSelection)(cm, pos, pos); - - var oldCSS = display.input.style.cssText; - display.inputDiv.style.position = "absolute"; - display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + - "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" + - "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; - focusInput(cm); - resetInput(cm, true); - // Adds "Select all" to context menu in FF - if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " "; - - function rehide() { - display.inputDiv.style.position = "relative"; - display.input.style.cssText = oldCSS; - if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos; - slowPoll(cm); - - // Try to detect the user choosing select-all - if (display.input.selectionStart != null) { - clearTimeout(detectingSelectAll); - var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value), i = 0; - display.prevInput = " "; - display.input.selectionStart = 1; display.input.selectionEnd = extval.length; - detectingSelectAll = setTimeout(function poll(){ - if (display.prevInput == " " && display.input.selectionStart == 0) - operation(cm, commands.selectAll)(cm); - else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); - else resetInput(cm); - }, 200); - } - } - - if (gecko) { - e_stop(e); - on(window, "mouseup", function mouseup() { - off(window, "mouseup", mouseup); - setTimeout(rehide, 20); - }); - } else { - setTimeout(rehide, 50); - } - } - - // UPDATING - - // Replace the range from from to to by the strings in newText. - // Afterwards, set the selection to selFrom, selTo. - function updateDoc(cm, from, to, newText, selUpdate, origin) { - // Possibly split or suppress the update based on the presence - // of read-only spans in its range. - var split = sawReadOnlySpans && - removeReadOnlyRanges(cm.view.doc, from, to); - if (split) { - for (var i = split.length - 1; i >= 1; --i) - updateDocInner(cm, split[i].from, split[i].to, [""], origin); - if (split.length) - return updateDocInner(cm, split[0].from, split[0].to, newText, selUpdate, origin); - } else { - return updateDocInner(cm, from, to, newText, selUpdate, origin); - } - } - - function updateDocInner(cm, from, to, newText, selUpdate, origin) { - if (cm.view.suppressEdits) return; - - var view = cm.view, doc = view.doc, old = []; - doc.iter(from.line, to.line + 1, function(line) { - old.push(newHL(line.text, line.markedSpans)); - }); - var startSelFrom = view.sel.from, startSelTo = view.sel.to; - var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText); - var retval = updateDocNoUndo(cm, from, to, lines, selUpdate, origin); - if (view.history) addChange(cm, from.line, newText.length, old, origin, - startSelFrom, startSelTo, view.sel.from, view.sel.to); - return retval; - } - - function unredoHelper(cm, type) { - var doc = cm.view.doc, hist = cm.view.history; - var set = (type == "undo" ? hist.done : hist.undone).pop(); - if (!set) return; - var anti = {events: [], fromBefore: set.fromAfter, toBefore: set.toAfter, - fromAfter: set.fromBefore, toAfter: set.toBefore}; - for (var i = set.events.length - 1; i >= 0; i -= 1) { - hist.dirtyCounter += type == "undo" ? -1 : 1; - var change = set.events[i]; - var replaced = [], end = change.start + change.added; - doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); }); - anti.events.push({start: change.start, added: change.old.length, old: replaced}); - var selPos = i ? null : {from: set.fromBefore, to: set.toBefore}; - updateDocNoUndo(cm, {line: change.start, ch: 0}, {line: end - 1, ch: getLine(doc, end-1).text.length}, - change.old, selPos, type); - } - (type == "undo" ? hist.undone : hist.done).push(anti); - } - - function updateDocNoUndo(cm, from, to, lines, selUpdate, origin) { - var view = cm.view, doc = view.doc, display = cm.display; - if (view.suppressEdits) return; - - var nlines = to.line - from.line, firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); - var recomputeMaxLength = false, checkWidthStart = from.line; - if (!cm.options.lineWrapping) { - checkWidthStart = lineNo(visualLine(doc, firstLine)); - doc.iter(checkWidthStart, to.line + 1, function(line) { - if (lineLength(doc, line) == view.maxLineLength) { - recomputeMaxLength = true; - return true; - } - }); - } - - var lastHL = lst(lines), th = textHeight(display); - - // First adjust the line structure - if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") { - // This is a whole-line replace. Treated specially to make - // sure line objects move the way they are supposed to. - var added = []; - for (var i = 0, e = lines.length - 1; i < e; ++i) - added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th)); - updateLine(cm, lastLine, lastLine.text, hlSpans(lastHL)); - if (nlines) doc.remove(from.line, nlines, cm); - if (added.length) doc.insert(from.line, added); - } else if (firstLine == lastLine) { - if (lines.length == 1) { - updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]) + - firstLine.text.slice(to.ch), hlSpans(lines[0])); - } else { - for (var added = [], i = 1, e = lines.length - 1; i < e; ++i) - added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th)); - added.push(makeLine(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL), th)); - updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); - doc.insert(from.line + 1, added); - } - } else if (lines.length == 1) { - updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]) + - lastLine.text.slice(to.ch), hlSpans(lines[0])); - doc.remove(from.line + 1, nlines, cm); - } else { - var added = []; - updateLine(cm, firstLine, firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); - updateLine(cm, lastLine, hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL)); - for (var i = 1, e = lines.length - 1; i < e; ++i) - added.push(makeLine(hlText(lines[i]), hlSpans(lines[i]), th)); - if (nlines > 1) doc.remove(from.line + 1, nlines - 1, cm); - doc.insert(from.line + 1, added); - } - - if (cm.options.lineWrapping) { - var perLine = Math.max(5, display.scroller.clientWidth / charWidth(display) - 3); - doc.iter(from.line, from.line + lines.length, function(line) { - if (line.height == 0) return; - var guess = (Math.ceil(line.text.length / perLine) || 1) * th; - if (guess != line.height) updateLineHeight(line, guess); - }); - } else { - doc.iter(checkWidthStart, from.line + lines.length, function(line) { - var len = lineLength(doc, line); - if (len > view.maxLineLength) { - view.maxLine = line; - view.maxLineLength = len; - view.maxLineChanged = true; - recomputeMaxLength = false; - } - }); - if (recomputeMaxLength) cm.curOp.updateMaxLine = true; - } - - // Adjust frontier, schedule worker - view.frontier = Math.min(view.frontier, from.line); - startWorker(cm, 400); - - var lendiff = lines.length - nlines - 1; - // Remember that these lines changed, for updating the display - regChange(cm, from.line, to.line + 1, lendiff); - if (hasHandler(cm, "change")) { - // Normalize lines to contain only strings, since that's what - // the change event handler expects - for (var i = 0; i < lines.length; ++i) - if (typeof lines[i] != "string") lines[i] = lines[i].text; - var changeObj = {from: from, to: to, text: lines, origin: origin}; - if (cm.curOp.textChanged) { - for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {} - cur.next = changeObj; - } else cm.curOp.textChanged = changeObj; - } - - // Update the selection - var newSelFrom, newSelTo, end = {line: from.line + lines.length - 1, - ch: hlText(lastHL).length + (lines.length == 1 ? from.ch : 0)}; - if (selUpdate && typeof selUpdate != "string") { - if (selUpdate.from) { newSelFrom = selUpdate.from; newSelTo = selUpdate.to; } - else newSelFrom = newSelTo = selUpdate; - } else if (selUpdate == "end") { - newSelFrom = newSelTo = end; - } else if (selUpdate == "start") { - newSelFrom = newSelTo = from; - } else if (selUpdate == "around") { - newSelFrom = from; newSelTo = end; - } else { - var adjustPos = function(pos) { - if (posLess(pos, from)) return pos; - if (!posLess(to, pos)) return end; - var line = pos.line + lendiff; - var ch = pos.ch; - if (pos.line == to.line) - ch += hlText(lastHL).length - (to.ch - (to.line == from.line ? from.ch : 0)); - return {line: line, ch: ch}; - }; - newSelFrom = adjustPos(view.sel.from); - newSelTo = adjustPos(view.sel.to); - } - setSelection(cm, newSelFrom, newSelTo, null, true); - return end; - } - - function replaceRange(cm, code, from, to, origin) { - if (!to) to = from; - if (posLess(to, from)) { var tmp = to; to = from; from = tmp; } - return updateDoc(cm, from, to, splitLines(code), null, origin); - } - - // SELECTION - - function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} - function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} - function copyPos(x) {return {line: x.line, ch: x.ch};} - - function clipLine(doc, n) {return Math.max(0, Math.min(n, doc.size-1));} - function clipPos(doc, pos) { - if (pos.line < 0) return {line: 0, ch: 0}; - if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc, doc.size-1).text.length}; - var ch = pos.ch, linelen = getLine(doc, pos.line).text.length; - if (ch == null || ch > linelen) return {line: pos.line, ch: linelen}; - else if (ch < 0) return {line: pos.line, ch: 0}; - else return pos; - } - function isLine(doc, l) {return l >= 0 && l < doc.size;} - - // If shift is held, this will move the selection anchor. Otherwise, - // it'll set the whole selection. - function extendSelection(cm, pos, other, bias) { - var sel = cm.view.sel; - if (sel.shift || sel.extend) { - var anchor = sel.anchor; - if (other) { - var posBefore = posLess(pos, anchor); - if (posBefore != posLess(other, anchor)) { - anchor = pos; - pos = other; - } else if (posBefore != posLess(pos, other)) { - pos = other; - } - } - setSelection(cm, anchor, pos, bias); - } else { - setSelection(cm, pos, other || pos, bias); - } - cm.curOp.userSelChange = true; - } - - // Update the selection. Last two args are only used by - // updateDoc, since they have to be expressed in the line - // numbers before the update. - function setSelection(cm, anchor, head, bias, checkAtomic) { - cm.view.goalColumn = null; - var sel = cm.view.sel; - // Skip over atomic spans. - if (checkAtomic || !posEq(anchor, sel.anchor)) - anchor = skipAtomic(cm, anchor, bias, checkAtomic != "push"); - if (checkAtomic || !posEq(head, sel.head)) - head = skipAtomic(cm, head, bias, checkAtomic != "push"); - - if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return; - - sel.anchor = anchor; sel.head = head; - var inv = posLess(head, anchor); - sel.from = inv ? head : anchor; - sel.to = inv ? anchor : head; - - cm.curOp.updateInput = true; - cm.curOp.selectionChanged = true; - } - - function reCheckSelection(cm) { - setSelection(cm, cm.view.sel.from, cm.view.sel.to, null, "push"); - } - - function skipAtomic(cm, pos, bias, mayClear) { - var doc = cm.view.doc, flipped = false, curPos = pos; - var dir = bias || 1; - cm.view.cantEdit = false; - search: for (;;) { - var line = getLine(doc, curPos.line), toClear; - if (line.markedSpans) { - for (var i = 0; i < line.markedSpans.length; ++i) { - var sp = line.markedSpans[i], m = sp.marker; - if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && - (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { - if (mayClear && m.clearOnEnter) { - (toClear || (toClear = [])).push(m); - continue; - } else if (!m.atomic) continue; - var newPos = m.find()[dir < 0 ? "from" : "to"]; - if (posEq(newPos, curPos)) { - newPos.ch += dir; - if (newPos.ch < 0) { - if (newPos.line) newPos = clipPos(doc, {line: newPos.line - 1}); - else newPos = null; - } else if (newPos.ch > line.text.length) { - if (newPos.line < doc.size - 1) newPos = {line: newPos.line + 1, ch: 0}; - else newPos = null; - } - if (!newPos) { - if (flipped) { - // Driven in a corner -- no valid cursor position found at all - // -- try again *with* clearing, if we didn't already - if (!mayClear) return skipAtomic(cm, pos, bias, true); - // Otherwise, turn off editing until further notice, and return the start of the doc - cm.view.cantEdit = true; - return {line: 0, ch: 0}; - } - flipped = true; newPos = pos; dir = -dir; - } - } - curPos = newPos; - continue search; - } - } - if (toClear) for (var i = 0; i < toClear.length; ++i) toClear[i].clear(); - } - return curPos; - } - } - - // SCROLLING - - function scrollCursorIntoView(cm) { - var view = cm.view; - var coords = scrollPosIntoView(cm, view.sel.head); - if (!view.focused) return; - var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; - if (coords.top + box.top < 0) doScroll = true; - else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; - if (doScroll != null && !phantom) { - var hidden = display.cursor.style.display == "none"; - if (hidden) { - display.cursor.style.display = ""; - display.cursor.style.left = coords.left + "px"; - display.cursor.style.top = (coords.top - display.viewOffset) + "px"; - } - display.cursor.scrollIntoView(doScroll); - if (hidden) display.cursor.style.display = "none"; - } - } - - function scrollPosIntoView(cm, pos) { - for (;;) { - var changed = false, coords = cursorCoords(cm, pos); - var scrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom); - var startTop = cm.view.scrollTop, startLeft = cm.view.scrollLeft; - if (scrollPos.scrollTop != null) { - setScrollTop(cm, scrollPos.scrollTop); - if (Math.abs(cm.view.scrollTop - startTop) > 1) changed = true; - } - if (scrollPos.scrollLeft != null) { - setScrollLeft(cm, scrollPos.scrollLeft); - if (Math.abs(cm.view.scrollLeft - startLeft) > 1) changed = true; - } - if (!changed) return coords; - } - } - - function scrollIntoView(cm, x1, y1, x2, y2) { - var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); - if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); - if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); - } - - function calculateScrollPos(cm, x1, y1, x2, y2) { - var display = cm.display, pt = paddingTop(display); - y1 += pt; y2 += pt; - var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {}; - var docBottom = cm.view.doc.height + 2 * pt; - var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10; - if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1); - else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen; - - var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft; - x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth; - var gutterw = display.gutters.offsetWidth; - var atLeft = x1 < gutterw + 10; - if (x1 < screenleft + gutterw || atLeft) { - if (atLeft) x1 = 0; - result.scrollLeft = Math.max(0, x1 - 10 - gutterw); - } else if (x2 > screenw + screenleft - 3) { - result.scrollLeft = x2 + 10 - screenw; - } - return result; - } - - // API UTILITIES - - function indentLine(cm, n, how, aggressive) { - var doc = cm.view.doc; - if (!how) how = "add"; - if (how == "smart") { - if (!cm.view.mode.indent) how = "prev"; - else var state = getStateBefore(cm, n); - } - - var tabSize = cm.options.tabSize; - var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); - var curSpaceString = line.text.match(/^\s*/)[0], indentation; - if (how == "smart") { - indentation = cm.view.mode.indent(state, line.text.slice(curSpaceString.length), line.text); - if (indentation == Pass) { - if (!aggressive) return; - how = "prev"; - } - } - if (how == "prev") { - if (n) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); - else indentation = 0; - } - else if (how == "add") indentation = curSpace + cm.options.indentUnit; - else if (how == "subtract") indentation = curSpace - cm.options.indentUnit; - indentation = Math.max(0, indentation); - - var indentString = "", pos = 0; - if (cm.options.indentWithTabs) - for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} - if (pos < indentation) indentString += spaceStr(indentation - pos); - - if (indentString != curSpaceString) - replaceRange(cm, indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}, "input"); - line.stateAfter = null; - } - - function changeLine(cm, handle, op) { - var no = handle, line = handle, doc = cm.view.doc; - if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); - else no = lineNo(handle); - if (no == null) return null; - if (op(line, no)) regChange(cm, no, no + 1); - else return null; - return line; - } - - function findPosH(cm, dir, unit, visually) { - var doc = cm.view.doc, end = cm.view.sel.head, line = end.line, ch = end.ch; - var lineObj = getLine(doc, line); - function findNextLine() { - var l = line + dir; - if (l < 0 || l == doc.size) return false; - line = l; - return lineObj = getLine(doc, l); - } - function moveOnce(boundToLine) { - var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); - if (next == null) { - if (!boundToLine && findNextLine()) { - if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); - else ch = dir < 0 ? lineObj.text.length : 0; - } else return false; - } else ch = next; - return true; - } - if (unit == "char") moveOnce(); - else if (unit == "column") moveOnce(true); - else if (unit == "word") { - var sawWord = false; - for (;;) { - if (dir < 0) if (!moveOnce()) break; - if (isWordChar(lineObj.text.charAt(ch))) sawWord = true; - else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;} - if (dir > 0) if (!moveOnce()) break; - } - } - return skipAtomic(cm, {line: line, ch: ch}, dir, true); - } - - function findWordAt(line, pos) { - var start = pos.ch, end = pos.ch; - if (line) { - if (pos.after === false || end == line.length) --start; else ++end; - var startChar = line.charAt(start); - var check = isWordChar(startChar) ? isWordChar : - /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} : - function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; - while (start > 0 && check(line.charAt(start - 1))) --start; - while (end < line.length && check(line.charAt(end))) ++end; - } - return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}}; - } - - function selectLine(cm, line) { - extendSelection(cm, {line: line, ch: 0}, clipPos(cm.view.doc, {line: line + 1, ch: 0})); - } - - // PROTOTYPE - - // The publicly visible API. Note that operation(null, f) means - // 'wrap f in an operation, performed on its `this` parameter' - - CodeMirror.prototype = { - getValue: function(lineSep) { - var text = [], doc = this.view.doc; - doc.iter(0, doc.size, function(line) { text.push(line.text); }); - return text.join(lineSep || "\n"); - }, - - setValue: operation(null, function(code) { - var doc = this.view.doc, top = {line: 0, ch: 0}, lastLen = getLine(doc, doc.size-1).text.length; - updateDocInner(this, top, {line: doc.size - 1, ch: lastLen}, splitLines(code), top, top, "setValue"); - }), - - getSelection: function(lineSep) { return this.getRange(this.view.sel.from, this.view.sel.to, lineSep); }, - - replaceSelection: operation(null, function(code, collapse, origin) { - var sel = this.view.sel; - updateDoc(this, sel.from, sel.to, splitLines(code), collapse || "around", origin); - }), - - focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);}, - - setOption: function(option, value) { - var options = this.options, old = options[option]; - if (options[option] == value && option != "mode") return; - options[option] = value; - if (optionHandlers.hasOwnProperty(option)) - operation(this, optionHandlers[option])(this, value, old); - }, - - getOption: function(option) {return this.options[option];}, - - getMode: function() {return this.view.mode;}, - - addKeyMap: function(map) { - this.view.keyMaps.push(map); - }, - - removeKeyMap: function(map) { - var maps = this.view.keyMaps; - for (var i = 0; i < maps.length; ++i) - if ((typeof map == "string" ? maps[i].name : maps[i]) == map) { - maps.splice(i, 1); - return true; - } - }, - - undo: operation(null, function() {unredoHelper(this, "undo");}), - redo: operation(null, function() {unredoHelper(this, "redo");}), - - indentLine: operation(null, function(n, dir, aggressive) { - if (typeof dir != "string") { - if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; - else dir = dir ? "add" : "subtract"; - } - if (isLine(this.view.doc, n)) indentLine(this, n, dir, aggressive); - }), - - indentSelection: operation(null, function(how) { - var sel = this.view.sel; - if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how); - var e = sel.to.line - (sel.to.ch ? 0 : 1); - for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how); - }), - - historySize: function() { - var hist = this.view.history; - return {undo: hist.done.length, redo: hist.undone.length}; - }, - - clearHistory: function() {this.view.history = makeHistory();}, - - markClean: function() { - this.view.history.dirtyCounter = 0; - this.view.history.lastOp = this.view.history.lastOrigin = null; - }, - - isClean: function () {return this.view.history.dirtyCounter == 0;}, - - getHistory: function() { - var hist = this.view.history; - function cp(arr) { - for (var i = 0, nw = [], nwelt; i < arr.length; ++i) { - var set = arr[i]; - nw.push({events: nwelt = [], fromBefore: set.fromBefore, toBefore: set.toBefore, - fromAfter: set.fromAfter, toAfter: set.toAfter}); - for (var j = 0, elt = set.events; j < elt.length; ++j) { - var old = [], cur = elt[j]; - nwelt.push({start: cur.start, added: cur.added, old: old}); - for (var k = 0; k < cur.old.length; ++k) old.push(hlText(cur.old[k])); - } - } - return nw; - } - return {done: cp(hist.done), undone: cp(hist.undone)}; - }, - - setHistory: function(histData) { - var hist = this.view.history = makeHistory(); - hist.done = histData.done; - hist.undone = histData.undone; - }, - - // Fetch the parser token for a given character. Useful for hacks - // that want to inspect the mode state (say, for completion). - getTokenAt: function(pos) { - var doc = this.view.doc; - pos = clipPos(doc, pos); - var state = getStateBefore(this, pos.line), mode = this.view.mode; - var line = getLine(doc, pos.line); - var stream = new StringStream(line.text, this.options.tabSize); - while (stream.pos < pos.ch && !stream.eol()) { - stream.start = stream.pos; - var style = mode.token(stream, state); - } - return {start: stream.start, - end: stream.pos, - string: stream.current(), - className: style || null, // Deprecated, use 'type' instead - type: style || null, - state: state}; - }, - - getStateAfter: function(line) { - var doc = this.view.doc; - line = clipLine(doc, line == null ? doc.size - 1: line); - return getStateBefore(this, line + 1); - }, - - cursorCoords: function(start, mode) { - var pos, sel = this.view.sel; - if (start == null) pos = sel.head; - else if (typeof start == "object") pos = clipPos(this.view.doc, start); - else pos = start ? sel.from : sel.to; - return cursorCoords(this, pos, mode || "page"); - }, - - charCoords: function(pos, mode) { - return charCoords(this, clipPos(this.view.doc, pos), mode || "page"); - }, - - coordsChar: function(coords) { - var off = this.display.lineSpace.getBoundingClientRect(); - return coordsChar(this, coords.left - off.left, coords.top - off.top); - }, - - defaultTextHeight: function() { return textHeight(this.display); }, - - markText: operation(null, function(from, to, options) { - return markText(this, clipPos(this.view.doc, from), clipPos(this.view.doc, to), - options, "range"); - }), - - setBookmark: operation(null, function(pos, widget) { - pos = clipPos(this.view.doc, pos); - return markText(this, pos, pos, widget ? {replacedWith: widget} : {}, "bookmark"); - }), - - findMarksAt: function(pos) { - var doc = this.view.doc; - pos = clipPos(doc, pos); - var markers = [], spans = getLine(doc, pos.line).markedSpans; - if (spans) for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if ((span.from == null || span.from <= pos.ch) && - (span.to == null || span.to >= pos.ch)) - markers.push(span.marker); - } - return markers; - }, - - setGutterMarker: operation(null, function(line, gutterID, value) { - return changeLine(this, line, function(line) { - var markers = line.gutterMarkers || (line.gutterMarkers = {}); - markers[gutterID] = value; - if (!value && isEmpty(markers)) line.gutterMarkers = null; - return true; - }); - }), - - clearGutter: operation(null, function(gutterID) { - var i = 0, cm = this, doc = cm.view.doc; - doc.iter(0, doc.size, function(line) { - if (line.gutterMarkers && line.gutterMarkers[gutterID]) { - line.gutterMarkers[gutterID] = null; - regChange(cm, i, i + 1); - if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; - } - ++i; - }); - }), - - addLineClass: operation(null, function(handle, where, cls) { - return changeLine(this, handle, function(line) { - var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; - if (!line[prop]) line[prop] = cls; - else if (new RegExp("\\b" + cls + "\\b").test(line[prop])) return false; - else line[prop] += " " + cls; - return true; - }); - }), - - removeLineClass: operation(null, function(handle, where, cls) { - return changeLine(this, handle, function(line) { - var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; - var cur = line[prop]; - if (!cur) return false; - else if (cls == null) line[prop] = null; - else { - var upd = cur.replace(new RegExp("^" + cls + "\\b\\s*|\\s*\\b" + cls + "\\b"), ""); - if (upd == cur) return false; - line[prop] = upd || null; - } - return true; - }); - }), - - addLineWidget: operation(null, function(handle, node, options) { - var widget = options || {}; - widget.node = node; - if (widget.noHScroll) this.display.alignWidgets = true; - changeLine(this, handle, function(line) { - (line.widgets || (line.widgets = [])).push(widget); - widget.line = line; - return true; - }); - return widget; - }), - - removeLineWidget: operation(null, function(widget) { - var ws = widget.line.widgets, no = lineNo(widget.line); - if (no == null) return; - for (var i = 0; i < ws.length; ++i) if (ws[i] == widget) ws.splice(i--, 1); - regChange(this, no, no + 1); - }), - - lineInfo: function(line) { - if (typeof line == "number") { - if (!isLine(this.view.doc, line)) return null; - var n = line; - line = getLine(this.view.doc, line); - if (!line) return null; - } else { - var n = lineNo(line); - if (n == null) return null; - } - return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, - textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, - widgets: line.widgets}; - }, - - getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};}, - - addWidget: function(pos, node, scroll, vert, horiz) { - var display = this.display; - pos = cursorCoords(this, clipPos(this.view.doc, pos)); - var top = pos.top, left = pos.left; - node.style.position = "absolute"; - display.sizer.appendChild(node); - if (vert == "over") top = pos.top; - else if (vert == "near") { - var vspace = Math.max(display.wrapper.clientHeight, this.view.doc.height), - hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); - if (pos.bottom + node.offsetHeight > vspace && pos.top > node.offsetHeight) - top = pos.top - node.offsetHeight; - if (left + node.offsetWidth > hspace) - left = hspace - node.offsetWidth; - } - node.style.top = (top + paddingTop(display)) + "px"; - node.style.left = node.style.right = ""; - if (horiz == "right") { - left = display.sizer.clientWidth - node.offsetWidth; - node.style.right = "0px"; - } else { - if (horiz == "left") left = 0; - else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; - node.style.left = left + "px"; - } - if (scroll) - scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); - }, - - lineCount: function() {return this.view.doc.size;}, - - clipPos: function(pos) {return clipPos(this.view.doc, pos);}, - - getCursor: function(start) { - var sel = this.view.sel, pos; - if (start == null || start == "head") pos = sel.head; - else if (start == "anchor") pos = sel.anchor; - else if (start == "end" || start === false) pos = sel.to; - else pos = sel.from; - return copyPos(pos); - }, - - somethingSelected: function() {return !posEq(this.view.sel.from, this.view.sel.to);}, - - setCursor: operation(null, function(line, ch, extend) { - var pos = clipPos(this.view.doc, typeof line == "number" ? {line: line, ch: ch || 0} : line); - if (extend) extendSelection(this, pos); - else setSelection(this, pos, pos); - }), - - setSelection: operation(null, function(anchor, head) { - var doc = this.view.doc; - setSelection(this, clipPos(doc, anchor), clipPos(doc, head || anchor)); - }), - - extendSelection: operation(null, function(from, to) { - var doc = this.view.doc; - extendSelection(this, clipPos(doc, from), to && clipPos(doc, to)); - }), - - setExtending: function(val) {this.view.sel.extend = val;}, - - getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, - - getLineHandle: function(line) { - var doc = this.view.doc; - if (isLine(doc, line)) return getLine(doc, line); - }, - - getLineNumber: function(line) {return lineNo(line);}, - - setLine: operation(null, function(line, text) { - if (isLine(this.view.doc, line)) - replaceRange(this, text, {line: line, ch: 0}, {line: line, ch: getLine(this.view.doc, line).text.length}); - }), - - removeLine: operation(null, function(line) { - if (isLine(this.view.doc, line)) - replaceRange(this, "", {line: line, ch: 0}, clipPos(this.view.doc, {line: line+1, ch: 0})); - }), - - replaceRange: operation(null, function(code, from, to) { - var doc = this.view.doc; - from = clipPos(doc, from); - to = to ? clipPos(doc, to) : from; - return replaceRange(this, code, from, to); - }), - - getRange: function(from, to, lineSep) { - var doc = this.view.doc; - from = clipPos(doc, from); to = clipPos(doc, to); - var l1 = from.line, l2 = to.line; - if (l1 == l2) return getLine(doc, l1).text.slice(from.ch, to.ch); - var code = [getLine(doc, l1).text.slice(from.ch)]; - doc.iter(l1 + 1, l2, function(line) { code.push(line.text); }); - code.push(getLine(doc, l2).text.slice(0, to.ch)); - return code.join(lineSep || "\n"); - }, - - triggerOnKeyDown: operation(null, onKeyDown), - - execCommand: function(cmd) {return commands[cmd](this);}, - - // Stuff used by commands, probably not much use to outside code. - moveH: operation(null, function(dir, unit) { - var sel = this.view.sel, pos = dir < 0 ? sel.from : sel.to; - if (sel.shift || sel.extend || posEq(sel.from, sel.to)) pos = findPosH(this, dir, unit, true); - extendSelection(this, pos, pos, dir); - }), - - deleteH: operation(null, function(dir, unit) { - var sel = this.view.sel; - if (!posEq(sel.from, sel.to)) replaceRange(this, "", sel.from, sel.to, "delete"); - else replaceRange(this, "", sel.from, findPosH(this, dir, unit, false), "delete"); - this.curOp.userSelChange = true; - }), - - moveV: operation(null, function(dir, unit) { - var view = this.view, doc = view.doc, display = this.display; - var cur = view.sel.head, pos = cursorCoords(this, cur, "div"); - var x = pos.left, y; - if (view.goalColumn != null) x = view.goalColumn; - if (unit == "page") { - var pageSize = Math.min(display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); - y = pos.top + dir * pageSize; - } else if (unit == "line") { - y = dir > 0 ? pos.bottom + 3 : pos.top - 3; - } - do { - var target = coordsChar(this, x, y); - y += dir * 5; - } while (target.outside && (dir < 0 ? y > 0 : y < doc.height)); - - if (unit == "page") display.scrollbarV.scrollTop += charCoords(this, target, "div").top - pos.top; - extendSelection(this, target, target, dir); - view.goalColumn = x; - }), - - toggleOverwrite: function() { - if (this.view.overwrite = !this.view.overwrite) - this.display.cursor.className += " CodeMirror-overwrite"; - else - this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", ""); - }, - - posFromIndex: function(off) { - var lineNo = 0, ch, doc = this.view.doc; - doc.iter(0, doc.size, function(line) { - var sz = line.text.length + 1; - if (sz > off) { ch = off; return true; } - off -= sz; - ++lineNo; - }); - return clipPos(doc, {line: lineNo, ch: ch}); - }, - indexFromPos: function (coords) { - if (coords.line < 0 || coords.ch < 0) return 0; - var index = coords.ch; - this.view.doc.iter(0, coords.line, function (line) { - index += line.text.length + 1; - }); - return index; - }, - - scrollTo: function(x, y) { - if (x != null) this.display.scrollbarH.scrollLeft = this.display.scroller.scrollLeft = x; - if (y != null) this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = y; - updateDisplay(this, []); - }, - getScrollInfo: function() { - var scroller = this.display.scroller, co = scrollerCutOff; - return {left: scroller.scrollLeft, top: scroller.scrollTop, - height: scroller.scrollHeight - co, width: scroller.scrollWidth - co, - clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; - }, - - scrollIntoView: function(pos) { - if (typeof pos == "number") pos = {line: pos, ch: 0}; - pos = pos ? clipPos(this.view.doc, pos) : this.view.sel.head; - scrollPosIntoView(this, pos); - }, - - setSize: function(width, height) { - function interpret(val) { - return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; - } - if (width != null) this.display.wrapper.style.width = interpret(width); - if (height != null) this.display.wrapper.style.height = interpret(height); - this.refresh(); - }, - - on: function(type, f) {on(this, type, f);}, - off: function(type, f) {off(this, type, f);}, - - operation: function(f){return operation(this, f)();}, - - refresh: function() { - clearCaches(this); - if (this.display.scroller.scrollHeight > this.view.scrollTop) - this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = this.view.scrollTop; - updateDisplay(this, true); - }, - - getInputField: function(){return this.display.input;}, - getWrapperElement: function(){return this.display.wrapper;}, - getScrollerElement: function(){return this.display.scroller;}, - getGutterElement: function(){return this.display.gutters;} - }; - - // OPTION DEFAULTS - - var optionHandlers = CodeMirror.optionHandlers = {}; - - // The default configuration options. - var defaults = CodeMirror.defaults = {}; - - function option(name, deflt, handle, notOnInit) { - CodeMirror.defaults[name] = deflt; - if (handle) optionHandlers[name] = - notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; - } - - var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; - - // These two are, on init, called from the constructor because they - // have to be initialized before the editor can start at all. - option("value", "", function(cm, val) {cm.setValue(val);}, true); - option("mode", null, loadMode, true); - - option("indentUnit", 2, loadMode, true); - option("indentWithTabs", false); - option("smartIndent", true); - option("tabSize", 4, function(cm) { - loadMode(cm); - clearCaches(cm); - updateDisplay(cm, true); - }, true); - option("electricChars", true); - - option("theme", "default", function(cm) { - themeChanged(cm); - guttersChanged(cm); - }, true); - option("keyMap", "default", keyMapChanged); - option("extraKeys", null); - - option("onKeyEvent", null); - option("onDragEvent", null); - - option("lineWrapping", false, wrappingChanged, true); - option("gutters", [], function(cm) { - setGuttersForLineNumbers(cm.options); - guttersChanged(cm); - }, true); - option("lineNumbers", false, function(cm) { - setGuttersForLineNumbers(cm.options); - guttersChanged(cm); - }, true); - option("firstLineNumber", 1, guttersChanged, true); - option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); - option("showCursorWhenSelecting", false, updateSelection, true); - - option("readOnly", false, function(cm, val) { - if (val == "nocursor") {onBlur(cm); cm.display.input.blur();} - else if (!val) resetInput(cm, true); - }); - option("dragDrop", true); - - option("cursorBlinkRate", 530); - option("cursorHeight", 1); - option("workTime", 100); - option("workDelay", 100); - option("flattenSpans", true); - option("pollInterval", 100); - option("undoDepth", 40); - option("viewportMargin", 10, function(cm){cm.refresh();}, true); - - option("tabindex", null, function(cm, val) { - cm.display.input.tabIndex = val || ""; - }); - option("autofocus", null); - - // MODE DEFINITION AND QUERYING - - // Known modes, by name and by MIME - var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; - - CodeMirror.defineMode = function(name, mode) { - if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; - if (arguments.length > 2) { - mode.dependencies = []; - for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); - } - modes[name] = mode; - }; - - CodeMirror.defineMIME = function(mime, spec) { - mimeModes[mime] = spec; - }; - - CodeMirror.resolveMode = function(spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) - spec = mimeModes[spec]; - else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) - return CodeMirror.resolveMode("application/xml"); - if (typeof spec == "string") return {name: spec}; - else return spec || {name: "null"}; - }; - - CodeMirror.getMode = function(options, spec) { - var spec = CodeMirror.resolveMode(spec); - var mfactory = modes[spec.name]; - if (!mfactory) return CodeMirror.getMode(options, "text/plain"); - var modeObj = mfactory(options, spec); - if (modeExtensions.hasOwnProperty(spec.name)) { - var exts = modeExtensions[spec.name]; - for (var prop in exts) { - if (!exts.hasOwnProperty(prop)) continue; - if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; - modeObj[prop] = exts[prop]; - } - } - modeObj.name = spec.name; - return modeObj; - }; - - CodeMirror.defineMode("null", function() { - return {token: function(stream) {stream.skipToEnd();}}; - }); - CodeMirror.defineMIME("text/plain", "null"); - - var modeExtensions = CodeMirror.modeExtensions = {}; - CodeMirror.extendMode = function(mode, properties) { - var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); - for (var prop in properties) if (properties.hasOwnProperty(prop)) - exts[prop] = properties[prop]; - }; - - // EXTENSIONS - - CodeMirror.defineExtension = function(name, func) { - CodeMirror.prototype[name] = func; - }; - - CodeMirror.defineOption = option; - - var initHooks = []; - CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; - - // MODE STATE HANDLING - - // Utility functions for working with state. Exported because modes - // sometimes need to do this. - function copyState(mode, state) { - if (state === true) return state; - if (mode.copyState) return mode.copyState(state); - var nstate = {}; - for (var n in state) { - var val = state[n]; - if (val instanceof Array) val = val.concat([]); - nstate[n] = val; - } - return nstate; - } - CodeMirror.copyState = copyState; - - function startState(mode, a1, a2) { - return mode.startState ? mode.startState(a1, a2) : true; - } - CodeMirror.startState = startState; - - CodeMirror.innerMode = function(mode, state) { - while (mode.innerMode) { - var info = mode.innerMode(state); - state = info.state; - mode = info.mode; - } - return info || {mode: mode, state: state}; - }; - - // STANDARD COMMANDS - - var commands = CodeMirror.commands = { - selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});}, - killLine: function(cm) { - var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); - if (!sel && cm.getLine(from.line).length == from.ch) - cm.replaceRange("", from, {line: from.line + 1, ch: 0}, "delete"); - else cm.replaceRange("", from, sel ? to : {line: from.line}, "delete"); - }, - deleteLine: function(cm) { - var l = cm.getCursor().line; - cm.replaceRange("", {line: l, ch: 0}, {line: l}, "delete"); - }, - undo: function(cm) {cm.undo();}, - redo: function(cm) {cm.redo();}, - goDocStart: function(cm) {cm.extendSelection({line: 0, ch: 0});}, - goDocEnd: function(cm) {cm.extendSelection({line: cm.lineCount() - 1});}, - goLineStart: function(cm) { - cm.extendSelection(lineStart(cm, cm.getCursor().line)); - }, - goLineStartSmart: function(cm) { - var cur = cm.getCursor(), start = lineStart(cm, cur.line); - var line = cm.getLineHandle(start.line); - var order = getOrder(line); - if (!order || order[0].level == 0) { - var firstNonWS = Math.max(0, line.text.search(/\S/)); - var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch; - cm.extendSelection({line: start.line, ch: inWS ? 0 : firstNonWS}); - } else cm.extendSelection(start); - }, - goLineEnd: function(cm) { - cm.extendSelection(lineEnd(cm, cm.getCursor().line)); - }, - goLineUp: function(cm) {cm.moveV(-1, "line");}, - goLineDown: function(cm) {cm.moveV(1, "line");}, - goPageUp: function(cm) {cm.moveV(-1, "page");}, - goPageDown: function(cm) {cm.moveV(1, "page");}, - goCharLeft: function(cm) {cm.moveH(-1, "char");}, - goCharRight: function(cm) {cm.moveH(1, "char");}, - goColumnLeft: function(cm) {cm.moveH(-1, "column");}, - goColumnRight: function(cm) {cm.moveH(1, "column");}, - goWordLeft: function(cm) {cm.moveH(-1, "word");}, - goWordRight: function(cm) {cm.moveH(1, "word");}, - delCharBefore: function(cm) {cm.deleteH(-1, "char");}, - delCharAfter: function(cm) {cm.deleteH(1, "char");}, - delWordBefore: function(cm) {cm.deleteH(-1, "word");}, - delWordAfter: function(cm) {cm.deleteH(1, "word");}, - indentAuto: function(cm) {cm.indentSelection("smart");}, - indentMore: function(cm) {cm.indentSelection("add");}, - indentLess: function(cm) {cm.indentSelection("subtract");}, - insertTab: function(cm) {cm.replaceSelection("\t", "end", "input");}, - defaultTab: function(cm) { - if (cm.somethingSelected()) cm.indentSelection("add"); - else cm.replaceSelection("\t", "end", "input"); - }, - transposeChars: function(cm) { - var cur = cm.getCursor(), line = cm.getLine(cur.line); - if (cur.ch > 0 && cur.ch < line.length - 1) - cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), - {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1}); - }, - newlineAndIndent: function(cm) { - operation(cm, function() { - cm.replaceSelection("\n", "end", "input"); - cm.indentLine(cm.getCursor().line, null, true); - })(); - }, - toggleOverwrite: function(cm) {cm.toggleOverwrite();} - }; - - // STANDARD KEYMAPS - - var keyMap = CodeMirror.keyMap = {}; - keyMap.basic = { - "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", - "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", - "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto", - "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" - }; - // Note that the save and find-related commands aren't defined by - // default. Unknown commands are simply ignored. - keyMap.pcDefault = { - "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", - "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", - "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", - "Ctrl-Backspace": "delWordBefore", "Ctrl-Delete": "delWordAfter", "Ctrl-S": "save", "Ctrl-F": "find", - "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", - "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", - fallthrough: "basic" - }; - keyMap.macDefault = { - "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", - "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft", - "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordBefore", - "Ctrl-Alt-Backspace": "delWordAfter", "Alt-Delete": "delWordAfter", "Cmd-S": "save", "Cmd-F": "find", - "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", - "Cmd-[": "indentLess", "Cmd-]": "indentMore", - fallthrough: ["basic", "emacsy"] - }; - keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; - keyMap.emacsy = { - "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", - "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", - "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", - "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" - }; - - // KEYMAP DISPATCH - - function getKeyMap(val) { - if (typeof val == "string") return keyMap[val]; - else return val; - } - - function lookupKey(name, maps, handle, stop) { - function lookup(map) { - map = getKeyMap(map); - var found = map[name]; - if (found === false) { - if (stop) stop(); - return true; - } - if (found != null && handle(found)) return true; - if (map.nofallthrough) { - if (stop) stop(); - return true; - } - var fallthrough = map.fallthrough; - if (fallthrough == null) return false; - if (Object.prototype.toString.call(fallthrough) != "[object Array]") - return lookup(fallthrough); - for (var i = 0, e = fallthrough.length; i < e; ++i) { - if (lookup(fallthrough[i])) return true; - } - return false; - } - - for (var i = 0; i < maps.length; ++i) - if (lookup(maps[i])) return true; - } - function isModifierKey(event) { - var name = keyNames[e_prop(event, "keyCode")]; - return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; - } - CodeMirror.isModifierKey = isModifierKey; - - // FROMTEXTAREA - - CodeMirror.fromTextArea = function(textarea, options) { - if (!options) options = {}; - options.value = textarea.value; - if (!options.tabindex && textarea.tabindex) - options.tabindex = textarea.tabindex; - // Set autofocus to true if this textarea is focused, or if it has - // autofocus and no other element is focused. - if (options.autofocus == null) { - var hasFocus = document.body; - // doc.activeElement occasionally throws on IE - try { hasFocus = document.activeElement; } catch(e) {} - options.autofocus = hasFocus == textarea || - textarea.getAttribute("autofocus") != null && hasFocus == document.body; - } - - function save() {textarea.value = cm.getValue();} - if (textarea.form) { - // Deplorable hack to make the submit method do the right thing. - on(textarea.form, "submit", save); - var form = textarea.form, realSubmit = form.submit; - try { - form.submit = function wrappedSubmit() { - save(); - form.submit = realSubmit; - form.submit(); - form.submit = wrappedSubmit; - }; - } catch(e) {} - } - - textarea.style.display = "none"; - var cm = CodeMirror(function(node) { - textarea.parentNode.insertBefore(node, textarea.nextSibling); - }, options); - cm.save = save; - cm.getTextArea = function() { return textarea; }; - cm.toTextArea = function() { - save(); - textarea.parentNode.removeChild(cm.getWrapperElement()); - textarea.style.display = ""; - if (textarea.form) { - off(textarea.form, "submit", save); - if (typeof textarea.form.submit == "function") - textarea.form.submit = realSubmit; - } - }; - return cm; - }; - - // STRING STREAM - - // Fed to the mode parsers, provides helper functions to make - // parsers more succinct. - - // The character stream used by a mode's parser. - function StringStream(string, tabSize) { - this.pos = this.start = 0; - this.string = string; - this.tabSize = tabSize || 8; - } - - StringStream.prototype = { - eol: function() {return this.pos >= this.string.length;}, - sol: function() {return this.pos == 0;}, - peek: function() {return this.string.charAt(this.pos) || undefined;}, - next: function() { - if (this.pos < this.string.length) - return this.string.charAt(this.pos++); - }, - eat: function(match) { - var ch = this.string.charAt(this.pos); - if (typeof match == "string") var ok = ch == match; - else var ok = ch && (match.test ? match.test(ch) : match(ch)); - if (ok) {++this.pos; return ch;} - }, - eatWhile: function(match) { - var start = this.pos; - while (this.eat(match)){} - return this.pos > start; - }, - eatSpace: function() { - var start = this.pos; - while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; - return this.pos > start; - }, - skipToEnd: function() {this.pos = this.string.length;}, - skipTo: function(ch) { - var found = this.string.indexOf(ch, this.pos); - if (found > -1) {this.pos = found; return true;} - }, - backUp: function(n) {this.pos -= n;}, - column: function() {return countColumn(this.string, this.start, this.tabSize);}, - indentation: function() {return countColumn(this.string, null, this.tabSize);}, - match: function(pattern, consume, caseInsensitive) { - if (typeof pattern == "string") { - var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; - if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { - if (consume !== false) this.pos += pattern.length; - return true; - } - } else { - var match = this.string.slice(this.pos).match(pattern); - if (match && match.index > 0) return null; - if (match && consume !== false) this.pos += match[0].length; - return match; - } - }, - current: function(){return this.string.slice(this.start, this.pos);} - }; - CodeMirror.StringStream = StringStream; - - // TEXTMARKERS - - function TextMarker(cm, type) { - this.lines = []; - this.type = type; - this.cm = cm; - } - - TextMarker.prototype.clear = function() { - if (this.explicitlyCleared) return; - startOperation(this.cm); - var min = null, max = null; - for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this); - if (span.to != null) max = lineNo(line); - line.markedSpans = removeMarkedSpan(line.markedSpans, span); - if (span.from != null) - min = lineNo(line); - else if (this.collapsed && !lineIsHidden(line)) - updateLineHeight(line, textHeight(this.cm.display)); - } - if (min != null) regChange(this.cm, min, max + 1); - this.lines.length = 0; - this.explicitlyCleared = true; - if (this.collapsed && this.cm.view.cantEdit) { - this.cm.view.cantEdit = false; - reCheckSelection(this.cm); - } - endOperation(this.cm); - signalLater(this.cm, this, "clear"); - }; - - TextMarker.prototype.find = function() { - var from, to; - for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this); - if (span.from != null || span.to != null) { - var found = lineNo(line); - if (span.from != null) from = {line: found, ch: span.from}; - if (span.to != null) to = {line: found, ch: span.to}; - } - } - if (this.type == "bookmark") return from; - return from && {from: from, to: to}; - }; - - function markText(cm, from, to, options, type) { - var doc = cm.view.doc; - var marker = new TextMarker(cm, type); - if (type == "range" && !posLess(from, to)) return marker; - if (options) for (var opt in options) if (options.hasOwnProperty(opt)) - marker[opt] = options[opt]; - if (marker.replacedWith) { - marker.collapsed = true; - marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget"); - } - if (marker.collapsed) sawCollapsedSpans = true; - - var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd; - doc.iter(curLine, to.line + 1, function(line) { - var span = {from: null, to: null, marker: marker}; - size += line.text.length; - if (curLine == from.line) {span.from = from.ch; size -= from.ch;} - if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;} - if (marker.collapsed) { - if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch); - if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch); - else updateLineHeight(line, 0); - } - addMarkedSpan(line, span); - if (marker.collapsed && curLine == from.line && lineIsHidden(line)) - updateLineHeight(line, 0); - ++curLine; - }); - - if (marker.readOnly) { - sawReadOnlySpans = true; - if (cm.view.history.done.length || cm.view.history.undone.length) - cm.clearHistory(); - } - if (marker.collapsed) { - if (collapsedAtStart != collapsedAtEnd) - throw new Error("Inserting collapsed marker overlapping an existing one"); - marker.size = size; - marker.atomic = true; - } - if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed) - regChange(cm, from.line, to.line + 1); - if (marker.atomic) reCheckSelection(cm); - return marker; - } - - // TEXTMARKER SPANS - - function getMarkedSpanFor(spans, marker) { - if (spans) for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if (span.marker == marker) return span; - } - } - function removeMarkedSpan(spans, span) { - for (var r, i = 0; i < spans.length; ++i) - if (spans[i] != span) (r || (r = [])).push(spans[i]); - return r; - } - function addMarkedSpan(line, span) { - line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; - span.marker.lines.push(line); - } - - function markedSpansBefore(old, startCh) { - if (old) for (var i = 0, nw; i < old.length; ++i) { - var span = old[i], marker = span.marker; - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); - if (startsBefore || marker.type == "bookmark" && span.from == startCh) { - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); - (nw || (nw = [])).push({from: span.from, - to: endsAfter ? null : span.to, - marker: marker}); - } - } - return nw; - } - - function markedSpansAfter(old, startCh, endCh) { - if (old) for (var i = 0, nw; i < old.length; ++i) { - var span = old[i], marker = span.marker; - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); - if (endsAfter || marker.type == "bookmark" && span.from == endCh && span.from != startCh) { - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); - (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh, - to: span.to == null ? null : span.to - endCh, - marker: marker}); - } - } - return nw; - } - - function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) { - if (!oldFirst && !oldLast) return newText; - // Get the spans that 'stick out' on both sides - var first = markedSpansBefore(oldFirst, startCh); - var last = markedSpansAfter(oldLast, startCh, endCh); - - // Next, merge those two ends - var sameLine = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0); - if (first) { - // Fix up .to properties of first - for (var i = 0; i < first.length; ++i) { - var span = first[i]; - if (span.to == null) { - var found = getMarkedSpanFor(last, span.marker); - if (!found) span.to = startCh; - else if (sameLine) span.to = found.to == null ? null : found.to + offset; - } - } - } - if (last) { - // Fix up .from in last (or move them into first in case of sameLine) - for (var i = 0; i < last.length; ++i) { - var span = last[i]; - if (span.to != null) span.to += offset; - if (span.from == null) { - var found = getMarkedSpanFor(first, span.marker); - if (!found) { - span.from = offset; - if (sameLine) (first || (first = [])).push(span); - } - } else { - span.from += offset; - if (sameLine) (first || (first = [])).push(span); - } - } - } - - var newMarkers = [newHL(newText[0], first)]; - if (!sameLine) { - // Fill gap with whole-line-spans - var gap = newText.length - 2, gapMarkers; - if (gap > 0 && first) - for (var i = 0; i < first.length; ++i) - if (first[i].to == null) - (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker}); - for (var i = 0; i < gap; ++i) - newMarkers.push(newHL(newText[i+1], gapMarkers)); - newMarkers.push(newHL(lst(newText), last)); - } - return newMarkers; - } - - function removeReadOnlyRanges(doc, from, to) { - var markers = null; - doc.iter(from.line, to.line + 1, function(line) { - if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { - var mark = line.markedSpans[i].marker; - if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) - (markers || (markers = [])).push(mark); - } - }); - if (!markers) return null; - var parts = [{from: from, to: to}]; - for (var i = 0; i < markers.length; ++i) { - var m = markers[i].find(); - for (var j = 0; j < parts.length; ++j) { - var p = parts[j]; - if (!posLess(m.from, p.to) || posLess(m.to, p.from)) continue; - var newParts = [j, 1]; - if (posLess(p.from, m.from)) newParts.push({from: p.from, to: m.from}); - if (posLess(m.to, p.to)) newParts.push({from: m.to, to: p.to}); - parts.splice.apply(parts, newParts); - j += newParts.length - 1; - } - } - return parts; - } - - function collapsedSpanAt(line, ch) { - var sps = sawCollapsedSpans && line.markedSpans, found; - if (sps) for (var sp, i = 0; i < sps.length; ++i) { - sp = sps[i]; - if (!sp.marker.collapsed) continue; - if ((sp.from == null || sp.from < ch) && - (sp.to == null || sp.to > ch) && - (!found || found.width < sp.marker.width)) - found = sp.marker; - } - return found; - } - function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); } - function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); } - - function visualLine(doc, line) { - var merged; - while (merged = collapsedSpanAtStart(line)) - line = getLine(doc, merged.find().from.line); - return line; - } - - function lineIsHidden(line) { - var sps = sawCollapsedSpans && line.markedSpans; - if (sps) for (var sp, i = 0; i < sps.length; ++i) { - sp = sps[i]; - if (!sp.marker.collapsed) continue; - if (sp.from == null) return true; - if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(line, sp)) - return true; - } - } - window.lineIsHidden = lineIsHidden; - function lineIsHiddenInner(line, span) { - if (span.to == null || span.marker.inclusiveRight && span.to == line.text.length) - return true; - for (var sp, i = 0; i < line.markedSpans.length; ++i) { - sp = line.markedSpans[i]; - if (sp.marker.collapsed && sp.from == span.to && - (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && - lineIsHiddenInner(line, sp)) return true; - } - } - - // hl stands for history-line, a data structure that can be either a - // string (line without markers) or a {text, markedSpans} object. - function hlText(val) { return typeof val == "string" ? val : val.text; } - function hlSpans(val) { - if (typeof val == "string") return null; - var spans = val.markedSpans, out = null; - for (var i = 0; i < spans.length; ++i) { - if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } - else if (out) out.push(spans[i]); - } - return !out ? spans : out.length ? out : null; - } - function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; } - - function detachMarkedSpans(line) { - var spans = line.markedSpans; - if (!spans) return; - for (var i = 0; i < spans.length; ++i) { - var lines = spans[i].marker.lines; - var ix = indexOf(lines, line); - lines.splice(ix, 1); - } - line.markedSpans = null; - } - - function attachMarkedSpans(line, spans) { - if (!spans) return; - for (var i = 0; i < spans.length; ++i) - spans[i].marker.lines.push(line); - line.markedSpans = spans; - } - - // LINE DATA STRUCTURE - - // Line objects. These hold state related to a line, including - // highlighting info (the styles array). - function makeLine(text, markedSpans, height) { - var line = {text: text, height: height}; - attachMarkedSpans(line, markedSpans); - if (lineIsHidden(line)) line.height = 0; - return line; - } - - function updateLine(cm, line, text, markedSpans) { - line.text = text; - line.stateAfter = line.styles = null; - if (line.order != null) line.order = null; - detachMarkedSpans(line); - attachMarkedSpans(line, markedSpans); - if (lineIsHidden(line)) line.height = 0; - else if (!line.height) line.height = textHeight(cm.display); - signalLater(cm, line, "change"); - } - - function cleanUpLine(line) { - line.parent = null; - detachMarkedSpans(line); - } - - // Run the given mode's parser over a line, update the styles - // array, which contains alternating fragments of text and CSS - // classes. - function highlightLine(cm, line, state) { - var mode = cm.view.mode, flattenSpans = cm.options.flattenSpans; - var changed = !line.styles, pos = 0, curText = "", curStyle = null; - var stream = new StringStream(line.text, cm.options.tabSize), st = line.styles || (line.styles = []); - if (line.text == "" && mode.blankLine) mode.blankLine(state); - while (!stream.eol()) { - var style = mode.token(stream, state), substr = stream.current(); - stream.start = stream.pos; - if (!flattenSpans || curStyle != style) { - if (curText) { - changed = changed || pos >= st.length || curText != st[pos] || curStyle != st[pos+1]; - st[pos++] = curText; st[pos++] = curStyle; - } - curText = substr; curStyle = style; - } else curText = curText + substr; - // Give up when line is ridiculously long - if (stream.pos > 5000) break; - } - if (curText) { - changed = changed || pos >= st.length || curText != st[pos] || curStyle != st[pos+1]; - st[pos++] = curText; st[pos++] = curStyle; - } - if (stream.pos > 5000) { st[pos++] = line.text.slice(stream.pos); st[pos++] = null; } - if (pos != st.length) { st.length = pos; changed = true; } - return changed; - } - - // Lightweight form of highlight -- proceed over this line and - // update state, but don't save a style array. - function processLine(cm, line, state) { - var mode = cm.view.mode; - var stream = new StringStream(line.text, cm.options.tabSize); - if (line.text == "" && mode.blankLine) mode.blankLine(state); - while (!stream.eol() && stream.pos <= 5000) { - mode.token(stream, state); - stream.start = stream.pos; - } - } - - var styleToClassCache = {}; - function styleToClass(style) { - if (!style) return null; - return styleToClassCache[style] || - (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-")); - } - - function lineContent(cm, realLine, measure) { - var merged, line = realLine, lineBefore, sawBefore, simple = true; - while (merged = collapsedSpanAtStart(line)) { - simple = false; - line = getLine(cm.view.doc, merged.find().from.line); - if (!lineBefore) lineBefore = line; - } - - var builder = {pre: elt("pre"), col: 0, pos: 0, display: !measure, - measure: null, addedOne: false, cm: cm}; - if (line.textClass) builder.pre.className = line.textClass; - - do { - if (!line.styles) - highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); - builder.measure = line == realLine && measure; - builder.pos = 0; - builder.addToken = builder.measure ? buildTokenMeasure : buildToken; - if (measure && sawBefore && line != realLine && !builder.addedOne) { - measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure)); - builder.addedOne = true; - } - var next = insertLineContent(line, builder); - sawBefore = line == lineBefore; - if (next) { - line = getLine(cm.view.doc, next.to.line); - simple = false; - } - } while (next); - - if (measure && !builder.addedOne) - measure[0] = builder.pre.appendChild(simple ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure)); - if (!builder.pre.firstChild && !lineIsHidden(realLine)) - builder.pre.appendChild(document.createTextNode("\u00a0")); - - return builder.pre; - } - - var tokenSpecialChars = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g; - function buildToken(builder, text, style, startStyle, endStyle) { - if (!text) return; - if (!tokenSpecialChars.test(text)) { - builder.col += text.length; - var content = document.createTextNode(text); - } else { - var content = document.createDocumentFragment(), pos = 0; - while (true) { - tokenSpecialChars.lastIndex = pos; - var m = tokenSpecialChars.exec(text); - var skipped = m ? m.index - pos : text.length - pos; - if (skipped) { - content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); - builder.col += skipped; - } - if (!m) break; - pos += skipped + 1; - if (m[0] == "\t") { - var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; - content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); - builder.col += tabWidth; - } else { - var token = elt("span", "\u2022", "cm-invalidchar"); - token.title = "\\u" + m[0].charCodeAt(0).toString(16); - content.appendChild(token); - builder.col += 1; - } - } - } - if (style || startStyle || endStyle || builder.measure) { - var fullStyle = style || ""; - if (startStyle) fullStyle += startStyle; - if (endStyle) fullStyle += endStyle; - return builder.pre.appendChild(elt("span", [content], fullStyle)); - } - builder.pre.appendChild(content); - } - - function buildTokenMeasure(builder, text, style, startStyle, endStyle) { - for (var i = 0; i < text.length; ++i) { - if (i && i < text.length - 1 && - builder.cm.options.lineWrapping && - spanAffectsWrapping.test(text.slice(i - 1, i + 1))) - builder.pre.appendChild(elt("wbr")); - builder.measure[builder.pos++] = - buildToken(builder, text.charAt(i), style, - i == 0 && startStyle, i == text.length - 1 && endStyle); - } - if (text.length) builder.addedOne = true; - } - - function buildCollapsedSpan(builder, size, widget) { - if (widget) { - if (!builder.display) widget = widget.cloneNode(true); - builder.pre.appendChild(widget); - if (builder.measure && size) { - builder.measure[builder.pos] = widget; - builder.addedOne = true; - } - } - builder.pos += size; - } - - // Outputs a number of spans to make up a line, taking highlighting - // and marked text into account. - function insertLineContent(line, builder) { - var st = line.styles, spans = line.markedSpans; - if (!spans) { - for (var i = 0; i < st.length; i+=2) - builder.addToken(builder, st[i], styleToClass(st[i+1])); - return; - } - - var allText = line.text, len = allText.length; - var pos = 0, i = 0, text = "", style; - var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed; - for (;;) { - if (nextChange == pos) { // Update current marker set - spanStyle = spanEndStyle = spanStartStyle = ""; - collapsed = null; nextChange = Infinity; - var foundBookmark = null; - for (var j = 0; j < spans.length; ++j) { - var sp = spans[j], m = sp.marker; - if (sp.from <= pos && (sp.to == null || sp.to > pos)) { - if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; } - if (m.className) spanStyle += " " + m.className; - if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; - if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; - if (m.collapsed && (!collapsed || collapsed.marker.width < m.width)) - collapsed = sp; - } else if (sp.from > pos && nextChange > sp.from) { - nextChange = sp.from; - } - if (m.type == "bookmark" && sp.from == pos && m.replacedWith) - foundBookmark = m.replacedWith; - } - if (collapsed && (collapsed.from || 0) == pos) { - buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos, - collapsed.from != null && collapsed.marker.replacedWith); - if (collapsed.to == null) return collapsed.marker.find(); - } - if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark); - } - if (pos >= len) break; - - var upto = Math.min(len, nextChange); - while (true) { - if (text) { - var end = pos + text.length; - if (!collapsed) { - var tokenText = end > upto ? text.slice(0, upto - pos) : text; - builder.addToken(builder, tokenText, style + spanStyle, - spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : ""); - } - if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} - pos = end; - spanStartStyle = ""; - } - text = st[i++]; style = styleToClass(st[i++]); - } - } - } - - // DOCUMENT DATA STRUCTURE - - function LeafChunk(lines) { - this.lines = lines; - this.parent = null; - for (var i = 0, e = lines.length, height = 0; i < e; ++i) { - lines[i].parent = this; - height += lines[i].height; - } - this.height = height; - } - - LeafChunk.prototype = { - chunkSize: function() { return this.lines.length; }, - remove: function(at, n, cm) { - for (var i = at, e = at + n; i < e; ++i) { - var line = this.lines[i]; - this.height -= line.height; - cleanUpLine(line); - signalLater(cm, line, "delete"); - } - this.lines.splice(at, n); - }, - collapse: function(lines) { - lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); - }, - insertHeight: function(at, lines, height) { - this.height += height; - this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); - for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; - }, - iterN: function(at, n, op) { - for (var e = at + n; at < e; ++at) - if (op(this.lines[at])) return true; - } - }; - - function BranchChunk(children) { - this.children = children; - var size = 0, height = 0; - for (var i = 0, e = children.length; i < e; ++i) { - var ch = children[i]; - size += ch.chunkSize(); height += ch.height; - ch.parent = this; - } - this.size = size; - this.height = height; - this.parent = null; - } - - BranchChunk.prototype = { - chunkSize: function() { return this.size; }, - remove: function(at, n, callbacks) { - this.size -= n; - for (var i = 0; i < this.children.length; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at < sz) { - var rm = Math.min(n, sz - at), oldHeight = child.height; - child.remove(at, rm, callbacks); - this.height -= oldHeight - child.height; - if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } - if ((n -= rm) == 0) break; - at = 0; - } else at -= sz; - } - if (this.size - n < 25) { - var lines = []; - this.collapse(lines); - this.children = [new LeafChunk(lines)]; - this.children[0].parent = this; - } - }, - collapse: function(lines) { - for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines); - }, - insert: function(at, lines) { - var height = 0; - for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; - this.insertHeight(at, lines, height); - }, - insertHeight: function(at, lines, height) { - this.size += lines.length; - this.height += height; - for (var i = 0, e = this.children.length; i < e; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at <= sz) { - child.insertHeight(at, lines, height); - if (child.lines && child.lines.length > 50) { - while (child.lines.length > 50) { - var spilled = child.lines.splice(child.lines.length - 25, 25); - var newleaf = new LeafChunk(spilled); - child.height -= newleaf.height; - this.children.splice(i + 1, 0, newleaf); - newleaf.parent = this; - } - this.maybeSpill(); - } - break; - } - at -= sz; - } - }, - maybeSpill: function() { - if (this.children.length <= 10) return; - var me = this; - do { - var spilled = me.children.splice(me.children.length - 5, 5); - var sibling = new BranchChunk(spilled); - if (!me.parent) { // Become the parent node - var copy = new BranchChunk(me.children); - copy.parent = me; - me.children = [copy, sibling]; - me = copy; - } else { - me.size -= sibling.size; - me.height -= sibling.height; - var myIndex = indexOf(me.parent.children, me); - me.parent.children.splice(myIndex + 1, 0, sibling); - } - sibling.parent = me.parent; - } while (me.children.length > 10); - me.parent.maybeSpill(); - }, - iter: function(from, to, op) { this.iterN(from, to - from, op); }, - iterN: function(at, n, op) { - for (var i = 0, e = this.children.length; i < e; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at < sz) { - var used = Math.min(n, sz - at); - if (child.iterN(at, used, op)) return true; - if ((n -= used) == 0) break; - at = 0; - } else at -= sz; - } - } - }; - - // LINE UTILITIES - - function getLine(chunk, n) { - while (!chunk.lines) { - for (var i = 0;; ++i) { - var child = chunk.children[i], sz = child.chunkSize(); - if (n < sz) { chunk = child; break; } - n -= sz; - } - } - return chunk.lines[n]; - } - - function updateLineHeight(line, height) { - var diff = height - line.height; - for (var n = line; n; n = n.parent) n.height += diff; - } - - function lineNo(line) { - if (line.parent == null) return null; - var cur = line.parent, no = indexOf(cur.lines, line); - for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { - for (var i = 0;; ++i) { - if (chunk.children[i] == cur) break; - no += chunk.children[i].chunkSize(); - } - } - return no; - } - - function lineAtHeight(chunk, h) { - var n = 0; - outer: do { - for (var i = 0, e = chunk.children.length; i < e; ++i) { - var child = chunk.children[i], ch = child.height; - if (h < ch) { chunk = child; continue outer; } - h -= ch; - n += child.chunkSize(); - } - return n; - } while (!chunk.lines); - for (var i = 0, e = chunk.lines.length; i < e; ++i) { - var line = chunk.lines[i], lh = line.height; - if (h < lh) break; - h -= lh; - } - return n + i; - } - - function heightAtLine(cm, lineObj) { - lineObj = visualLine(cm.view.doc, lineObj); - - var h = 0, chunk = lineObj.parent; - for (var i = 0; i < chunk.lines.length; ++i) { - var line = chunk.lines[i]; - if (line == lineObj) break; - else h += line.height; - } - for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { - for (var i = 0; i < p.children.length; ++i) { - var cur = p.children[i]; - if (cur == chunk) break; - else h += cur.height; - } - } - return h; - } - - function getOrder(line) { - var order = line.order; - if (order == null) order = line.order = bidiOrdering(line.text); - return order; - } - - // HISTORY - - function makeHistory() { - return { - // Arrays of history events. Doing something adds an event to - // done and clears undo. Undoing moves events from done to - // undone, redoing moves them in the other direction. - done: [], undone: [], - // Used to track when changes can be merged into a single undo - // event - lastTime: 0, lastOp: null, lastOrigin: null, - // Used by the isClean() method - dirtyCounter: 0 - }; - } - - function addChange(cm, start, added, old, origin, fromBefore, toBefore, fromAfter, toAfter) { - var history = cm.view.history; - history.undone.length = 0; - var time = +new Date, cur = lst(history.done); - - if (cur && - (history.lastOp == cm.curOp.id || - history.lastOrigin == origin && (origin == "input" || origin == "delete") && - history.lastTime > time - 600)) { - // Merge this change into the last event - var last = lst(cur.events); - if (last.start > start + old.length || last.start + last.added < start) { - // Doesn't intersect with last sub-event, add new sub-event - cur.events.push({start: start, added: added, old: old}); - } else { - // Patch up the last sub-event - var startBefore = Math.max(0, last.start - start), - endAfter = Math.max(0, (start + old.length) - (last.start + last.added)); - for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]); - for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]); - if (startBefore) last.start = start; - last.added += added - (old.length - startBefore - endAfter); - } - cur.fromAfter = fromAfter; cur.toAfter = toAfter; - } else { - // Can not be merged, start a new event. - cur = {events: [{start: start, added: added, old: old}], - fromBefore: fromBefore, toBefore: toBefore, fromAfter: fromAfter, toAfter: toAfter}; - history.done.push(cur); - while (history.done.length > cm.options.undoDepth) - history.done.shift(); - if (history.dirtyCounter < 0) - // The user has made a change after undoing past the last clean state. - // We can never get back to a clean state now until markClean() is called. - history.dirtyCounter = NaN; - else - history.dirtyCounter++; - } - history.lastTime = time; - history.lastOp = cm.curOp.id; - history.lastOrigin = origin; - } - - // EVENT OPERATORS - - function stopMethod() {e_stop(this);} - // Ensure an event has a stop method. - function addStop(event) { - if (!event.stop) event.stop = stopMethod; - return event; - } - - function e_preventDefault(e) { - if (e.preventDefault) e.preventDefault(); - else e.returnValue = false; - } - function e_stopPropagation(e) { - if (e.stopPropagation) e.stopPropagation(); - else e.cancelBubble = true; - } - function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} - CodeMirror.e_stop = e_stop; - CodeMirror.e_preventDefault = e_preventDefault; - CodeMirror.e_stopPropagation = e_stopPropagation; - - function e_target(e) {return e.target || e.srcElement;} - function e_button(e) { - var b = e.which; - if (b == null) { - if (e.button & 1) b = 1; - else if (e.button & 2) b = 3; - else if (e.button & 4) b = 2; - } - if (mac && e.ctrlKey && b == 1) b = 3; - return b; - } - - // Allow 3rd-party code to override event properties by adding an override - // object to an event object. - function e_prop(e, prop) { - var overridden = e.override && e.override.hasOwnProperty(prop); - return overridden ? e.override[prop] : e[prop]; - } - - // EVENT HANDLING - - function on(emitter, type, f) { - if (emitter.addEventListener) - emitter.addEventListener(type, f, false); - else if (emitter.attachEvent) - emitter.attachEvent("on" + type, f); - else { - var map = emitter._handlers || (emitter._handlers = {}); - var arr = map[type] || (map[type] = []); - arr.push(f); - } - } - - function off(emitter, type, f) { - if (emitter.removeEventListener) - emitter.removeEventListener(type, f, false); - else if (emitter.detachEvent) - emitter.detachEvent("on" + type, f); - else { - var arr = emitter._handlers && emitter._handlers[type]; - if (!arr) return; - for (var i = 0; i < arr.length; ++i) - if (arr[i] == f) { arr.splice(i, 1); break; } - } - } - - function signal(emitter, type /*, values...*/) { - var arr = emitter._handlers && emitter._handlers[type]; - if (!arr) return; - var args = Array.prototype.slice.call(arguments, 2); - for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); - } - - function signalLater(cm, emitter, type /*, values...*/) { - var arr = emitter._handlers && emitter._handlers[type]; - if (!arr) return; - var args = Array.prototype.slice.call(arguments, 3), flist = cm.curOp && cm.curOp.delayedCallbacks; - function bnd(f) {return function(){f.apply(null, args);};}; - for (var i = 0; i < arr.length; ++i) - if (flist) flist.push(bnd(arr[i])); - else arr[i].apply(null, args); - } - - function hasHandler(emitter, type) { - var arr = emitter._handlers && emitter._handlers[type]; - return arr && arr.length > 0; - } - - CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal; - - // MISC UTILITIES - - // Number of pixels added to scroller and sizer to hide scrollbar - var scrollerCutOff = 30; - - // Returned or thrown by various protocols to signal 'I'm not - // handling this'. - var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; - - function Delayed() {this.id = null;} - Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; - - // Counts the column offset in a string, taking tabs into account. - // Used mostly to find indentation. - function countColumn(string, end, tabSize) { - if (end == null) { - end = string.search(/[^\s\u00a0]/); - if (end == -1) end = string.length; - } - for (var i = 0, n = 0; i < end; ++i) { - if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); - else ++n; - } - return n; - } - CodeMirror.countColumn = countColumn; - - var spaceStrs = [""]; - function spaceStr(n) { - while (spaceStrs.length <= n) - spaceStrs.push(lst(spaceStrs) + " "); - return spaceStrs[n]; - } - - function lst(arr) { return arr[arr.length-1]; } - - function selectInput(node) { - if (ios) { // Mobile Safari apparently has a bug where select() is broken. - node.selectionStart = 0; - node.selectionEnd = node.value.length; - } else node.select(); - } - - function indexOf(collection, elt) { - if (collection.indexOf) return collection.indexOf(elt); - for (var i = 0, e = collection.length; i < e; ++i) - if (collection[i] == elt) return i; - return -1; - } - - function emptyArray(size) { - for (var a = [], i = 0; i < size; ++i) a.push(undefined); - return a; - } - - function bind(f) { - var args = Array.prototype.slice.call(arguments, 1); - return function(){return f.apply(null, args);}; - } - - var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/; - function isWordChar(ch) { - return /\w/.test(ch) || ch > "\x80" && - (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); - } - - function isEmpty(obj) { - var c = 0; - for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) ++c; - return !c; - } - - var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F]/; - - // DOM UTILITIES - - function elt(tag, content, className, style) { - var e = document.createElement(tag); - if (className) e.className = className; - if (style) e.style.cssText = style; - if (typeof content == "string") setTextContent(e, content); - else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); - return e; - } - - function removeChildren(e) { - e.innerHTML = ""; - return e; - } - - function removeChildrenAndAdd(parent, e) { - return removeChildren(parent).appendChild(e); - } - - function setTextContent(e, str) { - if (ie_lt9) { - e.innerHTML = ""; - e.appendChild(document.createTextNode(str)); - } else e.textContent = str; - } - - // FEATURE DETECTION - - // Detect drag-and-drop - var dragAndDrop = function() { - // There is *some* kind of drag-and-drop support in IE6-8, but I - // couldn't get it to work yet. - if (ie_lt9) return false; - var div = elt('div'); - return "draggable" in div || "dragDrop" in div; - }(); - - // For a reason I have yet to figure out, some browsers disallow - // word wrapping between certain characters *only* if a new inline - // element is started between them. This makes it hard to reliably - // measure the position of things, since that requires inserting an - // extra span. This terribly fragile set of regexps matches the - // character combinations that suffer from this phenomenon on the - // various browsers. - var spanAffectsWrapping = /^$/; // Won't match any two-character string - if (gecko) spanAffectsWrapping = /$'/; - else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; - else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/; - - var knownScrollbarWidth; - function scrollbarWidth(measure) { - if (knownScrollbarWidth != null) return knownScrollbarWidth; - var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll"); - removeChildrenAndAdd(measure, test); - if (test.offsetWidth) - knownScrollbarWidth = test.offsetHeight - test.clientHeight; - return knownScrollbarWidth || 0; - } - - var zwspSupported; - function zeroWidthElement(measure) { - if (zwspSupported == null) { - var test = elt("span", "\u200b"); - removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); - if (measure.firstChild.offsetHeight != 0) - zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8; - } - if (zwspSupported) return elt("span", "\u200b"); - else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); - } - - // See if "".split is the broken IE version, if so, provide an - // alternative way to split lines. - var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { - var pos = 0, result = [], l = string.length; - while (pos <= l) { - var nl = string.indexOf("\n", pos); - if (nl == -1) nl = string.length; - var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); - var rt = line.indexOf("\r"); - if (rt != -1) { - result.push(line.slice(0, rt)); - pos += rt + 1; - } else { - result.push(line); - pos = nl + 1; - } - } - return result; - } : function(string){return string.split(/\r\n?|\n/);}; - CodeMirror.splitLines = splitLines; - - var hasSelection = window.getSelection ? function(te) { - try { return te.selectionStart != te.selectionEnd; } - catch(e) { return false; } - } : function(te) { - try {var range = te.ownerDocument.selection.createRange();} - catch(e) {} - if (!range || range.parentElement() != te) return false; - return range.compareEndPoints("StartToEnd", range) != 0; - }; - - var hasCopyEvent = (function() { - var e = elt("div"); - if ("oncopy" in e) return true; - e.setAttribute("oncopy", "return;"); - return typeof e.oncopy == 'function'; - })(); - - // KEY NAMING - - var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", - 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", - 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", - 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", - 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", - 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", - 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; - CodeMirror.keyNames = keyNames; - (function() { - // Number keys - for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i); - // Alphabetic keys - for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); - // Function keys - for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; - })(); - - // BIDI HELPERS - - function iterateBidiSections(order, from, to, f) { - if (!order) return f(from, to, "ltr"); - for (var i = 0; i < order.length; ++i) { - var part = order[i]; - if (part.from < to && part.to > from) - f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); - } - } - - function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } - function bidiRight(part) { return part.level % 2 ? part.from : part.to; } - - function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } - function lineRight(line) { - var order = getOrder(line); - if (!order) return line.text.length; - return bidiRight(lst(order)); - } - - function lineStart(cm, lineN) { - var line = getLine(cm.view.doc, lineN); - var visual = visualLine(cm.view.doc, line); - if (visual != line) lineN = lineNo(visual); - var order = getOrder(visual); - var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); - return {line: lineN, ch: ch}; - } - function lineEnd(cm, lineNo) { - var merged, line; - while (merged = collapsedSpanAtEnd(line = getLine(cm.view.doc, lineNo))) - lineNo = merged.find().to.line; - var order = getOrder(line); - var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); - return {line: lineNo, ch: ch}; - } - - // This is somewhat involved. It is needed in order to move - // 'visually' through bi-directional text -- i.e., pressing left - // should make the cursor go left, even when in RTL text. The - // tricky part is the 'jumps', where RTL and LTR text touch each - // other. This often requires the cursor offset to move more than - // one unit, in order to visually move one unit. - function moveVisually(line, start, dir, byUnit) { - var bidi = getOrder(line); - if (!bidi) return moveLogically(line, start, dir, byUnit); - var moveOneUnit = byUnit ? function(pos, dir) { - do pos += dir; - while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); - return pos; - } : function(pos, dir) { return pos + dir; }; - var linedir = bidi[0].level; - for (var i = 0; i < bidi.length; ++i) { - var part = bidi[i], sticky = part.level % 2 == linedir; - if ((part.from < start && part.to > start) || - (sticky && (part.from == start || part.to == start))) break; - } - var target = moveOneUnit(start, part.level % 2 ? -dir : dir); - - while (target != null) { - if (part.level % 2 == linedir) { - if (target < part.from || target > part.to) { - part = bidi[i += dir]; - target = part && (dir > 0 == part.level % 2 ? moveOneUnit(part.to, -1) : moveOneUnit(part.from, 1)); - } else break; - } else { - if (target == bidiLeft(part)) { - part = bidi[--i]; - target = part && bidiRight(part); - } else if (target == bidiRight(part)) { - part = bidi[++i]; - target = part && bidiLeft(part); - } else break; - } - } - - return target < 0 || target > line.text.length ? null : target; - } - - function moveLogically(line, start, dir, byUnit) { - var target = start + dir; - if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir; - return target < 0 || target > line.text.length ? null : target; - } - - // Bidirectional ordering algorithm - // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm - // that this (partially) implements. - - // One-char codes used for character types: - // L (L): Left-to-Right - // R (R): Right-to-Left - // r (AL): Right-to-Left Arabic - // 1 (EN): European Number - // + (ES): European Number Separator - // % (ET): European Number Terminator - // n (AN): Arabic Number - // , (CS): Common Number Separator - // m (NSM): Non-Spacing Mark - // b (BN): Boundary Neutral - // s (B): Paragraph Separator - // t (S): Segment Separator - // w (WS): Whitespace - // N (ON): Other Neutrals - - // Returns null if characters are ordered as they appear - // (left-to-right), or an array of sections ({from, to, level} - // objects) in the order in which they occur visually. - var bidiOrdering = (function() { - // Character types for codepoints 0 to 0xff - var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL"; - // Character types for codepoints 0x600 to 0x6ff - var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr"; - function charType(code) { - if (code <= 0xff) return lowTypes.charAt(code); - else if (0x590 <= code && code <= 0x5f4) return "R"; - else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600); - else if (0x700 <= code && code <= 0x8ac) return "r"; - else return "L"; - } - - var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; - var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; - - return function charOrdering(str) { - if (!bidiRE.test(str)) return false; - var len = str.length, types = [], startType = null; - for (var i = 0, type; i < len; ++i) { - types.push(type = charType(str.charCodeAt(i))); - if (startType == null) { - if (type == "L") startType = "L"; - else if (type == "R" || type == "r") startType = "R"; - } - } - if (startType == null) startType = "L"; - - // W1. Examine each non-spacing mark (NSM) in the level run, and - // change the type of the NSM to the type of the previous - // character. If the NSM is at the start of the level run, it will - // get the type of sor. - for (var i = 0, prev = startType; i < len; ++i) { - var type = types[i]; - if (type == "m") types[i] = prev; - else prev = type; - } - - // W2. Search backwards from each instance of a European number - // until the first strong type (R, L, AL, or sor) is found. If an - // AL is found, change the type of the European number to Arabic - // number. - // W3. Change all ALs to R. - for (var i = 0, cur = startType; i < len; ++i) { - var type = types[i]; - if (type == "1" && cur == "r") types[i] = "n"; - else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } - } - - // W4. A single European separator between two European numbers - // changes to a European number. A single common separator between - // two numbers of the same type changes to that type. - for (var i = 1, prev = types[0]; i < len - 1; ++i) { - var type = types[i]; - if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; - else if (type == "," && prev == types[i+1] && - (prev == "1" || prev == "n")) types[i] = prev; - prev = type; - } - - // W5. A sequence of European terminators adjacent to European - // numbers changes to all European numbers. - // W6. Otherwise, separators and terminators change to Other - // Neutral. - for (var i = 0; i < len; ++i) { - var type = types[i]; - if (type == ",") types[i] = "N"; - else if (type == "%") { - for (var end = i + 1; end < len && types[end] == "%"; ++end) {} - var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N"; - for (var j = i; j < end; ++j) types[j] = replace; - i = end - 1; - } - } - - // W7. Search backwards from each instance of a European number - // until the first strong type (R, L, or sor) is found. If an L is - // found, then change the type of the European number to L. - for (var i = 0, cur = startType; i < len; ++i) { - var type = types[i]; - if (cur == "L" && type == "1") types[i] = "L"; - else if (isStrong.test(type)) cur = type; - } - - // N1. A sequence of neutrals takes the direction of the - // surrounding strong text if the text on both sides has the same - // direction. European and Arabic numbers act as if they were R in - // terms of their influence on neutrals. Start-of-level-run (sor) - // and end-of-level-run (eor) are used at level run boundaries. - // N2. Any remaining neutrals take the embedding direction. - for (var i = 0; i < len; ++i) { - if (isNeutral.test(types[i])) { - for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} - var before = (i ? types[i-1] : startType) == "L"; - var after = (end < len - 1 ? types[end] : startType) == "L"; - var replace = before || after ? "L" : "R"; - for (var j = i; j < end; ++j) types[j] = replace; - i = end - 1; - } - } - - // Here we depart from the documented algorithm, in order to avoid - // building up an actual levels array. Since there are only three - // levels (0, 1, 2) in an implementation that doesn't take - // explicit embedding into account, we can build up the order on - // the fly, without following the level-based algorithm. - var order = [], m; - for (var i = 0; i < len;) { - if (countsAsLeft.test(types[i])) { - var start = i; - for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} - order.push({from: start, to: i, level: 0}); - } else { - var pos = i, at = order.length; - for (++i; i < len && types[i] != "L"; ++i) {} - for (var j = pos; j < i;) { - if (countsAsNum.test(types[j])) { - if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1}); - var nstart = j; - for (++j; j < i && countsAsNum.test(types[j]); ++j) {} - order.splice(at, 0, {from: nstart, to: j, level: 2}); - pos = j; - } else ++j; - } - if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1}); - } - } - if (order[0].level == 1 && (m = str.match(/^\s+/))) { - order[0].from = m[0].length; - order.unshift({from: 0, to: m[0].length, level: 0}); - } - if (lst(order).level == 1 && (m = str.match(/\s+$/))) { - lst(order).to -= m[0].length; - order.push({from: len - m[0].length, to: len, level: 0}); - } - if (order[0].level != lst(order).level) - order.push({from: len, to: len, level: order[0].level}); - - return order; - }; - })(); - - // THE END - - CodeMirror.version = "3.0"; - - return CodeMirror; -})(); diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/js/codemirror/mode/clike.js --- a/dew/src/main/resources/org/apidesign/bck2brwsr/dew/js/codemirror/mode/clike.js Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,300 +0,0 @@ -CodeMirror.defineMode("clike", function(config, parserConfig) { - var indentUnit = config.indentUnit, - statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, - keywords = parserConfig.keywords || {}, - builtin = parserConfig.builtin || {}, - blockKeywords = parserConfig.blockKeywords || {}, - atoms = parserConfig.atoms || {}, - hooks = parserConfig.hooks || {}, - multiLineStrings = parserConfig.multiLineStrings; - var isOperatorChar = /[+\-*&%=<>!?|\/]/; - - var curPunc; - - function tokenBase(stream, state) { - var ch = stream.next(); - if (hooks[ch]) { - var result = hooks[ch](stream, state); - if (result !== false) return result; - } - if (ch == '"' || ch == "'") { - state.tokenize = tokenString(ch); - return state.tokenize(stream, state); - } - if (/[\[\]{}\(\),;\:\.]/.test(ch)) { - curPunc = ch; - return null; - } - if (/\d/.test(ch)) { - stream.eatWhile(/[\w\.]/); - return "number"; - } - if (ch == "/") { - if (stream.eat("*")) { - state.tokenize = tokenComment; - return tokenComment(stream, state); - } - if (stream.eat("/")) { - stream.skipToEnd(); - return "comment"; - } - } - if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return "operator"; - } - stream.eatWhile(/[\w\$_]/); - var cur = stream.current(); - if (keywords.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "keyword"; - } - if (builtin.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "builtin"; - } - if (atoms.propertyIsEnumerable(cur)) return "atom"; - return "variable"; - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next, end = false; - while ((next = stream.next()) != null) { - if (next == quote && !escaped) {end = true; break;} - escaped = !escaped && next == "\\"; - } - if (end || !(escaped || multiLineStrings)) - state.tokenize = null; - return "string"; - }; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize = null; - break; - } - maybeEnd = (ch == "*"); - } - return "comment"; - } - - function Context(indented, column, type, align, prev) { - this.indented = indented; - this.column = column; - this.type = type; - this.align = align; - this.prev = prev; - } - function pushContext(state, col, type) { - var indent = state.indented; - if (state.context && state.context.type == "statement") - indent = state.context.indented; - return state.context = new Context(indent, col, type, null, state.context); - } - function popContext(state) { - var t = state.context.type; - if (t == ")" || t == "]" || t == "}") - state.indented = state.context.indented; - return state.context = state.context.prev; - } - - // Interface - - return { - startState: function(basecolumn) { - return { - tokenize: null, - context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), - indented: 0, - startOfLine: true - }; - }, - - token: function(stream, state) { - var ctx = state.context; - if (stream.sol()) { - if (ctx.align == null) ctx.align = false; - state.indented = stream.indentation(); - state.startOfLine = true; - } - if (stream.eatSpace()) return null; - curPunc = null; - var style = (state.tokenize || tokenBase)(stream, state); - if (style == "comment" || style == "meta") return style; - if (ctx.align == null) ctx.align = true; - - if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement") popContext(state); - else if (curPunc == "{") pushContext(state, stream.column(), "}"); - else if (curPunc == "[") pushContext(state, stream.column(), "]"); - else if (curPunc == "(") pushContext(state, stream.column(), ")"); - else if (curPunc == "}") { - while (ctx.type == "statement") ctx = popContext(state); - if (ctx.type == "}") ctx = popContext(state); - while (ctx.type == "statement") ctx = popContext(state); - } - else if (curPunc == ctx.type) popContext(state); - else if (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || (ctx.type == "statement" && curPunc == "newstatement")) - pushContext(state, stream.column(), "statement"); - state.startOfLine = false; - return style; - }, - - indent: function(state, textAfter) { - if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; - var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); - if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; - var closing = firstChar == ctx.type; - if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); - else if (ctx.align) return ctx.column + (closing ? 0 : 1); - else return ctx.indented + (closing ? 0 : indentUnit); - }, - - electricChars: "{}" - }; -}); - -(function() { - function words(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - var cKeywords = "auto if break int case long char register continue return default short do sizeof " + - "double static else struct entry switch extern typedef float union for unsigned " + - "goto while enum void const signed volatile"; - - function cppHook(stream, state) { - if (!state.startOfLine) return false; - for (;;) { - if (stream.skipTo("\\")) { - stream.next(); - if (stream.eol()) { - state.tokenize = cppHook; - break; - } - } else { - stream.skipToEnd(); - state.tokenize = null; - break; - } - } - return "meta"; - } - - // C#-style strings where "" escapes a quote. - function tokenAtString(stream, state) { - var next; - while ((next = stream.next()) != null) { - if (next == '"' && !stream.eat('"')) { - state.tokenize = null; - break; - } - } - return "string"; - } - - function mimes(ms, mode) { - for (var i = 0; i < ms.length; ++i) CodeMirror.defineMIME(ms[i], mode); - } - - mimes(["text/x-csrc", "text/x-c", "text/x-chdr"], { - name: "clike", - keywords: words(cKeywords), - blockKeywords: words("case do else for if switch while struct"), - atoms: words("null"), - hooks: {"#": cppHook} - }); - mimes(["text/x-c++src", "text/x-c++hdr"], { - name: "clike", - keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " + - "static_cast typeid catch operator template typename class friend private " + - "this using const_cast inline public throw virtual delete mutable protected " + - "wchar_t"), - blockKeywords: words("catch class do else finally for if struct switch try while"), - atoms: words("true false null"), - hooks: {"#": cppHook} - }); - CodeMirror.defineMIME("text/x-java", { - name: "clike", - keywords: words("abstract assert boolean break byte case catch char class const continue default " + - "do double else enum extends final finally float for goto if implements import " + - "instanceof int interface long native new package private protected public " + - "return short static strictfp super switch synchronized this throw throws transient " + - "try void volatile while"), - blockKeywords: words("catch class do else finally for if switch try while"), - atoms: words("true false null"), - hooks: { - "@": function(stream) { - stream.eatWhile(/[\w\$_]/); - return "meta"; - } - } - }); - CodeMirror.defineMIME("text/x-csharp", { - name: "clike", - keywords: words("abstract as base break case catch checked class const continue" + - " default delegate do else enum event explicit extern finally fixed for" + - " foreach goto if implicit in interface internal is lock namespace new" + - " operator out override params private protected public readonly ref return sealed" + - " sizeof stackalloc static struct switch this throw try typeof unchecked" + - " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + - " global group into join let orderby partial remove select set value var yield"), - blockKeywords: words("catch class do else finally for foreach if struct switch try while"), - builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" + - " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" + - " UInt64 bool byte char decimal double short int long object" + - " sbyte float string ushort uint ulong"), - atoms: words("true false null"), - hooks: { - "@": function(stream, state) { - if (stream.eat('"')) { - state.tokenize = tokenAtString; - return tokenAtString(stream, state); - } - stream.eatWhile(/[\w\$_]/); - return "meta"; - } - } - }); - CodeMirror.defineMIME("text/x-scala", { - name: "clike", - keywords: words( - - /* scala */ - "abstract case catch class def do else extends false final finally for forSome if " + - "implicit import lazy match new null object override package private protected return " + - "sealed super this throw trait try trye type val var while with yield _ : = => <- <: " + - "<% >: # @ " + - - /* package scala */ - "assert assume require print println printf readLine readBoolean readByte readShort " + - "readChar readInt readLong readFloat readDouble " + - - "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " + - "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " + - "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " + - "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " + - "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector :: #:: " + - - /* package java.lang */ - "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + - "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + - "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + - "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void" - - - ), - blockKeywords: words("catch class do else finally for forSome if match switch try while"), - atoms: words("true false null"), - hooks: { - "@": function(stream) { - stream.eatWhile(/[\w\$_]/); - return "meta"; - } - } - }); -}()); diff -r e995e8d39240 -r ba912ef24b27 dew/src/main/resources/org/apidesign/bck2brwsr/dew/js/codemirror/mode/xml.js --- a/dew/src/main/resources/org/apidesign/bck2brwsr/dew/js/codemirror/mode/xml.js Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,324 +0,0 @@ -CodeMirror.defineMode("xml", function(config, parserConfig) { - var indentUnit = config.indentUnit; - var Kludges = parserConfig.htmlMode ? { - autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, - 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, - 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, - 'track': true, 'wbr': true}, - implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, - 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, - 'th': true, 'tr': true}, - contextGrabbers: { - 'dd': {'dd': true, 'dt': true}, - 'dt': {'dd': true, 'dt': true}, - 'li': {'li': true}, - 'option': {'option': true, 'optgroup': true}, - 'optgroup': {'optgroup': true}, - 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true, - 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true, - 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, - 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true, - 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true}, - 'rp': {'rp': true, 'rt': true}, - 'rt': {'rp': true, 'rt': true}, - 'tbody': {'tbody': true, 'tfoot': true}, - 'td': {'td': true, 'th': true}, - 'tfoot': {'tbody': true}, - 'th': {'td': true, 'th': true}, - 'thead': {'tbody': true, 'tfoot': true}, - 'tr': {'tr': true} - }, - doNotIndent: {"pre": true}, - allowUnquoted: true, - allowMissing: true - } : { - autoSelfClosers: {}, - implicitlyClosed: {}, - contextGrabbers: {}, - doNotIndent: {}, - allowUnquoted: false, - allowMissing: false - }; - var alignCDATA = parserConfig.alignCDATA; - - // Return variables for tokenizers - var tagName, type; - - function inText(stream, state) { - function chain(parser) { - state.tokenize = parser; - return parser(stream, state); - } - - var ch = stream.next(); - if (ch == "<") { - if (stream.eat("!")) { - if (stream.eat("[")) { - if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); - else return null; - } - else if (stream.match("--")) return chain(inBlock("comment", "-->")); - else if (stream.match("DOCTYPE", true, true)) { - stream.eatWhile(/[\w\._\-]/); - return chain(doctype(1)); - } - else return null; - } - else if (stream.eat("?")) { - stream.eatWhile(/[\w\._\-]/); - state.tokenize = inBlock("meta", "?>"); - return "meta"; - } - else { - var isClose = stream.eat("/"); - tagName = ""; - var c; - while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; - if (!tagName) return "error"; - type = isClose ? "closeTag" : "openTag"; - state.tokenize = inTag; - return "tag"; - } - } - else if (ch == "&") { - var ok; - if (stream.eat("#")) { - if (stream.eat("x")) { - ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); - } else { - ok = stream.eatWhile(/[\d]/) && stream.eat(";"); - } - } else { - ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); - } - return ok ? "atom" : "error"; - } - else { - stream.eatWhile(/[^&<]/); - return null; - } - } - - function inTag(stream, state) { - var ch = stream.next(); - if (ch == ">" || (ch == "/" && stream.eat(">"))) { - state.tokenize = inText; - type = ch == ">" ? "endTag" : "selfcloseTag"; - return "tag"; - } - else if (ch == "=") { - type = "equals"; - return null; - } - else if (/[\'\"]/.test(ch)) { - state.tokenize = inAttribute(ch); - return state.tokenize(stream, state); - } - else { - stream.eatWhile(/[^\s\u00a0=<>\"\']/); - return "word"; - } - } - - function inAttribute(quote) { - return function(stream, state) { - while (!stream.eol()) { - if (stream.next() == quote) { - state.tokenize = inTag; - break; - } - } - return "string"; - }; - } - - function inBlock(style, terminator) { - return function(stream, state) { - while (!stream.eol()) { - if (stream.match(terminator)) { - state.tokenize = inText; - break; - } - stream.next(); - } - return style; - }; - } - function doctype(depth) { - return function(stream, state) { - var ch; - while ((ch = stream.next()) != null) { - if (ch == "<") { - state.tokenize = doctype(depth + 1); - return state.tokenize(stream, state); - } else if (ch == ">") { - if (depth == 1) { - state.tokenize = inText; - break; - } else { - state.tokenize = doctype(depth - 1); - return state.tokenize(stream, state); - } - } - } - return "meta"; - }; - } - - var curState, setStyle; - function pass() { - for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]); - } - function cont() { - pass.apply(null, arguments); - return true; - } - - function pushContext(tagName, startOfLine) { - var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent); - curState.context = { - prev: curState.context, - tagName: tagName, - indent: curState.indented, - startOfLine: startOfLine, - noIndent: noIndent - }; - } - function popContext() { - if (curState.context) curState.context = curState.context.prev; - } - - function element(type) { - if (type == "openTag") { - curState.tagName = tagName; - return cont(attributes, endtag(curState.startOfLine)); - } else if (type == "closeTag") { - var err = false; - if (curState.context) { - if (curState.context.tagName != tagName) { - if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) { - popContext(); - } - err = !curState.context || curState.context.tagName != tagName; - } - } else { - err = true; - } - if (err) setStyle = "error"; - return cont(endclosetag(err)); - } - return cont(); - } - function endtag(startOfLine) { - return function(type) { - var tagName = curState.tagName; - curState.tagName = null; - if (type == "selfcloseTag" || - (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(tagName.toLowerCase()))) { - maybePopContext(tagName.toLowerCase()); - return cont(); - } - if (type == "endTag") { - maybePopContext(tagName.toLowerCase()); - pushContext(tagName, startOfLine); - return cont(); - } - return cont(); - }; - } - function endclosetag(err) { - return function(type) { - if (err) setStyle = "error"; - if (type == "endTag") { popContext(); return cont(); } - setStyle = "error"; - return cont(arguments.callee); - }; - } - function maybePopContext(nextTagName) { - var parentTagName; - while (true) { - if (!curState.context) { - return; - } - parentTagName = curState.context.tagName.toLowerCase(); - if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) || - !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { - return; - } - popContext(); - } - } - - function attributes(type) { - if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);} - if (type == "endTag" || type == "selfcloseTag") return pass(); - setStyle = "error"; - return cont(attributes); - } - function attribute(type) { - if (type == "equals") return cont(attvalue, attributes); - if (!Kludges.allowMissing) setStyle = "error"; - else if (type == "word") setStyle = "attribute"; - return (type == "endTag" || type == "selfcloseTag") ? pass() : cont(); - } - function attvalue(type) { - if (type == "string") return cont(attvaluemaybe); - if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();} - setStyle = "error"; - return (type == "endTag" || type == "selfCloseTag") ? pass() : cont(); - } - function attvaluemaybe(type) { - if (type == "string") return cont(attvaluemaybe); - else return pass(); - } - - return { - startState: function() { - return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null}; - }, - - token: function(stream, state) { - if (stream.sol()) { - state.startOfLine = true; - state.indented = stream.indentation(); - } - if (stream.eatSpace()) return null; - - setStyle = type = tagName = null; - var style = state.tokenize(stream, state); - state.type = type; - if ((style || type) && style != "comment") { - curState = state; - while (true) { - var comb = state.cc.pop() || element; - if (comb(type || style)) break; - } - } - state.startOfLine = false; - return setStyle || style; - }, - - indent: function(state, textAfter, fullLine) { - var context = state.context; - if ((state.tokenize != inTag && state.tokenize != inText) || - context && context.noIndent) - return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; - if (alignCDATA && / - * - * 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.dew; - -import java.io.IOException; -import static org.testng.Assert.*; -import org.testng.annotations.Test; - -/** - * - * @author Jaroslav Tulach - */ -public class CompileTest { - @Test public void testCompile() throws IOException { - String html = "" - + " " - + ""; - String java = "package x.y.z;" - + "import org.apidesign.bck2brwsr.htmlpage.api.*;" - + "import static org.apidesign.bck2brwsr.htmlpage.api.OnEvent.*;" - + "@Page(xhtml=\"index.html\", className=\"Index\")" - + "class X { " - + " @On(event=CLICK, id=\"btn\") static void clcs() {}" - + "}"; - Compile result = Compile.create(html, java); - - assertNotNull(result.get("x/y/z/X.class"), "Class X is compiled: " + result); - assertNotNull(result.get("x/y/z/Index.class"), "Class Index is compiled: " + result); - } -} diff -r e995e8d39240 -r ba912ef24b27 ide/editor/pom.xml --- a/ide/editor/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,185 +0,0 @@ - - - 4.0.0 - - ide - org.apidesign.bck2brwsr - 0.8-SNAPSHOT - - - org.apidesign.bck2brwsr.ide - editor - 0.8-SNAPSHOT - nbm - - Editor Support for Bck2Brwsr - - - UTF-8 - ${project.build.directory}/endorsed - - - - - - netbeans - NetBeans - http://bits.netbeans.org/maven2/ - - false - - - - - - - org.netbeans.api - org-netbeans-api-annotations-common - - - org.netbeans.api - org-netbeans-modules-java-source - - - org.netbeans.api - org-netbeans-libs-javacapi - - - org.netbeans.api - org-netbeans-spi-java-hints - - - org.netbeans.api - org-netbeans-modules-parsing-api - - - org.netbeans.api - org-netbeans-spi-editor-hints - - - org.netbeans.api - org-openide-util - - - org.netbeans.api - org-netbeans-modules-java-lexer - - - org.netbeans.api - org-netbeans-modules-lexer - - - org.apidesign.bck2brwsr - core - 0.8-SNAPSHOT - jar - test - - - org.netbeans.api - org-netbeans-modules-java-hints-test - test - - - org.netbeans.api - org-netbeans-libs-junit4 - test - - - org.netbeans.modules - org-netbeans-lib-nbjavac - test - - - org.testng - testng - test - - - - - - - org.codehaus.mojo - nbm-maven-plugin - 3.8 - true - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.5.1 - - 1.6 - 1.6 - - ${endorsed.dir} - - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - true - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - endorsed - validate - - copy - - - - - ${endorsed.dir} - true - - - org.netbeans.api - org-netbeans-libs-javacapi - ${netbeans.version} - - - org.netbeans.external - nb-javac-api - ${netbeans.version} - - - org.netbeans.modules - org-netbeans-libs-javacimpl - ${netbeans.version} - - - org.netbeans.external - nb-javac-impl - ${netbeans.version} - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - -Djava.endorsed.dirs=${endorsed.dir} - - - - - diff -r e995e8d39240 -r ba912ef24b27 ide/editor/src/main/java/org/apidesign/bck2brwsr/ide/editor/JNIHelper.java --- a/ide/editor/src/main/java/org/apidesign/bck2brwsr/ide/editor/JNIHelper.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +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.ide.editor; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; - -/** - * JNI Helper. - * To facilitate lookup of methods by name and signature, instead of manually parsing signatures, - * constructs the map of all methods and uses Class.getName() to generate almost-correct signatures. - */ -class JNIHelper { - - static Method method(String clazz, String method, String signature) { - final Map methods = methodMap(JNIHelper.clazz(clazz)); - return methods.get(methodKey(method, signature)); - } - - static Class clazz(String clazz) { - try { - return Class.forName(clazz); - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException(e); - } - } - - static Map methodMap(final Class clazz) { - final Map map = new HashMap(); - final Method[] methods = clazz.getDeclaredMethods(); - for (int i = 0; i < methods.length; i++) { - final Method method = methods[i]; - map.put(methodKey(method.getName(), signature(method)), method); - } - return map; - } - - static String methodKey(String method, String signature) { - return method + '@' + signature; - } - - static String signature(final Method method) { - final Class[] parameterTypes = method.getParameterTypes(); - final StringBuilder b = new StringBuilder(); - for (int j = 0; j < parameterTypes.length; j++) { - b.append(signature(parameterTypes[j])); - } - return b.toString(); - } - - static String signature(final Class clazz) { - if (clazz == boolean.class) return "Z"; - else if (clazz == byte.class) return "B"; - else if (clazz == char.class) return "C"; - else if (clazz == double.class) return "D"; - else if (clazz == float.class) return "F"; - else if (clazz == int.class) return "I"; - else if (clazz == long.class) return "J"; - else if (clazz == short.class) return "S"; - else if (clazz == void.class) return "V"; - else if (clazz.isArray()) return clazz.getName().replace('.','/'); - else return "L" + clazz.getName().replace('.','/') + ";"; - } -} diff -r e995e8d39240 -r ba912ef24b27 ide/editor/src/main/java/org/apidesign/bck2brwsr/ide/editor/JSEmbeddingProvider.java --- a/ide/editor/src/main/java/org/apidesign/bck2brwsr/ide/editor/JSEmbeddingProvider.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,188 +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.ide.editor; - -import com.sun.source.tree.AnnotationTree; -import com.sun.source.tree.AssignmentTree; -import com.sun.source.tree.CompilationUnitTree; -import com.sun.source.tree.ExpressionTree; -import com.sun.source.tree.LiteralTree; -import com.sun.source.tree.MethodTree; -import com.sun.source.util.SourcePositions; -import com.sun.source.util.TreePath; -import com.sun.source.util.TreePathScanner; -import com.sun.source.util.Trees; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import javax.lang.model.element.TypeElement; -import javax.swing.text.Document; -import org.netbeans.api.editor.mimelookup.MimeRegistration; -import org.netbeans.api.java.source.CompilationInfo; -import org.netbeans.api.java.source.JavaParserResultTask; -import org.netbeans.api.java.source.JavaSource; -import org.netbeans.api.lexer.Language; -import org.netbeans.api.lexer.TokenHierarchy; -import org.netbeans.api.lexer.TokenSequence; -import org.netbeans.modules.parsing.api.Snapshot; -import org.netbeans.modules.parsing.spi.Parser; -import org.netbeans.modules.parsing.spi.Scheduler; -import org.netbeans.modules.parsing.spi.SchedulerEvent; -import org.netbeans.modules.parsing.spi.SchedulerTask; -import org.netbeans.modules.parsing.spi.TaskFactory; -import org.openide.util.Exceptions; - -/** - * - * @author Tomas Zezula - */ -public final class JSEmbeddingProvider extends JavaParserResultTask { - - private static final int PRIORITY = 1000; - private static final String JS_ANNOTATION = "org.apidesign.bck2brwsr.core.JavaScriptBody"; //NOI18N - private static final String BODY = "body"; //NOI18N - private static final String JAVA_MIME_TYPE = "text/x-java"; //NOI18N - private static final String JAVASCRIPT_MIME_TYPE = "text/javascript"; //NOI18N - private final AtomicBoolean canceled = new AtomicBoolean(); - - private JSEmbeddingProvider() { - super(JavaSource.Phase.ELEMENTS_RESOLVED); - } - - @Override - public int getPriority() { - return PRIORITY; - } - - @Override - public void cancel() { - canceled.set(true); - } - - @Override - public Class getSchedulerClass() { - return Scheduler.EDITOR_SENSITIVE_TASK_SCHEDULER; - } - - @Override - public void run(Parser.Result t, SchedulerEvent se) { - canceled.set(false); - final CompilationInfo ci = CompilationInfo.get(t); - final CompilationUnitTree cu = ci.getCompilationUnit(); - final Trees trees = ci.getTrees(); - final SourcePositions sp = trees.getSourcePositions(); - final Finder f = new Finder(trees); - final List result = new ArrayList(); - f.scan(cu, result); - if (!result.isEmpty()) { - try { - final TokenHierarchy tk = TokenHierarchy.get(ci.getDocument()); - final Language java = Language.find(JAVA_MIME_TYPE); - final Language javaScript = Language.find(JAVASCRIPT_MIME_TYPE); - if (java != null && javaScript != null) { - final TokenSequence seq = tk.tokenSequence(java); - if (seq != null) { - for (LiteralTree lt : result) { - final int start = (int) sp.getStartPosition(cu, lt); - final int end = (int) sp.getEndPosition(cu, lt); - seq.move(start); - while (seq.moveNext() && seq.offset() < end) { - seq.createEmbedding(javaScript, 1, 1, true); - } - } - } - } - } catch (IOException ioe) { - Exceptions.printStackTrace(ioe); - } - } - } - - - - - private static final class Finder extends TreePathScanner> { - - private final Trees trees; - private CompilationUnitTree cu; - private boolean inEmbedding; - - Finder(final Trees trees) { - this.trees = trees; - } - - @Override - public Void visitCompilationUnit( - final CompilationUnitTree unit, - final List p) { - this.cu = unit; - return super.visitCompilationUnit(unit, p); - } - - - - @Override - public Void visitMethod( - final MethodTree m, - final List p) { - for (AnnotationTree a : m.getModifiers().getAnnotations()) { - final TypeElement ae = (TypeElement) trees.getElement(TreePath.getPath(cu, a.getAnnotationType())); - if (ae != null && JS_ANNOTATION.contentEquals(ae.getQualifiedName())) { - final List args = a.getArguments(); - for (ExpressionTree kvp : args) { - if (kvp instanceof AssignmentTree) { - final AssignmentTree assignemt = (AssignmentTree) kvp; - if (BODY.equals(assignemt.getVariable().toString())) { - inEmbedding = true; - try { - scan(assignemt.getExpression(), p); - } finally { - inEmbedding = false; - } - } - } - } - } - } - return null; - } - - @Override - public Void visitLiteral(LiteralTree node, List p) { - if (inEmbedding) { - p.add(node); - } - return super.visitLiteral(node, p); - } - - } - - @MimeRegistration( - service = TaskFactory.class, - mimeType = JAVA_MIME_TYPE) - public static final class Factory extends TaskFactory { - @Override - public Collection create(Snapshot snpsht) { - return Collections.singleton(new JSEmbeddingProvider()); - } - } - -} diff -r e995e8d39240 -r ba912ef24b27 ide/editor/src/main/java/org/apidesign/bck2brwsr/ide/editor/JSNI2JavaScriptBody.java --- a/ide/editor/src/main/java/org/apidesign/bck2brwsr/ide/editor/JSNI2JavaScriptBody.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +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.ide.editor; - -import com.sun.source.tree.AnnotationTree; -import com.sun.source.tree.ExpressionTree; -import com.sun.source.tree.LiteralTree; -import com.sun.source.tree.MethodTree; -import com.sun.source.tree.Tree.Kind; -import com.sun.source.tree.VariableTree; -import com.sun.source.util.TreePath; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import org.netbeans.api.java.lexer.JavaTokenId; -import static org.netbeans.api.java.lexer.JavaTokenId.BLOCK_COMMENT; -import static org.netbeans.api.java.lexer.JavaTokenId.JAVADOC_COMMENT; -import static org.netbeans.api.java.lexer.JavaTokenId.LINE_COMMENT; -import static org.netbeans.api.java.lexer.JavaTokenId.WHITESPACE; -import org.netbeans.api.java.source.CompilationInfo; -import org.netbeans.api.java.source.TreeMaker; -import org.netbeans.api.lexer.Token; -import org.netbeans.api.lexer.TokenSequence; -import org.netbeans.spi.editor.hints.ErrorDescription; -import org.netbeans.spi.editor.hints.Fix; -import org.netbeans.spi.java.hints.ErrorDescriptionFactory; -import org.netbeans.spi.java.hints.Hint; -import org.netbeans.spi.java.hints.HintContext; -import org.netbeans.spi.java.hints.JavaFix; -import org.netbeans.spi.java.hints.TriggerTreeKind; -import org.openide.util.NbBundle.Messages; - -@Hint(displayName = "#DN_JSNI2JavaScriptBody", description = "#DESC_JSNI2JavaScriptBody", category = "general") -@Messages({ - "DN_JSNI2JavaScriptBody=JSNI to @JavaScriptBody", - "DESC_JSNI2JavaScriptBody=JSNI to @JavaScriptBody" -}) -public class JSNI2JavaScriptBody { - - @TriggerTreeKind(Kind.METHOD) - @Messages("ERR_JSNI2JavaScriptBody=Can convert JSNI to @JavaScriptBody") - public static ErrorDescription computeWarning(final HintContext ctx) { - Token token = findBlockToken(ctx.getInfo(), ctx.getPath(), ctx); - - if (token == null) { - return null; - } - - Fix fix = new FixImpl(ctx.getInfo(), ctx.getPath()).toEditorFix(); - return ErrorDescriptionFactory.forName(ctx, ctx.getPath(), Bundle.ERR_JSNI2JavaScriptBody(), fix); - } - - private static Token findBlockToken(CompilationInfo info, TreePath path, HintContext ctx) { - int end = (int) info.getTrees().getSourcePositions().getEndPosition(path.getCompilationUnit(), path.getLeaf()); - TokenSequence ts = info.getTokenHierarchy().tokenSequence(JavaTokenId.language()); - - if (ts == null) return null; - - ts.move(end); - - if ((ctx != null && ctx.isCanceled()) || !ts.movePrevious() || ts.token().id() != JavaTokenId.SEMICOLON) return null; - - OUTER: while (ts.movePrevious()) { - if (ctx != null && ctx.isCanceled()) return null; - - switch (ts.token().id()) { - case WHITESPACE: break; - case LINE_COMMENT: break; - case JAVADOC_COMMENT: break; - case BLOCK_COMMENT: - final CharSequence tok = ts.token().text(); - final int l = tok.length(); - if (l > 4 - && tok.subSequence(0, 4).toString().equals("/*-{") // NOI18N - && tok.subSequence(l - 4, l).toString().equals("}-*/") // NOI18N - ) { - return ts.offsetToken(); - } - break; - default: - break OUTER; - } - } - - return null; - } - - private static final class FixImpl extends JavaFix { - - public FixImpl(CompilationInfo info, TreePath tp) { - super(info, tp); - } - - @Override - @Messages("FIX_JSNI2JavaScriptBody=Convert JSNI to @JavaScriptBody") - protected String getText() { - return Bundle.FIX_JSNI2JavaScriptBody(); - } - - @Override - protected void performRewrite(TransformationContext ctx) { - Token jsniComment = findBlockToken(ctx.getWorkingCopy(), ctx.getPath(), null); - - if (jsniComment == null) { - //XXX: warn? - return ; - } - - JsniCommentTokenizer tok = new JsniCommentTokenizer(); - ManglingSink ms = new ManglingSink(); - final CharSequence cmnt = jsniComment.text(); - tok.process(cmnt.subSequence(4, cmnt.length() - 4), ms); - - TreeMaker make = ctx.getWorkingCopy().getTreeMaker(); - MethodTree mt = (MethodTree) ctx.getPath().getLeaf(); - List params = new ArrayList(); - - for (VariableTree p : mt.getParameters()) { - params.add(make.Literal(p.getName().toString())); - } - - AnnotationTree jsBody = make.Annotation(make.QualIdent("org.apidesign.bck2brwsr.core.JavaScriptBody"), - Arrays.asList( - make.Assignment(make.Identifier("args"), make.NewArray(null, Collections.emptyList(), params)), - make.Assignment(make.Identifier("body"), make.Literal(ms.out.toString())) - ) - ); - - - ctx.getWorkingCopy().rewrite(mt.getModifiers(), make.addModifiersAnnotation(mt.getModifiers(), jsBody)); - } - } -} diff -r e995e8d39240 -r ba912ef24b27 ide/editor/src/main/java/org/apidesign/bck2brwsr/ide/editor/JsniCommentTokenizer.java --- a/ide/editor/src/main/java/org/apidesign/bck2brwsr/ide/editor/JsniCommentTokenizer.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +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.ide.editor; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -final class JsniCommentTokenizer { - - /** - * Tokenize the contents of JSNI comment into the provided {@linkplain Sink}. - * @param in the contents of JSNI comment - * @param out the sink that consumes parsed tokens - */ - public void process(final CharSequence in, final Sink out) { - final Matcher member = Pattern.compile("@([^:]+)::([a-zA-Z_$][a-zA-Z\\d_$]*)").matcher(in); - final Matcher signature = Pattern.compile("\\(([^\\)]*)\\)").matcher(in); - - int i = 0; - while (true) { - if (member.find(i)) { - final int memberStart = member.start(); - final int memberEnd = member.end(); - if (memberStart > i) out.javascript(in.subSequence(i, memberStart).toString()); - - final String clazz = member.group(1); - final String name = member.group(2); - - if (in.charAt(memberEnd) == '(') { - if (!signature.find(memberEnd)) { - throw new IllegalStateException("Expected method signature"); - } - assert signature.start() == memberEnd; - out.method(clazz, name, signature.group(1)); - i = signature.end(); - } else { - out.field(clazz, name); - i = memberEnd; - } - } else { - out.javascript(in.subSequence(i, in.length()).toString()); - break; - } - } - } - - - static interface Sink { - void javascript(String s); - - void method(String clazz, String method, String signature); - - void field(String clazz, String field); - } -} diff -r e995e8d39240 -r ba912ef24b27 ide/editor/src/main/java/org/apidesign/bck2brwsr/ide/editor/ManglingSink.java --- a/ide/editor/src/main/java/org/apidesign/bck2brwsr/ide/editor/ManglingSink.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +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.ide.editor; - -/** - * An implementation of {@linkplain JsniCommentTokenizer.Sink} that generates B2B - */ -class ManglingSink implements JsniCommentTokenizer.Sink { - - final StringBuilder out = new StringBuilder(); - - public void javascript(String s) { - out.append(s); - } - - public void method(String clazz, String method, String signature) { - out.append(mangle(clazz, method, signature)); - } - - public void field(String clazz, String field) { -// out.append(field); - out.append('_').append(field).append('(').append(')'); - } - - - @Override - public String toString() { - return out.toString(); - } - - - static String mangle(String clazz, String method, String signature) { - final StringBuilder builder = new StringBuilder(); - builder.append(method); - builder.append("__"); - builder.append(mangle(JNIHelper.signature(JNIHelper.method(clazz, method, signature).getReturnType()))); - builder.append(mangle(signature)); - return builder.toString(); - } - - - static String mangle(String txt) { - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < txt.length(); i++) { - final char ch = txt.charAt(i); - switch (ch) { - case '/': sb.append('_'); break; - case '_': sb.append("_1"); break; - case ';': sb.append("_2"); break; - case '[': sb.append("_3"); break; - default: sb.append(ch); break; - } - } - return sb.toString(); - } -} diff -r e995e8d39240 -r ba912ef24b27 ide/editor/src/main/nbm/manifest.mf --- a/ide/editor/src/main/nbm/manifest.mf Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -Manifest-Version: 1.0 -OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/jackpot30/test/hints/Bundle.properties diff -r e995e8d39240 -r ba912ef24b27 ide/editor/src/main/resources/org/netbeans/modules/jackpot30/test/hints/Bundle.properties --- a/ide/editor/src/main/resources/org/netbeans/modules/jackpot30/test/hints/Bundle.properties Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +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. -# - -OpenIDE-Module-Name=Bck2Brwsr Editor Support -OpenIDE-Module-Short-Description=Provides hints, coloring, etc. for the bck2brwsr.apidesign.org project -OpenIDE-Module-Display-Category=Java diff -r e995e8d39240 -r ba912ef24b27 ide/editor/src/test/java/org/apidesign/bck2brwsr/ide/editor/DejsniReaderTest.java --- a/ide/editor/src/test/java/org/apidesign/bck2brwsr/ide/editor/DejsniReaderTest.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +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.ide.editor; - -import org.apidesign.bck2brwsr.core.JavaScriptBody; -import org.netbeans.modules.java.hints.test.api.HintTest; -import org.openide.filesystems.FileUtil; -import org.testng.annotations.Test; - -public class DejsniReaderTest { - - @Test - public void test1() throws Exception { - String s = "class Test {\n" + - " /** javadoc */\n" + - " public native void test() /*-{\n" + - " // body\n" + - " }-*/;\n" + - "}\n"; - - String expected = " import org.apidesign.bck2brwsr.core.JavaScriptBody;\n" - + "class Test {\n" + - "\n" + - " /** javadoc */\n" + - " @JavaScriptBody(args = {}, body = \"\\n // body\\n \")\n" + - " public native void test();\n" + - "}\n"; - - HintTest.create() - .input(s) - .classpath(FileUtil.getArchiveRoot(JavaScriptBody.class.getProtectionDomain().getCodeSource().getLocation())) - .run(JSNI2JavaScriptBody.class) - .findWarning("2:23-2:27:verifier:" + Bundle.ERR_JSNI2JavaScriptBody()) - .applyFix() - .assertCompilable() - .assertOutput(expected); - } - - - @Test - public void test2() throws Exception { - String s = "class Test {\n" + - " /** javadoc */\n" + - " @SuppressWarnings(\"unused\")\n" + - " // comment\n" + - " public native void test() /*-{\n" + - " // body\n" + - " }-*/;\n" + - "}\n"; - - String expected = " import org.apidesign.bck2brwsr.core.JavaScriptBody;\n" - + "class Test {\n" + - "\n" + - " /** javadoc */\n" + - " @SuppressWarnings(\"unused\")\n" + - " // comment\n" + - " @JavaScriptBody(args = {}, body = \"\\n // body\\n \")\n" + - " public native void test();\n" + - "}\n"; - HintTest.create() - .input(s) - .classpath(FileUtil.getArchiveRoot(JavaScriptBody.class.getProtectionDomain().getCodeSource().getLocation())) - .run(JSNI2JavaScriptBody.class) - .findWarning("4:23-4:27:verifier:" + Bundle.ERR_JSNI2JavaScriptBody()) - .applyFix() - .assertCompilable() - .assertOutput(expected); - } -} \ No newline at end of file diff -r e995e8d39240 -r ba912ef24b27 ide/editor/src/test/java/org/apidesign/bck2brwsr/ide/editor/JSNI2JavaScriptBodyTest.java --- a/ide/editor/src/test/java/org/apidesign/bck2brwsr/ide/editor/JSNI2JavaScriptBodyTest.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +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.ide.editor; - -import org.apidesign.bck2brwsr.core.JavaScriptBody; -import org.netbeans.modules.java.hints.test.api.HintTest; -import org.openide.filesystems.FileUtil; -import org.testng.annotations.Test; - -public class JSNI2JavaScriptBodyTest { - - @Test - public void testFixWorking() throws Exception { - HintTest.create() - .input("package test;\n" + - "public class Test {\n" + - " public native void run(int a) /*-{ this.a = a; }-*/;\n" + - "}\n") - .classpath(FileUtil.getArchiveRoot(JavaScriptBody.class.getProtectionDomain().getCodeSource().getLocation())) - .run(JSNI2JavaScriptBody.class) - .findWarning("2:23-2:26:verifier:" + Bundle.ERR_JSNI2JavaScriptBody()) - .applyFix() - .assertCompilable() - .assertOutput("package test;\n" + - "import org.apidesign.bck2brwsr.core.JavaScriptBody;\n" + - "public class Test {\n" + - " @JavaScriptBody(args = {\"a\"}, body = \" this.a = a; \")\n" + - " public native void run(int a);\n" + - "}\n"); - } -} diff -r e995e8d39240 -r ba912ef24b27 ide/editor/src/test/java/org/apidesign/bck2brwsr/ide/editor/JsniCommentTokenizerTest.java --- a/ide/editor/src/test/java/org/apidesign/bck2brwsr/ide/editor/JsniCommentTokenizerTest.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,154 +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.ide.editor; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import org.testng.Assert; -import org.testng.annotations.Test; - -public class JsniCommentTokenizerTest { - - private static class MockSink implements JsniCommentTokenizer.Sink { - final List out = new ArrayList(); - - public void javascript(String s) { - out.add("J " + s); - } - - public void method(String clazz, String method, String signature) { - out.add("M " + clazz + "|" + method + "|" + signature); - } - - public void field(String clazz, String field) { - out.add("F " + clazz + "|" + field); - } - } - - - @Test - public void testProcess_nop() throws IOException { - final String in = "foo bar"; - final List expected = new ArrayList(); - expected.add("J foo bar"); - - final JsniCommentTokenizer jsniCommentTokenizer = new JsniCommentTokenizer(); - final MockSink out = new MockSink(); - jsniCommentTokenizer.process(in, out); - - Assert.assertEquals(expected, out.out); - } - - @Test - public void testProcess_read_static_field() throws IOException { - final String in = " @com.google.gwt.examples.JSNIExample::myStaticField = val + \" and stuff\";"; - final List expected = new ArrayList(); - expected.add("J "); - expected.add("F com.google.gwt.examples.JSNIExample|myStaticField"); - expected.add("J = val + \" and stuff\";"); - - final JsniCommentTokenizer jsniCommentTokenizer = new JsniCommentTokenizer(); - final MockSink out = new MockSink(); - jsniCommentTokenizer.process(in, out); - - Assert.assertEquals(expected, out.out); - } - - @Test - public void testProcess_write_instance_field() throws IOException { - final String in = " x.@com.google.gwt.examples.JSNIExample::myInstanceField = val + \" and stuff\";"; - final List expected = new ArrayList(); - expected.add("J x."); - expected.add("F com.google.gwt.examples.JSNIExample|myInstanceField"); - expected.add("J = val + \" and stuff\";"); - - final JsniCommentTokenizer jsniCommentTokenizer = new JsniCommentTokenizer(); - final MockSink out = new MockSink(); - jsniCommentTokenizer.process(in, out); - - Assert.assertEquals(expected, out.out); - } - - @Test - public void testProcess_read_instance_field() throws IOException { - final String in = " var val = this.@com.google.gwt.examples.JSNIExample::myInstanceField;"; - final List expected = new ArrayList(); - expected.add("J var val = this."); - expected.add("F com.google.gwt.examples.JSNIExample|myInstanceField"); - expected.add("J ;"); - - final JsniCommentTokenizer jsniCommentTokenizer = new JsniCommentTokenizer(); - final MockSink out = new MockSink(); - jsniCommentTokenizer.process(in, out); - - Assert.assertEquals(expected, out.out); - } - - - @Test - public void testProcess_static_method() throws IOException { - final String in = " @com.google.gwt.examples.JSNIExample::staticFoo(Ljava/lang/String;)(s);"; - final List expected = new ArrayList(); - expected.add("J "); - expected.add("M com.google.gwt.examples.JSNIExample|staticFoo|Ljava/lang/String;"); - expected.add("J (s);"); - - final JsniCommentTokenizer jsniCommentTokenizer = new JsniCommentTokenizer(); - final MockSink out = new MockSink(); - jsniCommentTokenizer.process(in, out); - - Assert.assertEquals(expected, out.out); - } - - - @Test - public void testProcess_instance_method() throws IOException { - final String in = " x.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);"; - final List expected = new ArrayList(); - expected.add("J x."); - expected.add("M com.google.gwt.examples.JSNIExample|instanceFoo|Ljava/lang/String;"); - expected.add("J (s);"); - - final JsniCommentTokenizer jsniCommentTokenizer = new JsniCommentTokenizer(); - final MockSink out = new MockSink(); - jsniCommentTokenizer.process(in, out); - - Assert.assertEquals(expected, out.out); - } - - - @Test - public void testProcess_multiline() throws IOException { - final String in = - " x.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);" + - " @com.google.gwt.examples.JSNIExample::myStaticField = val + \" and stuff\";"; - final List expected = new ArrayList(); - expected.add("J x."); - expected.add("M com.google.gwt.examples.JSNIExample|instanceFoo|Ljava/lang/String;"); - expected.add("J (s); "); - expected.add("F com.google.gwt.examples.JSNIExample|myStaticField"); - expected.add("J = val + \" and stuff\";"); - - final JsniCommentTokenizer jsniCommentTokenizer = new JsniCommentTokenizer(); - final MockSink out = new MockSink(); - jsniCommentTokenizer.process(in, out); - - Assert.assertEquals(expected, out.out); - } -} diff -r e995e8d39240 -r ba912ef24b27 ide/editor/src/test/java/org/apidesign/bck2brwsr/ide/editor/ManglingSinkTest.java --- a/ide/editor/src/test/java/org/apidesign/bck2brwsr/ide/editor/ManglingSinkTest.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +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.ide.editor; - -import org.testng.Assert; -import org.testng.annotations.Test; - - -public class ManglingSinkTest { - - @Test - public void testMangle_1() { - Assert.assertEquals( - "binarySearch__I_3BIIB", - ManglingSink.mangle("java.util.Arrays", "binarySearch", "[BIIB") - ); - } - - @Test - public void testMangle_2() { - Assert.assertEquals( - "sort__V_3I", - ManglingSink.mangle("java.util.Arrays", "sort", "[I") - ); - } - - @Test - public void testMangle_3() { - Assert.assertEquals( - "binarySearch__I_3Ljava_lang_Object_2IILjava_lang_Object_2", - ManglingSink.mangle("java.util.Arrays", "binarySearch", "[Ljava/lang/Object;IILjava/lang/Object;") - ); - } - - - @Test - public void testField() { - final ManglingSink manglingSink = new ManglingSink(); - manglingSink.field(null, "value"); - - Assert.assertEquals("_value()", manglingSink.toString()); - } -} diff -r e995e8d39240 -r ba912ef24b27 ide/pom.xml --- a/ide/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ - - - 4.0.0 - - bck2brwsr - org.apidesign - 0.8-SNAPSHOT - - org.apidesign.bck2brwsr - ide - 0.8-SNAPSHOT - pom - IDE Support - - editor - - - - - org.apache.maven.plugins - maven-deploy-plugin - 2.7 - - true - - - - - diff -r e995e8d39240 -r ba912ef24b27 javaquery/api/pom.xml --- a/javaquery/api/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/javaquery/api/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr javaquery - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr javaquery.api - 0.8-SNAPSHOT + 0.9-SNAPSHOT JavaQuery API http://maven.apache.org diff -r e995e8d39240 -r ba912ef24b27 javaquery/demo-calculator-dynamic/pom.xml --- a/javaquery/demo-calculator-dynamic/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/javaquery/demo-calculator-dynamic/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,7 +4,7 @@ org.apidesign.bck2brwsr demo.calculator - 0.8-SNAPSHOT + 0.9-SNAPSHOT jar JavaQuery Demo - Calculator diff -r e995e8d39240 -r ba912ef24b27 javaquery/demo-calculator/pom.xml --- a/javaquery/demo-calculator/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/javaquery/demo-calculator/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,7 +4,7 @@ org.apidesign.bck2brwsr demo.static.calculator - 0.8-SNAPSHOT + 0.9-SNAPSHOT jar JavaQuery Demo - Calculator - Static Compilation diff -r e995e8d39240 -r ba912ef24b27 javaquery/demo-twitter/bck2brwsr-assembly.xml --- a/javaquery/demo-twitter/bck2brwsr-assembly.xml Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ - - - - - 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 e995e8d39240 -r ba912ef24b27 javaquery/demo-twitter/nb-configuration.xml --- a/javaquery/demo-twitter/nb-configuration.xml Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ - - - - - - - none - - diff -r e995e8d39240 -r ba912ef24b27 javaquery/demo-twitter/nbactions.xml --- a/javaquery/demo-twitter/nbactions.xml Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ - - - - - run - - process-classes - bck2brwsr:brwsr - - - diff -r e995e8d39240 -r ba912ef24b27 javaquery/demo-twitter/pom.xml --- a/javaquery/demo-twitter/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,151 +0,0 @@ - - - 4.0.0 - - javaquery - org.apidesign.bck2brwsr - 0.8-SNAPSHOT - - - org.apidesign.bck2brwsr - demo-twitter - 0.8-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 e995e8d39240 -r ba912ef24b27 javaquery/demo-twitter/src/main/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClient.java --- a/javaquery/demo-twitter/src/main/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClient.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +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.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 e995e8d39240 -r ba912ef24b27 javaquery/demo-twitter/src/main/resources/org/apidesign/bck2brwsr/demo/twitter/index.html --- a/javaquery/demo-twitter/src/main/resources/org/apidesign/bck2brwsr/demo/twitter/index.html Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ - - - - - - - - - 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 e995e8d39240 -r ba912ef24b27 javaquery/demo-twitter/src/main/resources/org/apidesign/bck2brwsr/demo/twitter/twitterExample.css --- a/javaquery/demo-twitter/src/main/resources/org/apidesign/bck2brwsr/demo/twitter/twitterExample.css Tue Apr 29 15:25:58 2014 +0200 +++ /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. - */ - -/* - 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 e995e8d39240 -r ba912ef24b27 javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClientTest.java --- a/javaquery/demo-twitter/src/test/java/org/apidesign/bck2brwsr/demo/twitter/TwitterClientTest.java Tue Apr 29 15:25:58 2014 +0200 +++ /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.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 e995e8d39240 -r ba912ef24b27 javaquery/pom.xml --- a/javaquery/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/javaquery/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,17 +4,16 @@ bck2brwsr org.apidesign - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr javaquery - 0.8-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 e995e8d39240 -r ba912ef24b27 ko/archetype-test/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype-test/pom.xml Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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(); + + // no longer 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 e995e8d39240 -r ba912ef24b27 ko/archetype/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/pom.xml Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,14 @@ + + + + run + + package + bck2brwsr:brwsr + + + true + NONE + + + diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,20 @@ + + + + run + + process-classes + bck2brwsr:brwsr + + + + debug + + process-classes + bck2brwsr:brwsr + + + maven + + + diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,20 @@ + + + + run + + process-classes + bck2brwsr:brwsr + + + + debug + + process-classes + bck2brwsr:brwsr + + + maven + + + diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,283 @@ + + + 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 + none + + + + + org.apidesign.bck2brwsr + bck2brwsr-maven-plugin + \${bck2brwsr.launcher.version} + + + + brwsr + + + + + \${basedir}/src/main/webapp/ + ${brwsr.startpage} + ${brwsr} + + + + org.netbeans.html + html4j-maven-plugin + ${net.java.html.version} + + + js-classes + + process-js-annotations + + + + + + 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.7 + test + + + org.apidesign.bck2brwsr + launcher.http + \${bck2brwsr.launcher.version} + test + + + org.apidesign.bck2brwsr + vmtest + \${bck2brwsr.version} + test + + + org.netbeans.html + net.java.html.json + \${net.java.html.version} + jar + + + org.netbeans.html + net.java.html.boot + \${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.netbeans.html + ko4j + \${net.java.html.version} + + + org.apidesign.bck2brwsr + launcher.fx + \${bck2brwsr.launcher.version} + runtime + + + + + bck2brwsr + + + brwsr + bck2brwsr + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + netbeans.ignore.jdk.bootclasspath + + + **/JsInteractionTest* + + + + + maven-assembly-plugin + 2.4 + + + distro-assembly + package + + single + + + + src/main/assembly/bck2brwsr.xml + + + + + + + + + + org.apidesign.bck2brwsr + emul + \${bck2brwsr.version} + rt + + + org.apidesign.bck2brwsr + ko-bck2brwsr + \${bck2brwsr.version} + runtime + + + org.apidesign.bck2brwsr + vm4brwsr + js + zip + \${bck2brwsr.version} + provided + + + + + diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,43 @@ + + + + bck2brwsr + + zip + + public_html + + + false + runtime + lib + + *:jar + *:rt + + + + false + provided + + *:js + + true + / + + + + + src/main/webapp/pages + / + true + + + + + ${project.build.directory}/${project.build.finalName}.jar + / + + + \ No newline at end of file diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,45 @@ +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(final Data model) { + confirmByUser("Really turn off?", new Runnable() { + @Override + public void run() { + model.setOn(false); + } + }); + } + + /** Shows direct interaction with JavaScript */ + @net.java.html.js.JavaScriptBody( + args = { "msg", "callback" }, + javacall = true, + body = "alert(msg); callback.@java.lang.Runnable::run()();" + ) + static native void confirmByUser(String msg, Runnable callback); +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,72 @@ + + + + + + + + + + +

Words Demo

+ +
+ + + +
+ + + +
+ + + + diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 ko/archetype/src/main/resources/archetype-resources/src/test/java/JsInteractionTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/archetype/src/main/resources/archetype-resources/src/test/java/JsInteractionTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,103 @@ +package ${package}; + +import java.io.Closeable; +import java.io.Reader; +import java.net.URL; +import java.util.ArrayList; +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 static org.testng.Assert.assertEquals; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** Tests for behavior of @JavaScriptBody methods. Set your JavaScript + * environment up (for example define alert or use some + * emulation library like env.js), register script presenter + * and then you can call methods that deal with JavaScript in your tests. + */ +public class JsInteractionTest { + private Closeable jsEngine; + @BeforeMethod public void initializeJSEngine() throws Exception { + jsEngine = Fn.activate(new ScriptPresenter()); + } + + @AfterMethod public void shutdownJSEngine() throws Exception { + jsEngine.close(); + } + + @Test public void testCallbackFromJavaScript() throws Exception { + class R implements Runnable { + int called; + + @Override + public void run() { + called++; + } + } + R callback = new R(); + + DataModel.confirmByUser("Hello", callback); + + assertEquals(callback.called, 1, "One immediate callback"); + } + + private static class ScriptPresenter implements Fn.Presenter { + private final ScriptEngine eng; + + public ScriptPresenter() throws ScriptException { + eng = new ScriptEngineManager().getEngineByName("javascript"); + eng.eval("function alert(msg) { Packages.java.lang.System.out.println(msg); };"); + } + + @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("})()"); + + final Object fn; + try { + fn = eng.eval(sb.toString()); + } catch (ScriptException ex) { + throw new IllegalStateException(ex); + } + 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 ? fn : thiz); + for (int i = 0; i < args.length; i++) { + all.add(args[i]); + } + Object ret = ((Invocable)eng).invokeMethod(fn, "call", all.toArray()); // NOI18N + return fn.equals(ret) ? null : thiz; + } + }; + } + + @Override + public void displayPage(URL page, Runnable onPageLoad) { + // not really displaying anything + onPageLoad.run(); + } + + @Override + public void loadScript(Reader code) throws Exception { + eng.eval(code); + } + } +} diff -r e995e8d39240 -r ba912ef24b27 ko/bck2brwsr/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/bck2brwsr/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,135 @@ + + + 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 + + + json + org.json + + + + + org.apidesign.bck2brwsr + vmtest + ${project.version} + test + + + org.apidesign.bck2brwsr + launcher.http + ${project.version} + test + + + asm + org.ow2.asm + + + + + org.netbeans.html + net.java.html.json + ${net.java.html.version} + + + org.netbeans.html + net.java.html.json.tck + ${net.java.html.version} + test + + + org.apidesign.bck2brwsr + core + ${project.version} + jar + + + org.netbeans.html + net.java.html.boot + ${net.java.html.version} + jar + + + asm + org.ow2.asm + + + + + org.netbeans.html + ko4j + ${net.java.html.version} + + + json + org.json + + + org.json-osgi + de.twentyeleven.skysail + + + + + diff -r e995e8d39240 -r ba912ef24b27 ko/bck2brwsr/src/test/java/org/apidesign/bck2brwsr/ko2brwsr/Bck2BrwsrJavaScriptBodyTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/bck2brwsr/src/test/java/org/apidesign/bck2brwsr/ko2brwsr/Bck2BrwsrJavaScriptBodyTest.java Wed Apr 30 15:04:10 2014 +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.ko2brwsr; + +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.apidesign.html.json.tck.JavaScriptTCK; +import org.apidesign.html.json.tck.KOTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class Bck2BrwsrJavaScriptBodyTest extends JavaScriptTCK { + @Factory public static Object[] tests() { + return VMTest.newTests().withClasses(testClasses()) + .withTestAnnotation(KOTest.class).build(); + } +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,122 @@ +/** + * 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.netbeans.html.ko4j.KO4J; +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() { + KO4J ko = new KO4J(null); + return Contexts.newBuilder(). + register(Transfer.class, ko.transfer(), 9). + register(WSTransfer.class, ko.websockets(), 9). + register(Technology.class, ko.knockout(), 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 e995e8d39240 -r ba912ef24b27 ko/fx/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/fx/pom.xml Wed Apr 30 15:04:10 2014 +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.netbeans.html + net.java.html.json + ${net.java.html.version} + + + org.testng + testng + test + + + org.netbeans.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.netbeans.html + net.java.html.boot + ${net.java.html.version} + jar + + + org.netbeans.html + ko4j + ${net.java.html.version} + jar + + + org.apidesign.bck2brwsr + vmtest + ${project.version} + test + jar + + + org.netbeans.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 e995e8d39240 -r ba912ef24b27 ko/fx/src/test/java/org/apidesign/bck2brwsr/kofx/JavaScriptBodyFXBrwsrTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/fx/src/test/java/org/apidesign/bck2brwsr/kofx/JavaScriptBodyFXBrwsrTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,36 @@ +/** + * 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 org.apidesign.bck2brwsr.vmtest.VMTest; +import org.apidesign.html.json.tck.JavaScriptTCK; +import org.apidesign.html.json.tck.KOTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class JavaScriptBodyFXBrwsrTest extends JavaScriptTCK { + @Factory public static Object[] create() { + return VMTest.newTests(). + withLaunchers("fxbrwsr"). + withClasses(testClasses()). + withTestAnnotation(KOTest.class).build(); + } +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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.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 java.util.concurrent.Executor; +import net.java.html.BrwsrCtx; +import net.java.html.js.JavaScriptBody; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.apidesign.html.boot.spi.Fn; +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.json.JSONException; +import org.json.JSONObject; +import org.netbeans.html.ko4j.KO4J; +import org.netbeans.html.wstyrus.TyrusContext; +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() { + final Fn.Presenter p = Fn.activePresenter(); + KO4J ko = new KO4J(p); + TyrusContext tc = new TyrusContext(); + Contexts.Builder b = Contexts.newBuilder(). + register(Technology.class, ko.knockout(), 10). + register(Transfer.class, ko.transfer(), 10); + if (p instanceof Executor) { + b.register(Executor.class, (Executor)p, 10); + } + try { + Class.forName("java.util.function.Function"); + // prefer WebView's WebSockets on JDK8 + b.register(WSTransfer.class, ko.websockets(), 10); + } catch (ClassNotFoundException ex) { + // ok, JDK7 needs tyrus + b.register(WSTransfer.class, tc, 20); + b.register(Transfer.class, tc, 5); + } + return b.build(); + } + + @Override + public Object createJSON(Map values) { + Object json = createJSON(); + for (Map.Entry entry : values.entrySet()) { + setProperty(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 setProperty(Object json, String key, Object value); + + @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 e995e8d39240 -r ba912ef24b27 ko/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko/pom.xml Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 launcher/api/pom.xml --- a/launcher/api/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/api/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr launcher-pom - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr launcher - 0.8-SNAPSHOT + 0.9-SNAPSHOT Launcher API http://maven.apache.org diff -r e995e8d39240 -r ba912ef24b27 launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java --- a/launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/InvocationContext.java Wed Apr 30 15:04:10 2014 +0200 @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -95,7 +96,6 @@ wait.countDown(); } - static final class Resource { final InputStream httpContent; final String httpType; diff -r e995e8d39240 -r ba912ef24b27 launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java --- a/launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/api/src/main/java/org/apidesign/bck2brwsr/launcher/Launcher.java Wed Apr 30 15:04:10 2014 +0200 @@ -135,14 +135,34 @@ * 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 { - Launcher l = createBrowser(null); - l.showDirectory(directory, startpage); - return (Closeable) l; + return showDir(null, directory, null, startpage); } abstract InvocationContext runMethod(InvocationContext c) throws IOException; @@ -152,7 +172,7 @@ return Launcher.class.getClassLoader().loadClass(cn); } - void showDirectory(File directory, String startpage) throws IOException { + void showDirectory(File directory, String startpage, boolean addClasses) throws IOException { throw new UnsupportedOperationException(); } diff -r e995e8d39240 -r ba912ef24b27 launcher/fx/pom.xml --- a/launcher/fx/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/fx/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr launcher-pom - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr launcher.fx - 0.8-SNAPSHOT + 0.9-SNAPSHOT FXBrwsr Launcher http://maven.apache.org @@ -18,8 +18,8 @@ maven-compiler-plugin 2.3.2 - 1.7 - 1.7 + 1.6 + 1.6 @@ -58,5 +58,44 @@ testng test + + + ${project.groupId} + core + ${project.version} + compile + + + org.ow2.asm + asm + 4.1 + + + org.netbeans.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 e995e8d39240 -r ba912ef24b27 launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/BaseHTTPLauncher.java --- a/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/BaseHTTPLauncher.java Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/BaseHTTPLauncher.java Wed Apr 30 15:04:10 2014 +0200 @@ -17,6 +17,8 @@ */ package org.apidesign.bck2brwsr.launcher; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.IOException; @@ -52,7 +54,13 @@ 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; /** * Lightweight server to launch Bck2Brwsr applications and tests. @@ -62,8 +70,8 @@ abstract class BaseHTTPLauncher extends Launcher implements Closeable, Callable { static final Logger LOG = Logger.getLogger(BaseHTTPLauncher.class.getName()); private static final InvocationContext END = new InvocationContext(null, null, null); - private final Set loaders = new LinkedHashSet<>(); - private final BlockingQueue methods = new LinkedBlockingQueue<>(); + private final Set loaders = new LinkedHashSet(); + private final BlockingQueue methods = new LinkedBlockingQueue(); private long timeOut; private final Res resources = new Res(); private final String cmd; @@ -105,7 +113,7 @@ if (!startpage.startsWith("/")) { startpage = "/" + startpage; } - HttpServer s = initServer(".", true); + HttpServer s = initServer(".", true, ""); int last = startpage.lastIndexOf('/'); String prefix = startpage.substring(0, last); String simpleName = startpage.substring(last); @@ -113,19 +121,24 @@ server = s; try { launchServerAndBrwsr(s, simpleName); - } catch (URISyntaxException | InterruptedException ex) { + } catch (Exception ex) { throw new IOException(ex); } } - void showDirectory(File dir, String startpage) throws IOException { + void showDirectory(File dir, String startpage, boolean addClasses) throws IOException { if (!startpage.startsWith("/")) { startpage = "/" + startpage; } - HttpServer s = initServer(dir.getPath(), false); + 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 (URISyntaxException | InterruptedException ex) { + } catch (Exception ex) { throw new IOException(ex); } } @@ -149,16 +162,16 @@ } } - private HttpServer initServer(String path, boolean addClasses) throws IOException { - HttpServer s = HttpServer.createSimpleServer(path, new PortRange(8080, 65535)); -/* + 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(1). + setCorePoolSize(3). setMaxPoolSize(5); ThreadPoolConfig oneKernel = ThreadPoolConfig.defaultConfig().copy(). setPoolName("Kernel Fx/Bck2"). - setCorePoolSize(1). + setCorePoolSize(3). setMaxPoolSize(3); for (NetworkListener nl : s.getListeners()) { nl.getTransport().setWorkerThreadPoolConfig(fewThreads); @@ -166,36 +179,81 @@ } */ 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 VM(), "/bck2brwsr.js"); 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); + 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) { - if (ic == null || ic.resources.isEmpty()) { - throw new NullPointerException(); - } this.ic = ic; for (Resource r : ic.resources) { conf.addHttpHandler(this, r.httpPath); } } - public void close() { + public void close(DynamicResourceHandler del) { conf.removeHttpHandler(this); + delegate = del; } @Override public void service(Request request, Response response) throws Exception { + if (delegate != null) { + delegate.service(request, response); + return; + } + + if ("/dynamic".equals(request.getRequestURI())) { + boolean webSocket = false; + String mimeType = request.getParameter("mimeType"); + List params = new ArrayList(); + for (int i = 0; ; i++) { + String p = request.getParameter("param" + i); + if (p == null) { + break; + } + params.add(p); + if ("protocol:ws".equals(p)) { + webSocket = true; + continue; + } } + final String cnt = request.getParameter("content"); + String mangle = cnt.replace("%20", " ").replace("%0A", "\n"); + ByteArrayInputStream is = new ByteArrayInputStream(mangle.getBytes("UTF-8")); + URI url; + final Resource res = new Resource(is, mimeType, "/dynamic/res" + ++resourcesCount, params.toArray(new String[params.size()])); + if (webSocket) { + url = registerWebSocket(res); + } else { + url = registerResource(res); + } + response.getWriter().write(url.toString()); + response.getWriter().write("\n"); + return; + } + for (Resource r : ic.resources) { if (r.httpPath.equals(request.getRequestURI())) { LOG.log(Level.INFO, "Serving HttpResource for {0}", request.getRequestURI()); @@ -232,13 +290,26 @@ } } } + + private URI registerWebSocket(Resource r) { + WebSocketEngine.getEngine().register("", r.httpPath, new WS(r)); + return pageURL("ws", server, r.httpPath); + } + + private URI registerResource(Resource r) { + if (!ic.resources.contains(r)) { + ic.resources.add(r); + conf.addHttpHandler(this, r.httpPath); + } + return pageURL("http", server, r.httpPath); + } } conf.addHttpHandler(new Page(resources, harnessResource()), "/execute"); conf.addHttpHandler(new HttpHandler() { int cnt; - List cases = new ArrayList<>(); + List cases = new ArrayList(); DynamicResourceHandler prev; @Override public void service(Request request, Response response) throws Exception { @@ -270,11 +341,6 @@ } } - if (prev != null) { - prev.close(); - prev = null; - } - if (mi == null) { mi = methods.take(); caseNmbr = cnt++; @@ -286,10 +352,12 @@ LOG.log(Level.INFO, "End of data reached. Exiting."); return; } - - if (!mi.resources.isEmpty()) { - prev = new DynamicResourceHandler(mi); + final DynamicResourceHandler newRH = new DynamicResourceHandler(mi); + if (prev != null) { + prev.close(newRH); } + prev = newRH; + conf.addHttpHandler(prev, "/dynamic"); cases.add(mi); final String cn = mi.clazz.getName(); @@ -382,10 +450,7 @@ private Object[] launchServerAndBrwsr(HttpServer server, final String page) throws IOException, URISyntaxException, InterruptedException { server.start(); - NetworkListener listener = server.getListeners().iterator().next(); - int port = listener.getPort(); - - URI uri = new URI("http://localhost:" + port + page); + URI uri = pageURL("http", server, page); return showBrwsr(uri); } private static String toUTF8(String value) throws UnsupportedEncodingException { @@ -503,6 +568,16 @@ return null; } + 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); + } + } + final class Res { String compileJar(JarFile jar) throws IOException { return BaseHTTPLauncher.this.compileJar(jar); @@ -510,7 +585,10 @@ String compileFromClassPath(URL f) throws IOException { return BaseHTTPLauncher.this.compileFromClassPath(f, this); } - public URL get(String resource) throws IOException { + public URL get(String resource, int skip) throws IOException { + if (!resource.endsWith(".class")) { + return getResource(resource, skip); + } URL u = null; for (ClassLoader l : loaders) { Enumeration en = l.getResources(resource); @@ -523,12 +601,30 @@ } if (u != null) { if (u.toExternalForm().contains("rt.jar")) { - LOG.log(Level.WARNING, "Fallback to bootclasspath for {0}", u); + LOG.log(Level.WARNING, "No fallback to bootclasspath for {0}", u); + return null; } return u; } throw new IOException("Can't find " + resource); } + private URL getResource(String resource, int skip) throws IOException { + for (ClassLoader l : loaders) { + Enumeration en = l.getResources(resource); + while (en.hasMoreElements()) { + final URL now = en.nextElement(); + if (now.toExternalForm().contains("sisu-inject-bean")) { + // certainly we don't want this resource, as that + // module is not compiled with target 1.6, currently + continue; + } + if (--skip < 0) { + return now; + } + } + } + throw new IOException("Not found (anymore of) " + resource); + } } private static class Page extends HttpHandler { @@ -560,7 +656,8 @@ replace = args; } OutputStream os = response.getOutputStream(); - try (InputStream is = res.get(r).openStream()) { + try { + InputStream is = res.get(r, 0).openStream(); copyStream(is, os, request.getRequestURL().toString(), replace); } catch (IOException ex) { response.setDetailMessage(ex.getLocalizedMessage()); @@ -592,14 +689,28 @@ } - private class VM extends HttpHandler { + private class VMAndPages extends StaticHttpHandler { + private String vmResource; + + public VMAndPages() { + super((String[]) null); + } + @Override public void service(Request request, Response response) throws Exception { - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/javascript"); - StringBuilder sb = new StringBuilder(); - generateBck2BrwsrJS(sb, BaseHTTPLauncher.this.resources); - response.getWriter().write(sb.toString()); + 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; } } @@ -616,7 +727,9 @@ if (res.startsWith("/")) { res = res.substring(1); } - URL url = loader.get(res); + String skip = request.getParameter("skip"); + int skipCnt = skip == null ? 0 : Integer.parseInt(skip); + URL url = loader.get(res, skipCnt); if (url.getProtocol().equals("jar")) { JarURLConnection juc = (JarURLConnection) url.openConnection(); String s = loader.compileJar(juc.getJarFile()); @@ -639,6 +752,13 @@ Exception ex = new Exception("Won't server bytes of " + url); /* try (InputStream is = url.openStream()) { +======= + InputStream is = null; + try { + String skip = request.getParameter("skip"); + int skipCnt = skip == null ? 0 : Integer.parseInt(skip); + is = loader.get(res, skipCnt); +>>>>>>> other response.setContentType("text/javascript"); Writer w = response.getWriter(); w.append("(["); @@ -664,8 +784,33 @@ 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) { + LOG.log(Level.WARNING, null, ex); } } - } -} + }} diff -r e995e8d39240 -r ba912ef24b27 launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/FXBrwsrLauncher.java --- a/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/FXBrwsrLauncher.java Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/FXBrwsrLauncher.java Wed Apr 30 15:04:10 2014 +0200 @@ -17,13 +17,17 @@ */ 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; @@ -67,7 +71,17 @@ public void run() { LOG.log(Level.INFO, "In FX thread. Launching!"); try { - FXBrwsr.launch(FXBrwsr.class, url.toString()); + List params = new ArrayList(); + params.add(url.toString()); + if (isDebugged()) { + params.add("--toolbar=true"); + params.add("--firebug=true"); + String ud = System.getProperty("netbeans.user"); + if (ud != null) { + params.add("--userdir=" + ud); + } + } + FXBrwsr.launch(FXBrwsr.class, params.toArray(new String[params.size()])); LOG.log(Level.INFO, "Launcher is back. Closing"); close(); System.exit(0); @@ -87,17 +101,6 @@ sb.append("(function() {\n" + " var impl = this.bck2brwsr;\n" + " this.bck2brwsr = function() { return impl; };\n"); - if (isDebugged()) { - sb.append("var scr = window.document.createElement('script');\n"); - sb.append("scr.type = 'text/javascript';\n"); - sb.append("scr.src = 'https://getfirebug.com/firebug-lite.js';\n"); - sb.append("scr.text = '{ startOpened: true }';\n"); - sb.append("var head = window.document.getElementsByTagName('head')[0];"); - sb.append("head.appendChild(scr);\n"); - sb.append("var html = window.document.getElementsByTagName('html')[0];"); - sb.append("html.debug = true;\n"); - } - sb.append("})(window);\n"); JVMBridge.onBck2BrwsrLoad(); } @@ -116,25 +119,46 @@ String startPage = null; final ClassLoader cl = FXBrwsrLauncher.class.getClassLoader(); - startPage = findStartPage(cl, startPage); + URL[] manifestURL = { null }; + startPage = findStartPage(cl, startPage, manifestURL); if (startPage == null) { throw new NullPointerException("Can't find StartPage tag in manifests!"); } - Launcher.showURL("fxbrwsr", cl, startPage); + 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) throws IOException { + 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; - try (InputStream is = url.openStream()) { + InputStream is = null; + try { + is = url.openStream(); mf = new Manifest(is); + } finally { + if (is != null) is.close(); } String sp = mf.getMainAttributes().getValue("StartPage"); if (sp != null) { startPage = sp; + if (startURL != null) { + startURL[0] = url; + } break; } } diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,381 @@ +/** + * 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.setScaleX(zoom); + webView.setScaleY(zoom); + webView.setScaleZ(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 e995e8d39240 -r ba912ef24b27 launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java --- a/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Console.java Wed Apr 30 15:04:10 2014 +0200 @@ -25,7 +25,7 @@ import java.lang.reflect.Modifier; import java.net.URL; import java.util.Enumeration; -import javafx.scene.web.WebEngine; +import net.java.html.js.JavaScriptBody; import netscape.javascript.JSObject; /** @@ -35,17 +35,15 @@ public final class Console { public Console() { } - - private static Object getAttr(Object elem, String attr) { - return InvokeJS.CObject.call("getAttr", elem, attr); - } - private static void setAttr(String id, String attr, Object value) { - InvokeJS.CObject.call("setAttrId", id, attr, value); - } - private static void setAttr(Object id, String attr, Object value) { - InvokeJS.CObject.call("setAttr", id, attr, value); - } + @JavaScriptBody(args = { "elem", "attr" }, body = "return elem[attr].toString();") + private static native Object getAttr(Object elem, String attr); + + @JavaScriptBody(args = { "id", "attr", "value" }, body = "window.document.getElementById(id)[attr] = value;") + private static native void setAttr(String id, String attr, Object value); + + @JavaScriptBody(args = { "elem", "attr", "value" }, body = "elem[attr] = value;") + private static native void setAttr(Object id, String attr, Object value); private static void closeWindow() {} @@ -62,8 +60,7 @@ } private static void beginTest(Case c) { - Object[] arr = new Object[2]; - beginTest(c.getClassName() + "." + c.getMethodName(), c, arr); + Object[] arr = beginTest(c.getClassName() + "." + c.getMethodName(), c, new Object[2]); textArea = arr[0]; statusArea = arr[1]; } @@ -78,7 +75,7 @@ textArea = null; } - private static final String BEGIN_TEST = + @JavaScriptBody(args = { "test", "c", "arr" }, body = "var ul = window.document.getElementById('bck2brwsr.result');\n" + "var li = window.document.createElement('li');\n" + "var span = window.document.createElement('span');" @@ -103,27 +100,24 @@ + "p.appendChild(pre);\n" + "ul.appendChild(li);\n" + "arr[0] = pre;\n" - + "arr[1] = status;\n"; - - private static void beginTest(String test, Case c, Object[] arr) { - InvokeJS.CObject.call("beginTest", test, c, arr); - } + + "arr[1] = status;\n" + + "return arr;" + ) + private static native Object[] beginTest(String test, Case c, Object[] arr); - private static final String LOAD_TEXT = + @JavaScriptBody(args = { "url", "callback" }, javacall = true, 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 {" - + " arr[0] = this.responseText;\n" - + " callback.run__V();\n" - + " } catch (e) { alert(e); }" - + "};" - + "request.send();"; - private static void loadText(String url, Runnable callback, String[] arr) throws IOException { - InvokeJS.CObject.call("loadText", url, new Run(callback), arr); - } + + " try {\n" + + " callback.@org.apidesign.bck2brwsr.launcher.fximpl.OnMessage::onMessage(Ljava/lang/String;)(this.responseText);\n" + + " } catch (e) { alert(e); }\n" + + "};\n" + + "request.send();\n" + ) + private static native void loadText(String url, OnMessage callback) throws IOException; public static void runHarness(String url) throws IOException { new Console().harness(url); @@ -134,7 +128,7 @@ Request r = new Request(url); } - private static class Request implements Runnable { + private static class Request implements Runnable, OnMessage { private final String[] arr = { null }; private final String url; private Case c; @@ -142,15 +136,22 @@ private Request(String url) throws IOException { this.url = url; - loadText(url, this, arr); + loadText(url, this); } private Request(String url, String u) throws IOException { this.url = url; - loadText(u, this, arr); + loadText(u, this); + } + + @Override + public void onMessage(String msg) { + arr[0] = msg; + run(); } @Override public void run() { + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); try { if (c == null) { String data = arr[0]; @@ -232,7 +233,9 @@ if (u == null) { throw new IOException("Can't find " + name); } - try (InputStream is = u.openStream()) { + InputStream is = null; + try { + is = u.openStream(); byte[] arr; arr = new byte[is.available()]; int offset = 0; @@ -244,15 +247,20 @@ offset += len; } return arr; + } finally { + if (is != null) is.close(); } } private static void turnAssetionStatusOn() { } - private static Object schedule(Runnable r, int time) { - return InvokeJS.CObject.call("schedule", new Run(r), time); - } + @JavaScriptBody(args = { "r", "time" }, javacall = true, body = + "return window.setTimeout(function() { " + + "r.@java.lang.Runnable::run()(); " + + "}, time);" + ) + private static native Object schedule(Runnable r, int time); private static final class Case { private final Object data; @@ -348,48 +356,16 @@ } return res; } - - private static Object toJSON(String s) { - return InvokeJS.CObject.call("toJSON", s); - } + + @JavaScriptBody(args = { "s" }, body = "return eval('(' + s + ')');") + private static native Object toJSON(String s); private static Object value(String p, Object d) { return ((JSObject)d).getMember(p); } } - private static String safe(String txt) { - return "try {" + txt + "} catch (err) { alert(err); }"; - } - static { turnAssetionStatusOn(); } - - private static final class InvokeJS { - static final JSObject CObject = initJS(); - - private static JSObject initJS() { - WebEngine web = (WebEngine) System.getProperties().get("webEngine"); - return (JSObject) web.executeScript("(function() {" - + "var CObject = {};" - - + "CObject.getAttr = function(elem, attr) { return elem[attr].toString(); };" - - + "CObject.setAttrId = function(id, attr, value) { window.document.getElementById(id)[attr] = value; };" - + "CObject.setAttr = function(elem, attr, value) { elem[attr] = value; };" - - + "CObject.beginTest = function(test, c, arr) {" + safe(BEGIN_TEST) + "};" - - + "CObject.loadText = function(url, callback, arr) {" + safe(LOAD_TEXT.replace("run__V", "run")) + "};" - - + "CObject.schedule = function(r, time) { return window.setTimeout(function() { r.run(); }, time); };" - - + "CObject.toJSON = function(s) { return eval('(' + s + ')'); };" - - + "return CObject;" - + "})(this)"); - } - } - } diff -r e995e8d39240 -r ba912ef24b27 launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/FXBrwsr.java --- a/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/FXBrwsr.java Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/FXBrwsr.java Wed Apr 30 15:04:10 2014 +0200 @@ -27,16 +27,13 @@ import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; -import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.geometry.VPos; -import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Button; -import javafx.scene.layout.ColumnConstraints; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.Pane; +import javafx.scene.control.ToolBar; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.scene.text.Text; @@ -55,30 +52,52 @@ */ public class FXBrwsr extends Application { private static final Logger LOG = Logger.getLogger(FXBrwsr.class.getName()); - + @Override public void start(Stage primaryStage) throws Exception { - Pane root = new WebViewPane(getParameters().getUnnamed()); - primaryStage.setScene(new Scene(root, 1024, 768)); - LOG.info("Showing the stage"); + WebView view = new WebView(); + 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); + HBox.setHgrow(vbox, Priority.ALWAYS); + vbox.getChildren().add(view); + VBox.setVgrow(view, Priority.ALWAYS); + + 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(); - LOG.log(Level.INFO, "State shown: {0}", primaryStage.isShowing()); } /** * Create a resizable WebView pane */ - private class WebViewPane extends Pane { - private final JVMBridge bridge = new JVMBridge(); + private static class WebController { + private final JVMBridge bridge; - public WebViewPane(List params) { + public WebController(WebView view, List params) { + this.bridge = new JVMBridge(view.getEngine()); LOG.log(Level.INFO, "Initializing WebView with {0}", params); - VBox.setVgrow(this, Priority.ALWAYS); - setMaxWidth(Double.MAX_VALUE); - setMaxHeight(Double.MAX_VALUE); - WebView view = new WebView(); - view.setMinSize(500, 400); - view.setPrefSize(500, 400); final WebEngine eng = view.getEngine(); try { JVMBridge.addBck2BrwsrLoad(new InitBck2Brwsr(eng)); @@ -120,13 +139,6 @@ dialogStage.showAndWait(); } }); - GridPane grid = new GridPane(); - grid.setVgap(5); - grid.setHgap(5); - GridPane.setConstraints(view, 0, 1, 2, 1, HPos.CENTER, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS); - grid.getColumnConstraints().addAll(new ColumnConstraints(100, 100, Double.MAX_VALUE, Priority.ALWAYS, HPos.CENTER, true), new ColumnConstraints(40, 40, 40, Priority.NEVER, HPos.CENTER, true)); - grid.getChildren().addAll(view); - getChildren().add(grid); } boolean initBck2Brwsr(WebEngine webEngine) { @@ -134,28 +146,12 @@ LOG.log(Level.FINE, "window: {0}", jsobj); Object prev = jsobj.getMember("bck2brwsr"); if ("undefined".equals(prev)) { - System.getProperties().put("webEngine", webEngine); jsobj.setMember("bck2brwsr", bridge); return true; } return false; } - @Override - protected void layoutChildren() { - List managed = getManagedChildren(); - double width = getWidth(); - double height = getHeight(); - double top = getInsets().getTop(); - double right = getInsets().getRight(); - double left = getInsets().getLeft(); - double bottom = getInsets().getBottom(); - for (int i = 0; i < managed.size(); i++) { - Node child = managed.get(i); - layoutInArea(child, left, top, width - left - right, height - top - bottom, 0, Insets.EMPTY, true, true, HPos.CENTER, VPos.CENTER); - } - } - private class InitBck2Brwsr implements ChangeListener, Runnable { private final WebEngine eng; diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,111 @@ +/** + * 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; + +/** + * + * @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 e995e8d39240 -r ba912ef24b27 launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/JVMBridge.java --- a/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/JVMBridge.java Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/JVMBridge.java Wed Apr 30 15:04:10 2014 +0200 @@ -17,16 +17,45 @@ */ package org.apidesign.bck2brwsr.launcher.fximpl; +import java.io.BufferedReader; +import java.io.Reader; +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 java.util.concurrent.Executor; +import java.util.logging.Level; +import java.util.logging.Logger; +import javafx.application.Platform; import javafx.beans.value.ChangeListener; +import javafx.scene.web.WebEngine; +import netscape.javascript.JSObject; +import org.apidesign.html.boot.spi.Fn; +import org.netbeans.html.boot.impl.FindResources; +import org.netbeans.html.boot.impl.FnUtils; /** * * @author Jaroslav Tulach */ public final class JVMBridge { + static final Logger LOG = Logger.getLogger(JVMBridge.class.getName()); + + 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(); @@ -47,18 +76,189 @@ } public Class loadClass(String name) throws ClassNotFoundException { - System.err.println("trying to load " + name); - ClassNotFoundException ex = null; - if (ldrs != null) for (ClassLoader l : ldrs) { - try { - return Class.forName(name, true, l); - } catch (ClassNotFoundException ex2) { - ex = ex2; + Fn.activate(presenter); + return Class.forName(name, true, cl); + } + + private final class WebPresenter + implements FindResources, Fn.Presenter, Fn.ToJavaScript, Fn.FromJavaScript, Executor { + @Override + public void findResources(String name, Collection results, boolean oneIsEnough) { + if (ldrs != null) for (ClassLoader l : ldrs) { + URL u = l.getResource(name); + if (u != null) { + results.add(u); + } } } - if (ex == null) { - ex = new ClassNotFoundException("No loaders"); + + @Override + public Fn defineFn(String code, String... names) { + return defineJSFn(code, names); } - throw ex; + private JSFn defineJSFn(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()); + } + + @Override + public Object toJava(Object js) { + return checkArray(js); + } + + @Override + public Object toJavaScript(Object toReturn) { + if (toReturn instanceof Object[]) { + return convertArrays((Object[]) toReturn); + } + return toReturn; + } + + @Override + public void execute(Runnable command) { + if (Platform.isFxApplicationThread()) { + command.run(); + } else { + Platform.runLater(command); + } + } + + final JSObject convertArrays(Object[] arr) { + for (int i = 0; i < arr.length; i++) { + if (arr[i] instanceof Object[]) { + arr[i] = convertArrays((Object[]) arr[i]); + } + } + final JSObject wrapArr = (JSObject) wrapArrFn().call("array", arr); // NOI18N + return wrapArr; + } + + private JSObject wrapArrImpl; + + private final JSObject wrapArrFn() { + if (wrapArrImpl == null) { + try { + wrapArrImpl = (JSObject) defineJSFn(" var k = {};" + + " k.array= function() {" + + " return Array.prototype.slice.call(arguments);" + + " };" + + " return k;" + ).invokeImpl(null, false); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + return wrapArrImpl; + } + + final Object checkArray(Object val) { + int length = ((Number) arraySizeFn().call("array", val, null)).intValue(); + if (length == -1) { + return val; + } + Object[] arr = new Object[length]; + arraySizeFn().call("array", val, arr); + return arr; + } + private JSObject arraySize; + + private final JSObject arraySizeFn() { + if (arraySize == null) { + try { + arraySize = (JSObject) defineJSFn(" var k = {};" + + " k.array = function(arr, to) {" + + " if (to === null) {" + + " if (Object.prototype.toString.call(arr) === '[object Array]') return arr.length;" + + " else return -1;" + + " } else {" + + " var l = arr.length;" + + " for (var i = 0; i < l; i++) to[i] = arr[i];" + + " return l;" + + " }" + + " };" + + " return k;" + ).invokeImpl(null, false); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + return arraySize; + } + + } + + 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 { + return invokeImpl(thiz, true, args); + } + + final Object invokeImpl(Object thiz, boolean arrayChecks, Object... args) throws Exception { + try { + List all = new ArrayList(args.length + 1); + all.add(thiz == null ? fn : thiz); + for (int i = 0; i < args.length; i++) { + if (arrayChecks && args[i] instanceof Object[]) { + Object[] arr = (Object[]) args[i]; + Object conv = ((WebPresenter) presenter()).convertArrays(arr); + args[i] = conv; + } + all.add(args[i]); + } + Object ret = fn.call("call", all.toArray()); // NOI18N + if (ret == fn) { + return null; + } + if (!arrayChecks) { + return ret; + } + return ((WebPresenter) presenter()).checkArray(ret); + } catch (Error t) { + t.printStackTrace(); + throw t; + } catch (Exception t) { + t.printStackTrace(); + throw t; + } + } } } diff -r e995e8d39240 -r ba912ef24b27 launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/OnMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/OnMessage.java Wed Apr 30 15:04:10 2014 +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.launcher.fximpl; + +/** + * + * @author Jaroslav Tulach + */ +interface OnMessage { + public void onMessage(String msg); +} diff -r e995e8d39240 -r ba912ef24b27 launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Run.java --- a/launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/Run.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +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.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 e995e8d39240 -r ba912ef24b27 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 e995e8d39240 -r ba912ef24b27 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 e995e8d39240 -r ba912ef24b27 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 e995e8d39240 -r ba912ef24b27 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 e995e8d39240 -r ba912ef24b27 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 e995e8d39240 -r ba912ef24b27 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 e995e8d39240 -r ba912ef24b27 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 e995e8d39240 -r ba912ef24b27 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 e995e8d39240 -r ba912ef24b27 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 e995e8d39240 -r ba912ef24b27 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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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.netbeans.html.boot.impl.FindResources; +import org.netbeans.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 val.equals(ret) ? 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() { + Fn.activate(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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 net.java.html.js.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 e995e8d39240 -r ba912ef24b27 launcher/http/pom.xml --- a/launcher/http/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/http/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr launcher-pom - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr launcher.http - 0.8-SNAPSHOT + 0.9-SNAPSHOT Bck2Brwsr Launcher http://maven.apache.org diff -r e995e8d39240 -r ba912ef24b27 launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java --- a/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/Bck2BrwsrLauncher.java Wed Apr 30 15:04:10 2014 +0200 @@ -17,9 +17,17 @@ */ package org.apidesign.bck2brwsr.launcher; +import java.io.File; +import java.io.FileReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.MalformedURLException; import java.net.URL; import java.util.jar.JarFile; +import java.util.logging.Level; +import org.apidesign.vm4brwsr.Bck2Brwsr; /** * Lightweight server to launch Bck2Brwsr applications and tests. @@ -48,15 +56,54 @@ @Override void generateBck2BrwsrJS(StringBuilder sb, final Res loader) throws IOException { - CompileCP.compileVM(sb, loader); + String b2b = System.getProperty("bck2brwsr.js"); + if (b2b != null) { + LOG.log(Level.INFO, "Serving bck2brwsr.js from {0}", b2b); + URL bu; + try { + bu = new URL(b2b); + } catch (MalformedURLException ex) { + File f = new File(b2b); + if (f.exists()) { + bu = f.toURI().toURL(); + } else { + throw ex; + } + } + try (Reader r = new InputStreamReader(bu.openStream())) { + char[] arr = new char[4096]; + for (;;) { + int len = r.read(arr); + if (len == -1) { + break; + } + sb.append(arr, 0, len); + } + } + } else { + LOG.log(Level.INFO, "Generating bck2brwsr.js from scratch", b2b); + CompileCP.compileVM(sb, loader); + } sb.append( - "(function WrapperVM(global) {" - + " function ldCls(res) {\n" + "(function WrapperVM(global) {\n" + + " var cache = {};\n" + + " function ldCls(res, skip) {\n" + + " var c = cache[res];\n" + + " if (c) {\n" + + " if (c[skip]) return c[skip];\n" + + " if (c[skip] === null) return null;\n" + + " } else {\n" + + " cache[res] = c = new Array();\n" + + " }\n" + " var request = new XMLHttpRequest();\n" - + " request.open('GET', '/classes/' + res, false);\n" + + " request.open('GET', '/classes/' + res + '?skip=' + skip, false);\n" + " request.send();\n" - + " if (request.status !== 200) return null;\n" + + " if (request.status !== 200) {\n" + + " c[skip] = null;\n" + + " return null;\n" + + " }\n" + " var arr = eval(request.responseText);\n" + + " c[skip] = arr;\n" + " return arr;\n" + " }\n" + " var prevvm = global.bck2brwsr;\n" @@ -68,6 +115,7 @@ + " global.bck2brwsr.registerExtension = prevvm.registerExtension;\n" + "})(this);\n" ); + LOG.log(Level.INFO, "Serving bck2brwsr.js", b2b); } } diff -r e995e8d39240 -r ba912ef24b27 launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/CompileCP.java --- a/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/CompileCP.java Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/CompileCP.java Wed Apr 30 15:04:10 2014 +0200 @@ -93,7 +93,7 @@ .resources(new EmulationResources() { @Override public InputStream get(String resource) throws IOException { - return r != null ? r.get(resource).openStream() : super.get(resource); + return r != null ? r.get(resource, 0).openStream() : super.get(resource); } }) .generate(w); @@ -156,7 +156,7 @@ } static void compileVM(StringBuilder sb, final Res r) throws IOException { - URL u = r.get(InterruptedException.class.getName().replace('.', '/') + ".class"); + URL u = r.get(InterruptedException.class.getName().replace('.', '/') + ".class", 0); JarURLConnection juc = (JarURLConnection)u.openConnection(); List arr = new ArrayList<>(); @@ -167,7 +167,7 @@ .resources(new Bck2Brwsr.Resources() { @Override public InputStream get(String resource) throws IOException { - return r.get(resource).openStream(); + return r.get(resource, 0).openStream(); } }).generate(sb); } diff -r e995e8d39240 -r ba912ef24b27 launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/JSLauncher.java --- a/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/JSLauncher.java Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/JSLauncher.java Wed Apr 30 15:04:10 2014 +0200 @@ -126,6 +126,10 @@ u = en.nextElement(); } if (u != null) { + if (u.toExternalForm().contains("rt.jar")) { + LOG.log(Level.WARNING, "No fallback to bootclasspath for {0}", u); + return null; + } return u.openStream(); } } diff -r e995e8d39240 -r ba912ef24b27 launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java --- a/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/http/src/main/java/org/apidesign/bck2brwsr/launcher/impl/Console.java Wed Apr 30 15:04:10 2014 +0200 @@ -221,14 +221,22 @@ * @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 { + public static byte[] read(String name, int skip) throws IOException { URL u = null; - Enumeration en = Console.class.getClassLoader().getResources(name); - while (en.hasMoreElements()) { - u = en.nextElement(); + if (!name.endsWith(".class")) { + u = getResource(name, skip); + } else { + Enumeration en = Console.class.getClassLoader().getResources(name); + while (en.hasMoreElements()) { + u = en.nextElement(); + } } if (u == null) { - throw new IOException("Can't find " + name); + if (name.endsWith(".class")) { + throw new IOException("Can't find " + name); + } else { + return null; + } } try (InputStream is = u.openStream()) { byte[] arr; @@ -245,6 +253,19 @@ } } + private static URL getResource(String resource, int skip) throws IOException { + URL u = null; + Enumeration en = Console.class.getClassLoader().getResources(resource); + while (en.hasMoreElements()) { + final URL now = en.nextElement(); + if (--skip < 0) { + u = now; + break; + } + } + return u; + } + @JavaScriptBody(args = {}, body = "vm.desiredAssertionStatus = true;") private static void turnAssetionStatusOn() { } diff -r e995e8d39240 -r ba912ef24b27 launcher/pom.xml --- a/launcher/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/launcher/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,15 +4,15 @@ bck2brwsr org.apidesign - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr launcher-pom - 0.8-SNAPSHOT + 0.9-SNAPSHOT pom Launchers - 2.3.2 + 2.3.3 api diff -r e995e8d39240 -r ba912ef24b27 pom.xml --- a/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -3,7 +3,7 @@ 4.0.0 org.apidesign bck2brwsr - 0.8-SNAPSHOT + 0.9-SNAPSHOT pom Back 2 Browser @@ -15,13 +15,13 @@ UTF-8 RELEASE80 COPYING + 0.7.6 none - dew javaquery benchmarks - ide + ko launcher rt @@ -85,12 +85,13 @@ * .*/** - rt/emul/*/src/main/** + rt/emul/mini/src/main/** + rt/emul/compact/src/main/** rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java rt/archetype/src/main/resources/archetype-resources/** rt/emul/*/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/** diff -r e995e8d39240 -r ba912ef24b27 rt/archetype/pom.xml --- a/rt/archetype/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ - - - 4.0.0 - - rt - org.apidesign.bck2brwsr - 0.8-SNAPSHOT - - org.apidesign.bck2brwsr - bck2brwsr-archetype-html-sample - 0.8-SNAPSHOT - jar - Bck2Brwsr Maven Archetype - - Creates a skeletal HTML page and associated Java controller class. - Runs in any browser (even without Java plugin) with the help of 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 - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - - - test - - test - - integration-test - - - ${project.build.directory}/bck2brwsr-archetype-html-sample-${project.version}.jar - - false - - - - - - - - - - org.testng - testng - test - - - diff -r e995e8d39240 -r ba912ef24b27 rt/archetype/src/main/java/org/apidesign/bck2brwsr/archetype/package-info.java --- a/rt/archetype/src/main/java/org/apidesign/bck2brwsr/archetype/package-info.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +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.archetype; diff -r e995e8d39240 -r ba912ef24b27 rt/archetype/src/main/resources/META-INF/maven/archetype-metadata.xml --- a/rt/archetype/src/main/resources/META-INF/maven/archetype-metadata.xml Tue Apr 29 15:25:58 2014 +0200 +++ /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 e995e8d39240 -r ba912ef24b27 rt/archetype/src/main/resources/archetype-resources/bck2brwsr-assembly.xml --- a/rt/archetype/src/main/resources/archetype-resources/bck2brwsr-assembly.xml Tue Apr 29 15:25:58 2014 +0200 +++ /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 e995e8d39240 -r ba912ef24b27 rt/archetype/src/main/resources/archetype-resources/nbactions.xml --- a/rt/archetype/src/main/resources/archetype-resources/nbactions.xml Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ - - - - run - - process-classes - bck2brwsr:brwsr - - - diff -r e995e8d39240 -r ba912ef24b27 rt/archetype/src/main/resources/archetype-resources/pom.xml --- a/rt/archetype/src/main/resources/archetype-resources/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ /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/releases/ - - true - - - - netbeans - NetBeans - http://bits.netbeans.org/maven2/ - - - - - java.net - Java.net - https://maven.java.net/content/repositories/releases/ - - true - - - - - - UTF-8 - - - - - org.apidesign.bck2brwsr - bck2brwsr-maven-plugin - ${project.version} - - - - 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 - ${project.version} - rt - - - org.apidesign.bck2brwsr - javaquery.api - ${project.version} - - - org.testng - testng - 6.5.2 - test - - - org.apidesign.bck2brwsr - vm4brwsr - js - zip - ${project.version} - provided - - - org.apidesign.bck2brwsr - vmtest - ${project.version} - test - - - diff -r e995e8d39240 -r ba912ef24b27 rt/archetype/src/main/resources/archetype-resources/src/main/java/App.java --- a/rt/archetype/src/main/resources/archetype-resources/src/main/java/App.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -package ${package}; - -import java.util.List; -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; - -/** This is the controller class for associated index.html page. The Index - * is autogenerated by parsing the index.html page. It fields represent individual - * elements annotated by "id" in the page. - */ -@Page(xhtml="index.html", className="Index", properties={ - @Property(name="name", type=String.class), - @Property(name="messages", type=String.class, array=true), -}) -public class App { - static { - Index model = new Index(); - model.setName("World"); - model.applyBindings(); - } - - /** - * @param m the model of the index page creates in static initializer - */ - @On(event = CLICK, id="hello") - static void hello(Index m) { - display(m.getHelloMessage(), m); - m.getMessages().add(m.getHelloMessage()); - } - - /** Reacts when mouse moves over the canvas. - * - * @param m the model of the page - * @param x property "x" extracted from the event generated by the browser - * @param y property "y" from the mouse event - */ - @On(event = MOUSE_MOVE, id="canvas") - static void clearPoint(Index m, int x, int y) { - GraphicsContext g = m.canvas.getContext(); - boolean even = (x + y) % 2 == 0; - if (even) { - g.setFillStyle("blue"); - } else { - g.setFillStyle("red"); - } - g.clearRect(0, 0, 1000, 1000); - g.setFont("italic 40px Calibri"); - g.fillText(m.getHelloMessage(), 10, 40); - } - - /** Callback function called by the KnockOut/Java binding on elements - * representing href's with individual messages being their data. - * - * @param data the data associated with the element - * @param m the model of the page - */ - @OnFunction - static void display(String data, Index m) { - GraphicsContext g = m.canvas.getContext(); - g.clearRect(0, 0, 1000, 1000); - g.setFillStyle("black"); - g.setFont("italic 40px Calibri"); - g.fillText(data, 10, 40); - } - - /** Callback function. - * - * @param data data associated with the actual element on the page - * @param m the model of the page - */ - @OnFunction - static void remove(String data, Index m) { - m.getMessages().remove(data); - } - - @ComputedProperty - static String helloMessage(String name) { - return "Hello " + name + "!"; - } - - @ComputedProperty - static boolean noMessages(List messages) { - return messages.isEmpty(); - } -} diff -r e995e8d39240 -r ba912ef24b27 rt/archetype/src/main/resources/archetype-resources/src/main/resources/index.html --- a/rt/archetype/src/main/resources/archetype-resources/src/main/resources/index.html Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ - - - - - Bck2Brwsr's Hello World - - -

Loading Bck2Brwsr's Hello World...

- Your name: - -

- - -

- - -
No message displayed yet.
- - - - - - diff -r e995e8d39240 -r ba912ef24b27 rt/archetype/src/main/resources/archetype-resources/src/test/java/AppTest.java --- a/rt/archetype/src/main/resources/archetype-resources/src/test/java/AppTest.java Tue Apr 29 15:25:58 2014 +0200 +++ /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 e995e8d39240 -r ba912ef24b27 rt/archetype/src/main/resources/archetype-resources/src/test/java/InconsistencyTest.java --- a/rt/archetype/src/main/resources/archetype-resources/src/test/java/InconsistencyTest.java Tue Apr 29 15:25:58 2014 +0200 +++ /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 e995e8d39240 -r ba912ef24b27 rt/archetype/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java --- a/rt/archetype/src/main/resources/archetype-resources/src/test/java/IntegrationTest.java Tue Apr 29 15:25:58 2014 +0200 +++ /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 e995e8d39240 -r ba912ef24b27 rt/archetype/src/test/java/org/apidesign/bck2brwsr/archetype/ArchetypeVersionTest.java --- a/rt/archetype/src/test/java/org/apidesign/bck2brwsr/archetype/ArchetypeVersionTest.java Tue Apr 29 15:25:58 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +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.archetype; - -import java.net.URL; -import javax.xml.XMLConstants; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; -import javax.xml.xpath.XPathFactory; -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; - -/** - * - * @author Jaroslav Tulach - */ -public class ArchetypeVersionTest { - private String version; - - public ArchetypeVersionTest() { - } - - @BeforeClass public void readCurrentVersion() throws Exception { - final ClassLoader l = ArchetypeVersionTest.class.getClassLoader(); - URL u = l.getResource("META-INF/maven/org.apidesign.bck2brwsr/bck2brwsr-archetype-html-sample/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()); - version = xp.evaluate(dom); - - 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( - "//version[../groupId/text() = 'org.apidesign.bck2brwsr']/text()" - ); - - Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(r.openStream()); - NodeList arch = (NodeList) xp2.evaluate(dom, XPathConstants.NODESET); - - if (arch.getLength() < 3) { - fail("There should be at least three dependencies to bck2brwsr APIs: " + arch.getLength()); - } - - for (int i = 0; i < arch.getLength(); i++) { - assertEquals(arch.item(i).getTextContent(), version, i + "th dependency needs to be on latest version of bck2brwsr"); - } - } - - @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("bck2brwsr")) { - assertFalse(s.matches(".*bck2brwsr.*[0-9].*"), "No numbers: " + s); - } - } - } -} diff -r e995e8d39240 -r ba912ef24b27 rt/core/pom.xml --- a/rt/core/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/core/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr rt - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr core - 0.8-SNAPSHOT + 0.9-SNAPSHOT Bck2Brwsr Native Annotations http://maven.apache.org diff -r e995e8d39240 -r ba912ef24b27 rt/emul/brwsrtest/pom.xml --- a/rt/emul/brwsrtest/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/brwsrtest/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr emul.pom - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr brwsrtest - 0.8-SNAPSHOT + 0.9-SNAPSHOT Tests Inside Real Browser http://maven.apache.org diff -r e995e8d39240 -r ba912ef24b27 rt/emul/brwsrtest/src/test/java/org/apidesign/bck2brwsr/brwsrtest/ResourcesInBrwsrTest.java --- a/rt/emul/brwsrtest/src/test/java/org/apidesign/bck2brwsr/brwsrtest/ResourcesInBrwsrTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/brwsrtest/src/test/java/org/apidesign/bck2brwsr/brwsrtest/ResourcesInBrwsrTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -17,7 +17,10 @@ */ package org.apidesign.bck2brwsr.brwsrtest; +import java.io.IOException; import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; import org.apidesign.bck2brwsr.vmtest.Compare; import org.apidesign.bck2brwsr.vmtest.VMTest; import org.testng.annotations.Factory; @@ -30,14 +33,36 @@ @Compare public String readResourceAsStream() throws Exception { InputStream is = getClass().getResourceAsStream("Resources.txt"); - assert is != null : "The stream for Resources.txt should be found"; - byte[] b = new byte[30]; - int len = is.read(b); + return readString(is); + } + + @Compare public String readResourceViaConnection() throws Exception { + InputStream is = getClass().getResource("Resources.txt").openConnection().getInputStream(); + return readString(is); + } + + private String readString(InputStream is) throws IOException { StringBuilder sb = new StringBuilder(); - for (int i = 0; i < len; i++) { - sb.append((char)b[i]); + byte[] b = new byte[512]; + for (;;) { + int len = is.read(b); + if (len == -1) { + return sb.toString(); + } + for (int i = 0; i < len; i++) { + sb.append((char)b[i]); + } } - return sb.toString(); + } + + @Compare public String readResourceAsStreamFromClassLoader() throws Exception { + InputStream is = getClass().getClassLoader().getResourceAsStream("org/apidesign/bck2brwsr/brwsrtest/Resources.txt"); + return readString(is); + } + + @Compare public String toURIFromURL() throws Exception { + URL u = new URL("http://apidesign.org"); + return u.toURI().toString(); } @Factory public static Object[] create() { diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/pom.xml --- a/rt/emul/compact/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/compact/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr emul.pom - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr emul - 0.8-SNAPSHOT + 0.9-SNAPSHOT Bck2Brwsr API Profile http://maven.apache.org diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/io/BufferedInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/BufferedInputStream.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,458 @@ +/* + * Copyright (c) 1994, 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.io; + +/** + * A BufferedInputStream adds + * functionality to another input stream-namely, + * the ability to buffer the input and to + * support the mark and reset + * methods. When the BufferedInputStream + * is created, an internal buffer array is + * created. As bytes from the stream are read + * or skipped, the internal buffer is refilled + * as necessary from the contained input stream, + * many bytes at a time. The mark + * operation remembers a point in the input + * stream and the reset operation + * causes all the bytes read since the most + * recent mark operation to be + * reread before new bytes are taken from + * the contained input stream. + * + * @author Arthur van Hoff + * @since JDK1.0 + */ +public +class BufferedInputStream extends FilterInputStream { + + private static int defaultBufferSize = 8192; + + /** + * The internal buffer array where the data is stored. When necessary, + * it may be replaced by another array of + * a different size. + */ + protected volatile byte buf[]; + + + /** + * The index one greater than the index of the last valid byte in + * the buffer. + * This value is always + * in the range 0 through buf.length; + * elements buf[0] through buf[count-1] + * contain buffered input data obtained + * from the underlying input stream. + */ + protected int count; + + /** + * The current position in the buffer. This is the index of the next + * character to be read from the buf array. + *

+ * This value is always in the range 0 + * through count. If it is less + * than count, then buf[pos] + * is the next byte to be supplied as input; + * if it is equal to count, then + * the next read or skip + * operation will require more bytes to be + * read from the contained input stream. + * + * @see java.io.BufferedInputStream#buf + */ + protected int pos; + + /** + * The value of the pos field at the time the last + * mark method was called. + *

+ * This value is always + * in the range -1 through pos. + * If there is no marked position in the input + * stream, this field is -1. If + * there is a marked position in the input + * stream, then buf[markpos] + * is the first byte to be supplied as input + * after a reset operation. If + * markpos is not -1, + * then all bytes from positions buf[markpos] + * through buf[pos-1] must remain + * in the buffer array (though they may be + * moved to another place in the buffer array, + * with suitable adjustments to the values + * of count, pos, + * and markpos); they may not + * be discarded unless and until the difference + * between pos and markpos + * exceeds marklimit. + * + * @see java.io.BufferedInputStream#mark(int) + * @see java.io.BufferedInputStream#pos + */ + protected int markpos = -1; + + /** + * The maximum read ahead allowed after a call to the + * mark method before subsequent calls to the + * reset method fail. + * Whenever the difference between pos + * and markpos exceeds marklimit, + * then the mark may be dropped by setting + * markpos to -1. + * + * @see java.io.BufferedInputStream#mark(int) + * @see java.io.BufferedInputStream#reset() + */ + protected int marklimit; + + /** + * Check to make sure that underlying input stream has not been + * nulled out due to close; if not return it; + */ + private InputStream getInIfOpen() throws IOException { + InputStream input = in; + if (input == null) + throw new IOException("Stream closed"); + return input; + } + + /** + * Check to make sure that buffer has not been nulled out due to + * close; if not return it; + */ + private byte[] getBufIfOpen() throws IOException { + byte[] buffer = buf; + if (buffer == null) + throw new IOException("Stream closed"); + return buffer; + } + + /** + * Creates a BufferedInputStream + * and saves its argument, the input stream + * in, for later use. An internal + * buffer array is created and stored in buf. + * + * @param in the underlying input stream. + */ + public BufferedInputStream(InputStream in) { + this(in, defaultBufferSize); + } + + /** + * Creates a BufferedInputStream + * with the specified buffer size, + * and saves its argument, the input stream + * in, for later use. An internal + * buffer array of length size + * is created and stored in buf. + * + * @param in the underlying input stream. + * @param size the buffer size. + * @exception IllegalArgumentException if size <= 0. + */ + public BufferedInputStream(InputStream in, int size) { + super(in); + if (size <= 0) { + throw new IllegalArgumentException("Buffer size <= 0"); + } + buf = new byte[size]; + } + + /** + * Fills the buffer with more data, taking into account + * shuffling and other tricks for dealing with marks. + * Assumes that it is being called by a synchronized method. + * This method also assumes that all data has already been read in, + * hence pos > count. + */ + private void fill() throws IOException { + byte[] buffer = getBufIfOpen(); + if (markpos < 0) + pos = 0; /* no mark: throw away the buffer */ + else if (pos >= buffer.length) /* no room left in buffer */ + if (markpos > 0) { /* can throw away early part of the buffer */ + int sz = pos - markpos; + System.arraycopy(buffer, markpos, buffer, 0, sz); + pos = sz; + markpos = 0; + } else if (buffer.length >= marklimit) { + markpos = -1; /* buffer got too big, invalidate mark */ + pos = 0; /* drop buffer contents */ + } else { /* grow buffer */ + int nsz = pos * 2; + if (nsz > marklimit) + nsz = marklimit; + byte nbuf[] = new byte[nsz]; + System.arraycopy(buffer, 0, nbuf, 0, pos); + buffer = nbuf; + } + count = pos; + int n = getInIfOpen().read(buffer, pos, buffer.length - pos); + if (n > 0) + count = n + pos; + } + + /** + * See + * the general contract of the read + * method of InputStream. + * + * @return the next byte of data, or -1 if the end of the + * stream is reached. + * @exception IOException if this input stream has been closed by + * invoking its {@link #close()} method, + * or an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + public synchronized int read() throws IOException { + if (pos >= count) { + fill(); + if (pos >= count) + return -1; + } + return getBufIfOpen()[pos++] & 0xff; + } + + /** + * Read characters into a portion of an array, reading from the underlying + * stream at most once if necessary. + */ + private int read1(byte[] b, int off, int len) throws IOException { + int avail = count - pos; + if (avail <= 0) { + /* If the requested length is at least as large as the buffer, and + if there is no mark/reset activity, do not bother to copy the + bytes into the local buffer. In this way buffered streams will + cascade harmlessly. */ + if (len >= getBufIfOpen().length && markpos < 0) { + return getInIfOpen().read(b, off, len); + } + fill(); + avail = count - pos; + if (avail <= 0) return -1; + } + int cnt = (avail < len) ? avail : len; + System.arraycopy(getBufIfOpen(), pos, b, off, cnt); + pos += cnt; + return cnt; + } + + /** + * Reads bytes from this byte-input stream into the specified byte array, + * starting at the given offset. + * + *

This method implements the general contract of the corresponding + * {@link InputStream#read(byte[], int, int) read} method of + * the {@link InputStream} class. As an additional + * convenience, it attempts to read as many bytes as possible by repeatedly + * invoking the read method of the underlying stream. This + * iterated read continues until one of the following + * conditions becomes true:

    + * + *
  • The specified number of bytes have been read, + * + *
  • The read method of the underlying stream returns + * -1, indicating end-of-file, or + * + *
  • The available method of the underlying stream + * returns zero, indicating that further input requests would block. + * + *
If the first read on the underlying stream returns + * -1 to indicate end-of-file then this method returns + * -1. Otherwise this method returns the number of bytes + * actually read. + * + *

Subclasses of this class are encouraged, but not required, to + * attempt to read as many bytes as possible in the same fashion. + * + * @param b destination buffer. + * @param off offset at which to start storing bytes. + * @param len maximum number of bytes to read. + * @return the number of bytes read, or -1 if the end of + * the stream has been reached. + * @exception IOException if this input stream has been closed by + * invoking its {@link #close()} method, + * or an I/O error occurs. + */ + public synchronized int read(byte b[], int off, int len) + throws IOException + { + getBufIfOpen(); // Check for closed stream + if ((off | len | (off + len) | (b.length - (off + len))) < 0) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + int n = 0; + for (;;) { + int nread = read1(b, off + n, len - n); + if (nread <= 0) + return (n == 0) ? nread : n; + n += nread; + if (n >= len) + return n; + // if not closed but no bytes available, return + InputStream input = in; + if (input != null && input.available() <= 0) + return n; + } + } + + /** + * See the general contract of the skip + * method of InputStream. + * + * @exception IOException if the stream does not support seek, + * or if this input stream has been closed by + * invoking its {@link #close()} method, or an + * I/O error occurs. + */ + public synchronized long skip(long n) throws IOException { + getBufIfOpen(); // Check for closed stream + if (n <= 0) { + return 0; + } + long avail = count - pos; + + if (avail <= 0) { + // If no mark position set then don't keep in buffer + if (markpos <0) + return getInIfOpen().skip(n); + + // Fill in buffer to save bytes for reset + fill(); + avail = count - pos; + if (avail <= 0) + return 0; + } + + long skipped = (avail < n) ? avail : n; + pos += skipped; + return skipped; + } + + /** + * Returns an estimate of the number of bytes that can be read (or + * skipped over) from this input stream without blocking by the next + * invocation of a method for this input stream. The next invocation might be + * the same thread or another thread. A single read or skip of this + * many bytes will not block, but may read or skip fewer bytes. + *

+ * This method returns the sum of the number of bytes remaining to be read in + * the buffer (count - pos) and the result of calling the + * {@link java.io.FilterInputStream#in in}.available(). + * + * @return an estimate of the number of bytes that can be read (or skipped + * over) from this input stream without blocking. + * @exception IOException if this input stream has been closed by + * invoking its {@link #close()} method, + * or an I/O error occurs. + */ + public synchronized int available() throws IOException { + int n = count - pos; + int avail = getInIfOpen().available(); + return n > (Integer.MAX_VALUE - avail) + ? Integer.MAX_VALUE + : n + avail; + } + + /** + * See the general contract of the mark + * method of InputStream. + * + * @param readlimit the maximum limit of bytes that can be read before + * the mark position becomes invalid. + * @see java.io.BufferedInputStream#reset() + */ + public synchronized void mark(int readlimit) { + marklimit = readlimit; + markpos = pos; + } + + /** + * See the general contract of the reset + * method of InputStream. + *

+ * If markpos is -1 + * (no mark has been set or the mark has been + * invalidated), an IOException + * is thrown. Otherwise, pos is + * set equal to markpos. + * + * @exception IOException if this stream has not been marked or, + * if the mark has been invalidated, or the stream + * has been closed by invoking its {@link #close()} + * method, or an I/O error occurs. + * @see java.io.BufferedInputStream#mark(int) + */ + public synchronized void reset() throws IOException { + getBufIfOpen(); // Cause exception if closed + if (markpos < 0) + throw new IOException("Resetting to invalid mark"); + pos = markpos; + } + + /** + * Tests if this input stream supports the mark + * and reset methods. The markSupported + * method of BufferedInputStream returns + * true. + * + * @return a boolean indicating if this stream type supports + * the mark and reset methods. + * @see java.io.InputStream#mark(int) + * @see java.io.InputStream#reset() + */ + public boolean markSupported() { + return true; + } + + /** + * Closes this input stream and releases any system resources + * associated with the stream. + * Once the stream has been closed, further read(), available(), reset(), + * or skip() invocations will throw an IOException. + * Closing a previously closed stream has no effect. + * + * @exception IOException if an I/O error occurs. + */ + public void close() throws IOException { + byte[] buffer; + while ( (buffer = buf) != null) { + InputStream input = in; + buf = null; + in = null; + if (input != null) + input.close(); + return; + // Else retry in case a new buf was CASed in fill() + } + } +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/io/FileDescriptor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FileDescriptor.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,146 @@ +/* + * Copyright (c) 1995, 2012, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +package java.io; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Instances of the file descriptor class serve as an opaque handle + * to the underlying machine-specific structure representing an open + * file, an open socket, or another source or sink of bytes. The + * main practical use for a file descriptor is to create a + * FileInputStream or FileOutputStream to + * contain it. + *

+ * Applications should not create their own file descriptors. + * + * @author Pavani Diwanji + * @see java.io.FileInputStream + * @see java.io.FileOutputStream + * @since JDK1.0 + */ +public final class FileDescriptor { + + private int fd; + + /** + * A counter for tracking the FIS/FOS/RAF instances that + * use this FileDescriptor. The FIS/FOS.finalize() will not release + * the FileDescriptor if it is still under user by a stream. + */ + private AtomicInteger useCount; + + /** + * Constructs an (invalid) FileDescriptor + * object. + */ + public /**/ FileDescriptor() { + fd = -1; + useCount = new AtomicInteger(); + } + + private /* */ FileDescriptor(int fd) { + this.fd = fd; + useCount = new AtomicInteger(); + } + + /** + * A handle to the standard input stream. Usually, this file + * descriptor is not used directly, but rather via the input stream + * known as System.in. + * + * @see java.lang.System#in + */ + public static final FileDescriptor in = new FileDescriptor(0); + + /** + * A handle to the standard output stream. Usually, this file + * descriptor is not used directly, but rather via the output stream + * known as System.out. + * @see java.lang.System#out + */ + public static final FileDescriptor out = new FileDescriptor(1); + + /** + * A handle to the standard error stream. Usually, this file + * descriptor is not used directly, but rather via the output stream + * known as System.err. + * + * @see java.lang.System#err + */ + public static final FileDescriptor err = new FileDescriptor(2); + + /** + * Tests if this file descriptor object is valid. + * + * @return true if the file descriptor object represents a + * valid, open file, socket, or other active I/O connection; + * false otherwise. + */ + public boolean valid() { + return fd != -1; + } + + /** + * Force all system buffers to synchronize with the underlying + * device. This method returns after all modified data and + * attributes of this FileDescriptor have been written to the + * relevant device(s). In particular, if this FileDescriptor + * refers to a physical storage medium, such as a file in a file + * system, sync will not return until all in-memory modified copies + * of buffers associated with this FileDescriptor have been + * written to the physical medium. + * + * sync is meant to be used by code that requires physical + * storage (such as a file) to be in a known state For + * example, a class that provided a simple transaction facility + * might use sync to ensure that all changes to a file caused + * by a given transaction were recorded on a storage medium. + * + * sync only affects buffers downstream of this FileDescriptor. If + * any in-memory buffering is being done by the application (for + * example, by a BufferedOutputStream object), those buffers must + * be flushed into the FileDescriptor (for example, by invoking + * OutputStream.flush) before that data will be affected by sync. + * + * @exception SyncFailedException + * Thrown when the buffers cannot be flushed, + * or because the system cannot guarantee that all the + * buffers have been synchronized with physical media. + * @since JDK1.1 + */ + public native void sync() throws SyncFailedException; + + // package private methods used by FIS, FOS and RAF + + int incrementAndGetUseCount() { + return useCount.incrementAndGet(); + } + + int decrementAndGetUseCount() { + return useCount.decrementAndGet(); + } +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/io/FileInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FileInputStream.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,382 @@ +/* + * 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; + + + +/** + * A FileInputStream obtains input bytes + * from a file in a file system. What files + * are available depends on the host environment. + * + *

FileInputStream is meant for reading streams of raw bytes + * such as image data. For reading streams of characters, consider using + * FileReader. + * + * @author Arthur van Hoff + * @see java.io.File + * @see java.io.FileDescriptor + * @see java.io.FileOutputStream + * @see java.nio.file.Files#newInputStream + * @since JDK1.0 + */ +public +class FileInputStream extends InputStream +{ + /* File Descriptor - handle to the open file */ + private final FileDescriptor fd; + +// private FileChannel channel = null; + + private final Object closeLock = new Object(); + private volatile boolean closed = false; + + private static final ThreadLocal runningFinalize = + new ThreadLocal<>(); + + private static boolean isRunningFinalize() { + Boolean val; + if ((val = runningFinalize.get()) != null) + return val.booleanValue(); + return false; + } + + /** + * Creates a FileInputStream by + * opening a connection to an actual file, + * the file named by the path name name + * in the file system. A new FileDescriptor + * object is created to represent this file + * connection. + *

+ * First, if there is a security + * manager, its checkRead method + * is called with the name argument + * as its argument. + *

+ * If the named file does not exist, is a directory rather than a regular + * file, or for some other reason cannot be opened for reading then a + * FileNotFoundException is thrown. + * + * @param name the system-dependent file name. + * @exception FileNotFoundException if the file does not exist, + * is a directory rather than a regular file, + * or for some other reason cannot be opened for + * reading. + * @exception SecurityException if a security manager exists and its + * checkRead method denies read access + * to the file. + * @see java.lang.SecurityManager#checkRead(java.lang.String) + */ + public FileInputStream(String name) throws FileNotFoundException { + this(name != null ? new File(name) : null); + } + + /** + * Creates a FileInputStream by + * opening a connection to an actual file, + * the file named by the File + * object file in the file system. + * A new FileDescriptor object + * is created to represent this file connection. + *

+ * First, if there is a security manager, + * its checkRead method is called + * with the path represented by the file + * argument as its argument. + *

+ * If the named file does not exist, is a directory rather than a regular + * file, or for some other reason cannot be opened for reading then a + * FileNotFoundException is thrown. + * + * @param file the file to be opened for reading. + * @exception FileNotFoundException if the file does not exist, + * is a directory rather than a regular file, + * or for some other reason cannot be opened for + * reading. + * @exception SecurityException if a security manager exists and its + * checkRead method denies read access to the file. + * @see java.io.File#getPath() + * @see java.lang.SecurityManager#checkRead(java.lang.String) + */ + public FileInputStream(File file) throws FileNotFoundException { + throw new SecurityException(); + } + + /** + * Creates a FileInputStream by using the file descriptor + * fdObj, which represents an existing connection to an + * actual file in the file system. + *

+ * If there is a security manager, its checkRead method is + * called with the file descriptor fdObj as its argument to + * see if it's ok to read the file descriptor. If read access is denied + * to the file descriptor a SecurityException is thrown. + *

+ * If fdObj is null then a NullPointerException + * is thrown. + *

+ * This constructor does not throw an exception if fdObj + * is {@link java.io.FileDescriptor#valid() invalid}. + * However, if the methods are invoked on the resulting stream to attempt + * I/O on the stream, an IOException is thrown. + * + * @param fdObj the file descriptor to be opened for reading. + * @throws SecurityException if a security manager exists and its + * checkRead method denies read access to the + * file descriptor. + * @see SecurityManager#checkRead(java.io.FileDescriptor) + */ + public FileInputStream(FileDescriptor fdObj) { + throw new SecurityException(); + } + + /** + * Opens the specified file for reading. + * @param name the name of the file + */ + private native void open(String name) throws FileNotFoundException; + + /** + * Reads a byte of data from this input stream. This method blocks + * if no input is yet available. + * + * @return the next byte of data, or -1 if the end of the + * file is reached. + * @exception IOException if an I/O error occurs. + */ + public native int read() throws IOException; + + /** + * Reads a subarray as a sequence of bytes. + * @param b the data to be written + * @param off the start offset in the data + * @param len the number of bytes that are written + * @exception IOException If an I/O error has occurred. + */ + private native int readBytes(byte b[], int off, int len) throws IOException; + + /** + * Reads up to b.length bytes of data from this input + * stream into an array of bytes. This method blocks until some input + * is available. + * + * @param b the buffer into which the data is read. + * @return the total number of bytes read into the buffer, or + * -1 if there is no more data because the end of + * the file has been reached. + * @exception IOException if an I/O error occurs. + */ + public int read(byte b[]) throws IOException { + return readBytes(b, 0, b.length); + } + + /** + * Reads up to len bytes of data from this input stream + * into an array of bytes. If len is not zero, the method + * blocks until some input is available; otherwise, no + * bytes are read and 0 is returned. + * + * @param b the buffer into which the data is read. + * @param off the start offset in the destination array b + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or + * -1 if there is no more data because the end of + * the file has been reached. + * @exception NullPointerException If b is null. + * @exception IndexOutOfBoundsException If off is negative, + * len is negative, or len is greater than + * b.length - off + * @exception IOException if an I/O error occurs. + */ + public int read(byte b[], int off, int len) throws IOException { + return readBytes(b, off, len); + } + + /** + * Skips over and discards n bytes of data from the + * input stream. + * + *

The skip method may, for a variety of + * reasons, end up skipping over some smaller number of bytes, + * possibly 0. If n is negative, an + * IOException is thrown, even though the skip + * method of the {@link InputStream} superclass does nothing in this case. + * The actual number of bytes skipped is returned. + * + *

This method may skip more bytes than are remaining in the backing + * file. This produces no exception and the number of bytes skipped + * may include some number of bytes that were beyond the EOF of the + * backing file. Attempting to read from the stream after skipping past + * the end will result in -1 indicating the end of the file. + * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. + * @exception IOException if n is negative, if the stream does not + * support seek, or if an I/O error occurs. + */ + public native long skip(long n) throws IOException; + + /** + * Returns an estimate of the number of remaining bytes that can be read (or + * skipped over) from this input stream without blocking by the next + * invocation of a method for this input stream. The next invocation might be + * the same thread or another thread. A single read or skip of this + * many bytes will not block, but may read or skip fewer bytes. + * + *

In some cases, a non-blocking read (or skip) may appear to be + * blocked when it is merely slow, for example when reading large + * files over slow networks. + * + * @return an estimate of the number of remaining bytes that can be read + * (or skipped over) from this input stream without blocking. + * @exception IOException if this file input stream has been closed by calling + * {@code close} or an I/O error occurs. + */ + public native int available() throws IOException; + + /** + * Closes this file input stream and releases any system resources + * associated with the stream. + * + *

If this stream has an associated channel then the channel is closed + * as well. + * + * @exception IOException if an I/O error occurs. + * + * @revised 1.4 + * @spec JSR-51 + */ + public void close() throws IOException { + synchronized (closeLock) { + if (closed) { + return; + } + closed = true; + } +// if (channel != null) { +// /* +// * Decrement the FD use count associated with the channel +// * The use count is incremented whenever a new channel +// * is obtained from this stream. +// */ +// fd.decrementAndGetUseCount(); +// channel.close(); +// } + + /* + * Decrement the FD use count associated with this stream + */ + int useCount = fd.decrementAndGetUseCount(); + + /* + * If FileDescriptor is still in use by another stream, the finalizer + * will not close it. + */ + if ((useCount <= 0) || !isRunningFinalize()) { + close0(); + } + } + + /** + * Returns the FileDescriptor + * object that represents the connection to + * the actual file in the file system being + * used by this FileInputStream. + * + * @return the file descriptor object associated with this stream. + * @exception IOException if an I/O error occurs. + * @see java.io.FileDescriptor + */ + public final FileDescriptor getFD() throws IOException { + if (fd != null) return fd; + throw new IOException(); + } + + /** + * Returns the unique {@link java.nio.channels.FileChannel FileChannel} + * object associated with this file input stream. + * + *

The initial {@link java.nio.channels.FileChannel#position() + * position} of the returned channel will be equal to the + * number of bytes read from the file so far. Reading bytes from this + * stream will increment the channel's position. Changing the channel's + * position, either explicitly or by reading, will change this stream's + * file position. + * + * @return the file channel associated with this file input stream + * + * @since 1.4 + * @spec JSR-51 + */ +// public FileChannel getChannel() { +// synchronized (this) { +// if (channel == null) { +// channel = FileChannelImpl.open(fd, true, false, this); +// +// /* +// * Increment fd's use count. Invoking the channel's close() +// * method will result in decrementing the use count set for +// * the channel. +// */ +// fd.incrementAndGetUseCount(); +// } +// return channel; +// } +// } + + private static native void initIDs(); + + private native void close0() throws IOException; + + static { + initIDs(); + } + + /** + * Ensures that the close method of this file input stream is + * called when there are no more references to it. + * + * @exception IOException if an I/O error occurs. + * @see java.io.FileInputStream#close() + */ + protected void finalize() throws IOException { + if ((fd != null) && (fd != FileDescriptor.in)) { + + /* + * Finalizer should not release the FileDescriptor if another + * stream is still using it. If the user directly invokes + * close() then the FileDescriptor is also released. + */ + runningFinalize.set(Boolean.TRUE); + try { + close(); + } finally { + runningFinalize.set(Boolean.FALSE); + } + } + } +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/io/FileOutputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FileOutputStream.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,422 @@ +/* + * 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; + + + +/** + * A file output stream is an output stream for writing data to a + * File or to a FileDescriptor. Whether or not + * a file is available or may be created depends upon the underlying + * platform. Some platforms, in particular, allow a file to be opened + * for writing by only one FileOutputStream (or other + * file-writing object) at a time. In such situations the constructors in + * this class will fail if the file involved is already open. + * + *

FileOutputStream is meant for writing streams of raw bytes + * such as image data. For writing streams of characters, consider using + * FileWriter. + * + * @author Arthur van Hoff + * @see java.io.File + * @see java.io.FileDescriptor + * @see java.io.FileInputStream + * @see java.nio.file.Files#newOutputStream + * @since JDK1.0 + */ +public +class FileOutputStream extends OutputStream +{ + /** + * The system dependent file descriptor. + */ + private final FileDescriptor fd; + + /** + * True if the file is opened for append. + */ + private final boolean append; + + /** + * The associated channel, initalized lazily. + */ +// private FileChannel channel; + + private final Object closeLock = new Object(); + private volatile boolean closed = false; + private static final ThreadLocal runningFinalize = + new ThreadLocal<>(); + + private static boolean isRunningFinalize() { + Boolean val; + if ((val = runningFinalize.get()) != null) + return val.booleanValue(); + return false; + } + + /** + * Creates a file output stream to write to the file with the + * specified name. A new FileDescriptor object is + * created to represent this file connection. + *

+ * First, if there is a security manager, its checkWrite + * method is called with name as its argument. + *

+ * If the file exists but is a directory rather than a regular file, does + * not exist but cannot be created, or cannot be opened for any other + * reason then a FileNotFoundException is thrown. + * + * @param name the system-dependent filename + * @exception FileNotFoundException if the file exists but is a directory + * rather than a regular file, does not exist but cannot + * be created, or cannot be opened for any other reason + * @exception SecurityException if a security manager exists and its + * checkWrite method denies write access + * to the file. + * @see java.lang.SecurityManager#checkWrite(java.lang.String) + */ + public FileOutputStream(String name) throws FileNotFoundException { + this(name != null ? new File(name) : null, false); + } + + /** + * Creates a file output stream to write to the file with the specified + * name. If the second argument is true, then + * bytes will be written to the end of the file rather than the beginning. + * A new FileDescriptor object is created to represent this + * file connection. + *

+ * First, if there is a security manager, its checkWrite + * method is called with name as its argument. + *

+ * If the file exists but is a directory rather than a regular file, does + * not exist but cannot be created, or cannot be opened for any other + * reason then a FileNotFoundException is thrown. + * + * @param name the system-dependent file name + * @param append if true, then bytes will be written + * to the end of the file rather than the beginning + * @exception FileNotFoundException if the file exists but is a directory + * rather than a regular file, does not exist but cannot + * be created, or cannot be opened for any other reason. + * @exception SecurityException if a security manager exists and its + * checkWrite method denies write access + * to the file. + * @see java.lang.SecurityManager#checkWrite(java.lang.String) + * @since JDK1.1 + */ + public FileOutputStream(String name, boolean append) + throws FileNotFoundException + { + this(name != null ? new File(name) : null, append); + } + + /** + * Creates a file output stream to write to the file represented by + * the specified File object. A new + * FileDescriptor object is created to represent this + * file connection. + *

+ * First, if there is a security manager, its checkWrite + * method is called with the path represented by the file + * argument as its argument. + *

+ * If the file exists but is a directory rather than a regular file, does + * not exist but cannot be created, or cannot be opened for any other + * reason then a FileNotFoundException is thrown. + * + * @param file the file to be opened for writing. + * @exception FileNotFoundException if the file exists but is a directory + * rather than a regular file, does not exist but cannot + * be created, or cannot be opened for any other reason + * @exception SecurityException if a security manager exists and its + * checkWrite method denies write access + * to the file. + * @see java.io.File#getPath() + * @see java.lang.SecurityException + * @see java.lang.SecurityManager#checkWrite(java.lang.String) + */ + public FileOutputStream(File file) throws FileNotFoundException { + this(file, false); + } + + /** + * Creates a file output stream to write to the file represented by + * the specified File object. If the second argument is + * true, then bytes will be written to the end of the file + * rather than the beginning. A new FileDescriptor object is + * created to represent this file connection. + *

+ * First, if there is a security manager, its checkWrite + * method is called with the path represented by the file + * argument as its argument. + *

+ * If the file exists but is a directory rather than a regular file, does + * not exist but cannot be created, or cannot be opened for any other + * reason then a FileNotFoundException is thrown. + * + * @param file the file to be opened for writing. + * @param append if true, then bytes will be written + * to the end of the file rather than the beginning + * @exception FileNotFoundException if the file exists but is a directory + * rather than a regular file, does not exist but cannot + * be created, or cannot be opened for any other reason + * @exception SecurityException if a security manager exists and its + * checkWrite method denies write access + * to the file. + * @see java.io.File#getPath() + * @see java.lang.SecurityException + * @see java.lang.SecurityManager#checkWrite(java.lang.String) + * @since 1.4 + */ + public FileOutputStream(File file, boolean append) + throws FileNotFoundException + { + throw new SecurityException(); + } + + /** + * Creates a file output stream to write to the specified file + * descriptor, which represents an existing connection to an actual + * file in the file system. + *

+ * First, if there is a security manager, its checkWrite + * method is called with the file descriptor fdObj + * argument as its argument. + *

+ * If fdObj is null then a NullPointerException + * is thrown. + *

+ * This constructor does not throw an exception if fdObj + * is {@link java.io.FileDescriptor#valid() invalid}. + * However, if the methods are invoked on the resulting stream to attempt + * I/O on the stream, an IOException is thrown. + * + * @param fdObj the file descriptor to be opened for writing + * @exception SecurityException if a security manager exists and its + * checkWrite method denies + * write access to the file descriptor + * @see java.lang.SecurityManager#checkWrite(java.io.FileDescriptor) + */ + public FileOutputStream(FileDescriptor fdObj) { + throw new SecurityException(); + } + + /** + * Opens a file, with the specified name, for overwriting or appending. + * @param name name of file to be opened + * @param append whether the file is to be opened in append mode + */ + private native void open(String name, boolean append) + throws FileNotFoundException; + + /** + * Writes the specified byte to this file output stream. + * + * @param b the byte to be written. + * @param append {@code true} if the write operation first + * advances the position to the end of file + */ + private native void write(int b, boolean append) throws IOException; + + /** + * Writes the specified byte to this file output stream. Implements + * the write method of OutputStream. + * + * @param b the byte to be written. + * @exception IOException if an I/O error occurs. + */ + public void write(int b) throws IOException { + write(b, append); + } + + /** + * Writes a sub array as a sequence of bytes. + * @param b the data to be written + * @param off the start offset in the data + * @param len the number of bytes that are written + * @param append {@code true} to first advance the position to the + * end of file + * @exception IOException If an I/O error has occurred. + */ + private native void writeBytes(byte b[], int off, int len, boolean append) + throws IOException; + + /** + * Writes b.length bytes from the specified byte array + * to this file output stream. + * + * @param b the data. + * @exception IOException if an I/O error occurs. + */ + public void write(byte b[]) throws IOException { + writeBytes(b, 0, b.length, append); + } + + /** + * Writes len bytes from the specified byte array + * starting at offset off to this file output stream. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * @exception IOException if an I/O error occurs. + */ + public void write(byte b[], int off, int len) throws IOException { + writeBytes(b, off, len, append); + } + + /** + * Closes this file output stream and releases any system resources + * associated with this stream. This file output stream may no longer + * be used for writing bytes. + * + *

If this stream has an associated channel then the channel is closed + * as well. + * + * @exception IOException if an I/O error occurs. + * + * @revised 1.4 + * @spec JSR-51 + */ + public void close() throws IOException { + synchronized (closeLock) { + if (closed) { + return; + } + closed = true; + } +// +// if (channel != null) { +// /* +// * Decrement FD use count associated with the channel +// * The use count is incremented whenever a new channel +// * is obtained from this stream. +// */ +// fd.decrementAndGetUseCount(); +// channel.close(); +// } + + /* + * Decrement FD use count associated with this stream + */ + int useCount = fd.decrementAndGetUseCount(); + + /* + * If FileDescriptor is still in use by another stream, the finalizer + * will not close it. + */ + if ((useCount <= 0) || !isRunningFinalize()) { + close0(); + } + } + + /** + * Returns the file descriptor associated with this stream. + * + * @return the FileDescriptor object that represents + * the connection to the file in the file system being used + * by this FileOutputStream object. + * + * @exception IOException if an I/O error occurs. + * @see java.io.FileDescriptor + */ + public final FileDescriptor getFD() throws IOException { + if (fd != null) return fd; + throw new IOException(); + } + + /** + * Returns the unique {@link java.nio.channels.FileChannel FileChannel} + * object associated with this file output stream.

+ * + *

The initial {@link java.nio.channels.FileChannel#position() + * position} of the returned channel will be equal to the + * number of bytes written to the file so far unless this stream is in + * append mode, in which case it will be equal to the size of the file. + * Writing bytes to this stream will increment the channel's position + * accordingly. Changing the channel's position, either explicitly or by + * writing, will change this stream's file position. + * + * @return the file channel associated with this file output stream + * + * @since 1.4 + * @spec JSR-51 + */ +// public FileChannel getChannel() { +// synchronized (this) { +// if (channel == null) { +// channel = FileChannelImpl.open(fd, false, true, append, this); +// +// /* +// * Increment fd's use count. Invoking the channel's close() +// * method will result in decrementing the use count set for +// * the channel. +// */ +// fd.incrementAndGetUseCount(); +// } +// return channel; +// } +// } + + /** + * Cleans up the connection to the file, and ensures that the + * close method of this file output stream is + * called when there are no more references to this stream. + * + * @exception IOException if an I/O error occurs. + * @see java.io.FileInputStream#close() + */ + protected void finalize() throws IOException { + if (fd != null) { + if (fd == FileDescriptor.out || fd == FileDescriptor.err) { + flush(); + } else { + + /* + * Finalizer should not release the FileDescriptor if another + * stream is still using it. If the user directly invokes + * close() then the FileDescriptor is also released. + */ + runningFinalize.set(Boolean.TRUE); + try { + close(); + } finally { + runningFinalize.set(Boolean.FALSE); + } + } + } + } + + private native void close0() throws IOException; + + private static native void initIDs(); + + static { + initIDs(); + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/io/FileReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FileReader.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,85 @@ +/* + * Copyright (c) 1996, 2001, 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; + + +/** + * Convenience class for reading character files. The constructors of this + * class assume that the default character encoding and the default byte-buffer + * size are appropriate. To specify these values yourself, construct an + * InputStreamReader on a FileInputStream. + * + *

FileReader is meant for reading streams of characters. + * For reading streams of raw bytes, consider using a + * FileInputStream. + * + * @see InputStreamReader + * @see FileInputStream + * + * @author Mark Reinhold + * @since JDK1.1 + */ +public class FileReader extends InputStreamReader { + + /** + * Creates a new FileReader, given the name of the + * file to read from. + * + * @param fileName the name of the file to read from + * @exception FileNotFoundException if the named file does not exist, + * is a directory rather than a regular file, + * or for some other reason cannot be opened for + * reading. + */ + public FileReader(String fileName) throws FileNotFoundException { + super(new FileInputStream(fileName)); + } + + /** + * Creates a new FileReader, given the File + * to read from. + * + * @param file the File to read from + * @exception FileNotFoundException if the file does not exist, + * is a directory rather than a regular file, + * or for some other reason cannot be opened for + * reading. + */ + public FileReader(File file) throws FileNotFoundException { + super(new FileInputStream(file)); + } + + /** + * Creates a new FileReader, given the + * FileDescriptor to read from. + * + * @param fd the FileDescriptor to read from + */ + public FileReader(FileDescriptor fd) { + super(new FileInputStream(fd)); + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/io/FileWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FileWriter.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,119 @@ +/* + * Copyright (c) 1996, 2001, 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; + + +/** + * Convenience class for writing character files. The constructors of this + * class assume that the default character encoding and the default byte-buffer + * size are acceptable. To specify these values yourself, construct an + * OutputStreamWriter on a FileOutputStream. + * + *

Whether or not a file is available or may be created depends upon the + * underlying platform. Some platforms, in particular, allow a file to be + * opened for writing by only one FileWriter (or other file-writing + * object) at a time. In such situations the constructors in this class + * will fail if the file involved is already open. + * + *

FileWriter is meant for writing streams of characters. + * For writing streams of raw bytes, consider using a + * FileOutputStream. + * + * @see OutputStreamWriter + * @see FileOutputStream + * + * @author Mark Reinhold + * @since JDK1.1 + */ + +public class FileWriter extends OutputStreamWriter { + + /** + * Constructs a FileWriter object given a file name. + * + * @param fileName String The system-dependent filename. + * @throws IOException if the named file exists but is a directory rather + * than a regular file, does not exist but cannot be + * created, or cannot be opened for any other reason + */ + public FileWriter(String fileName) throws IOException { + super(new FileOutputStream(fileName)); + } + + /** + * Constructs a FileWriter object given a file name with a boolean + * indicating whether or not to append the data written. + * + * @param fileName String The system-dependent filename. + * @param append boolean if true, then data will be written + * to the end of the file rather than the beginning. + * @throws IOException if the named file exists but is a directory rather + * than a regular file, does not exist but cannot be + * created, or cannot be opened for any other reason + */ + public FileWriter(String fileName, boolean append) throws IOException { + super(new FileOutputStream(fileName, append)); + } + + /** + * Constructs a FileWriter object given a File object. + * + * @param file a File object to write to. + * @throws IOException if the file exists but is a directory rather than + * a regular file, does not exist but cannot be created, + * or cannot be opened for any other reason + */ + public FileWriter(File file) throws IOException { + super(new FileOutputStream(file)); + } + + /** + * Constructs a FileWriter object given a File object. If the second + * argument is true, then bytes will be written to the end + * of the file rather than the beginning. + * + * @param file a File object to write to + * @param append if true, then bytes will be written + * to the end of the file rather than the beginning + * @throws IOException if the file exists but is a directory rather than + * a regular file, does not exist but cannot be created, + * or cannot be opened for any other reason + * @since 1.4 + */ + public FileWriter(File file, boolean append) throws IOException { + super(new FileOutputStream(file, append)); + } + + /** + * Constructs a FileWriter object associated with a file descriptor. + * + * @param fd FileDescriptor object to write to. + */ + public FileWriter(FileDescriptor fd) { + super(new FileOutputStream(fd)); + } + +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/io/FilterReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FilterReader.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,124 @@ +/* + * 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 reading filtered character streams. + * The abstract class FilterReader itself + * provides default methods that pass all requests to + * the contained stream. Subclasses of FilterReader + * should override some of these methods and may also provide + * additional methods and fields. + * + * @author Mark Reinhold + * @since JDK1.1 + */ + +public abstract class FilterReader extends Reader { + + /** + * The underlying character-input stream. + */ + protected Reader in; + + /** + * Creates a new filtered reader. + * + * @param in a Reader object providing the underlying stream. + * @throws NullPointerException if in is null + */ + protected FilterReader(Reader in) { + super(in); + this.in = in; + } + + /** + * Reads a single character. + * + * @exception IOException If an I/O error occurs + */ + public int read() throws IOException { + return in.read(); + } + + /** + * Reads characters into a portion of an array. + * + * @exception IOException If an I/O error occurs + */ + public int read(char cbuf[], int off, int len) throws IOException { + return in.read(cbuf, off, len); + } + + /** + * Skips characters. + * + * @exception IOException If an I/O error occurs + */ + public long skip(long n) throws IOException { + return in.skip(n); + } + + /** + * Tells whether this stream is ready to be read. + * + * @exception IOException If an I/O error occurs + */ + public boolean ready() throws IOException { + return in.ready(); + } + + /** + * Tells whether this stream supports the mark() operation. + */ + public boolean markSupported() { + return in.markSupported(); + } + + /** + * Marks the present position in the stream. + * + * @exception IOException If an I/O error occurs + */ + public void mark(int readAheadLimit) throws IOException { + in.mark(readAheadLimit); + } + + /** + * Resets the stream. + * + * @exception IOException If an I/O error occurs + */ + public void reset() throws IOException { + in.reset(); + } + + public void close() throws IOException { + in.close(); + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/io/FilterWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FilterWriter.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,107 @@ +/* + * 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 filtered character streams. + * The abstract class FilterWriter itself + * provides default methods that pass all requests to the + * contained stream. Subclasses of FilterWriter + * should override some of these methods and may also + * provide additional methods and fields. + * + * @author Mark Reinhold + * @since JDK1.1 + */ + +public abstract class FilterWriter extends Writer { + + /** + * The underlying character-output stream. + */ + protected Writer out; + + /** + * Create a new filtered writer. + * + * @param out a Writer object to provide the underlying stream. + * @throws NullPointerException if out is null + */ + protected FilterWriter(Writer out) { + super(out); + this.out = out; + } + + /** + * Writes a single character. + * + * @exception IOException If an I/O error occurs + */ + public void write(int c) throws IOException { + out.write(c); + } + + /** + * Writes a portion of an array of characters. + * + * @param cbuf Buffer of characters 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(char cbuf[], int off, int len) throws IOException { + out.write(cbuf, off, len); + } + + /** + * Writes a portion of a string. + * + * @param str 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 str, int off, int len) throws IOException { + out.write(str, off, len); + } + + /** + * 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(); + } + +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/io/LineNumberInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/LineNumberInputStream.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,292 @@ +/* + * Copyright (c) 1995, 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 java.io; + +/** + * This class is an input stream filter that provides the added + * functionality of keeping track of the current line number. + *

+ * A line is a sequence of bytes ending with a carriage return + * character ('\r'), a newline character + * ('\n'), or a carriage return character followed + * immediately by a linefeed character. In all three cases, the line + * terminating character(s) are returned as a single newline character. + *

+ * The line number begins at 0, and is incremented by + * 1 when a read returns a newline character. + * + * @author Arthur van Hoff + * @see java.io.LineNumberReader + * @since JDK1.0 + * @deprecated This class incorrectly assumes that bytes adequately represent + * characters. As of JDK 1.1, the preferred way to operate on + * character streams is via the new character-stream classes, which + * include a class for counting line numbers. + */ +@Deprecated +public +class LineNumberInputStream extends FilterInputStream { + int pushBack = -1; + int lineNumber; + int markLineNumber; + int markPushBack = -1; + + /** + * Constructs a newline number input stream that reads its input + * from the specified input stream. + * + * @param in the underlying input stream. + */ + public LineNumberInputStream(InputStream in) { + super(in); + } + + /** + * Reads the next byte of data from this input stream. The value + * byte is returned as an int in the range + * 0 to 255. If no byte is available + * because the end of the stream has been reached, the value + * -1 is returned. This method blocks until input data + * is available, the end of the stream is detected, or an exception + * is thrown. + *

+ * The read method of + * LineNumberInputStream calls the read + * method of the underlying input stream. It checks for carriage + * returns and newline characters in the input, and modifies the + * current line number as appropriate. A carriage-return character or + * a carriage return followed by a newline character are both + * converted into a single newline character. + * + * @return the next byte of data, or -1 if the end of this + * stream is reached. + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + * @see java.io.LineNumberInputStream#getLineNumber() + */ + public int read() throws IOException { + int c = pushBack; + + if (c != -1) { + pushBack = -1; + } else { + c = in.read(); + } + + switch (c) { + case '\r': + pushBack = in.read(); + if (pushBack == '\n') { + pushBack = -1; + } + case '\n': + lineNumber++; + return '\n'; + } + return c; + } + + /** + * Reads up to len bytes of data from this input stream + * into an array of bytes. This method blocks until some input is available. + *

+ * The read method of + * LineNumberInputStream repeatedly calls the + * read method of zero arguments to fill in the byte array. + * + * @param b the buffer into which the data is read. + * @param off the start offset of the data. + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or + * -1 if there is no more data because the end of + * this stream has been reached. + * @exception IOException if an I/O error occurs. + * @see java.io.LineNumberInputStream#read() + */ + public int read(byte b[], int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) > b.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + int c = read(); + if (c == -1) { + return -1; + } + b[off] = (byte)c; + + int i = 1; + try { + for (; i < len ; i++) { + c = read(); + if (c == -1) { + break; + } + if (b != null) { + b[off + i] = (byte)c; + } + } + } catch (IOException ee) { + } + return i; + } + + /** + * Skips over and discards n bytes of data from this + * input stream. The skip method may, for a variety of + * reasons, end up skipping over some smaller number of bytes, + * possibly 0. The actual number of bytes skipped is + * returned. If n is negative, no bytes are skipped. + *

+ * The skip method of LineNumberInputStream creates + * a byte array and then repeatedly reads into it until + * n bytes have been read or the end of the stream has + * been reached. + * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + public long skip(long n) throws IOException { + int chunk = 2048; + long remaining = n; + byte data[]; + int nr; + + if (n <= 0) { + return 0; + } + + data = new byte[chunk]; + while (remaining > 0) { + nr = read(data, 0, (int) Math.min(chunk, remaining)); + if (nr < 0) { + break; + } + remaining -= nr; + } + + return n - remaining; + } + + /** + * Sets the line number to the specified argument. + * + * @param lineNumber the new line number. + * @see #getLineNumber + */ + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + /** + * Returns the current line number. + * + * @return the current line number. + * @see #setLineNumber + */ + public int getLineNumber() { + return lineNumber; + } + + + /** + * Returns the number of bytes that can be read from this input + * stream without blocking. + *

+ * Note that if the underlying input stream is able to supply + * k input characters without blocking, the + * LineNumberInputStream can guarantee only to provide + * k/2 characters without blocking, because the + * k characters from the underlying input stream might + * consist of k/2 pairs of '\r' and + * '\n', which are converted to just + * k/2 '\n' characters. + * + * @return the number of bytes that can be read from this input stream + * without blocking. + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + public int available() throws IOException { + return (pushBack == -1) ? super.available()/2 : super.available()/2 + 1; + } + + /** + * Marks the current position in this input stream. A subsequent + * call to the reset method repositions this stream at + * the last marked position so that subsequent reads re-read the same bytes. + *

+ * The mark method of + * LineNumberInputStream remembers the current line + * number in a private variable, and then calls the mark + * method of the underlying input stream. + * + * @param readlimit the maximum limit of bytes that can be read before + * the mark position becomes invalid. + * @see java.io.FilterInputStream#in + * @see java.io.LineNumberInputStream#reset() + */ + public void mark(int readlimit) { + markLineNumber = lineNumber; + markPushBack = pushBack; + in.mark(readlimit); + } + + /** + * Repositions this stream to the position at the time the + * mark method was last called on this input stream. + *

+ * The reset method of + * LineNumberInputStream resets the line number to be + * the line number at the time the mark method was + * called, and then calls the reset method of the + * underlying input stream. + *

+ * Stream marks are intended to be used in + * situations where you need to read ahead a little to see what's in + * the stream. Often this is most easily done by invoking some + * general parser. If the stream is of the type handled by the + * parser, it just chugs along happily. If the stream is not of + * that type, the parser should toss an exception when it fails, + * which, if it happens within readlimit bytes, allows the outer + * code to reset the stream and try another parser. + * + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + * @see java.io.LineNumberInputStream#mark(int) + */ + public void reset() throws IOException { + lineNumber = markLineNumber; + pushBack = markPushBack; + in.reset(); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/io/LineNumberReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/LineNumberReader.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,281 @@ +/* + * 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; + + +/** + * A buffered character-input stream that keeps track of line numbers. This + * class defines methods {@link #setLineNumber(int)} and {@link + * #getLineNumber()} for setting and getting the current line number + * respectively. + * + *

By default, line numbering begins at 0. This number increments at every + * line terminator as the data is read, and can be changed + * with a call to setLineNumber(int). Note however, that + * setLineNumber(int) does not actually change the current position in + * the stream; it only changes the value that will be returned by + * getLineNumber(). + * + *

A line is considered to be terminated by any one of a + * line feed ('\n'), a carriage return ('\r'), or a carriage return followed + * immediately by a linefeed. + * + * @author Mark Reinhold + * @since JDK1.1 + */ + +public class LineNumberReader extends BufferedReader { + + /** The current line number */ + private int lineNumber = 0; + + /** The line number of the mark, if any */ + private int markedLineNumber; // Defaults to 0 + + /** If the next character is a line feed, skip it */ + private boolean skipLF; + + /** The skipLF flag when the mark was set */ + private boolean markedSkipLF; + + /** + * Create a new line-numbering reader, using the default input-buffer + * size. + * + * @param in + * A Reader object to provide the underlying stream + */ + public LineNumberReader(Reader in) { + super(in); + } + + /** + * Create a new line-numbering reader, reading characters into a buffer of + * the given size. + * + * @param in + * A Reader object to provide the underlying stream + * + * @param sz + * An int specifying the size of the buffer + */ + public LineNumberReader(Reader in, int sz) { + super(in, sz); + } + + /** + * Set the current line number. + * + * @param lineNumber + * An int specifying the line number + * + * @see #getLineNumber + */ + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + /** + * Get the current line number. + * + * @return The current line number + * + * @see #setLineNumber + */ + public int getLineNumber() { + return lineNumber; + } + + /** + * Read a single character. Line terminators are + * compressed into single newline ('\n') characters. Whenever a line + * terminator is read the current line number is incremented. + * + * @return The character read, or -1 if the end of the stream has been + * reached + * + * @throws IOException + * If an I/O error occurs + */ + public int read() throws IOException { + synchronized (lock) { + int c = super.read(); + if (skipLF) { + if (c == '\n') + c = super.read(); + skipLF = false; + } + switch (c) { + case '\r': + skipLF = true; + case '\n': /* Fall through */ + lineNumber++; + return '\n'; + } + return c; + } + } + + /** + * Read characters into a portion of an array. Whenever a line terminator is read the current line number is + * incremented. + * + * @param cbuf + * Destination buffer + * + * @param off + * Offset at which to start storing characters + * + * @param len + * Maximum number of characters to read + * + * @return The number of bytes read, or -1 if the end of the stream has + * already been reached + * + * @throws IOException + * If an I/O error occurs + */ + public int read(char cbuf[], int off, int len) throws IOException { + synchronized (lock) { + int n = super.read(cbuf, off, len); + + for (int i = off; i < off + n; i++) { + int c = cbuf[i]; + if (skipLF) { + skipLF = false; + if (c == '\n') + continue; + } + switch (c) { + case '\r': + skipLF = true; + case '\n': /* Fall through */ + lineNumber++; + break; + } + } + + return n; + } + } + + /** + * Read a line of text. Whenever a line terminator is + * read the current line number is incremented. + * + * @return A String containing the contents of the line, not including + * any line termination characters, or + * null if the end of the stream has been reached + * + * @throws IOException + * If an I/O error occurs + */ + public String readLine() throws IOException { + synchronized (lock) { + String l = super.readLine(skipLF); + skipLF = false; + if (l != null) + lineNumber++; + return l; + } + } + + /** Maximum skip-buffer size */ + private static final int maxSkipBufferSize = 8192; + + /** Skip buffer, null until allocated */ + private char skipBuffer[] = null; + + /** + * Skip characters. + * + * @param n + * The number of characters to skip + * + * @return The number of characters actually skipped + * + * @throws IOException + * If an I/O error occurs + * + * @throws IllegalArgumentException + * If n is negative + */ + public long skip(long n) throws IOException { + if (n < 0) + throw new IllegalArgumentException("skip() value is negative"); + int nn = (int) Math.min(n, maxSkipBufferSize); + synchronized (lock) { + if ((skipBuffer == null) || (skipBuffer.length < nn)) + skipBuffer = new char[nn]; + long r = n; + while (r > 0) { + int nc = read(skipBuffer, 0, (int) Math.min(r, nn)); + if (nc == -1) + break; + r -= nc; + } + return n - r; + } + } + + /** + * Mark the present position in the stream. Subsequent calls to reset() + * will attempt to reposition the stream to this point, and will also reset + * the line number appropriately. + * + * @param readAheadLimit + * Limit on the number of characters that may be read while still + * preserving the mark. After reading this many characters, + * attempting to reset the stream may fail. + * + * @throws IOException + * If an I/O error occurs + */ + public void mark(int readAheadLimit) throws IOException { + synchronized (lock) { + super.mark(readAheadLimit); + markedLineNumber = lineNumber; + markedSkipLF = skipLF; + } + } + + /** + * Reset the stream to the most recent mark. + * + * @throws IOException + * If the stream has not been marked, or if the mark has been + * invalidated + */ + public void reset() throws IOException { + synchronized (lock) { + super.reset(); + lineNumber = markedLineNumber; + skipLF = markedSkipLF; + } + } + +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,241 @@ +/* + * 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; + +import java.nio.charset.Charset; + +/** + * 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) { + this(out); + } + + /** + * 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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1123 @@ +/* + * 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.nio.charset.Charset; +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 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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1029 @@ +/* + * 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.Formatter; +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * 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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/io/StringReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/StringReader.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,201 @@ +/* + * 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; + + +/** + * A character stream whose source is a string. + * + * @author Mark Reinhold + * @since JDK1.1 + */ + +public class StringReader extends Reader { + + private String str; + private int length; + private int next = 0; + private int mark = 0; + + /** + * Creates a new string reader. + * + * @param s String providing the character stream. + */ + public StringReader(String s) { + this.str = s; + this.length = s.length(); + } + + /** Check to make sure that the stream has not been closed */ + private void ensureOpen() throws IOException { + if (str == null) + throw new IOException("Stream closed"); + } + + /** + * Reads a single character. + * + * @return The character read, or -1 if the end of the stream has been + * reached + * + * @exception IOException If an I/O error occurs + */ + public int read() throws IOException { + synchronized (lock) { + ensureOpen(); + if (next >= length) + return -1; + return str.charAt(next++); + } + } + + /** + * Reads characters into a portion of an array. + * + * @param cbuf Destination buffer + * @param off Offset at which to start writing characters + * @param len Maximum number of characters to read + * + * @return The number of characters read, or -1 if the end of the + * stream has been reached + * + * @exception IOException If an I/O error occurs + */ + public int read(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 0; + } + if (next >= length) + return -1; + int n = Math.min(length - next, len); + str.getChars(next, next + n, cbuf, off); + next += n; + return n; + } + } + + /** + * Skips the specified number of characters in the stream. Returns + * the number of characters that were skipped. + * + *

The ns parameter may be negative, even though the + * skip method of the {@link Reader} superclass throws + * an exception in this case. Negative values of ns cause the + * stream to skip backwards. Negative return values indicate a skip + * backwards. It is not possible to skip backwards past the beginning of + * the string. + * + *

If the entire string has been read or skipped, then this method has + * no effect and always returns 0. + * + * @exception IOException If an I/O error occurs + */ + public long skip(long ns) throws IOException { + synchronized (lock) { + ensureOpen(); + if (next >= length) + return 0; + // Bound skip by beginning and end of the source + long n = Math.min(length - next, ns); + n = Math.max(-next, n); + next += n; + return n; + } + } + + /** + * Tells whether this stream is ready to be read. + * + * @return True if the next read() is guaranteed not to block for input + * + * @exception IOException If the stream is closed + */ + public boolean ready() throws IOException { + synchronized (lock) { + ensureOpen(); + return true; + } + } + + /** + * Tells whether this stream supports the mark() operation, which it does. + */ + public boolean markSupported() { + return true; + } + + /** + * Marks the present position in the stream. Subsequent calls to reset() + * will reposition the stream to this point. + * + * @param readAheadLimit Limit on the number of characters that may be + * read while still preserving the mark. Because + * the stream's input comes from a string, there + * is no actual limit, so this argument must not + * be negative, but is otherwise ignored. + * + * @exception IllegalArgumentException If readAheadLimit is < 0 + * @exception IOException If an I/O error occurs + */ + public void mark(int readAheadLimit) throws IOException { + if (readAheadLimit < 0){ + throw new IllegalArgumentException("Read-ahead limit < 0"); + } + synchronized (lock) { + ensureOpen(); + mark = next; + } + } + + /** + * Resets the stream to the most recent mark, or to the beginning of the + * string if it has never been marked. + * + * @exception IOException If an I/O error occurs + */ + public void reset() throws IOException { + synchronized (lock) { + ensureOpen(); + next = mark; + } + } + + /** + * Closes the stream and releases any system resources associated with + * it. Once the stream has been closed, further read(), + * ready(), mark(), or reset() invocations will throw an IOException. + * Closing a previously closed stream has no effect. + */ + public void close() { + str = null; + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/io/StringWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/StringWriter.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,236 @@ +/* + * Copyright (c) 1996, 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 java.io; + + +/** + * A character stream that collects its output in a string buffer, which can + * then be used to construct a string. + *

+ * Closing a StringWriter has no effect. The methods in this class + * can be called after the stream has been closed without generating an + * IOException. + * + * @author Mark Reinhold + * @since JDK1.1 + */ + +public class StringWriter extends Writer { + + private StringBuffer buf; + + /** + * Create a new string writer using the default initial string-buffer + * size. + */ + public StringWriter() { + buf = new StringBuffer(); + lock = buf; + } + + /** + * Create a new string writer using the specified initial string-buffer + * size. + * + * @param initialSize + * The number of char values that will fit into this buffer + * before it is automatically expanded + * + * @throws IllegalArgumentException + * If initialSize is negative + */ + public StringWriter(int initialSize) { + if (initialSize < 0) { + throw new IllegalArgumentException("Negative buffer size"); + } + buf = new StringBuffer(initialSize); + lock = buf; + } + + /** + * Write a single character. + */ + public void write(int c) { + buf.append((char) c); + } + + /** + * Write 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 + */ + public void write(char cbuf[], int off, int len) { + if ((off < 0) || (off > cbuf.length) || (len < 0) || + ((off + len) > cbuf.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + buf.append(cbuf, off, len); + } + + /** + * Write a string. + */ + public void write(String str) { + buf.append(str); + } + + /** + * Write a portion of a string. + * + * @param str String to be written + * @param off Offset from which to start writing characters + * @param len Number of characters to write + */ + public void write(String str, int off, int len) { + buf.append(str.substring(off, off + 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 + * + * @since 1.5 + */ + public StringWriter 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 StringWriter 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 StringWriter append(char c) { + write(c); + return this; + } + + /** + * Return the buffer's current value as a string. + */ + public String toString() { + return buf.toString(); + } + + /** + * Return the string buffer itself. + * + * @return StringBuffer holding the current buffer value. + */ + public StringBuffer getBuffer() { + return buf; + } + + /** + * Flush the stream. + */ + public void flush() { + } + + /** + * Closing a StringWriter has no effect. The methods in this + * class can be called after the stream has been closed without generating + * an IOException. + */ + public void close() throws IOException { + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/io/SyncFailedException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/SyncFailedException.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,48 @@ +/* + * Copyright (c) 1996, 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 a sync operation has failed. + * + * @author Ken Arnold + * @see java.io.FileDescriptor#sync + * @see java.io.IOException + * @since JDK1.1 + */ +public class SyncFailedException extends IOException { + private static final long serialVersionUID = -2353342684412443330L; + + /** + * Constructs an SyncFailedException with a detail message. + * A detail message is a String that describes this particular exception. + * + * @param desc a String describing the exception. + */ + public SyncFailedException(String desc) { + super(desc); + } +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/lang/System.java --- a/rt/emul/compact/src/main/java/java/lang/System.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/compact/src/main/java/java/lang/System.java Wed Apr 30 15:04:10 2014 +0200 @@ -17,6 +17,15 @@ */ package java.lang; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Properties; +import org.apidesign.bck2brwsr.core.JavaScriptBody; + /** Poor man's re-implementation of most important System methods. * * @author Jaroslav Tulach @@ -33,4 +42,90 @@ return org.apidesign.bck2brwsr.emul.lang.System.currentTimeMillis(); } + public static int identityHashCode(Object obj) { + return Class.defaultHashCode(obj); + } + + public static String getProperty(String name) { + if ("os.name".equals(name)) { + return userAgent(); + } + return null; + } + + @JavaScriptBody(args = {}, body = "return (typeof navigator !== 'undefined') ? navigator.userAgent : 'unknown';") + private static native String userAgent(); + + public static String getProperty(String key, String def) { + return def; + } + + public static Properties getProperties() { + throw new SecurityException(); + } + + public static void setProperties(Properties p) { + throw new SecurityException(); + } + + /** + * 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) { + } + + public final static InputStream in; + + public final static PrintStream out; + + public final static PrintStream err; + + public static void setOut(PrintStream out) { + throw new SecurityException(); + } + + public static void setIn(InputStream in) { + throw new SecurityException(); + } + + public static void setErr(PrintStream err) { + throw new SecurityException(); + } + + static { + in = new ByteArrayInputStream(new byte[0]); + out = new PrintStream(new BufferedOutputStream(new SystemStream("log"))); + err = new PrintStream(new BufferedOutputStream(new SystemStream("warn"))); + } + + private static final class SystemStream extends OutputStream { + private final String method; + + public SystemStream(String method) { + this.method = method; + } + + @Override + public void write(byte b[], int off, int len) throws IOException { + write(method, new String(b, off, len, "UTF-8")); + } + + @JavaScriptBody(args = { "method", "b" }, body = "if (typeof console !== 'undefined') console[method](b.toString());") + private static native void write(String method, String b); + + @Override + public void write(int b) throws IOException { + write(new byte[] { (byte)b }); + } + } // end of SystemStream } diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1547 @@ +/* + * 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 ClassLoader.getSystemClassLoader(); + } + + /** + * 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) { + if (cl == ClassLoader.getSystemClassLoader()) { + return; + } + 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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/lang/annotation/Annotation.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/annotation/Annotation.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2003, 2009, 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.annotation; + +/** + * The common interface extended by all annotation types. Note that an + * interface that manually extends this one does not define + * an annotation type. Also note that this interface does not itself + * define an annotation type. + * + * More information about annotation types can be found in section 9.6 of + * The Java™ Language Specification. + * + * @author Josh Bloch + * @since 1.5 + */ +public interface Annotation { + /** + * Returns true if the specified object represents an annotation + * that is logically equivalent to this one. In other words, + * returns true if the specified object is an instance of the same + * annotation type as this instance, all of whose members are equal + * to the corresponding member of this annotation, as defined below: + *

    + *
  • Two corresponding primitive typed members whose values are + * x and y are considered equal if x == y, + * unless their type is float or double. + * + *
  • Two corresponding float members whose values + * are x and y are considered equal if + * Float.valueOf(x).equals(Float.valueOf(y)). + * (Unlike the == operator, NaN is considered equal + * to itself, and 0.0f unequal to -0.0f.) + * + *
  • Two corresponding double members whose values + * are x and y are considered equal if + * Double.valueOf(x).equals(Double.valueOf(y)). + * (Unlike the == operator, NaN is considered equal + * to itself, and 0.0 unequal to -0.0.) + * + *
  • Two corresponding String, Class, enum, or + * annotation typed members whose values are x and y + * are considered equal if x.equals(y). (Note that this + * definition is recursive for annotation typed members.) + * + *
  • Two corresponding array typed members x and y + * are considered equal if Arrays.equals(x, y), for the + * appropriate overloading of {@link java.util.Arrays#equals}. + *
+ * + * @return true if the specified object represents an annotation + * that is logically equivalent to this one, otherwise false + */ + boolean equals(Object obj); + + /** + * Returns the hash code of this annotation, as defined below: + * + *

The hash code of an annotation is the sum of the hash codes + * of its members (including those with default values), as defined + * below: + * + * The hash code of an annotation member is (127 times the hash code + * of the member-name as computed by {@link String#hashCode()}) XOR + * the hash code of the member-value, as defined below: + * + *

The hash code of a member-value depends on its type: + *

    + *
  • The hash code of a primitive value v is equal to + * WrapperType.valueOf(v).hashCode(), where + * WrapperType is the wrapper type corresponding + * to the primitive type of v ({@link Byte}, + * {@link Character}, {@link Double}, {@link Float}, {@link Integer}, + * {@link Long}, {@link Short}, or {@link Boolean}). + * + *
  • The hash code of a string, enum, class, or annotation member-value + I v is computed as by calling + * v.hashCode(). (In the case of annotation + * member values, this is a recursive definition.) + * + *
  • The hash code of an array member-value is computed by calling + * the appropriate overloading of + * {@link java.util.Arrays#hashCode(long[]) Arrays.hashCode} + * on the value. (There is one overloading for each primitive + * type, and one for object reference types.) + *
+ * + * @return the hash code of this annotation + */ + int hashCode(); + + /** + * Returns a string representation of this annotation. The details + * of the representation are implementation-dependent, but the following + * may be regarded as typical: + *
+     *   @com.acme.util.Name(first=Alfred, middle=E., last=Neuman)
+     * 
+ * + * @return a string representation of this annotation + */ + String toString(); + + /** + * Returns the annotation type of this annotation. + */ + Class annotationType(); +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/lang/annotation/AnnotationFormatError.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/annotation/AnnotationFormatError.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2004, 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.annotation; + +/** + * Thrown when the annotation parser attempts to read an annotation + * from a class file and determines that the annotation is malformed. + * This error can be thrown by the {@linkplain + * java.lang.reflect.AnnotatedElement API used to read annotations + * reflectively}. + * + * @author Josh Bloch + * @see java.lang.reflect.AnnotatedElement + * @since 1.5 + */ +public class AnnotationFormatError extends Error { + private static final long serialVersionUID = -4256701562333669892L; + + /** + * Constructs a new AnnotationFormatError with the specified + * detail message. + * + * @param message the detail message. + */ + public AnnotationFormatError(String message) { + super(message); + } + + /** + * Constructs a new AnnotationFormatError with the specified + * detail message and cause. Note that the detail message associated + * with cause is not automatically incorporated in + * this error's detail message. + * + * @param message the detail message + * @param cause the cause (A null value is permitted, and + * indicates that the cause is nonexistent or unknown.) + */ + public AnnotationFormatError(String message, Throwable cause) { + super(message, cause); + } + + + /** + * Constructs a new AnnotationFormatError with the specified + * cause and a detail message of + * (cause == null ? null : cause.toString()) (which + * typically contains the class and detail message of cause). + * + * @param cause the cause (A null value is permitted, and + * indicates that the cause is nonexistent or unknown.) + */ + public AnnotationFormatError(Throwable cause) { + super(cause); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/lang/annotation/AnnotationTypeMismatchException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/annotation/AnnotationTypeMismatchException.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2003, 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.annotation; +import java.lang.reflect.Method; + +/** + * Thrown to indicate that a program has attempted to access an element of + * an annotation whose type has changed after the annotation was compiled + * (or serialized). + * This exception can be thrown by the {@linkplain + * java.lang.reflect.AnnotatedElement API used to read annotations + * reflectively}. + * + * @author Josh Bloch + * @see java.lang.reflect.AnnotatedElement + * @since 1.5 + */ +public class AnnotationTypeMismatchException extends RuntimeException { + private static final long serialVersionUID = 8125925355765570191L; + + /** + * The Method object for the annotation element. + */ + private final Method element; + + /** + * The (erroneous) type of data found in the annotation. This string + * may, but is not required to, contain the value as well. The exact + * format of the string is unspecified. + */ + private final String foundType; + + /** + * Constructs an AnnotationTypeMismatchException for the specified + * annotation type element and found data type. + * + * @param element the Method object for the annotation element + * @param foundType the (erroneous) type of data found in the annotation. + * This string may, but is not required to, contain the value + * as well. The exact format of the string is unspecified. + */ + public AnnotationTypeMismatchException(Method element, String foundType) { + super("Incorrectly typed data found for annotation element " + element + + " (Found data of type " + foundType + ")"); + this.element = element; + this.foundType = foundType; + } + + /** + * Returns the Method object for the incorrectly typed element. + * + * @return the Method object for the incorrectly typed element + */ + public Method element() { + return this.element; + } + + /** + * Returns the type of data found in the incorrectly typed element. + * The returned string may, but is not required to, contain the value + * as well. The exact format of the string is unspecified. + * + * @return the type of data found in the incorrectly typed element + */ + public String foundType() { + return this.foundType; + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/lang/annotation/Documented.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/annotation/Documented.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2003, 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 java.lang.annotation; + +/** + * Indicates that annotations with a type are to be documented by javadoc + * and similar tools by default. This type should be used to annotate the + * declarations of types whose annotations affect the use of annotated + * elements by their clients. If a type declaration is annotated with + * Documented, its annotations become part of the public API + * of the annotated elements. + * + * @author Joshua Bloch + * @since 1.5 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +public @interface Documented { +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/lang/annotation/ElementType.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/annotation/ElementType.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,63 @@ +/* + * 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.lang.annotation; + +/** + * A program element type. The constants of this enumerated type + * provide a simple classification of the declared elements in a + * Java program. + * + *

These constants are used with the {@link Target} meta-annotation type + * to specify where it is legal to use an annotation type. + * + * @author Joshua Bloch + * @since 1.5 + */ +public enum ElementType { + /** Class, interface (including annotation type), or enum declaration */ + TYPE, + + /** Field declaration (includes enum constants) */ + FIELD, + + /** Method declaration */ + METHOD, + + /** Parameter declaration */ + PARAMETER, + + /** Constructor declaration */ + CONSTRUCTOR, + + /** Local variable declaration */ + LOCAL_VARIABLE, + + /** Annotation type declaration */ + ANNOTATION_TYPE, + + /** Package declaration */ + PACKAGE +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/lang/annotation/IncompleteAnnotationException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/annotation/IncompleteAnnotationException.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2003, 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.annotation; + +/** + * Thrown to indicate that a program has attempted to access an element of + * an annotation type that was added to the annotation type definition after + * the annotation was compiled (or serialized). This exception will not be + * thrown if the new element has a default value. + * This exception can be thrown by the {@linkplain + * java.lang.reflect.AnnotatedElement API used to read annotations + * reflectively}. + * + * @author Josh Bloch + * @see java.lang.reflect.AnnotatedElement + * @since 1.5 + */ +public class IncompleteAnnotationException extends RuntimeException { + private static final long serialVersionUID = 8445097402741811912L; + + private Class annotationType; + private String elementName; + + + /** + * Constructs an IncompleteAnnotationException to indicate that + * the named element was missing from the specified annotation type. + * + * @param annotationType the Class object for the annotation type + * @param elementName the name of the missing element + */ + public IncompleteAnnotationException( + Class annotationType, + String elementName) { + super(annotationType.getName() + " missing element " + elementName); + + this.annotationType = annotationType; + this.elementName = elementName; + } + + /** + * Returns the Class object for the annotation type with the + * missing element. + * + * @return the Class object for the annotation type with the + * missing element + */ + public Class annotationType() { + return annotationType; + } + + /** + * Returns the name of the missing element. + * + * @return the name of the missing element + */ + public String elementName() { + return elementName; + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/lang/annotation/Inherited.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/annotation/Inherited.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2003, 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 java.lang.annotation; + +/** + * Indicates that an annotation type is automatically inherited. If + * an Inherited meta-annotation is present on an annotation type + * declaration, and the user queries the annotation type on a class + * declaration, and the class declaration has no annotation for this type, + * then the class's superclass will automatically be queried for the + * annotation type. This process will be repeated until an annotation for this + * type is found, or the top of the class hierarchy (Object) + * is reached. If no superclass has an annotation for this type, then + * the query will indicate that the class in question has no such annotation. + * + *

Note that this meta-annotation type has no effect if the annotated + * type is used to annotate anything other than a class. Note also + * that this meta-annotation only causes annotations to be inherited + * from superclasses; annotations on implemented interfaces have no + * effect. + * + * @author Joshua Bloch + * @since 1.5 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +public @interface Inherited { +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/lang/annotation/Retention.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/annotation/Retention.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2003, 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.lang.annotation; + +/** + * Indicates how long annotations with the annotated type are to + * be retained. If no Retention annotation is present on + * an annotation type declaration, the retention policy defaults to + * {@code RetentionPolicy.CLASS}. + * + *

A Retention meta-annotation has effect only if the + * meta-annotated type is used directly for annotation. It has no + * effect if the meta-annotated type is used as a member type in + * another annotation type. + * + * @author Joshua Bloch + * @since 1.5 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +public @interface Retention { + RetentionPolicy value(); +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/lang/annotation/RetentionPolicy.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/annotation/RetentionPolicy.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2003, 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 java.lang.annotation; + +/** + * Annotation retention policy. The constants of this enumerated type + * describe the various policies for retaining annotations. They are used + * in conjunction with the {@link Retention} meta-annotation type to specify + * how long annotations are to be retained. + * + * @author Joshua Bloch + * @since 1.5 + */ +public enum RetentionPolicy { + /** + * Annotations are to be discarded by the compiler. + */ + SOURCE, + + /** + * Annotations are to be recorded in the class file by the compiler + * but need not be retained by the VM at run time. This is the default + * behavior. + */ + CLASS, + + /** + * Annotations are to be recorded in the class file by the compiler and + * retained by the VM at run time, so they may be read reflectively. + * + * @see java.lang.reflect.AnnotatedElement + */ + RUNTIME +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/lang/annotation/Target.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/annotation/Target.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2003, 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 java.lang.annotation; + +/** + * Indicates the kinds of program element to which an annotation type + * is applicable. If a Target meta-annotation is not present on an + * annotation type declaration, the declared type may be used on any + * program element. If such a meta-annotation is present, the compiler + * will enforce the specified usage restriction. + * + * For example, this meta-annotation indicates that the declared type is + * itself a meta-annotation type. It can only be used on annotation type + * declarations: + *

+ *    @Target(ElementType.ANNOTATION_TYPE)
+ *    public @interface MetaAnnotationType {
+ *        ...
+ *    }
+ * 
+ * This meta-annotation indicates that the declared type is intended solely + * for use as a member type in complex annotation type declarations. It + * cannot be used to annotate anything directly: + *
+ *    @Target({})
+ *    public @interface MemberType {
+ *        ...
+ *    }
+ * 
+ * It is a compile-time error for a single ElementType constant to + * appear more than once in a Target annotation. For example, the + * following meta-annotation is illegal: + *
+ *    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
+ *    public @interface Bogus {
+ *        ...
+ *    }
+ * 
+ */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +public @interface Target { + ElementType[] value(); +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/lang/annotation/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/lang/annotation/package-info.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2004, 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 library support for the Java programming language + * annotation facility. + * + * @author Josh Bloch + * @since 1.5 + */ +package java.lang.annotation; diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/net/URLConnection.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/net/URLConnection.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,2314 @@ +/* + * Copyright (c) 1995, 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.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.StringTokenizer; +import java.util.Collections; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * The abstract class URLConnection is the superclass + * of all classes that represent a communications link between the + * application and a URL. Instances of this class can be used both to + * read from and to write to the resource referenced by the URL. In + * general, creating a connection to a URL is a multistep process: + *

+ *

+ * + * + * + * + *
openConnection()connect()
Manipulate parameters that affect the connection to the remote + * resource.Interact with the resource; query header fields and + * contents.
+ * ----------------------------> + *
time
+ * + *
    + *
  1. The connection object is created by invoking the + * openConnection method on a URL. + *
  2. The setup parameters and general request properties are manipulated. + *
  3. The actual connection to the remote object is made, using the + * connect method. + *
  4. The remote object becomes available. The header fields and the contents + * of the remote object can be accessed. + *
+ *

+ * The setup parameters are modified using the following methods: + *

    + *
  • setAllowUserInteraction + *
  • setDoInput + *
  • setDoOutput + *
  • setIfModifiedSince + *
  • setUseCaches + *
+ *

+ * and the general request properties are modified using the method: + *

    + *
  • setRequestProperty + *
+ *

+ * Default values for the AllowUserInteraction and + * UseCaches parameters can be set using the methods + * setDefaultAllowUserInteraction and + * setDefaultUseCaches. + *

+ * Each of the above set methods has a corresponding + * get method to retrieve the value of the parameter or + * general request property. The specific parameters and general + * request properties that are applicable are protocol specific. + *

+ * The following methods are used to access the header fields and + * the contents after the connection is made to the remote object: + *

    + *
  • getContent + *
  • getHeaderField + *
  • getInputStream + *
  • getOutputStream + *
+ *

+ * Certain header fields are accessed frequently. The methods: + *

    + *
  • getContentEncoding + *
  • getContentLength + *
  • getContentType + *
  • getDate + *
  • getExpiration + *
  • getLastModifed + *
+ *

+ * provide convenient access to these fields. The + * getContentType method is used by the + * getContent method to determine the type of the remote + * object; subclasses may find it convenient to override the + * getContentType method. + *

+ * In the common case, all of the pre-connection parameters and + * general request properties can be ignored: the pre-connection + * parameters and request properties default to sensible values. For + * most clients of this interface, there are only two interesting + * methods: getInputStream and getContent, + * which are mirrored in the URL class by convenience methods. + *

+ * More information on the request properties and header fields of + * an http connection can be found at: + *

+ * http://www.ietf.org/rfc/rfc2616.txt
+ * 
+ * + * Note about fileNameMap: In versions prior to JDK 1.1.6, + * field fileNameMap of URLConnection was public. + * In JDK 1.1.6 and later, fileNameMap is private; accessor + * and mutator methods {@link #getFileNameMap() getFileNameMap} and + * {@link #setFileNameMap(java.net.FileNameMap) setFileNameMap} are added + * to access it. This change is also described on the + * Compatibility page. + * + * Invoking the close() methods on the InputStream or OutputStream of an + * URLConnection after a request may free network resources associated with this + * instance, unless particular protocol specifications specify different behaviours + * for it. + * + * @author James Gosling + * @see java.net.URL#openConnection() + * @see java.net.URLConnection#connect() + * @see java.net.URLConnection#getContent() + * @see java.net.URLConnection#getContentEncoding() + * @see java.net.URLConnection#getContentLength() + * @see java.net.URLConnection#getContentType() + * @see java.net.URLConnection#getDate() + * @see java.net.URLConnection#getExpiration() + * @see java.net.URLConnection#getHeaderField(int) + * @see java.net.URLConnection#getHeaderField(java.lang.String) + * @see java.net.URLConnection#getInputStream() + * @see java.net.URLConnection#getLastModified() + * @see java.net.URLConnection#getOutputStream() + * @see java.net.URLConnection#setAllowUserInteraction(boolean) + * @see java.net.URLConnection#setDefaultUseCaches(boolean) + * @see java.net.URLConnection#setDoInput(boolean) + * @see java.net.URLConnection#setDoOutput(boolean) + * @see java.net.URLConnection#setIfModifiedSince(long) + * @see java.net.URLConnection#setRequestProperty(java.lang.String, java.lang.String) + * @see java.net.URLConnection#setUseCaches(boolean) + * @since JDK1.0 + */ +public abstract class URLConnection { + + /** + * The URL represents the remote object on the World Wide Web to + * which this connection is opened. + *

+ * The value of this field can be accessed by the + * getURL method. + *

+ * The default value of this variable is the value of the URL + * argument in the URLConnection constructor. + * + * @see java.net.URLConnection#getURL() + * @see java.net.URLConnection#url + */ + protected URL url; + + /** + * This variable is set by the setDoInput method. Its + * value is returned by the getDoInput method. + *

+ * A URL connection can be used for input and/or output. Setting the + * doInput flag to true indicates that + * the application intends to read data from the URL connection. + *

+ * The default value of this field is true. + * + * @see java.net.URLConnection#getDoInput() + * @see java.net.URLConnection#setDoInput(boolean) + */ + protected boolean doInput = true; + + /** + * This variable is set by the setDoOutput method. Its + * value is returned by the getDoOutput method. + *

+ * A URL connection can be used for input and/or output. Setting the + * doOutput flag to true indicates + * that the application intends to write data to the URL connection. + *

+ * The default value of this field is false. + * + * @see java.net.URLConnection#getDoOutput() + * @see java.net.URLConnection#setDoOutput(boolean) + */ + protected boolean doOutput = false; + + private static boolean defaultAllowUserInteraction = false; + + /** + * If true, this URL is being examined in + * a context in which it makes sense to allow user interactions such + * as popping up an authentication dialog. If false, + * then no user interaction is allowed. + *

+ * The value of this field can be set by the + * setAllowUserInteraction method. + * Its value is returned by the + * getAllowUserInteraction method. + * Its default value is the value of the argument in the last invocation + * of the setDefaultAllowUserInteraction method. + * + * @see java.net.URLConnection#getAllowUserInteraction() + * @see java.net.URLConnection#setAllowUserInteraction(boolean) + * @see java.net.URLConnection#setDefaultAllowUserInteraction(boolean) + */ + protected boolean allowUserInteraction = defaultAllowUserInteraction; + + private static boolean defaultUseCaches = true; + + /** + * If true, the protocol is allowed to use caching + * whenever it can. If false, the protocol must always + * try to get a fresh copy of the object. + *

+ * This field is set by the setUseCaches method. Its + * value is returned by the getUseCaches method. + *

+ * Its default value is the value given in the last invocation of the + * setDefaultUseCaches method. + * + * @see java.net.URLConnection#setUseCaches(boolean) + * @see java.net.URLConnection#getUseCaches() + * @see java.net.URLConnection#setDefaultUseCaches(boolean) + */ + protected boolean useCaches = defaultUseCaches; + + /** + * Some protocols support skipping the fetching of the object unless + * the object has been modified more recently than a certain time. + *

+ * A nonzero value gives a time as the number of milliseconds since + * January 1, 1970, GMT. The object is fetched only if it has been + * modified more recently than that time. + *

+ * This variable is set by the setIfModifiedSince + * method. Its value is returned by the + * getIfModifiedSince method. + *

+ * The default value of this field is 0, indicating + * that the fetching must always occur. + * + * @see java.net.URLConnection#getIfModifiedSince() + * @see java.net.URLConnection#setIfModifiedSince(long) + */ + protected long ifModifiedSince = 0; + + /** + * If false, this connection object has not created a + * communications link to the specified URL. If true, + * the communications link has been established. + */ + protected boolean connected = false; + + /** + * @since 1.5 + */ + private int connectTimeout; + private int readTimeout; + + /** + * @since 1.6 + */ + private MessageHeader requests; + + /** + * @since JDK1.1 + */ + private static FileNameMap fileNameMap; + + /** + * @since 1.2.2 + */ + private static boolean fileNameMapLoaded = false; + + /** + * Loads filename map (a mimetable) from a data file. It will + * first try to load the user-specific table, defined + * by "content.types.user.table" property. If that fails, + * it tries to load the default built-in table at + * lib/content-types.properties under java home. + * + * @return the FileNameMap + * @since 1.2 + * @see #setFileNameMap(java.net.FileNameMap) + */ + public static synchronized FileNameMap getFileNameMap() { + if ((fileNameMap == null) && !fileNameMapLoaded) { + fileNameMap = new FileNameMap() { + @Override + public String getContentTypeFor(String fileName) { + return "text/plain"; + } + }; + fileNameMapLoaded = true; + } + + return new FileNameMap() { + private FileNameMap map = fileNameMap; + public String getContentTypeFor(String fileName) { + return map.getContentTypeFor(fileName); + } + }; + } + + /** + * Sets the FileNameMap. + *

+ * If there is a security manager, this method first calls + * the security manager's checkSetFactory method + * to ensure the operation is allowed. + * This could result in a SecurityException. + * + * @param map the FileNameMap to be set + * @exception SecurityException if a security manager exists and its + * checkSetFactory method doesn't allow the operation. + * @see SecurityManager#checkSetFactory + * @see #getFileNameMap() + * @since 1.2 + */ + public static void setFileNameMap(FileNameMap map) { + throw new SecurityException(); + } + + /** + * Opens a communications link to the resource referenced by this + * URL, if such a connection has not already been established. + *

+ * If the connect method is called when the connection + * has already been opened (indicated by the connected + * field having the value true), the call is ignored. + *

+ * URLConnection objects go through two phases: first they are + * created, then they are connected. After being created, and + * before being connected, various options can be specified + * (e.g., doInput and UseCaches). After connecting, it is an + * error to try to set them. Operations that depend on being + * connected, like getContentLength, will implicitly perform the + * connection, if necessary. + * + * @throws SocketTimeoutException if the timeout expires before + * the connection can be established + * @exception IOException if an I/O error occurs while opening the + * connection. + * @see java.net.URLConnection#connected + * @see #getConnectTimeout() + * @see #setConnectTimeout(int) + */ + abstract public void connect() throws IOException; + + /** + * Sets a specified timeout value, in milliseconds, to be used + * when opening a communications link to the resource referenced + * by this URLConnection. If the timeout expires before the + * connection can be established, a + * java.net.SocketTimeoutException is raised. A timeout of zero is + * interpreted as an infinite timeout. + + *

Some non-standard implmentation of this method may ignore + * the specified timeout. To see the connect timeout set, please + * call getConnectTimeout(). + * + * @param timeout an int that specifies the connect + * timeout value in milliseconds + * @throws IllegalArgumentException if the timeout parameter is negative + * + * @see #getConnectTimeout() + * @see #connect() + * @since 1.5 + */ + public void setConnectTimeout(int timeout) { + if (timeout < 0) { + throw new IllegalArgumentException("timeout can not be negative"); + } + connectTimeout = timeout; + } + + /** + * Returns setting for connect timeout. + *

+ * 0 return implies that the option is disabled + * (i.e., timeout of infinity). + * + * @return an int that indicates the connect timeout + * value in milliseconds + * @see #setConnectTimeout(int) + * @see #connect() + * @since 1.5 + */ + public int getConnectTimeout() { + return connectTimeout; + } + + /** + * Sets the read timeout to a specified timeout, in + * milliseconds. A non-zero value specifies the timeout when + * reading from Input stream when a connection is established to a + * resource. If the timeout expires before there is data available + * for read, a java.net.SocketTimeoutException is raised. A + * timeout of zero is interpreted as an infinite timeout. + * + *

Some non-standard implementation of this method ignores the + * specified timeout. To see the read timeout set, please call + * getReadTimeout(). + * + * @param timeout an int that specifies the timeout + * value to be used in milliseconds + * @throws IllegalArgumentException if the timeout parameter is negative + * + * @see #getReadTimeout() + * @see InputStream#read() + * @since 1.5 + */ + public void setReadTimeout(int timeout) { + if (timeout < 0) { + throw new IllegalArgumentException("timeout can not be negative"); + } + readTimeout = timeout; + } + + /** + * Returns setting for read timeout. 0 return implies that the + * option is disabled (i.e., timeout of infinity). + * + * @return an int that indicates the read timeout + * value in milliseconds + * + * @see #setReadTimeout(int) + * @see InputStream#read() + * @since 1.5 + */ + public int getReadTimeout() { + return readTimeout; + } + + /** + * Constructs a URL connection to the specified URL. A connection to + * the object referenced by the URL is not created. + * + * @param url the specified URL. + */ + protected URLConnection(URL url) { + this.url = url; + } + + /** + * Returns the value of this URLConnection's URL + * field. + * + * @return the value of this URLConnection's URL + * field. + * @see java.net.URLConnection#url + */ + public URL getURL() { + return url; + } + + /** + * Returns the value of the content-length header field. + *

+ * Note: {@link #getContentLengthLong() getContentLengthLong()} + * should be preferred over this method, since it returns a {@code long} + * instead and is therefore more portable.

+ * + * @return the content length of the resource that this connection's URL + * references, {@code -1} if the content length is not known, + * or if the content length is greater than Integer.MAX_VALUE. + */ + public int getContentLength() { + long l = getContentLengthLong(); + if (l > Integer.MAX_VALUE) + return -1; + return (int) l; + } + + /** + * Returns the value of the content-length header field as a + * long. + * + * @return the content length of the resource that this connection's URL + * references, or -1 if the content length is + * not known. + * @since 7.0 + */ + public long getContentLengthLong() { + return getHeaderFieldLong("content-length", -1); + } + + /** + * Returns the value of the content-type header field. + * + * @return the content type of the resource that the URL references, + * or null if not known. + * @see java.net.URLConnection#getHeaderField(java.lang.String) + */ + public String getContentType() { + return getHeaderField("content-type"); + } + + /** + * Returns the value of the content-encoding header field. + * + * @return the content encoding of the resource that the URL references, + * or null if not known. + * @see java.net.URLConnection#getHeaderField(java.lang.String) + */ + public String getContentEncoding() { + return getHeaderField("content-encoding"); + } + + /** + * Returns the value of the expires header field. + * + * @return the expiration date of the resource that this URL references, + * or 0 if not known. The value is the number of milliseconds since + * January 1, 1970 GMT. + * @see java.net.URLConnection#getHeaderField(java.lang.String) + */ + public long getExpiration() { + return getHeaderFieldDate("expires", 0); + } + + /** + * Returns the value of the date header field. + * + * @return the sending date of the resource that the URL references, + * or 0 if not known. The value returned is the + * number of milliseconds since January 1, 1970 GMT. + * @see java.net.URLConnection#getHeaderField(java.lang.String) + */ + public long getDate() { + return getHeaderFieldDate("date", 0); + } + + /** + * Returns the value of the last-modified header field. + * The result is the number of milliseconds since January 1, 1970 GMT. + * + * @return the date the resource referenced by this + * URLConnection was last modified, or 0 if not known. + * @see java.net.URLConnection#getHeaderField(java.lang.String) + */ + public long getLastModified() { + return getHeaderFieldDate("last-modified", 0); + } + + /** + * Returns the value of the named header field. + *

+ * If called on a connection that sets the same header multiple times + * with possibly different values, only the last value is returned. + * + * + * @param name the name of a header field. + * @return the value of the named header field, or null + * if there is no such field in the header. + */ + public String getHeaderField(String name) { + return null; + } + + /** + * Returns an unmodifiable Map of the header fields. + * The Map keys are Strings that represent the + * response-header field names. Each Map value is an + * unmodifiable List of Strings that represents + * the corresponding field values. + * + * @return a Map of header fields + * @since 1.4 + */ + public Map> getHeaderFields() { + return Collections.EMPTY_MAP; + } + + /** + * Returns the value of the named field parsed as a number. + *

+ * This form of getHeaderField exists because some + * connection types (e.g., http-ng) have pre-parsed + * headers. Classes for that connection type can override this method + * and short-circuit the parsing. + * + * @param name the name of the header field. + * @param Default the default value. + * @return the value of the named field, parsed as an integer. The + * Default value is returned if the field is + * missing or malformed. + */ + public int getHeaderFieldInt(String name, int Default) { + String value = getHeaderField(name); + try { + return Integer.parseInt(value); + } catch (Exception e) { } + return Default; + } + + /** + * Returns the value of the named field parsed as a number. + *

+ * This form of getHeaderField exists because some + * connection types (e.g., http-ng) have pre-parsed + * headers. Classes for that connection type can override this method + * and short-circuit the parsing. + * + * @param name the name of the header field. + * @param Default the default value. + * @return the value of the named field, parsed as a long. The + * Default value is returned if the field is + * missing or malformed. + * @since 7.0 + */ + public long getHeaderFieldLong(String name, long Default) { + String value = getHeaderField(name); + try { + return Long.parseLong(value); + } catch (Exception e) { } + return Default; + } + + /** + * Returns the value of the named field parsed as date. + * The result is the number of milliseconds since January 1, 1970 GMT + * represented by the named field. + *

+ * This form of getHeaderField exists because some + * connection types (e.g., http-ng) have pre-parsed + * headers. Classes for that connection type can override this method + * and short-circuit the parsing. + * + * @param name the name of the header field. + * @param Default a default value. + * @return the value of the field, parsed as a date. The value of the + * Default argument is returned if the field is + * missing or malformed. + */ + public long getHeaderFieldDate(String name, long Default) { + String value = getHeaderField(name); + try { + return Date.parse(value); + } catch (Exception e) { } + return Default; + } + + /** + * Returns the key for the nth header field. + * It returns null if there are fewer than n+1 fields. + * + * @param n an index, where n>=0 + * @return the key for the nth header field, + * or null if there are fewer than n+1 + * fields. + */ + public String getHeaderFieldKey(int n) { + return null; + } + + /** + * Returns the value for the nth header field. + * It returns null if there are fewer than + * n+1fields. + *

+ * This method can be used in conjunction with the + * {@link #getHeaderFieldKey(int) getHeaderFieldKey} method to iterate through all + * the headers in the message. + * + * @param n an index, where n>=0 + * @return the value of the nth header field + * or null if there are fewer than n+1 fields + * @see java.net.URLConnection#getHeaderFieldKey(int) + */ + public String getHeaderField(int n) { + return null; + } + + /** + * Retrieves the contents of this URL connection. + *

+ * This method first determines the content type of the object by + * calling the getContentType method. If this is + * the first time that the application has seen that specific content + * type, a content handler for that content type is created: + *

    + *
  1. If the application has set up a content handler factory instance + * using the setContentHandlerFactory method, the + * createContentHandler method of that instance is called + * with the content type as an argument; the result is a content + * handler for that content type. + *
  2. If no content handler factory has yet been set up, or if the + * factory's createContentHandler method returns + * null, then the application loads the class named: + *
    +     *         sun.net.www.content.<contentType>
    +     *     
    + * where <contentType> is formed by taking the + * content-type string, replacing all slash characters with a + * period ('.'), and all other non-alphanumeric characters + * with the underscore character '_'. The alphanumeric + * characters are specifically the 26 uppercase ASCII letters + * 'A' through 'Z', the 26 lowercase ASCII + * letters 'a' through 'z', and the 10 ASCII + * digits '0' through '9'. If the specified + * class does not exist, or is not a subclass of + * ContentHandler, then an + * UnknownServiceException is thrown. + *
+ * + * @return the object fetched. The instanceof operator + * should be used to determine the specific kind of object + * returned. + * @exception IOException if an I/O error occurs while + * getting the content. + * @exception UnknownServiceException if the protocol does not support + * the content type. + * @see java.net.ContentHandlerFactory#createContentHandler(java.lang.String) + * @see java.net.URLConnection#getContentType() + * @see java.net.URLConnection#setContentHandlerFactory(java.net.ContentHandlerFactory) + */ + public Object getContent() throws IOException { + // Must call getInputStream before GetHeaderField gets called + // so that FileNotFoundException has a chance to be thrown up + // from here without being caught. + getInputStream(); + return getContentHandler().getContent(this); + } + + /** + * Retrieves the contents of this URL connection. + * + * @param classes the Class array + * indicating the requested types + * @return the object fetched that is the first match of the type + * specified in the classes array. null if none of + * the requested types are supported. + * The instanceof operator should be used to + * determine the specific kind of object returned. + * @exception IOException if an I/O error occurs while + * getting the content. + * @exception UnknownServiceException if the protocol does not support + * the content type. + * @see java.net.URLConnection#getContent() + * @see java.net.ContentHandlerFactory#createContentHandler(java.lang.String) + * @see java.net.URLConnection#getContent(java.lang.Class[]) + * @see java.net.URLConnection#setContentHandlerFactory(java.net.ContentHandlerFactory) + * @since 1.3 + */ + public Object getContent(Class[] classes) throws IOException { + // Must call getInputStream before GetHeaderField gets called + // so that FileNotFoundException has a chance to be thrown up + // from here without being caught. + getInputStream(); + return getContentHandler().getContent(this, classes); + } + + /** + * Returns a permission object representing the permission + * necessary to make the connection represented by this + * object. This method returns null if no permission is + * required to make the connection. By default, this method + * returns java.security.AllPermission. Subclasses + * should override this method and return the permission + * that best represents the permission required to make a + * a connection to the URL. For example, a URLConnection + * representing a file: URL would return a + * java.io.FilePermission object. + * + *

The permission returned may dependent upon the state of the + * connection. For example, the permission before connecting may be + * different from that after connecting. For example, an HTTP + * sever, say foo.com, may redirect the connection to a different + * host, say bar.com. Before connecting the permission returned by + * the connection will represent the permission needed to connect + * to foo.com, while the permission returned after connecting will + * be to bar.com. + * + *

Permissions are generally used for two purposes: to protect + * caches of objects obtained through URLConnections, and to check + * the right of a recipient to learn about a particular URL. In + * the first case, the permission should be obtained + * after the object has been obtained. For example, in an + * HTTP connection, this will represent the permission to connect + * to the host from which the data was ultimately fetched. In the + * second case, the permission should be obtained and tested + * before connecting. + * + * @return the permission object representing the permission + * necessary to make the connection represented by this + * URLConnection. + * + * @exception IOException if the computation of the permission + * requires network or file I/O and an exception occurs while + * computing it. + */ +// public Permission getPermission() throws IOException { +// return SecurityConstants.ALL_PERMISSION; +// } + + /** + * Returns an input stream that reads from this open connection. + * + * A SocketTimeoutException can be thrown when reading from the + * returned input stream if the read timeout expires before data + * is available for read. + * + * @return an input stream that reads from this open connection. + * @exception IOException if an I/O error occurs while + * creating the input stream. + * @exception UnknownServiceException if the protocol does not support + * input. + * @see #setReadTimeout(int) + * @see #getReadTimeout() + */ + public InputStream getInputStream() throws IOException { + throw new UnknownServiceException("protocol doesn't support input"); + } + + /** + * Returns an output stream that writes to this connection. + * + * @return an output stream that writes to this connection. + * @exception IOException if an I/O error occurs while + * creating the output stream. + * @exception UnknownServiceException if the protocol does not support + * output. + */ + public OutputStream getOutputStream() throws IOException { + throw new UnknownServiceException("protocol doesn't support output"); + } + + /** + * Returns a String representation of this URL connection. + * + * @return a string representation of this URLConnection. + */ + public String toString() { + return this.getClass().getName() + ":" + url; + } + + /** + * Sets the value of the doInput field for this + * URLConnection to the specified value. + *

+ * A URL connection can be used for input and/or output. Set the DoInput + * flag to true if you intend to use the URL connection for input, + * false if not. The default is true. + * + * @param doinput the new value. + * @throws IllegalStateException if already connected + * @see java.net.URLConnection#doInput + * @see #getDoInput() + */ + public void setDoInput(boolean doinput) { + if (connected) + throw new IllegalStateException("Already connected"); + doInput = doinput; + } + + /** + * Returns the value of this URLConnection's + * doInput flag. + * + * @return the value of this URLConnection's + * doInput flag. + * @see #setDoInput(boolean) + */ + public boolean getDoInput() { + return doInput; + } + + /** + * Sets the value of the doOutput field for this + * URLConnection to the specified value. + *

+ * A URL connection can be used for input and/or output. Set the DoOutput + * flag to true if you intend to use the URL connection for output, + * false if not. The default is false. + * + * @param dooutput the new value. + * @throws IllegalStateException if already connected + * @see #getDoOutput() + */ + public void setDoOutput(boolean dooutput) { + if (connected) + throw new IllegalStateException("Already connected"); + doOutput = dooutput; + } + + /** + * Returns the value of this URLConnection's + * doOutput flag. + * + * @return the value of this URLConnection's + * doOutput flag. + * @see #setDoOutput(boolean) + */ + public boolean getDoOutput() { + return doOutput; + } + + /** + * Set the value of the allowUserInteraction field of + * this URLConnection. + * + * @param allowuserinteraction the new value. + * @throws IllegalStateException if already connected + * @see #getAllowUserInteraction() + */ + public void setAllowUserInteraction(boolean allowuserinteraction) { + if (connected) + throw new IllegalStateException("Already connected"); + allowUserInteraction = allowuserinteraction; + } + + /** + * Returns the value of the allowUserInteraction field for + * this object. + * + * @return the value of the allowUserInteraction field for + * this object. + * @see #setAllowUserInteraction(boolean) + */ + public boolean getAllowUserInteraction() { + return allowUserInteraction; + } + + /** + * Sets the default value of the + * allowUserInteraction field for all future + * URLConnection objects to the specified value. + * + * @param defaultallowuserinteraction the new value. + * @see #getDefaultAllowUserInteraction() + */ + public static void setDefaultAllowUserInteraction(boolean defaultallowuserinteraction) { + defaultAllowUserInteraction = defaultallowuserinteraction; + } + + /** + * Returns the default value of the allowUserInteraction + * field. + *

+ * Ths default is "sticky", being a part of the static state of all + * URLConnections. This flag applies to the next, and all following + * URLConnections that are created. + * + * @return the default value of the allowUserInteraction + * field. + * @see #setDefaultAllowUserInteraction(boolean) + */ + public static boolean getDefaultAllowUserInteraction() { + return defaultAllowUserInteraction; + } + + /** + * Sets the value of the useCaches field of this + * URLConnection to the specified value. + *

+ * Some protocols do caching of documents. Occasionally, it is important + * to be able to "tunnel through" and ignore the caches (e.g., the + * "reload" button in a browser). If the UseCaches flag on a connection + * is true, the connection is allowed to use whatever caches it can. + * If false, caches are to be ignored. + * The default value comes from DefaultUseCaches, which defaults to + * true. + * + * @param usecaches a boolean indicating whether + * or not to allow caching + * @throws IllegalStateException if already connected + * @see #getUseCaches() + */ + public void setUseCaches(boolean usecaches) { + if (connected) + throw new IllegalStateException("Already connected"); + useCaches = usecaches; + } + + /** + * Returns the value of this URLConnection's + * useCaches field. + * + * @return the value of this URLConnection's + * useCaches field. + * @see #setUseCaches(boolean) + */ + public boolean getUseCaches() { + return useCaches; + } + + /** + * Sets the value of the ifModifiedSince field of + * this URLConnection to the specified value. + * + * @param ifmodifiedsince the new value. + * @throws IllegalStateException if already connected + * @see #getIfModifiedSince() + */ + public void setIfModifiedSince(long ifmodifiedsince) { + if (connected) + throw new IllegalStateException("Already connected"); + ifModifiedSince = ifmodifiedsince; + } + + /** + * Returns the value of this object's ifModifiedSince field. + * + * @return the value of this object's ifModifiedSince field. + * @see #setIfModifiedSince(long) + */ + public long getIfModifiedSince() { + return ifModifiedSince; + } + + /** + * Returns the default value of a URLConnection's + * useCaches flag. + *

+ * Ths default is "sticky", being a part of the static state of all + * URLConnections. This flag applies to the next, and all following + * URLConnections that are created. + * + * @return the default value of a URLConnection's + * useCaches flag. + * @see #setDefaultUseCaches(boolean) + */ + public boolean getDefaultUseCaches() { + return defaultUseCaches; + } + + /** + * Sets the default value of the useCaches field to the + * specified value. + * + * @param defaultusecaches the new value. + * @see #getDefaultUseCaches() + */ + public void setDefaultUseCaches(boolean defaultusecaches) { + defaultUseCaches = defaultusecaches; + } + + /** + * Sets the general request property. If a property with the key already + * exists, overwrite its value with the new value. + * + *

NOTE: HTTP requires all request properties which can + * legally have multiple instances with the same key + * to use a comma-seperated list syntax which enables multiple + * properties to be appended into a single property. + * + * @param key the keyword by which the request is known + * (e.g., "Accept"). + * @param value the value associated with it. + * @throws IllegalStateException if already connected + * @throws NullPointerException if key is null + * @see #getRequestProperty(java.lang.String) + */ + public void setRequestProperty(String key, String value) { + if (connected) + throw new IllegalStateException("Already connected"); + if (key == null) + throw new NullPointerException ("key is null"); + + if (requests == null) + requests = new MessageHeader(); + + requests.set(key, value); + } + + /** + * Adds a general request property specified by a + * key-value pair. This method will not overwrite + * existing values associated with the same key. + * + * @param key the keyword by which the request is known + * (e.g., "Accept"). + * @param value the value associated with it. + * @throws IllegalStateException if already connected + * @throws NullPointerException if key is null + * @see #getRequestProperties() + * @since 1.4 + */ + public void addRequestProperty(String key, String value) { + if (connected) + throw new IllegalStateException("Already connected"); + if (key == null) + throw new NullPointerException ("key is null"); + + if (requests == null) + requests = new MessageHeader(); + + requests.add(key, value); + } + + + /** + * Returns the value of the named general request property for this + * connection. + * + * @param key the keyword by which the request is known (e.g., "Accept"). + * @return the value of the named general request property for this + * connection. If key is null, then null is returned. + * @throws IllegalStateException if already connected + * @see #setRequestProperty(java.lang.String, java.lang.String) + */ + public String getRequestProperty(String key) { + if (connected) + throw new IllegalStateException("Already connected"); + + if (requests == null) + return null; + + return requests.findValue(key); + } + + /** + * Returns an unmodifiable Map of general request + * properties for this connection. The Map keys + * are Strings that represent the request-header + * field names. Each Map value is a unmodifiable List + * of Strings that represents the corresponding + * field values. + * + * @return a Map of the general request properties for this connection. + * @throws IllegalStateException if already connected + * @since 1.4 + */ + public Map> getRequestProperties() { + if (connected) + throw new IllegalStateException("Already connected"); + + if (requests == null) + return Collections.EMPTY_MAP; + + return requests.getHeaders(null); + } + + /** + * Sets the default value of a general request property. When a + * URLConnection is created, it is initialized with + * these properties. + * + * @param key the keyword by which the request is known + * (e.g., "Accept"). + * @param value the value associated with the key. + * + * @see java.net.URLConnection#setRequestProperty(java.lang.String,java.lang.String) + * + * @deprecated The instance specific setRequestProperty method + * should be used after an appropriate instance of URLConnection + * is obtained. Invoking this method will have no effect. + * + * @see #getDefaultRequestProperty(java.lang.String) + */ + @Deprecated + public static void setDefaultRequestProperty(String key, String value) { + } + + /** + * Returns the value of the default request property. Default request + * properties are set for every connection. + * + * @param key the keyword by which the request is known (e.g., "Accept"). + * @return the value of the default request property + * for the specified key. + * + * @see java.net.URLConnection#getRequestProperty(java.lang.String) + * + * @deprecated The instance specific getRequestProperty method + * should be used after an appropriate instance of URLConnection + * is obtained. + * + * @see #setDefaultRequestProperty(java.lang.String, java.lang.String) + */ + @Deprecated + public static String getDefaultRequestProperty(String key) { + return null; + } + + /** + * The ContentHandler factory. + */ + static ContentHandlerFactory factory; + + /** + * Sets the ContentHandlerFactory of an + * application. It can be called at most once by an application. + *

+ * The ContentHandlerFactory instance is used to + * construct a content handler from a content type + *

+ * If there is a security manager, this method first calls + * the security manager's checkSetFactory method + * to ensure the operation is allowed. + * This could result in a SecurityException. + * + * @param fac the desired factory. + * @exception Error if the factory has already been defined. + * @exception SecurityException if a security manager exists and its + * checkSetFactory method doesn't allow the operation. + * @see java.net.ContentHandlerFactory + * @see java.net.URLConnection#getContent() + * @see SecurityManager#checkSetFactory + */ + public static synchronized void setContentHandlerFactory(ContentHandlerFactory fac) { + throw new SecurityException(); + } + + private static Hashtable handlers = new Hashtable(); + + /** + * Gets the Content Handler appropriate for this connection. + * @param connection the connection to use. + */ + synchronized ContentHandler getContentHandler() + throws UnknownServiceException + { + String contentType = stripOffParameters(getContentType()); + ContentHandler handler = null; + if (contentType == null) + throw new UnknownServiceException("no content-type"); + try { + handler = (ContentHandler) handlers.get(contentType); + if (handler != null) + return handler; + } catch(Exception e) { + } + + if (factory != null) + handler = factory.createContentHandler(contentType); + if (handler == null) { + try { + handler = lookupContentHandlerClassFor(contentType); + } catch(Exception e) { + e.printStackTrace(); + handler = UnknownContentHandler.INSTANCE; + } + handlers.put(contentType, handler); + } + return handler; + } + + /* + * Media types are in the format: type/subtype*(; parameter). + * For looking up the content handler, we should ignore those + * parameters. + */ + private String stripOffParameters(String contentType) + { + if (contentType == null) + return null; + int index = contentType.indexOf(';'); + + if (index > 0) + return contentType.substring(0, index); + else + return contentType; + } + + private static final String contentClassPrefix = "sun.net.www.content"; + private static final String contentPathProp = "java.content.handler.pkgs"; + + /** + * Looks for a content handler in a user-defineable set of places. + * By default it looks in sun.net.www.content, but users can define a + * vertical-bar delimited set of class prefixes to search through in + * addition by defining the java.content.handler.pkgs property. + * The class name must be of the form: + *

+     *     {package-prefix}.{major}.{minor}
+     * e.g.
+     *     YoyoDyne.experimental.text.plain
+     * 
+ */ + private ContentHandler lookupContentHandlerClassFor(String contentType) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + String contentHandlerClassName = typeToPackageName(contentType); + + String contentHandlerPkgPrefixes =getContentHandlerPkgPrefixes(); + + StringTokenizer packagePrefixIter = + new StringTokenizer(contentHandlerPkgPrefixes, "|"); + + while (packagePrefixIter.hasMoreTokens()) { + String packagePrefix = packagePrefixIter.nextToken().trim(); + + try { + String clsName = packagePrefix + "." + contentHandlerClassName; + Class cls = null; + try { + cls = Class.forName(clsName); + } catch (ClassNotFoundException e) { + ClassLoader cl = ClassLoader.getSystemClassLoader(); + if (cl != null) { + cls = cl.loadClass(clsName); + } + } + if (cls != null) { + ContentHandler handler = + (ContentHandler)cls.newInstance(); + return handler; + } + } catch(Exception e) { + } + } + + return UnknownContentHandler.INSTANCE; + } + + /** + * Utility function to map a MIME content type into an equivalent + * pair of class name components. For example: "text/html" would + * be returned as "text.html" + */ + private String typeToPackageName(String contentType) { + // make sure we canonicalize the class name: all lower case + contentType = contentType.toLowerCase(); + int len = contentType.length(); + char nm[] = new char[len]; + contentType.getChars(0, len, nm, 0); + for (int i = 0; i < len; i++) { + char c = nm[i]; + if (c == '/') { + nm[i] = '.'; + } else if (!('A' <= c && c <= 'Z' || + 'a' <= c && c <= 'z' || + '0' <= c && c <= '9')) { + nm[i] = '_'; + } + } + return new String(nm); + } + + + /** + * Returns a vertical bar separated list of package prefixes for potential + * content handlers. Tries to get the java.content.handler.pkgs property + * to use as a set of package prefixes to search. Whether or not + * that property has been defined, the sun.net.www.content is always + * the last one on the returned package list. + */ + private String getContentHandlerPkgPrefixes() { + String packagePrefixList = ""; + + if (packagePrefixList != "") { + packagePrefixList += "|"; + } + + return packagePrefixList + contentClassPrefix; + } + + /** + * Tries to determine the content type of an object, based + * on the specified "file" component of a URL. + * This is a convenience method that can be used by + * subclasses that override the getContentType method. + * + * @param fname a filename. + * @return a guess as to what the content type of the object is, + * based upon its file name. + * @see java.net.URLConnection#getContentType() + */ + public static String guessContentTypeFromName(String fname) { + return getFileNameMap().getContentTypeFor(fname); + } + + /** + * Tries to determine the type of an input stream based on the + * characters at the beginning of the input stream. This method can + * be used by subclasses that override the + * getContentType method. + *

+ * Ideally, this routine would not be needed. But many + * http servers return the incorrect content type; in + * addition, there are many nonstandard extensions. Direct inspection + * of the bytes to determine the content type is often more accurate + * than believing the content type claimed by the http server. + * + * @param is an input stream that supports marks. + * @return a guess at the content type, or null if none + * can be determined. + * @exception IOException if an I/O error occurs while reading the + * input stream. + * @see java.io.InputStream#mark(int) + * @see java.io.InputStream#markSupported() + * @see java.net.URLConnection#getContentType() + */ + static public String guessContentTypeFromStream(InputStream is) + throws IOException { + // If we can't read ahead safely, just give up on guessing + if (!is.markSupported()) + return null; + + is.mark(16); + int c1 = is.read(); + int c2 = is.read(); + int c3 = is.read(); + int c4 = is.read(); + int c5 = is.read(); + int c6 = is.read(); + int c7 = is.read(); + int c8 = is.read(); + int c9 = is.read(); + int c10 = is.read(); + int c11 = is.read(); + int c12 = is.read(); + int c13 = is.read(); + int c14 = is.read(); + int c15 = is.read(); + int c16 = is.read(); + is.reset(); + + if (c1 == 0xCA && c2 == 0xFE && c3 == 0xBA && c4 == 0xBE) { + return "application/java-vm"; + } + + if (c1 == 0xAC && c2 == 0xED) { + // next two bytes are version number, currently 0x00 0x05 + return "application/x-java-serialized-object"; + } + + if (c1 == '<') { + if (c2 == '!' + || ((c2 == 'h' && (c3 == 't' && c4 == 'm' && c5 == 'l' || + c3 == 'e' && c4 == 'a' && c5 == 'd') || + (c2 == 'b' && c3 == 'o' && c4 == 'd' && c5 == 'y'))) || + ((c2 == 'H' && (c3 == 'T' && c4 == 'M' && c5 == 'L' || + c3 == 'E' && c4 == 'A' && c5 == 'D') || + (c2 == 'B' && c3 == 'O' && c4 == 'D' && c5 == 'Y')))) { + return "text/html"; + } + + if (c2 == '?' && c3 == 'x' && c4 == 'm' && c5 == 'l' && c6 == ' ') { + return "application/xml"; + } + } + + // big and little (identical) endian UTF-8 encodings, with BOM + if (c1 == 0xef && c2 == 0xbb && c3 == 0xbf) { + if (c4 == '<' && c5 == '?' && c6 == 'x') { + return "application/xml"; + } + } + + // big and little endian UTF-16 encodings, with byte order mark + if (c1 == 0xfe && c2 == 0xff) { + if (c3 == 0 && c4 == '<' && c5 == 0 && c6 == '?' && + c7 == 0 && c8 == 'x') { + return "application/xml"; + } + } + + if (c1 == 0xff && c2 == 0xfe) { + if (c3 == '<' && c4 == 0 && c5 == '?' && c6 == 0 && + c7 == 'x' && c8 == 0) { + return "application/xml"; + } + } + + // big and little endian UTF-32 encodings, with BOM + if (c1 == 0x00 && c2 == 0x00 && c3 == 0xfe && c4 == 0xff) { + if (c5 == 0 && c6 == 0 && c7 == 0 && c8 == '<' && + c9 == 0 && c10 == 0 && c11 == 0 && c12 == '?' && + c13 == 0 && c14 == 0 && c15 == 0 && c16 == 'x') { + return "application/xml"; + } + } + + if (c1 == 0xff && c2 == 0xfe && c3 == 0x00 && c4 == 0x00) { + if (c5 == '<' && c6 == 0 && c7 == 0 && c8 == 0 && + c9 == '?' && c10 == 0 && c11 == 0 && c12 == 0 && + c13 == 'x' && c14 == 0 && c15 == 0 && c16 == 0) { + return "application/xml"; + } + } + + if (c1 == 'G' && c2 == 'I' && c3 == 'F' && c4 == '8') { + return "image/gif"; + } + + if (c1 == '#' && c2 == 'd' && c3 == 'e' && c4 == 'f') { + return "image/x-bitmap"; + } + + if (c1 == '!' && c2 == ' ' && c3 == 'X' && c4 == 'P' && + c5 == 'M' && c6 == '2') { + return "image/x-pixmap"; + } + + if (c1 == 137 && c2 == 80 && c3 == 78 && + c4 == 71 && c5 == 13 && c6 == 10 && + c7 == 26 && c8 == 10) { + return "image/png"; + } + + if (c1 == 0xFF && c2 == 0xD8 && c3 == 0xFF) { + if (c4 == 0xE0) { + return "image/jpeg"; + } + + /** + * File format used by digital cameras to store images. + * Exif Format can be read by any application supporting + * JPEG. Exif Spec can be found at: + * http://www.pima.net/standards/it10/PIMA15740/Exif_2-1.PDF + */ + if ((c4 == 0xE1) && + (c7 == 'E' && c8 == 'x' && c9 == 'i' && c10 =='f' && + c11 == 0)) { + return "image/jpeg"; + } + + if (c4 == 0xEE) { + return "image/jpg"; + } + } + + if (c1 == 0xD0 && c2 == 0xCF && c3 == 0x11 && c4 == 0xE0 && + c5 == 0xA1 && c6 == 0xB1 && c7 == 0x1A && c8 == 0xE1) { + + /* Above is signature of Microsoft Structured Storage. + * Below this, could have tests for various SS entities. + * For now, just test for FlashPix. + */ + if (checkfpx(is)) { + return "image/vnd.fpx"; + } + } + + if (c1 == 0x2E && c2 == 0x73 && c3 == 0x6E && c4 == 0x64) { + return "audio/basic"; // .au format, big endian + } + + if (c1 == 0x64 && c2 == 0x6E && c3 == 0x73 && c4 == 0x2E) { + return "audio/basic"; // .au format, little endian + } + + if (c1 == 'R' && c2 == 'I' && c3 == 'F' && c4 == 'F') { + /* I don't know if this is official but evidence + * suggests that .wav files start with "RIFF" - brown + */ + return "audio/x-wav"; + } + return null; + } + + /** + * Check for FlashPix image data in InputStream is. Return true if + * the stream has FlashPix data, false otherwise. Before calling this + * method, the stream should have already been checked to be sure it + * contains Microsoft Structured Storage data. + */ + static private boolean checkfpx(InputStream is) throws IOException { + + /* Test for FlashPix image data in Microsoft Structured Storage format. + * In general, should do this with calls to an SS implementation. + * Lacking that, need to dig via offsets to get to the FlashPix + * ClassID. Details: + * + * Offset to Fpx ClsID from beginning of stream should be: + * + * FpxClsidOffset = rootEntryOffset + clsidOffset + * + * where: clsidOffset = 0x50. + * rootEntryOffset = headerSize + sectorSize*sectDirStart + * + 128*rootEntryDirectory + * + * where: headerSize = 0x200 (always) + * sectorSize = 2 raised to power of uSectorShift, + * which is found in the header at + * offset 0x1E. + * sectDirStart = found in the header at offset 0x30. + * rootEntryDirectory = in general, should search for + * directory labelled as root. + * We will assume value of 0 (i.e., + * rootEntry is in first directory) + */ + + // Mark the stream so we can reset it. 0x100 is enough for the first + // few reads, but the mark will have to be reset and set again once + // the offset to the root directory entry is computed. That offset + // can be very large and isn't know until the stream has been read from + is.mark(0x100); + + // Get the byte ordering located at 0x1E. 0xFE is Intel, + // 0xFF is other + long toSkip = (long)0x1C; + long posn; + + if ((posn = skipForward(is, toSkip)) < toSkip) { + is.reset(); + return false; + } + + int c[] = new int[16]; + if (readBytes(c, 2, is) < 0) { + is.reset(); + return false; + } + + int byteOrder = c[0]; + + posn+=2; + int uSectorShift; + if (readBytes(c, 2, is) < 0) { + is.reset(); + return false; + } + + if(byteOrder == 0xFE) { + uSectorShift = c[0]; + uSectorShift += c[1] << 8; + } + else { + uSectorShift = c[0] << 8; + uSectorShift += c[1]; + } + + posn += 2; + toSkip = (long)0x30 - posn; + long skipped = 0; + if ((skipped = skipForward(is, toSkip)) < toSkip) { + is.reset(); + return false; + } + posn += skipped; + + if (readBytes(c, 4, is) < 0) { + is.reset(); + return false; + } + + int sectDirStart; + if(byteOrder == 0xFE) { + sectDirStart = c[0]; + sectDirStart += c[1] << 8; + sectDirStart += c[2] << 16; + sectDirStart += c[3] << 24; + } else { + sectDirStart = c[0] << 24; + sectDirStart += c[1] << 16; + sectDirStart += c[2] << 8; + sectDirStart += c[3]; + } + posn += 4; + is.reset(); // Reset back to the beginning + + toSkip = 0x200L + (long)(1<= 0;) + if (keys[i] == null) + return values[i]; + } else + for (int i = nkeys; --i >= 0;) { + if (k.equalsIgnoreCase(keys[i])) + return values[i]; + } + return null; + } + + // return the location of the key + public synchronized int getKey(String k) { + for (int i = nkeys; --i >= 0;) + if ((keys[i] == k) || + (k != null && k.equalsIgnoreCase(keys[i]))) + return i; + return -1; + } + + public synchronized String getKey(int n) { + if (n < 0 || n >= nkeys) return null; + return keys[n]; + } + + public synchronized String getValue(int n) { + if (n < 0 || n >= nkeys) return null; + return values[n]; + } + + /** Deprecated: Use multiValueIterator() instead. + * + * Find the next value that corresponds to this key. + * It finds the first value that follows v. To iterate + * over all the values of a key use: + *

+     *          for(String v=h.findValue(k); v!=null; v=h.findNextValue(k, v)) {
+     *              ...
+     *          }
+     *  
+ */ + public synchronized String findNextValue(String k, String v) { + boolean foundV = false; + if (k == null) { + for (int i = nkeys; --i >= 0;) + if (keys[i] == null) + if (foundV) + return values[i]; + else if (values[i] == v) + foundV = true; + } else + for (int i = nkeys; --i >= 0;) + if (k.equalsIgnoreCase(keys[i])) + if (foundV) + return values[i]; + else if (values[i] == v) + foundV = true; + return null; + } + + class HeaderIterator implements Iterator { + int index = 0; + int next = -1; + String key; + boolean haveNext = false; + Object lock; + + public HeaderIterator (String k, Object lock) { + key = k; + this.lock = lock; + } + public boolean hasNext () { + synchronized (lock) { + if (haveNext) { + return true; + } + while (index < nkeys) { + if (key.equalsIgnoreCase (keys[index])) { + haveNext = true; + next = index++; + return true; + } + index ++; + } + return false; + } + } + public String next() { + synchronized (lock) { + if (haveNext) { + haveNext = false; + return values [next]; + } + if (hasNext()) { + return next(); + } else { + throw new NoSuchElementException ("No more elements"); + } + } + } + public void remove () { + throw new UnsupportedOperationException ("remove not allowed"); + } + } + + /** + * return an Iterator that returns all values of a particular + * key in sequence + */ + public Iterator multiValueIterator (String k) { + return new HeaderIterator (k, this); + } + + public synchronized Map> getHeaders() { + return getHeaders(null); + } + + public synchronized Map> getHeaders(String[] excludeList) { + return filterAndAddHeaders(excludeList, null); + } + + public synchronized Map> filterAndAddHeaders(String[] excludeList, Map> include) { + boolean skipIt = false; + Map> m = new HashMap>(); + for (int i = nkeys; --i >= 0;) { + if (excludeList != null) { + // check if the key is in the excludeList. + // if so, don't include it in the Map. + for (int j = 0; j < excludeList.length; j++) { + if ((excludeList[j] != null) && + (excludeList[j].equalsIgnoreCase(keys[i]))) { + skipIt = true; + break; + } + } + } + if (!skipIt) { + List l = m.get(keys[i]); + if (l == null) { + l = new ArrayList(); + m.put(keys[i], l); + } + l.add(values[i]); + } else { + // reset the flag + skipIt = false; + } + } + + if (include != null) { + Iterator entries = include.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry entry = (Map.Entry)entries.next(); + List l = (List)m.get(entry.getKey()); + if (l == null) { + l = new ArrayList(); + m.put((String)entry.getKey(), l); + } + l.add(entry.getValue()); + } + } + + for (String key : m.keySet()) { + m.put(key, Collections.unmodifiableList(m.get(key))); + } + + return Collections.unmodifiableMap(m); + } + + /** Prints the key-value pairs represented by this + header. Also prints the RFC required blank line + at the end. Omits pairs with a null key. */ + public synchronized void print(PrintStream p) { + for (int i = 0; i < nkeys; i++) + if (keys[i] != null) { + p.print(keys[i] + + (values[i] != null ? ": "+values[i]: "") + "\r\n"); + } + p.print("\r\n"); + p.flush(); + } + + /** Adds a key value pair to the end of the + header. Duplicates are allowed */ + public synchronized void add(String k, String v) { + grow(); + keys[nkeys] = k; + values[nkeys] = v; + nkeys++; + } + + /** Prepends a key value pair to the beginning of the + header. Duplicates are allowed */ + public synchronized void prepend(String k, String v) { + grow(); + for (int i = nkeys; i > 0; i--) { + keys[i] = keys[i-1]; + values[i] = values[i-1]; + } + keys[0] = k; + values[0] = v; + nkeys++; + } + + /** Overwrite the previous key/val pair at location 'i' + * with the new k/v. If the index didn't exist before + * the key/val is simply tacked onto the end. + */ + + public synchronized void set(int i, String k, String v) { + grow(); + if (i < 0) { + return; + } else if (i >= nkeys) { + add(k, v); + } else { + keys[i] = k; + values[i] = v; + } + } + + + /** grow the key/value arrays as needed */ + + private void grow() { + if (keys == null || nkeys >= keys.length) { + String[] nk = new String[nkeys + 4]; + String[] nv = new String[nkeys + 4]; + if (keys != null) + System.arraycopy(keys, 0, nk, 0, nkeys); + if (values != null) + System.arraycopy(values, 0, nv, 0, nkeys); + keys = nk; + values = nv; + } + } + + /** + * Remove the key from the header. If there are multiple values under + * the same key, they are all removed. + * Nothing is done if the key doesn't exist. + * After a remove, the other pairs' order are not changed. + * @param k the key to remove + */ + public synchronized void remove(String k) { + if(k == null) { + for (int i = 0; i < nkeys; i++) { + while (keys[i] == null && i < nkeys) { + for(int j=i; j= 0;) + if (k.equalsIgnoreCase(keys[i])) { + values[i] = v; + return; + } + add(k, v); + } + + /** Set's the value of a key only if there is no + * key with that value already. + */ + + public synchronized void setIfNotSet(String k, String v) { + if (findValue(k) == null) { + add(k, v); + } + } + + /** Convert a message-id string to canonical form (strips off + leading and trailing <>s) */ + public static String canonicalID(String id) { + if (id == null) + return ""; + int st = 0; + int len = id.length(); + boolean substr = false; + int c; + while (st < len && ((c = id.charAt(st)) == '<' || + c <= ' ')) { + st++; + substr = true; + } + while (st < len && ((c = id.charAt(len - 1)) == '>' || + c <= ' ')) { + len--; + substr = true; + } + return substr ? id.substring(st, len) : id; + } + + /** Parse a MIME header from an input stream. */ + public void parseHeader(InputStream is) throws java.io.IOException { + synchronized (this) { + nkeys = 0; + } + mergeHeader(is); + } + + /** Parse and merge a MIME header from an input stream. */ + public void mergeHeader(InputStream is) throws java.io.IOException { + if (is == null) + return; + char s[] = new char[10]; + int firstc = is.read(); + while (firstc != '\n' && firstc != '\r' && firstc >= 0) { + int len = 0; + int keyend = -1; + int c; + boolean inKey = firstc > ' '; + s[len++] = (char) firstc; + parseloop:{ + while ((c = is.read()) >= 0) { + switch (c) { + case ':': + if (inKey && len > 0) + keyend = len; + inKey = false; + break; + case '\t': + c = ' '; + case ' ': + inKey = false; + break; + case '\r': + case '\n': + firstc = is.read(); + if (c == '\r' && firstc == '\n') { + firstc = is.read(); + if (firstc == '\r') + firstc = is.read(); + } + if (firstc == '\n' || firstc == '\r' || firstc > ' ') + break parseloop; + /* continuation */ + c = ' '; + break; + } + if (len >= s.length) { + char ns[] = new char[s.length * 2]; + System.arraycopy(s, 0, ns, 0, len); + s = ns; + } + s[len++] = (char) c; + } + firstc = -1; + } + while (len > 0 && s[len - 1] <= ' ') + len--; + String k; + if (keyend <= 0) { + k = null; + keyend = 0; + } else { + k = String.copyValueOf(s, 0, keyend); + if (keyend < len && s[keyend] == ':') + keyend++; + while (keyend < len && s[keyend] <= ' ') + keyend++; + } + String v; + if (keyend >= len) + v = new String(); + else + v = String.copyValueOf(s, keyend, len - keyend); + add(k, v); + } + } + + public synchronized String toString() { + String result = super.toString() + nkeys + " pairs: "; + for (int i = 0; i < keys.length && i < nkeys; i++) { + result += "{"+keys[i]+": "+values[i]+"}"; + } + return result; + } +} + +interface ContentHandlerFactory { + + public ContentHandler createContentHandler(String contentType); +} + +abstract class ContentHandler { + /** + * Given a URL connect stream positioned at the beginning of the + * representation of an object, this method reads that stream and + * creates an object from it. + * + * @param urlc a URL connection. + * @return the object read by the ContentHandler. + * @exception IOException if an I/O error occurs while reading the object. + */ + abstract public Object getContent(URLConnection urlc) throws IOException; + + /** + * Given a URL connect stream positioned at the beginning of the + * representation of an object, this method reads that stream and + * creates an object that matches one of the types specified. + * + * The default implementation of this method should call getContent() + * and screen the return type for a match of the suggested types. + * + * @param urlc a URL connection. + * @param classes an array of types requested + * @return the object read by the ContentHandler that is + * the first match of the suggested types. + * null if none of the requested are supported. + * @exception IOException if an I/O error occurs while reading the object. + * @since 1.3 + */ + public Object getContent(URLConnection urlc, Class[] classes) throws IOException { + Object obj = getContent(urlc); + + for (int i = 0; i < classes.length; i++) { + if (classes[i].isInstance(obj)) { + return obj; + } + } + return null; + } + +} +class UnknownServiceException extends IOException { + private static final long serialVersionUID = -4169033248853639508L; + + /** + * Constructs a new UnknownServiceException with no + * detail message. + */ + public UnknownServiceException() { + } + + /** + * Constructs a new UnknownServiceException with the + * specified detail message. + * + * @param msg the detail message. + */ + public UnknownServiceException(String msg) { + super(msg); + } +} +/** + * A simple interface which provides a mechanism to map + * between a file name and a MIME type string. + * + * @author Steven B. Byrne + * @since JDK1.1 + */ +interface FileNameMap { + + /** + * Gets the MIME type for the specified file name. + * @param fileName the specified file name + * @return a String indicating the MIME + * type for the specified file name. + */ + public String getContentTypeFor(String fileName); +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/nio/charset/Charset.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/nio/charset/Charset.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,765 @@ +/* + * 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.nio.charset; + +//import java.nio.ByteBuffer; +//import java.nio.CharBuffer; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + + +/** + * A named mapping between sequences of sixteen-bit Unicode code units and sequences of + * bytes. This class defines methods for creating decoders and encoders and + * for retrieving the various names associated with a charset. Instances of + * this class are immutable. + * + *

This class also defines static methods for testing whether a particular + * charset is supported, for locating charset instances by name, and for + * constructing a map that contains every charset for which support is + * available in the current Java virtual machine. Support for new charsets can + * be added via the service-provider interface defined in the {@link + * java.nio.charset.spi.CharsetProvider} class. + * + *

All of the methods defined in this class are safe for use by multiple + * concurrent threads. + * + * + * + *

Charset names

+ * + *

Charsets are named by strings composed of the following characters: + * + *

    + * + *
  • The uppercase letters 'A' through 'Z' + * ('\u0041' through '\u005a'), + * + *
  • The lowercase letters 'a' through 'z' + * ('\u0061' through '\u007a'), + * + *
  • The digits '0' through '9' + * ('\u0030' through '\u0039'), + * + *
  • The dash character '-' + * ('\u002d'HYPHEN-MINUS), + * + *
  • The plus character '+' + * ('\u002b'PLUS SIGN), + * + *
  • The period character '.' + * ('\u002e'FULL STOP), + * + *
  • The colon character ':' + * ('\u003a'COLON), and + * + *
  • The underscore character '_' + * ('\u005f'LOW LINE). + * + *
+ * + * A charset name must begin with either a letter or a digit. The empty string + * is not a legal charset name. Charset names are not case-sensitive; that is, + * case is always ignored when comparing charset names. Charset names + * generally follow the conventions documented in
RFC 2278: IANA Charset + * Registration Procedures. + * + *

Every charset has a canonical name and may also have one or more + * aliases. The canonical name is returned by the {@link #name() name} method + * of this class. Canonical names are, by convention, usually in upper case. + * The aliases of a charset are returned by the {@link #aliases() aliases} + * method. + * + * + * + *

Some charsets have an historical name that is defined for + * compatibility with previous versions of the Java platform. A charset's + * historical name is either its canonical name or one of its aliases. The + * historical name is returned by the getEncoding() methods of the + * {@link java.io.InputStreamReader#getEncoding InputStreamReader} and {@link + * java.io.OutputStreamWriter#getEncoding OutputStreamWriter} classes. + * + * + * + *

If a charset listed in the IANA Charset + * Registry is supported by an implementation of the Java platform then + * its canonical name must be the name listed in the registry. Many charsets + * are given more than one name in the registry, in which case the registry + * identifies one of the names as MIME-preferred. If a charset has more + * than one registry name then its canonical name must be the MIME-preferred + * name and the other names in the registry must be valid aliases. If a + * supported charset is not listed in the IANA registry then its canonical name + * must begin with one of the strings "X-" or "x-". + * + *

The IANA charset registry does change over time, and so the canonical + * name and the aliases of a particular charset may also change over time. To + * ensure compatibility it is recommended that no alias ever be removed from a + * charset, and that if the canonical name of a charset is changed then its + * previous canonical name be made into an alias. + * + * + *

Standard charsets

+ * + * + * + *

Every implementation of the Java platform is required to support the + * following standard charsets. Consult the release documentation for your + * implementation to see if any other charsets are supported. The behavior + * of such optional charsets may differ between implementations. + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + *

Charset

Description

US-ASCIISeven-bit ASCII, a.k.a. ISO646-US, + * a.k.a. the Basic Latin block of the Unicode character set
ISO-8859-1  ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1
UTF-8Eight-bit UCS Transformation Format
UTF-16BESixteen-bit UCS Transformation Format, + * big-endian byte order
UTF-16LESixteen-bit UCS Transformation Format, + * little-endian byte order
UTF-16Sixteen-bit UCS Transformation Format, + * byte order identified by an optional byte-order mark
+ * + *

The UTF-8 charset is specified by RFC 2279; the + * transformation format upon which it is based is specified in + * Amendment 2 of ISO 10646-1 and is also described in the Unicode + * Standard. + * + *

The UTF-16 charsets are specified by RFC 2781; the + * transformation formats upon which they are based are specified in + * Amendment 1 of ISO 10646-1 and are also described in the Unicode + * Standard. + * + *

The UTF-16 charsets use sixteen-bit quantities and are + * therefore sensitive to byte order. In these encodings the byte order of a + * stream may be indicated by an initial byte-order mark represented by + * the Unicode character '\uFEFF'. Byte-order marks are handled + * as follows: + * + *

    + * + *
  • When decoding, the UTF-16BE and UTF-16LE + * charsets interpret the initial byte-order marks as a ZERO-WIDTH + * NON-BREAKING SPACE; when encoding, they do not write + * byte-order marks.

  • + + * + *
  • When decoding, the UTF-16 charset interprets the + * byte-order mark at the beginning of the input stream to indicate the + * byte-order of the stream but defaults to big-endian if there is no + * byte-order mark; when encoding, it uses big-endian byte order and writes + * a big-endian byte-order mark.

  • + * + *
+ * + * In any case, byte order marks occuring after the first element of an + * input sequence are not omitted since the same code is used to represent + * ZERO-WIDTH NON-BREAKING SPACE. + * + *

Every instance of the Java virtual machine has a default charset, which + * may or may not be one of the standard charsets. The default charset is + * determined during virtual-machine startup and typically depends upon the + * locale and charset being used by the underlying operating system.

+ * + *

The {@link StandardCharsets} class defines constants for each of the + * standard charsets. + * + *

Terminology

+ * + *

The name of this class is taken from the terms used in + * RFC 2278. + * In that document a charset is defined as the combination of + * one or more coded character sets and a character-encoding scheme. + * (This definition is confusing; some other software systems define + * charset as a synonym for coded character set.) + * + *

A coded character set is a mapping between a set of abstract + * characters and a set of integers. US-ASCII, ISO 8859-1, + * JIS X 0201, and Unicode are examples of coded character sets. + * + *

Some standards have defined a character set to be simply a + * set of abstract characters without an associated assigned numbering. + * An alphabet is an example of such a character set. However, the subtle + * distinction between character set and coded character set + * is rarely used in practice; the former has become a short form for the + * latter, including in the Java API specification. + * + *

A character-encoding scheme is a mapping between one or more + * coded character sets and a set of octet (eight-bit byte) sequences. + * UTF-8, UTF-16, ISO 2022, and EUC are examples of + * character-encoding schemes. Encoding schemes are often associated with + * a particular coded character set; UTF-8, for example, is used only to + * encode Unicode. Some schemes, however, are associated with multiple + * coded character sets; EUC, for example, can be used to encode + * characters in a variety of Asian coded character sets. + * + *

When a coded character set is used exclusively with a single + * character-encoding scheme then the corresponding charset is usually + * named for the coded character set; otherwise a charset is usually named + * for the encoding scheme and, possibly, the locale of the coded + * character sets that it supports. Hence US-ASCII is both the + * name of a coded character set and of the charset that encodes it, while + * EUC-JP is the name of the charset that encodes the + * JIS X 0201, JIS X 0208, and JIS X 0212 + * coded character sets for the Japanese language. + * + *

The native character encoding of the Java programming language is + * UTF-16. A charset in the Java platform therefore defines a mapping + * between sequences of sixteen-bit UTF-16 code units (that is, sequences + * of chars) and sequences of bytes.

+ * + * + * @author Mark Reinhold + * @author JSR-51 Expert Group + * @since 1.4 + * + * @see CharsetDecoder + * @see CharsetEncoder + * @see java.nio.charset.spi.CharsetProvider + * @see java.lang.Character + */ + +public abstract class Charset + implements Comparable +{ + + /* -- Static methods -- */ + + private static volatile String bugLevel = null; + + /** + * Checks that the given string is a legal charset name.

+ * + * @param s + * A purported charset name + * + * @throws IllegalCharsetNameException + * If the given name is not a legal charset name + */ + private static void checkName(String s) { + int n = s.length(); + if (n == 0) + throw new IllegalCharsetNameException(s); + for (int i = 0; i < n; i++) { + char c = s.charAt(i); + if (c >= 'A' && c <= 'Z') continue; + if (c >= 'a' && c <= 'z') continue; + if (c >= '0' && c <= '9') continue; + if (c == '-' && i != 0) continue; + if (c == '+' && i != 0) continue; + if (c == ':' && i != 0) continue; + if (c == '_' && i != 0) continue; + if (c == '.' && i != 0) continue; + throw new IllegalCharsetNameException(s); + } + } + + // Cache of the most-recently-returned charsets, + // along with the names that were used to find them + // + private static volatile Object[] cache1 = null; // "Level 1" cache + private static volatile Object[] cache2 = null; // "Level 2" cache + + private static void cache(String charsetName, Charset cs) { + cache2 = cache1; + cache1 = new Object[] { charsetName, cs }; + } + + // Creates an iterator that walks over the available providers, ignoring + // those whose lookup or instantiation causes a security exception to be + // thrown. Should be invoked with full privileges. + // + private static Iterator providers() { + return Collections.emptyIterator(); + } + + // Thread-local gate to prevent recursive provider lookups + private static ThreadLocal gate = new ThreadLocal(); + + private static Charset lookupViaProviders(final String charsetName) { + return null; + } + + /* The extended set of charsets */ + private static Object extendedProviderLock = new Object(); + private static boolean extendedProviderProbed = false; + + + private static Charset lookupExtendedCharset(String charsetName) { + return null; + } + + private static Charset lookup(String charsetName) { + if (charsetName == null) + throw new IllegalArgumentException("Null charset name"); + + Object[] a; + if ((a = cache1) != null && charsetName.equals(a[0])) + return (Charset)a[1]; + // We expect most programs to use one Charset repeatedly. + // We convey a hint to this effect to the VM by putting the + // level 1 cache miss code in a separate method. + return lookup2(charsetName); + } + + private static Charset lookup2(String charsetName) { + Object[] a; + if ((a = cache2) != null && charsetName.equals(a[0])) { + cache2 = cache1; + cache1 = a; + return (Charset)a[1]; + } + + /* Only need to check the name if we didn't find a charset for it */ + checkName(charsetName); + return null; + } + + /** + * Tells whether the named charset is supported.

+ * + * @param charsetName + * The name of the requested charset; may be either + * a canonical name or an alias + * + * @return true if, and only if, support for the named charset + * is available in the current Java virtual machine + * + * @throws IllegalCharsetNameException + * If the given charset name is illegal + * + * @throws IllegalArgumentException + * If the given charsetName is null + */ + public static boolean isSupported(String charsetName) { + return (lookup(charsetName) != null); + } + + /** + * Returns a charset object for the named charset.

+ * + * @param charsetName + * The name of the requested charset; may be either + * a canonical name or an alias + * + * @return A charset object for the named charset + * + * @throws IllegalCharsetNameException + * If the given charset name is illegal + * + * @throws IllegalArgumentException + * If the given charsetName is null + * + * @throws UnsupportedCharsetException + * If no support for the named charset is available + * in this instance of the Java virtual machine + */ + public static Charset forName(String charsetName) { + Charset cs = lookup(charsetName); + if (cs != null) + return cs; + throw new UnsupportedCharsetException(charsetName); + } + + // Fold charsets from the given iterator into the given map, ignoring + // charsets whose names already have entries in the map. + // + private static void put(Iterator i, Map m) { + while (i.hasNext()) { + Charset cs = i.next(); + if (!m.containsKey(cs.name())) + m.put(cs.name(), cs); + } + } + + /** + * Constructs a sorted map from canonical charset names to charset objects. + * + *

The map returned by this method will have one entry for each charset + * for which support is available in the current Java virtual machine. If + * two or more supported charsets have the same canonical name then the + * resulting map will contain just one of them; which one it will contain + * is not specified.

+ * + *

The invocation of this method, and the subsequent use of the + * resulting map, may cause time-consuming disk or network I/O operations + * to occur. This method is provided for applications that need to + * enumerate all of the available charsets, for example to allow user + * charset selection. This method is not used by the {@link #forName + * forName} method, which instead employs an efficient incremental lookup + * algorithm. + * + *

This method may return different results at different times if new + * charset providers are dynamically made available to the current Java + * virtual machine. In the absence of such changes, the charsets returned + * by this method are exactly those that can be retrieved via the {@link + * #forName forName} method.

+ * + * @return An immutable, case-insensitive map from canonical charset names + * to charset objects + */ + public static SortedMap availableCharsets() { + TreeMap tm = new TreeMap(); + tm.put("UTF-8", Charset.defaultCharset()); + return tm; + } + + private static volatile Charset defaultCharset; + + /** + * Returns the default charset of this Java virtual machine. + * + *

The default charset is determined during virtual-machine startup and + * typically depends upon the locale and charset of the underlying + * operating system. + * + * @return A charset object for the default charset + * + * @since 1.5 + */ + public static Charset defaultCharset() { + if (defaultCharset == null) { + defaultCharset = forName("UTF-8"); + } + return defaultCharset; + } + + + /* -- Instance fields and methods -- */ + + private final String name; // tickles a bug in oldjavac + private final String[] aliases; // tickles a bug in oldjavac + private Set aliasSet = null; + + /** + * Initializes a new charset with the given canonical name and alias + * set.

+ * + * @param canonicalName + * The canonical name of this charset + * + * @param aliases + * An array of this charset's aliases, or null if it has no aliases + * + * @throws IllegalCharsetNameException + * If the canonical name or any of the aliases are illegal + */ + protected Charset(String canonicalName, String[] aliases) { + checkName(canonicalName); + String[] as = (aliases == null) ? new String[0] : aliases; + for (int i = 0; i < as.length; i++) + checkName(as[i]); + this.name = canonicalName; + this.aliases = as; + } + + /** + * Returns this charset's canonical name.

+ * + * @return The canonical name of this charset + */ + public final String name() { + return name; + } + + /** + * Returns a set containing this charset's aliases.

+ * + * @return An immutable set of this charset's aliases + */ + public final Set aliases() { + if (aliasSet != null) + return aliasSet; + int n = aliases.length; + HashSet hs = new HashSet(n); + for (int i = 0; i < n; i++) + hs.add(aliases[i]); + aliasSet = Collections.unmodifiableSet(hs); + return aliasSet; + } + + /** + * Returns this charset's human-readable name for the default locale. + * + *

The default implementation of this method simply returns this + * charset's canonical name. Concrete subclasses of this class may + * override this method in order to provide a localized display name.

+ * + * @return The display name of this charset in the default locale + */ + public String displayName() { + return name; + } + + /** + * Tells whether or not this charset is registered in the IANA Charset + * Registry.

+ * + * @return true if, and only if, this charset is known by its + * implementor to be registered with the IANA + */ + public final boolean isRegistered() { + return !name.startsWith("X-") && !name.startsWith("x-"); + } + + /** + * Returns this charset's human-readable name for the given locale. + * + *

The default implementation of this method simply returns this + * charset's canonical name. Concrete subclasses of this class may + * override this method in order to provide a localized display name.

+ * + * @param locale + * The locale for which the display name is to be retrieved + * + * @return The display name of this charset in the given locale + */ + public String displayName(Locale locale) { + return name; + } + + /** + * Tells whether or not this charset contains the given charset. + * + *

A charset C is said to contain a charset D if, + * and only if, every character representable in D is also + * representable in C. If this relationship holds then it is + * guaranteed that every string that can be encoded in D can also be + * encoded in C without performing any replacements. + * + *

That C contains D does not imply that each character + * representable in C by a particular byte sequence is represented + * in D by the same byte sequence, although sometimes this is the + * case. + * + *

Every charset contains itself. + * + *

This method computes an approximation of the containment relation: + * If it returns true then the given charset is known to be + * contained by this charset; if it returns false, however, then + * it is not necessarily the case that the given charset is not contained + * in this charset. + * + * @return true if the given charset is contained in this charset + */ + public abstract boolean contains(Charset cs); + + /** + * Constructs a new decoder for this charset.

+ * + * @return A new decoder for this charset + */ + public abstract CharsetDecoder newDecoder(); + + /** + * Constructs a new encoder for this charset.

+ * + * @return A new encoder for this charset + * + * @throws UnsupportedOperationException + * If this charset does not support encoding + */ + public abstract CharsetEncoder newEncoder(); + + /** + * Tells whether or not this charset supports encoding. + * + *

Nearly all charsets support encoding. The primary exceptions are + * special-purpose auto-detect charsets whose decoders can determine + * which of several possible encoding schemes is in use by examining the + * input byte sequence. Such charsets do not support encoding because + * there is no way to determine which encoding should be used on output. + * Implementations of such charsets should override this method to return + * false.

+ * + * @return true if, and only if, this charset supports encoding + */ + public boolean canEncode() { + return true; + } + + /** + * Convenience method that decodes bytes in this charset into Unicode + * characters. + * + *

An invocation of this method upon a charset cs returns the + * same result as the expression + * + *

+     *     cs.newDecoder()
+     *       .onMalformedInput(CodingErrorAction.REPLACE)
+     *       .onUnmappableCharacter(CodingErrorAction.REPLACE)
+     *       .decode(bb); 
+ * + * except that it is potentially more efficient because it can cache + * decoders between successive invocations. + * + *

This method always replaces malformed-input and unmappable-character + * sequences with this charset's default replacement byte array. In order + * to detect such sequences, use the {@link + * CharsetDecoder#decode(java.nio.ByteBuffer)} method directly.

+ * + * @param bb The byte buffer to be decoded + * + * @return A char buffer containing the decoded characters + */ +// public final CharBuffer decode(ByteBuffer bb) { +// try { +// return ThreadLocalCoders.decoderFor(this) +// .onMalformedInput(CodingErrorAction.REPLACE) +// .onUnmappableCharacter(CodingErrorAction.REPLACE) +// .decode(bb); +// } catch (CharacterCodingException x) { +// throw new Error(x); // Can't happen +// } +// } + + /** + * Convenience method that encodes Unicode characters into bytes in this + * charset. + * + *

An invocation of this method upon a charset cs returns the + * same result as the expression + * + *

+     *     cs.newEncoder()
+     *       .onMalformedInput(CodingErrorAction.REPLACE)
+     *       .onUnmappableCharacter(CodingErrorAction.REPLACE)
+     *       .encode(bb); 
+ * + * except that it is potentially more efficient because it can cache + * encoders between successive invocations. + * + *

This method always replaces malformed-input and unmappable-character + * sequences with this charset's default replacement string. In order to + * detect such sequences, use the {@link + * CharsetEncoder#encode(java.nio.CharBuffer)} method directly.

+ * + * @param cb The char buffer to be encoded + * + * @return A byte buffer containing the encoded characters + */ +// public final ByteBuffer encode(CharBuffer cb) { +// try { +// return ThreadLocalCoders.encoderFor(this) +// .onMalformedInput(CodingErrorAction.REPLACE) +// .onUnmappableCharacter(CodingErrorAction.REPLACE) +// .encode(cb); +// } catch (CharacterCodingException x) { +// throw new Error(x); // Can't happen +// } +// } + + /** + * Convenience method that encodes a string into bytes in this charset. + * + *

An invocation of this method upon a charset cs returns the + * same result as the expression + * + *

+     *     cs.encode(CharBuffer.wrap(s)); 
+ * + * @param str The string to be encoded + * + * @return A byte buffer containing the encoded characters + */ +// public final ByteBuffer encode(String str) { +// return encode(CharBuffer.wrap(str)); +// } + + /** + * Compares this charset to another. + * + *

Charsets are ordered by their canonical names, without regard to + * case.

+ * + * @param that + * The charset to which this charset is to be compared + * + * @return A negative integer, zero, or a positive integer as this charset + * is less than, equal to, or greater than the specified charset + */ + public final int compareTo(Charset that) { + return (name().compareToIgnoreCase(that.name())); + } + + /** + * Computes a hashcode for this charset.

+ * + * @return An integer hashcode + */ + public final int hashCode() { + return name().hashCode(); + } + + /** + * Tells whether or not this object is equal to another. + * + *

Two charsets are equal if, and only if, they have the same canonical + * names. A charset is never equal to any other type of object.

+ * + * @return true if, and only if, this charset is equal to the + * given object + */ + public final boolean equals(Object ob) { + if (!(ob instanceof Charset)) + return false; + if (this == ob) + return true; + return name.equals(((Charset)ob).name()); + } + + /** + * Returns a string describing this charset.

+ * + * @return A string describing this charset + */ + public final String toString() { + return name(); + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/nio/charset/CharsetDecoder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/nio/charset/CharsetDecoder.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,970 @@ +/* + * 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. + */ + +// -- This file was mechanically generated: Do not edit! -- // + +package java.nio.charset; + +//import java.nio.Buffer; +//import java.nio.ByteBuffer; +//import java.nio.CharBuffer; +//import java.nio.BufferOverflowException; +//import java.nio.BufferUnderflowException; +import java.lang.ref.WeakReference; +//import java.nio.charset.CoderMalfunctionError; // javadoc + + +/** + * An engine that can transform a sequence of bytes in a specific charset into a sequence of + * sixteen-bit Unicode characters. + * + * + * + *

The input byte sequence is provided in a byte buffer or a series + * of such buffers. The output character sequence is written to a character buffer + * or a series of such buffers. A decoder should always be used by making + * the following sequence of method invocations, hereinafter referred to as a + * decoding operation: + * + *

    + * + *
  1. Reset the decoder via the {@link #reset reset} method, unless it + * has not been used before;

  2. + * + *
  3. Invoke the {@link #decode decode} method zero or more times, as + * long as additional input may be available, passing false for the + * endOfInput argument and filling the input buffer and flushing the + * output buffer between invocations;

  4. + * + *
  5. Invoke the {@link #decode decode} method one final time, passing + * true for the endOfInput argument; and then

  6. + * + *
  7. Invoke the {@link #flush flush} method so that the decoder can + * flush any internal state to the output buffer.

  8. + * + *
+ * + * Each invocation of the {@link #decode decode} method will decode as many + * bytes as possible from the input buffer, writing the resulting characters + * to the output buffer. The {@link #decode decode} method returns when more + * input is required, when there is not enough room in the output buffer, or + * when a decoding error has occurred. In each case a {@link CoderResult} + * object is returned to describe the reason for termination. An invoker can + * examine this object and fill the input buffer, flush the output buffer, or + * attempt to recover from a decoding error, as appropriate, and try again. + * + *
+ * + *

There are two general types of decoding errors. If the input byte + * sequence is not legal for this charset then the input is considered malformed. If + * the input byte sequence is legal but cannot be mapped to a valid + * Unicode character then an unmappable character has been encountered. + * + * + * + *

How a decoding error is handled depends upon the action requested for + * that type of error, which is described by an instance of the {@link + * CodingErrorAction} class. The possible error actions are to {@link + * CodingErrorAction#IGNORE ignore} the erroneous input, {@link + * CodingErrorAction#REPORT report} the error to the invoker via + * the returned {@link CoderResult} object, or {@link CodingErrorAction#REPLACE + * replace} the erroneous input with the current value of the + * replacement string. The replacement + * + + + + + + * has the initial value "\uFFFD"; + + * + * its value may be changed via the {@link #replaceWith(java.lang.String) + * replaceWith} method. + * + *

The default action for malformed-input and unmappable-character errors + * is to {@link CodingErrorAction#REPORT report} them. The + * malformed-input error action may be changed via the {@link + * #onMalformedInput(CodingErrorAction) onMalformedInput} method; the + * unmappable-character action may be changed via the {@link + * #onUnmappableCharacter(CodingErrorAction) onUnmappableCharacter} method. + * + *

This class is designed to handle many of the details of the decoding + * process, including the implementation of error actions. A decoder for a + * specific charset, which is a concrete subclass of this class, need only + * implement the abstract {@link #decodeLoop decodeLoop} method, which + * encapsulates the basic decoding loop. A subclass that maintains internal + * state should, additionally, override the {@link #implFlush implFlush} and + * {@link #implReset implReset} methods. + * + *

Instances of this class are not safe for use by multiple concurrent + * threads.

+ * + * + * @author Mark Reinhold + * @author JSR-51 Expert Group + * @since 1.4 + * + * @see ByteBuffer + * @see CharBuffer + * @see Charset + * @see CharsetEncoder + */ + +public abstract class CharsetDecoder { + + private final Charset charset; + private final float averageCharsPerByte; + private final float maxCharsPerByte; + + private String replacement; +// private CodingErrorAction malformedInputAction +// = CodingErrorAction.REPORT; +// private CodingErrorAction unmappableCharacterAction +// = CodingErrorAction.REPORT; + + // Internal states + // + private static final int ST_RESET = 0; + private static final int ST_CODING = 1; + private static final int ST_END = 2; + private static final int ST_FLUSHED = 3; + + private int state = ST_RESET; + + private static String stateNames[] + = { "RESET", "CODING", "CODING_END", "FLUSHED" }; + + + /** + * Initializes a new decoder. The new decoder will have the given + * chars-per-byte and replacement values.

+ * + * @param averageCharsPerByte + * A positive float value indicating the expected number of + * characters that will be produced for each input byte + * + * @param maxCharsPerByte + * A positive float value indicating the maximum number of + * characters that will be produced for each input byte + * + * @param replacement + * The initial replacement; must not be null, must have + * non-zero length, must not be longer than maxCharsPerByte, + * and must be {@link #isLegalReplacement
legal} + * + * @throws IllegalArgumentException + * If the preconditions on the parameters do not hold + */ + private + CharsetDecoder(Charset cs, + float averageCharsPerByte, + float maxCharsPerByte, + String replacement) + { + this.charset = cs; + if (averageCharsPerByte <= 0.0f) + throw new IllegalArgumentException("Non-positive " + + "averageCharsPerByte"); + if (maxCharsPerByte <= 0.0f) + throw new IllegalArgumentException("Non-positive " + + "maxCharsPerByte"); + if (averageCharsPerByte > maxCharsPerByte) + throw new IllegalArgumentException("averageCharsPerByte" + + " exceeds " + + "maxCharsPerByte"); + this.replacement = replacement; + this.averageCharsPerByte = averageCharsPerByte; + this.maxCharsPerByte = maxCharsPerByte; + replaceWith(replacement); + } + + /** + * Initializes a new decoder. The new decoder will have the given + * chars-per-byte values and its replacement will be the + * string "\uFFFD".

+ * + * @param averageCharsPerByte + * A positive float value indicating the expected number of + * characters that will be produced for each input byte + * + * @param maxCharsPerByte + * A positive float value indicating the maximum number of + * characters that will be produced for each input byte + * + * @throws IllegalArgumentException + * If the preconditions on the parameters do not hold + */ + protected CharsetDecoder(Charset cs, + float averageCharsPerByte, + float maxCharsPerByte) + { + this(cs, + averageCharsPerByte, maxCharsPerByte, + "\uFFFD"); + } + + /** + * Returns the charset that created this decoder.

+ * + * @return This decoder's charset + */ + public final Charset charset() { + return charset; + } + + /** + * Returns this decoder's replacement value.

+ * + * @return This decoder's current replacement, + * which is never null and is never empty + */ + public final String replacement() { + return replacement; + } + + /** + * Changes this decoder's replacement value. + * + *

This method invokes the {@link #implReplaceWith implReplaceWith} + * method, passing the new replacement, after checking that the new + * replacement is acceptable.

+ * + * @param newReplacement + * + + * The new replacement; must not be null + * and must have non-zero length + + + + + + + + * + * @return This decoder + * + * @throws IllegalArgumentException + * If the preconditions on the parameter do not hold + */ + public final CharsetDecoder replaceWith(String newReplacement) { + if (newReplacement == null) + throw new IllegalArgumentException("Null replacement"); + int len = newReplacement.length(); + if (len == 0) + throw new IllegalArgumentException("Empty replacement"); + if (len > maxCharsPerByte) + throw new IllegalArgumentException("Replacement too long"); + + + + + this.replacement = newReplacement; + implReplaceWith(newReplacement); + return this; + } + + /** + * Reports a change to this decoder's replacement value. + * + *

The default implementation of this method does nothing. This method + * should be overridden by decoders that require notification of changes to + * the replacement.

+ * + * @param newReplacement + */ + protected void implReplaceWith(String newReplacement) { + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /** + * Returns this decoder's current action for malformed-input errors.

+ * + * @return The current malformed-input action, which is never null + */ +// public CodingErrorAction malformedInputAction() { +// return malformedInputAction; +// } + + /** + * Changes this decoder's action for malformed-input errors.

+ * + *

This method invokes the {@link #implOnMalformedInput + * implOnMalformedInput} method, passing the new action.

+ * + * @param newAction The new action; must not be null + * + * @return This decoder + * + * @throws IllegalArgumentException + * If the precondition on the parameter does not hold + */ +// public final CharsetDecoder onMalformedInput(CodingErrorAction newAction) { +// if (newAction == null) +// throw new IllegalArgumentException("Null action"); +// malformedInputAction = newAction; +// implOnMalformedInput(newAction); +// return this; +// } + + /** + * Reports a change to this decoder's malformed-input action. + * + *

The default implementation of this method does nothing. This method + * should be overridden by decoders that require notification of changes to + * the malformed-input action.

+ */ +// protected void implOnMalformedInput(CodingErrorAction newAction) { } + + /** + * Returns this decoder's current action for unmappable-character errors. + *

+ * + * @return The current unmappable-character action, which is never + * null + */ +// public CodingErrorAction unmappableCharacterAction() { +// return unmappableCharacterAction; +// } + + /** + * Changes this decoder's action for unmappable-character errors. + * + *

This method invokes the {@link #implOnUnmappableCharacter + * implOnUnmappableCharacter} method, passing the new action.

+ * + * @param newAction The new action; must not be null + * + * @return This decoder + * + * @throws IllegalArgumentException + * If the precondition on the parameter does not hold + */ +// public final CharsetDecoder onUnmappableCharacter(CodingErrorAction +// newAction) +// { +// if (newAction == null) +// throw new IllegalArgumentException("Null action"); +// unmappableCharacterAction = newAction; +// implOnUnmappableCharacter(newAction); +// return this; +// } + + /** + * Reports a change to this decoder's unmappable-character action. + * + *

The default implementation of this method does nothing. This method + * should be overridden by decoders that require notification of changes to + * the unmappable-character action.

+ */ +// protected void implOnUnmappableCharacter(CodingErrorAction newAction) { } + + /** + * Returns the average number of characters that will be produced for each + * byte of input. This heuristic value may be used to estimate the size + * of the output buffer required for a given input sequence.

+ * + * @return The average number of characters produced + * per byte of input + */ + public final float averageCharsPerByte() { + return averageCharsPerByte; + } + + /** + * Returns the maximum number of characters that will be produced for each + * byte of input. This value may be used to compute the worst-case size + * of the output buffer required for a given input sequence.

+ * + * @return The maximum number of characters that will be produced per + * byte of input + */ + public final float maxCharsPerByte() { + return maxCharsPerByte; + } + + /** + * Decodes as many bytes as possible from the given input buffer, + * writing the results to the given output buffer. + * + *

The buffers are read from, and written to, starting at their current + * positions. At most {@link Buffer#remaining in.remaining()} bytes + * will be read and at most {@link Buffer#remaining out.remaining()} + * characters will be written. The buffers' positions will be advanced to + * reflect the bytes read and the characters written, but their marks and + * limits will not be modified. + * + *

In addition to reading bytes from the input buffer and writing + * characters to the output buffer, this method returns a {@link CoderResult} + * object to describe its reason for termination: + * + *

    + * + *
  • {@link CoderResult#UNDERFLOW} indicates that as much of the + * input buffer as possible has been decoded. If there is no further + * input then the invoker can proceed to the next step of the + * decoding operation. Otherwise this method + * should be invoked again with further input.

  • + * + *
  • {@link CoderResult#OVERFLOW} indicates that there is + * insufficient space in the output buffer to decode any more bytes. + * This method should be invoked again with an output buffer that has + * more {@linkplain Buffer#remaining remaining} characters. This is + * typically done by draining any decoded characters from the output + * buffer.

  • + * + *
  • A {@link CoderResult#malformedForLength + * malformed-input} result indicates that a malformed-input + * error has been detected. The malformed bytes begin at the input + * buffer's (possibly incremented) position; the number of malformed + * bytes may be determined by invoking the result object's {@link + * CoderResult#length() length} method. This case applies only if the + * {@link #onMalformedInput malformed action} of this decoder + * is {@link CodingErrorAction#REPORT}; otherwise the malformed input + * will be ignored or replaced, as requested.

  • + * + *
  • An {@link CoderResult#unmappableForLength + * unmappable-character} result indicates that an + * unmappable-character error has been detected. The bytes that + * decode the unmappable character begin at the input buffer's (possibly + * incremented) position; the number of such bytes may be determined + * by invoking the result object's {@link CoderResult#length() length} + * method. This case applies only if the {@link #onUnmappableCharacter + * unmappable action} of this decoder is {@link + * CodingErrorAction#REPORT}; otherwise the unmappable character will be + * ignored or replaced, as requested.

  • + * + *
+ * + * In any case, if this method is to be reinvoked in the same decoding + * operation then care should be taken to preserve any bytes remaining + * in the input buffer so that they are available to the next invocation. + * + *

The endOfInput parameter advises this method as to whether + * the invoker can provide further input beyond that contained in the given + * input buffer. If there is a possibility of providing additional input + * then the invoker should pass false for this parameter; if there + * is no possibility of providing further input then the invoker should + * pass true. It is not erroneous, and in fact it is quite + * common, to pass false in one invocation and later discover that + * no further input was actually available. It is critical, however, that + * the final invocation of this method in a sequence of invocations always + * pass true so that any remaining undecoded input will be treated + * as being malformed. + * + *

This method works by invoking the {@link #decodeLoop decodeLoop} + * method, interpreting its results, handling error conditions, and + * reinvoking it as necessary.

+ * + * + * @param in + * The input byte buffer + * + * @param out + * The output character buffer + * + * @param endOfInput + * true if, and only if, the invoker can provide no + * additional input bytes beyond those in the given buffer + * + * @return A coder-result object describing the reason for termination + * + * @throws IllegalStateException + * If a decoding operation is already in progress and the previous + * step was an invocation neither of the {@link #reset reset} + * method, nor of this method with a value of false for + * the endOfInput parameter, nor of this method with a + * value of true for the endOfInput parameter + * but a return value indicating an incomplete decoding operation + * + * @throws CoderMalfunctionError + * If an invocation of the decodeLoop method threw + * an unexpected exception + */ +// public final CoderResult decode(ByteBuffer in, CharBuffer out, +// boolean endOfInput) +// { +// int newState = endOfInput ? ST_END : ST_CODING; +// if ((state != ST_RESET) && (state != ST_CODING) +// && !(endOfInput && (state == ST_END))) +// throwIllegalStateException(state, newState); +// state = newState; +// +// for (;;) { +// +// CoderResult cr; +// try { +// cr = decodeLoop(in, out); +// } catch (BufferUnderflowException x) { +// throw new CoderMalfunctionError(x); +// } catch (BufferOverflowException x) { +// throw new CoderMalfunctionError(x); +// } +// +// if (cr.isOverflow()) +// return cr; +// +// if (cr.isUnderflow()) { +// if (endOfInput && in.hasRemaining()) { +// cr = CoderResult.malformedForLength(in.remaining()); +// // Fall through to malformed-input case +// } else { +// return cr; +// } +// } +// +// CodingErrorAction action = null; +// if (cr.isMalformed()) +// action = malformedInputAction; +// else if (cr.isUnmappable()) +// action = unmappableCharacterAction; +// else +// assert false : cr.toString(); +// +// if (action == CodingErrorAction.REPORT) +// return cr; +// +// if (action == CodingErrorAction.REPLACE) { +// if (out.remaining() < replacement.length()) +// return CoderResult.OVERFLOW; +// out.put(replacement); +// } +// +// if ((action == CodingErrorAction.IGNORE) +// || (action == CodingErrorAction.REPLACE)) { +// // Skip erroneous input either way +// in.position(in.position() + cr.length()); +// continue; +// } +// +// assert false; +// } +// +// } + + /** + * Flushes this decoder. + * + *

Some decoders maintain internal state and may need to write some + * final characters to the output buffer once the overall input sequence has + * been read. + * + *

Any additional output is written to the output buffer beginning at + * its current position. At most {@link Buffer#remaining out.remaining()} + * characters will be written. The buffer's position will be advanced + * appropriately, but its mark and limit will not be modified. + * + *

If this method completes successfully then it returns {@link + * CoderResult#UNDERFLOW}. If there is insufficient room in the output + * buffer then it returns {@link CoderResult#OVERFLOW}. If this happens + * then this method must be invoked again, with an output buffer that has + * more room, in order to complete the current decoding + * operation. + * + *

If this decoder has already been flushed then invoking this method + * has no effect. + * + *

This method invokes the {@link #implFlush implFlush} method to + * perform the actual flushing operation.

+ * + * @param out + * The output character buffer + * + * @return A coder-result object, either {@link CoderResult#UNDERFLOW} or + * {@link CoderResult#OVERFLOW} + * + * @throws IllegalStateException + * If the previous step of the current decoding operation was an + * invocation neither of the {@link #flush flush} method nor of + * the three-argument {@link + * #decode(ByteBuffer,CharBuffer,boolean) decode} method + * with a value of true for the endOfInput + * parameter + */ +// public final CoderResult flush(CharBuffer out) { +// if (state == ST_END) { +// CoderResult cr = implFlush(out); +// if (cr.isUnderflow()) +// state = ST_FLUSHED; +// return cr; +// } +// +// if (state != ST_FLUSHED) +// throwIllegalStateException(state, ST_FLUSHED); +// +// return CoderResult.UNDERFLOW; // Already flushed +// } + + /** + * Flushes this decoder. + * + *

The default implementation of this method does nothing, and always + * returns {@link CoderResult#UNDERFLOW}. This method should be overridden + * by decoders that may need to write final characters to the output buffer + * once the entire input sequence has been read.

+ * + * @param out + * The output character buffer + * + * @return A coder-result object, either {@link CoderResult#UNDERFLOW} or + * {@link CoderResult#OVERFLOW} + */ +// protected CoderResult implFlush(CharBuffer out) { +// return CoderResult.UNDERFLOW; +// } + + /** + * Resets this decoder, clearing any internal state. + * + *

This method resets charset-independent state and also invokes the + * {@link #implReset() implReset} method in order to perform any + * charset-specific reset actions.

+ * + * @return This decoder + * + */ + public final CharsetDecoder reset() { + implReset(); + state = ST_RESET; + return this; + } + + /** + * Resets this decoder, clearing any charset-specific internal state. + * + *

The default implementation of this method does nothing. This method + * should be overridden by decoders that maintain internal state.

+ */ + protected void implReset() { } + + /** + * Decodes one or more bytes into one or more characters. + * + *

This method encapsulates the basic decoding loop, decoding as many + * bytes as possible until it either runs out of input, runs out of room + * in the output buffer, or encounters a decoding error. This method is + * invoked by the {@link #decode decode} method, which handles result + * interpretation and error recovery. + * + *

The buffers are read from, and written to, starting at their current + * positions. At most {@link Buffer#remaining in.remaining()} bytes + * will be read, and at most {@link Buffer#remaining out.remaining()} + * characters will be written. The buffers' positions will be advanced to + * reflect the bytes read and the characters written, but their marks and + * limits will not be modified. + * + *

This method returns a {@link CoderResult} object to describe its + * reason for termination, in the same manner as the {@link #decode decode} + * method. Most implementations of this method will handle decoding errors + * by returning an appropriate result object for interpretation by the + * {@link #decode decode} method. An optimized implementation may instead + * examine the relevant error action and implement that action itself. + * + *

An implementation of this method may perform arbitrary lookahead by + * returning {@link CoderResult#UNDERFLOW} until it receives sufficient + * input.

+ * + * @param in + * The input byte buffer + * + * @param out + * The output character buffer + * + * @return A coder-result object describing the reason for termination + */ +// protected abstract CoderResult decodeLoop(ByteBuffer in, +// CharBuffer out); + + /** + * Convenience method that decodes the remaining content of a single input + * byte buffer into a newly-allocated character buffer. + * + *

This method implements an entire decoding + * operation; that is, it resets this decoder, then it decodes the + * bytes in the given byte buffer, and finally it flushes this + * decoder. This method should therefore not be invoked if a decoding + * operation is already in progress.

+ * + * @param in + * The input byte buffer + * + * @return A newly-allocated character buffer containing the result of the + * decoding operation. The buffer's position will be zero and its + * limit will follow the last character written. + * + * @throws IllegalStateException + * If a decoding operation is already in progress + * + * @throws MalformedInputException + * If the byte sequence starting at the input buffer's current + * position is not legal for this charset and the current malformed-input action + * is {@link CodingErrorAction#REPORT} + * + * @throws UnmappableCharacterException + * If the byte sequence starting at the input buffer's current + * position cannot be mapped to an equivalent character sequence and + * the current unmappable-character action is {@link + * CodingErrorAction#REPORT} + */ +// public final CharBuffer decode(ByteBuffer in) +// throws CharacterCodingException +// { +// int n = (int)(in.remaining() * averageCharsPerByte()); +// CharBuffer out = CharBuffer.allocate(n); +// +// if ((n == 0) && (in.remaining() == 0)) +// return out; +// reset(); +// for (;;) { +// CoderResult cr = in.hasRemaining() ? +// decode(in, out, true) : CoderResult.UNDERFLOW; +// if (cr.isUnderflow()) +// cr = flush(out); +// +// if (cr.isUnderflow()) +// break; +// if (cr.isOverflow()) { +// n = 2*n + 1; // Ensure progress; n might be 0! +// CharBuffer o = CharBuffer.allocate(n); +// out.flip(); +// o.put(out); +// out = o; +// continue; +// } +// cr.throwException(); +// } +// out.flip(); +// return out; +// } + + + + /** + * Tells whether or not this decoder implements an auto-detecting charset. + * + *

The default implementation of this method always returns + * false; it should be overridden by auto-detecting decoders to + * return true.

+ * + * @return true if, and only if, this decoder implements an + * auto-detecting charset + */ + public boolean isAutoDetecting() { + return false; + } + + /** + * Tells whether or not this decoder has yet detected a + * charset  (optional operation). + * + *

If this decoder implements an auto-detecting charset then at a + * single point during a decoding operation this method may start returning + * true to indicate that a specific charset has been detected in + * the input byte sequence. Once this occurs, the {@link #detectedCharset + * detectedCharset} method may be invoked to retrieve the detected charset. + * + *

That this method returns false does not imply that no bytes + * have yet been decoded. Some auto-detecting decoders are capable of + * decoding some, or even all, of an input byte sequence without fixing on + * a particular charset. + * + *

The default implementation of this method always throws an {@link + * UnsupportedOperationException}; it should be overridden by + * auto-detecting decoders to return true once the input charset + * has been determined.

+ * + * @return true if, and only if, this decoder has detected a + * specific charset + * + * @throws UnsupportedOperationException + * If this decoder does not implement an auto-detecting charset + */ + public boolean isCharsetDetected() { + throw new UnsupportedOperationException(); + } + + /** + * Retrieves the charset that was detected by this + * decoder  (optional operation). + * + *

If this decoder implements an auto-detecting charset then this + * method returns the actual charset once it has been detected. After that + * point, this method returns the same value for the duration of the + * current decoding operation. If not enough input bytes have yet been + * read to determine the actual charset then this method throws an {@link + * IllegalStateException}. + * + *

The default implementation of this method always throws an {@link + * UnsupportedOperationException}; it should be overridden by + * auto-detecting decoders to return the appropriate value.

+ * + * @return The charset detected by this auto-detecting decoder, + * or null if the charset has not yet been determined + * + * @throws IllegalStateException + * If insufficient bytes have been read to determine a charset + * + * @throws UnsupportedOperationException + * If this decoder does not implement an auto-detecting charset + */ + public Charset detectedCharset() { + throw new UnsupportedOperationException(); + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + private void throwIllegalStateException(int from, int to) { + throw new IllegalStateException("Current state = " + stateNames[from] + + ", new state = " + stateNames[to]); + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/nio/charset/CharsetEncoder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/nio/charset/CharsetEncoder.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,970 @@ +/* + * 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. + */ + +// -- This file was mechanically generated: Do not edit! -- // + +package java.nio.charset; + +//import java.nio.Buffer; +//import java.nio.ByteBuffer; +//import java.nio.CharBuffer; +//import java.nio.BufferOverflowException; +//import java.nio.BufferUnderflowException; +import java.lang.ref.WeakReference; +//import java.nio.charset.CoderMalfunctionError; // javadoc + + +/** + * An engine that can transform a sequence of sixteen-bit Unicode characters into a sequence of + * bytes in a specific charset. + * + * + * + *

The input character sequence is provided in a character buffer or a series + * of such buffers. The output byte sequence is written to a byte buffer + * or a series of such buffers. An encoder should always be used by making + * the following sequence of method invocations, hereinafter referred to as an + * encoding operation: + * + *

    + * + *
  1. Reset the encoder via the {@link #reset reset} method, unless it + * has not been used before;

  2. + * + *
  3. Invoke the {@link #encode encode} method zero or more times, as + * long as additional input may be available, passing false for the + * endOfInput argument and filling the input buffer and flushing the + * output buffer between invocations;

  4. + * + *
  5. Invoke the {@link #encode encode} method one final time, passing + * true for the endOfInput argument; and then

  6. + * + *
  7. Invoke the {@link #flush flush} method so that the encoder can + * flush any internal state to the output buffer.

  8. + * + *
+ * + * Each invocation of the {@link #encode encode} method will encode as many + * characters as possible from the input buffer, writing the resulting bytes + * to the output buffer. The {@link #encode encode} method returns when more + * input is required, when there is not enough room in the output buffer, or + * when an encoding error has occurred. In each case a {@link CoderResult} + * object is returned to describe the reason for termination. An invoker can + * examine this object and fill the input buffer, flush the output buffer, or + * attempt to recover from an encoding error, as appropriate, and try again. + * + *
+ * + *

There are two general types of encoding errors. If the input character + * sequence is not a legal sixteen-bit Unicode sequence then the input is considered malformed. If + * the input character sequence is legal but cannot be mapped to a valid + * byte sequence in the given charset then an unmappable character has been encountered. + * + * + * + *

How an encoding error is handled depends upon the action requested for + * that type of error, which is described by an instance of the {@link + * CodingErrorAction} class. The possible error actions are to {@link + * CodingErrorAction#IGNORE ignore} the erroneous input, {@link + * CodingErrorAction#REPORT report} the error to the invoker via + * the returned {@link CoderResult} object, or {@link CodingErrorAction#REPLACE + * replace} the erroneous input with the current value of the + * replacement byte array. The replacement + * + + * is initially set to the encoder's default replacement, which often + * (but not always) has the initial value { (byte)'?' }; + + + + + * + * its value may be changed via the {@link #replaceWith(byte[]) + * replaceWith} method. + * + *

The default action for malformed-input and unmappable-character errors + * is to {@link CodingErrorAction#REPORT report} them. The + * malformed-input error action may be changed via the {@link + * #onMalformedInput(CodingErrorAction) onMalformedInput} method; the + * unmappable-character action may be changed via the {@link + * #onUnmappableCharacter(CodingErrorAction) onUnmappableCharacter} method. + * + *

This class is designed to handle many of the details of the encoding + * process, including the implementation of error actions. An encoder for a + * specific charset, which is a concrete subclass of this class, need only + * implement the abstract {@link #encodeLoop encodeLoop} method, which + * encapsulates the basic encoding loop. A subclass that maintains internal + * state should, additionally, override the {@link #implFlush implFlush} and + * {@link #implReset implReset} methods. + * + *

Instances of this class are not safe for use by multiple concurrent + * threads.

+ * + * + * @author Mark Reinhold + * @author JSR-51 Expert Group + * @since 1.4 + * + * @see ByteBuffer + * @see CharBuffer + * @see Charset + * @see CharsetDecoder + */ + +public abstract class CharsetEncoder { + + private final Charset charset; + private final float averageBytesPerChar; + private final float maxBytesPerChar; + + private byte[] replacement; +// private CodingErrorAction malformedInputAction +// = CodingErrorAction.REPORT; +// private CodingErrorAction unmappableCharacterAction +// = CodingErrorAction.REPORT; + + // Internal states + // + private static final int ST_RESET = 0; + private static final int ST_CODING = 1; + private static final int ST_END = 2; + private static final int ST_FLUSHED = 3; + + private int state = ST_RESET; + + private static String stateNames[] + = { "RESET", "CODING", "CODING_END", "FLUSHED" }; + + + /** + * Initializes a new encoder. The new encoder will have the given + * bytes-per-char and replacement values.

+ * + * @param averageBytesPerChar + * A positive float value indicating the expected number of + * bytes that will be produced for each input character + * + * @param maxBytesPerChar + * A positive float value indicating the maximum number of + * bytes that will be produced for each input character + * + * @param replacement + * The initial replacement; must not be null, must have + * non-zero length, must not be longer than maxBytesPerChar, + * and must be {@link #isLegalReplacement
legal} + * + * @throws IllegalArgumentException + * If the preconditions on the parameters do not hold + */ + protected + CharsetEncoder(Charset cs, + float averageBytesPerChar, + float maxBytesPerChar, + byte[] replacement) + { + this.charset = cs; + if (averageBytesPerChar <= 0.0f) + throw new IllegalArgumentException("Non-positive " + + "averageBytesPerChar"); + if (maxBytesPerChar <= 0.0f) + throw new IllegalArgumentException("Non-positive " + + "maxBytesPerChar"); + if (averageBytesPerChar > maxBytesPerChar) + throw new IllegalArgumentException("averageBytesPerChar" + + " exceeds " + + "maxBytesPerChar"); + this.replacement = replacement; + this.averageBytesPerChar = averageBytesPerChar; + this.maxBytesPerChar = maxBytesPerChar; + replaceWith(replacement); + } + + /** + * Initializes a new encoder. The new encoder will have the given + * bytes-per-char values and its replacement will be the + * byte array { (byte)'?' }.

+ * + * @param averageBytesPerChar + * A positive float value indicating the expected number of + * bytes that will be produced for each input character + * + * @param maxBytesPerChar + * A positive float value indicating the maximum number of + * bytes that will be produced for each input character + * + * @throws IllegalArgumentException + * If the preconditions on the parameters do not hold + */ + protected CharsetEncoder(Charset cs, + float averageBytesPerChar, + float maxBytesPerChar) + { + this(cs, + averageBytesPerChar, maxBytesPerChar, + new byte[] { (byte)'?' }); + } + + /** + * Returns the charset that created this encoder.

+ * + * @return This encoder's charset + */ + public final Charset charset() { + return charset; + } + + /** + * Returns this encoder's replacement value.

+ * + * @return This encoder's current replacement, + * which is never null and is never empty + */ + public final byte[] replacement() { + return replacement; + } + + /** + * Changes this encoder's replacement value. + * + *

This method invokes the {@link #implReplaceWith implReplaceWith} + * method, passing the new replacement, after checking that the new + * replacement is acceptable.

+ * + * @param newReplacement + * + + + + + + * The new replacement; must not be null, must have + * non-zero length, must not be longer than the value returned by + * the {@link #maxBytesPerChar() maxBytesPerChar} method, and + * must be {@link #isLegalReplacement
legal} + + * + * @return This encoder + * + * @throws IllegalArgumentException + * If the preconditions on the parameter do not hold + */ + public final CharsetEncoder replaceWith(byte[] newReplacement) { + if (newReplacement == null) + throw new IllegalArgumentException("Null replacement"); + int len = newReplacement.length; + if (len == 0) + throw new IllegalArgumentException("Empty replacement"); + if (len > maxBytesPerChar) + throw new IllegalArgumentException("Replacement too long"); + +// if (!isLegalReplacement(newReplacement)) +// throw new IllegalArgumentException("Illegal replacement"); + + this.replacement = newReplacement; + implReplaceWith(newReplacement); + return this; + } + + /** + * Reports a change to this encoder's replacement value. + * + *

The default implementation of this method does nothing. This method + * should be overridden by encoders that require notification of changes to + * the replacement.

+ * + * @param newReplacement + */ + protected void implReplaceWith(byte[] newReplacement) { + } + + + + private WeakReference cachedDecoder = null; + + /** + * Tells whether or not the given byte array is a legal replacement value + * for this encoder. + * + *

A replacement is legal if, and only if, it is a legal sequence of + * bytes in this encoder's charset; that is, it must be possible to decode + * the replacement into one or more sixteen-bit Unicode characters. + * + *

The default implementation of this method is not very efficient; it + * should generally be overridden to improve performance.

+ * + * @param repl The byte array to be tested + * + * @return true if, and only if, the given byte array + * is a legal replacement value for this encoder + */ +// public boolean isLegalReplacement(byte[] repl) { +// WeakReference wr = cachedDecoder; +// CharsetDecoder dec = null; +// if ((wr == null) || ((dec = wr.get()) == null)) { +// dec = charset().newDecoder(); +// dec.onMalformedInput(CodingErrorAction.REPORT); +// dec.onUnmappableCharacter(CodingErrorAction.REPORT); +// cachedDecoder = new WeakReference(dec); +// } else { +// dec.reset(); +// } +// ByteBuffer bb = ByteBuffer.wrap(repl); +// CharBuffer cb = CharBuffer.allocate((int)(bb.remaining() +// * dec.maxCharsPerByte())); +// CoderResult cr = dec.decode(bb, cb, true); +// return !cr.isError(); +// } + + + + /** + * Returns this encoder's current action for malformed-input errors.

+ * + * @return The current malformed-input action, which is never null + */ +// public CodingErrorAction malformedInputAction() { +// return malformedInputAction; +// } + + /** + * Changes this encoder's action for malformed-input errors.

+ * + *

This method invokes the {@link #implOnMalformedInput + * implOnMalformedInput} method, passing the new action.

+ * + * @param newAction The new action; must not be null + * + * @return This encoder + * + * @throws IllegalArgumentException + * If the precondition on the parameter does not hold + */ +// public final CharsetEncoder onMalformedInput(CodingErrorAction newAction) { +// if (newAction == null) +// throw new IllegalArgumentException("Null action"); +// malformedInputAction = newAction; +// implOnMalformedInput(newAction); +// return this; +// } + + /** + * Reports a change to this encoder's malformed-input action. + * + *

The default implementation of this method does nothing. This method + * should be overridden by encoders that require notification of changes to + * the malformed-input action.

+ */ +// protected void implOnMalformedInput(CodingErrorAction newAction) { } + + /** + * Returns this encoder's current action for unmappable-character errors. + *

+ * + * @return The current unmappable-character action, which is never + * null + */ +// public CodingErrorAction unmappableCharacterAction() { +// return unmappableCharacterAction; +// } + + /** + * Changes this encoder's action for unmappable-character errors. + * + *

This method invokes the {@link #implOnUnmappableCharacter + * implOnUnmappableCharacter} method, passing the new action.

+ * + * @param newAction The new action; must not be null + * + * @return This encoder + * + * @throws IllegalArgumentException + * If the precondition on the parameter does not hold + */ +// public final CharsetEncoder onUnmappableCharacter(CodingErrorAction +// newAction) +// { +// if (newAction == null) +// throw new IllegalArgumentException("Null action"); +// unmappableCharacterAction = newAction; +// implOnUnmappableCharacter(newAction); +// return this; +// } + + /** + * Reports a change to this encoder's unmappable-character action. + * + *

The default implementation of this method does nothing. This method + * should be overridden by encoders that require notification of changes to + * the unmappable-character action.

+ */ +// protected void implOnUnmappableCharacter(CodingErrorAction newAction) { } + + /** + * Returns the average number of bytes that will be produced for each + * character of input. This heuristic value may be used to estimate the size + * of the output buffer required for a given input sequence.

+ * + * @return The average number of bytes produced + * per character of input + */ + public final float averageBytesPerChar() { + return averageBytesPerChar; + } + + /** + * Returns the maximum number of bytes that will be produced for each + * character of input. This value may be used to compute the worst-case size + * of the output buffer required for a given input sequence.

+ * + * @return The maximum number of bytes that will be produced per + * character of input + */ + public final float maxBytesPerChar() { + return maxBytesPerChar; + } + + /** + * Encodes as many characters as possible from the given input buffer, + * writing the results to the given output buffer. + * + *

The buffers are read from, and written to, starting at their current + * positions. At most {@link Buffer#remaining in.remaining()} characters + * will be read and at most {@link Buffer#remaining out.remaining()} + * bytes will be written. The buffers' positions will be advanced to + * reflect the characters read and the bytes written, but their marks and + * limits will not be modified. + * + *

In addition to reading characters from the input buffer and writing + * bytes to the output buffer, this method returns a {@link CoderResult} + * object to describe its reason for termination: + * + *

+ * + * In any case, if this method is to be reinvoked in the same encoding + * operation then care should be taken to preserve any characters remaining + * in the input buffer so that they are available to the next invocation. + * + *

The endOfInput parameter advises this method as to whether + * the invoker can provide further input beyond that contained in the given + * input buffer. If there is a possibility of providing additional input + * then the invoker should pass false for this parameter; if there + * is no possibility of providing further input then the invoker should + * pass true. It is not erroneous, and in fact it is quite + * common, to pass false in one invocation and later discover that + * no further input was actually available. It is critical, however, that + * the final invocation of this method in a sequence of invocations always + * pass true so that any remaining unencoded input will be treated + * as being malformed. + * + *

This method works by invoking the {@link #encodeLoop encodeLoop} + * method, interpreting its results, handling error conditions, and + * reinvoking it as necessary.

+ * + * + * @param in + * The input character buffer + * + * @param out + * The output byte buffer + * + * @param endOfInput + * true if, and only if, the invoker can provide no + * additional input characters beyond those in the given buffer + * + * @return A coder-result object describing the reason for termination + * + * @throws IllegalStateException + * If an encoding operation is already in progress and the previous + * step was an invocation neither of the {@link #reset reset} + * method, nor of this method with a value of false for + * the endOfInput parameter, nor of this method with a + * value of true for the endOfInput parameter + * but a return value indicating an incomplete encoding operation + * + * @throws CoderMalfunctionError + * If an invocation of the encodeLoop method threw + * an unexpected exception + */ +// public final CoderResult encode(CharBuffer in, ByteBuffer out, +// boolean endOfInput) +// { +// int newState = endOfInput ? ST_END : ST_CODING; +// if ((state != ST_RESET) && (state != ST_CODING) +// && !(endOfInput && (state == ST_END))) +// throwIllegalStateException(state, newState); +// state = newState; +// +// for (;;) { +// +// CoderResult cr; +// try { +// cr = encodeLoop(in, out); +// } catch (BufferUnderflowException x) { +// throw new CoderMalfunctionError(x); +// } catch (BufferOverflowException x) { +// throw new CoderMalfunctionError(x); +// } +// +// if (cr.isOverflow()) +// return cr; +// +// if (cr.isUnderflow()) { +// if (endOfInput && in.hasRemaining()) { +// cr = CoderResult.malformedForLength(in.remaining()); +// // Fall through to malformed-input case +// } else { +// return cr; +// } +// } +// +// CodingErrorAction action = null; +// if (cr.isMalformed()) +// action = malformedInputAction; +// else if (cr.isUnmappable()) +// action = unmappableCharacterAction; +// else +// assert false : cr.toString(); +// +// if (action == CodingErrorAction.REPORT) +// return cr; +// +// if (action == CodingErrorAction.REPLACE) { +// if (out.remaining() < replacement.length) +// return CoderResult.OVERFLOW; +// out.put(replacement); +// } +// +// if ((action == CodingErrorAction.IGNORE) +// || (action == CodingErrorAction.REPLACE)) { +// // Skip erroneous input either way +// in.position(in.position() + cr.length()); +// continue; +// } +// +// assert false; +// } +// +// } + + /** + * Flushes this encoder. + * + *

Some encoders maintain internal state and may need to write some + * final bytes to the output buffer once the overall input sequence has + * been read. + * + *

Any additional output is written to the output buffer beginning at + * its current position. At most {@link Buffer#remaining out.remaining()} + * bytes will be written. The buffer's position will be advanced + * appropriately, but its mark and limit will not be modified. + * + *

If this method completes successfully then it returns {@link + * CoderResult#UNDERFLOW}. If there is insufficient room in the output + * buffer then it returns {@link CoderResult#OVERFLOW}. If this happens + * then this method must be invoked again, with an output buffer that has + * more room, in order to complete the current encoding + * operation. + * + *

If this encoder has already been flushed then invoking this method + * has no effect. + * + *

This method invokes the {@link #implFlush implFlush} method to + * perform the actual flushing operation.

+ * + * @param out + * The output byte buffer + * + * @return A coder-result object, either {@link CoderResult#UNDERFLOW} or + * {@link CoderResult#OVERFLOW} + * + * @throws IllegalStateException + * If the previous step of the current encoding operation was an + * invocation neither of the {@link #flush flush} method nor of + * the three-argument {@link + * #encode(CharBuffer,ByteBuffer,boolean) encode} method + * with a value of true for the endOfInput + * parameter + */ +// public final CoderResult flush(ByteBuffer out) { +// if (state == ST_END) { +// CoderResult cr = implFlush(out); +// if (cr.isUnderflow()) +// state = ST_FLUSHED; +// return cr; +// } +// +// if (state != ST_FLUSHED) +// throwIllegalStateException(state, ST_FLUSHED); +// +// return CoderResult.UNDERFLOW; // Already flushed +// } + + /** + * Flushes this encoder. + * + *

The default implementation of this method does nothing, and always + * returns {@link CoderResult#UNDERFLOW}. This method should be overridden + * by encoders that may need to write final bytes to the output buffer + * once the entire input sequence has been read.

+ * + * @param out + * The output byte buffer + * + * @return A coder-result object, either {@link CoderResult#UNDERFLOW} or + * {@link CoderResult#OVERFLOW} + */ +// protected CoderResult implFlush(ByteBuffer out) { +// return CoderResult.UNDERFLOW; +// } + + /** + * Resets this encoder, clearing any internal state. + * + *

This method resets charset-independent state and also invokes the + * {@link #implReset() implReset} method in order to perform any + * charset-specific reset actions.

+ * + * @return This encoder + * + */ + public final CharsetEncoder reset() { + implReset(); + state = ST_RESET; + return this; + } + + /** + * Resets this encoder, clearing any charset-specific internal state. + * + *

The default implementation of this method does nothing. This method + * should be overridden by encoders that maintain internal state.

+ */ + protected void implReset() { } + + /** + * Encodes one or more characters into one or more bytes. + * + *

This method encapsulates the basic encoding loop, encoding as many + * characters as possible until it either runs out of input, runs out of room + * in the output buffer, or encounters an encoding error. This method is + * invoked by the {@link #encode encode} method, which handles result + * interpretation and error recovery. + * + *

The buffers are read from, and written to, starting at their current + * positions. At most {@link Buffer#remaining in.remaining()} characters + * will be read, and at most {@link Buffer#remaining out.remaining()} + * bytes will be written. The buffers' positions will be advanced to + * reflect the characters read and the bytes written, but their marks and + * limits will not be modified. + * + *

This method returns a {@link CoderResult} object to describe its + * reason for termination, in the same manner as the {@link #encode encode} + * method. Most implementations of this method will handle encoding errors + * by returning an appropriate result object for interpretation by the + * {@link #encode encode} method. An optimized implementation may instead + * examine the relevant error action and implement that action itself. + * + *

An implementation of this method may perform arbitrary lookahead by + * returning {@link CoderResult#UNDERFLOW} until it receives sufficient + * input.

+ * + * @param in + * The input character buffer + * + * @param out + * The output byte buffer + * + * @return A coder-result object describing the reason for termination + */ +// protected abstract CoderResult encodeLoop(CharBuffer in, +// ByteBuffer out); + + /** + * Convenience method that encodes the remaining content of a single input + * character buffer into a newly-allocated byte buffer. + * + *

This method implements an entire encoding + * operation; that is, it resets this encoder, then it encodes the + * characters in the given character buffer, and finally it flushes this + * encoder. This method should therefore not be invoked if an encoding + * operation is already in progress.

+ * + * @param in + * The input character buffer + * + * @return A newly-allocated byte buffer containing the result of the + * encoding operation. The buffer's position will be zero and its + * limit will follow the last byte written. + * + * @throws IllegalStateException + * If an encoding operation is already in progress + * + * @throws MalformedInputException + * If the character sequence starting at the input buffer's current + * position is not a legal sixteen-bit Unicode sequence and the current malformed-input action + * is {@link CodingErrorAction#REPORT} + * + * @throws UnmappableCharacterException + * If the character sequence starting at the input buffer's current + * position cannot be mapped to an equivalent byte sequence and + * the current unmappable-character action is {@link + * CodingErrorAction#REPORT} + */ +// public final ByteBuffer encode(CharBuffer in) +// throws CharacterCodingException +// { +// int n = (int)(in.remaining() * averageBytesPerChar()); +// ByteBuffer out = ByteBuffer.allocate(n); +// +// if ((n == 0) && (in.remaining() == 0)) +// return out; +// reset(); +// for (;;) { +// CoderResult cr = in.hasRemaining() ? +// encode(in, out, true) : CoderResult.UNDERFLOW; +// if (cr.isUnderflow()) +// cr = flush(out); +// +// if (cr.isUnderflow()) +// break; +// if (cr.isOverflow()) { +// n = 2*n + 1; // Ensure progress; n might be 0! +// ByteBuffer o = ByteBuffer.allocate(n); +// out.flip(); +// o.put(out); +// out = o; +// continue; +// } +// cr.throwException(); +// } +// out.flip(); +// return out; +// } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// private boolean canEncode(CharBuffer cb) { +// if (state == ST_FLUSHED) +// reset(); +// else if (state != ST_RESET) +// throwIllegalStateException(state, ST_CODING); +// CodingErrorAction ma = malformedInputAction(); +// CodingErrorAction ua = unmappableCharacterAction(); +// try { +// onMalformedInput(CodingErrorAction.REPORT); +// onUnmappableCharacter(CodingErrorAction.REPORT); +// encode(cb); +// } catch (CharacterCodingException x) { +// return false; +// } finally { +// onMalformedInput(ma); +// onUnmappableCharacter(ua); +// reset(); +// } +// return true; +// } + + /** + * Tells whether or not this encoder can encode the given character. + * + *

This method returns false if the given character is a + * surrogate character; such characters can be interpreted only when they + * are members of a pair consisting of a high surrogate followed by a low + * surrogate. The {@link #canEncode(java.lang.CharSequence) + * canEncode(CharSequence)} method may be used to test whether or not a + * character sequence can be encoded. + * + *

This method may modify this encoder's state; it should therefore not + * be invoked if an encoding operation is already in + * progress. + * + *

The default implementation of this method is not very efficient; it + * should generally be overridden to improve performance.

+ * + * @return true if, and only if, this encoder can encode + * the given character + * + * @throws IllegalStateException + * If an encoding operation is already in progress + */ +// public boolean canEncode(char c) { +// CharBuffer cb = CharBuffer.allocate(1); +// cb.put(c); +// cb.flip(); +// return canEncode(cb); +// } + + /** + * Tells whether or not this encoder can encode the given character + * sequence. + * + *

If this method returns false for a particular character + * sequence then more information about why the sequence cannot be encoded + * may be obtained by performing a full encoding + * operation. + * + *

This method may modify this encoder's state; it should therefore not + * be invoked if an encoding operation is already in progress. + * + *

The default implementation of this method is not very efficient; it + * should generally be overridden to improve performance.

+ * + * @return true if, and only if, this encoder can encode + * the given character without throwing any exceptions and without + * performing any replacements + * + * @throws IllegalStateException + * If an encoding operation is already in progress + */ +// public boolean canEncode(CharSequence cs) { +// CharBuffer cb; +// if (cs instanceof CharBuffer) +// cb = ((CharBuffer)cs).duplicate(); +// else +// cb = CharBuffer.wrap(cs.toString()); +// return canEncode(cb); +// } + + + + + private void throwIllegalStateException(int from, int to) { + throw new IllegalStateException("Current state = " + stateNames[from] + + ", new state = " + stateNames[to]); + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/nio/charset/IllegalCharsetNameException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/nio/charset/IllegalCharsetNameException.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved. + * + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +// -- This file was mechanically generated: Do not edit! -- // + +package java.nio.charset; + + +/** + * Unchecked exception thrown when a string that is not a + * legal charset name is used as such. + * + * @since 1.4 + */ + +public class IllegalCharsetNameException + extends IllegalArgumentException +{ + + private static final long serialVersionUID = 1457525358470002989L; + + private String charsetName; + + /** + * Constructs an instance of this class.

+ * + * @param charsetName + * The illegal charset name + */ + public IllegalCharsetNameException(String charsetName) { + super(String.valueOf(charsetName)); + this.charsetName = charsetName; + } + + /** + * Retrieves the illegal charset name.

+ * + * @return The illegal charset name + */ + public String getCharsetName() { + return charsetName; + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/nio/charset/UnsupportedCharsetException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/nio/charset/UnsupportedCharsetException.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved. + * + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +// -- This file was mechanically generated: Do not edit! -- // + +package java.nio.charset; + + +/** + * Unchecked exception thrown when no support is available + * for a requested charset. + * + * @since 1.4 + */ + +public class UnsupportedCharsetException + extends IllegalArgumentException +{ + + private static final long serialVersionUID = 1490765524727386367L; + + private String charsetName; + + /** + * Constructs an instance of this class.

+ * + * @param charsetName + * The name of the unsupported charset + */ + public UnsupportedCharsetException(String charsetName) { + super(String.valueOf(charsetName)); + this.charsetName = charsetName; + } + + /** + * Retrieves the name of the unsupported charset.

+ * + * @return The name of the unsupported charset + */ + public String getCharsetName() { + return charsetName; + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/security/AccessController.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/security/AccessController.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,504 @@ +/* + * 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.security; + +/** + *

The AccessController class is used for access control operations + * and decisions. + * + *

More specifically, the AccessController class is used for + * three purposes: + * + *

    + *
  • to decide whether an access to a critical system + * resource is to be allowed or denied, based on the security policy + * currently in effect,

    + *

  • to mark code as being "privileged", thus affecting subsequent + * access determinations, and

    + *

  • to obtain a "snapshot" of the current calling context so + * access-control decisions from a different context can be made with + * respect to the saved context.
+ * + *

The {@link #checkPermission(Permission) checkPermission} method + * determines whether the access request indicated by a specified + * permission should be granted or denied. A sample call appears + * below. In this example, checkPermission will determine + * whether or not to grant "read" access to the file named "testFile" in + * the "/temp" directory. + * + *

+ *
+ * FilePermission perm = new FilePermission("/temp/testFile", "read");
+ * AccessController.checkPermission(perm);
+ *
+ * 
+ * + *

If a requested access is allowed, + * checkPermission returns quietly. If denied, an + * AccessControlException is + * thrown. AccessControlException can also be thrown if the requested + * permission is of an incorrect type or contains an invalid value. + * Such information is given whenever possible. + * + * Suppose the current thread traversed m callers, in the order of caller 1 + * to caller 2 to caller m. Then caller m invoked the + * checkPermission method. + * The checkPermission method determines whether access + * is granted or denied based on the following algorithm: + * + *

 {@code
+ * for (int i = m; i > 0; i--) {
+ *
+ *     if (caller i's domain does not have the permission)
+ *         throw AccessControlException
+ *
+ *     else if (caller i is marked as privileged) {
+ *         if (a context was specified in the call to doPrivileged)
+ *             context.checkPermission(permission)
+ *         return;
+ *     }
+ * };
+ *
+ * // Next, check the context inherited when the thread was created.
+ * // Whenever a new thread is created, the AccessControlContext at
+ * // that time is stored and associated with the new thread, as the
+ * // "inherited" context.
+ *
+ * inheritedContext.checkPermission(permission);
+ * }
+ * + *

A caller can be marked as being "privileged" + * (see {@link #doPrivileged(PrivilegedAction) doPrivileged} and below). + * When making access control decisions, the checkPermission + * method stops checking if it reaches a caller that + * was marked as "privileged" via a doPrivileged + * call without a context argument (see below for information about a + * context argument). If that caller's domain has the + * specified permission, no further checking is done and + * checkPermission + * returns quietly, indicating that the requested access is allowed. + * If that domain does not have the specified permission, an exception + * is thrown, as usual. + * + *

The normal use of the "privileged" feature is as follows. If you + * don't need to return a value from within the "privileged" block, do + * the following: + * + *

 {@code
+ * somemethod() {
+ *     ...normal code here...
+ *     AccessController.doPrivileged(new PrivilegedAction() {
+ *         public Void run() {
+ *             // privileged code goes here, for example:
+ *             System.loadLibrary("awt");
+ *             return null; // nothing to return
+ *         }
+ *     });
+ *     ...normal code here...
+ * }}
+ * + *

+ * PrivilegedAction is an interface with a single method, named + * run. + * The above example shows creation of an implementation + * of that interface; a concrete implementation of the + * run method is supplied. + * When the call to doPrivileged is made, an + * instance of the PrivilegedAction implementation is passed + * to it. The doPrivileged method calls the + * run method from the PrivilegedAction + * implementation after enabling privileges, and returns the + * run method's return value as the + * doPrivileged return value (which is + * ignored in this example). + * + *

If you need to return a value, you can do something like the following: + * + *

 {@code
+ * somemethod() {
+ *     ...normal code here...
+ *     String user = AccessController.doPrivileged(
+ *         new PrivilegedAction() {
+ *         public String run() {
+ *             return System.getProperty("user.name");
+ *             }
+ *         });
+ *     ...normal code here...
+ * }}
+ * + *

If the action performed in your run method could + * throw a "checked" exception (those listed in the throws clause + * of a method), then you need to use the + * PrivilegedExceptionAction interface instead of the + * PrivilegedAction interface: + * + *

 {@code
+ * somemethod() throws FileNotFoundException {
+ *     ...normal code here...
+ *     try {
+ *         FileInputStream fis = AccessController.doPrivileged(
+ *         new PrivilegedExceptionAction() {
+ *             public FileInputStream run() throws FileNotFoundException {
+ *                 return new FileInputStream("someFile");
+ *             }
+ *         });
+ *     } catch (PrivilegedActionException e) {
+ *         // e.getException() should be an instance of FileNotFoundException,
+ *         // as only "checked" exceptions will be "wrapped" in a
+ *         // PrivilegedActionException.
+ *         throw (FileNotFoundException) e.getException();
+ *     }
+ *     ...normal code here...
+ *  }}
+ * + *

Be *very* careful in your use of the "privileged" construct, and + * always remember to make the privileged code section as small as possible. + * + *

Note that checkPermission always performs security checks + * within the context of the currently executing thread. + * Sometimes a security check that should be made within a given context + * will actually need to be done from within a + * different context (for example, from within a worker thread). + * The {@link #getContext() getContext} method and + * AccessControlContext class are provided + * for this situation. The getContext method takes a "snapshot" + * of the current calling context, and places + * it in an AccessControlContext object, which it returns. A sample call is + * the following: + * + *

+ *
+ * AccessControlContext acc = AccessController.getContext()
+ *
+ * 
+ * + *

+ * AccessControlContext itself has a checkPermission method + * that makes access decisions based on the context it encapsulates, + * rather than that of the current execution thread. + * Code within a different context can thus call that method on the + * previously-saved AccessControlContext object. A sample call is the + * following: + * + *

+ *
+ * acc.checkPermission(permission)
+ *
+ * 
+ * + *

There are also times where you don't know a priori which permissions + * to check the context against. In these cases you can use the + * doPrivileged method that takes a context: + * + *

 {@code
+ * somemethod() {
+ *     AccessController.doPrivileged(new PrivilegedAction() {
+ *         public Object run() {
+ *             // Code goes here. Any permission checks within this
+ *             // run method will require that the intersection of the
+ *             // callers protection domain and the snapshot's
+ *             // context have the desired permission.
+ *         }
+ *     }, acc);
+ *     ...normal code here...
+ * }}
+ *
+ * @see AccessControlContext
+ *
+ * @author Li Gong
+ * @author Roland Schemers
+ */
+
+public final class AccessController {
+
+    /**
+     * Don't allow anyone to instantiate an AccessController
+     */
+    private AccessController() { }
+
+    /**
+     * Performs the specified PrivilegedAction with privileges
+     * enabled. The action is performed with all of the permissions
+     * possessed by the caller's protection domain.
+     *
+     * 

If the action's run method throws an (unchecked) + * exception, it will propagate through this method. + * + *

Note that any DomainCombiner associated with the current + * AccessControlContext will be ignored while the action is performed. + * + * @param action the action to be performed. + * + * @return the value returned by the action's run method. + * + * @exception NullPointerException if the action is null + * + * @see #doPrivileged(PrivilegedAction,AccessControlContext) + * @see #doPrivileged(PrivilegedExceptionAction) + * @see #doPrivilegedWithCombiner(PrivilegedAction) + * @see java.security.DomainCombiner + */ + + public static T doPrivileged(PrivilegedAction action) { + return action.run(); + } + + /** + * Performs the specified PrivilegedAction with privileges + * enabled. The action is performed with all of the permissions + * possessed by the caller's protection domain. + * + *

If the action's run method throws an (unchecked) + * exception, it will propagate through this method. + * + *

This method preserves the current AccessControlContext's + * DomainCombiner (which may be null) while the action is performed. + * + * @param action the action to be performed. + * + * @return the value returned by the action's run method. + * + * @exception NullPointerException if the action is null + * + * @see #doPrivileged(PrivilegedAction) + * @see java.security.DomainCombiner + * + * @since 1.6 + */ + public static T doPrivilegedWithCombiner(PrivilegedAction action) { + return action.run(); + } + + + /** + * Performs the specified PrivilegedAction with privileges + * enabled and restricted by the specified + * AccessControlContext. + * The action is performed with the intersection of the permissions + * possessed by the caller's protection domain, and those possessed + * by the domains represented by the specified + * AccessControlContext. + *

+ * If the action's run method throws an (unchecked) exception, + * it will propagate through this method. + * + * @param action the action to be performed. + * @param context an access control context + * representing the restriction to be applied to the + * caller's domain's privileges before performing + * the specified action. If the context is + * null, + * then no additional restriction is applied. + * + * @return the value returned by the action's run method. + * + * @exception NullPointerException if the action is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + */ +// public static native T doPrivileged(PrivilegedAction action, +// AccessControlContext context); + + /** + * Performs the specified PrivilegedExceptionAction with + * privileges enabled. The action is performed with all of the + * permissions possessed by the caller's protection domain. + * + *

If the action's run method throws an unchecked + * exception, it will propagate through this method. + * + *

Note that any DomainCombiner associated with the current + * AccessControlContext will be ignored while the action is performed. + * + * @param action the action to be performed + * + * @return the value returned by the action's run method + * + * @exception PrivilegedActionException if the specified action's + * run method threw a checked exception + * @exception NullPointerException if the action is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + * @see #doPrivilegedWithCombiner(PrivilegedExceptionAction) + * @see java.security.DomainCombiner + */ + public static T + doPrivileged(PrivilegedExceptionAction action) + throws PrivilegedActionException { + try { + return action.run(); + } catch (Exception ex) { + throw new PrivilegedActionException(ex); + } + } + + + /** + * Performs the specified PrivilegedExceptionAction with + * privileges enabled. The action is performed with all of the + * permissions possessed by the caller's protection domain. + * + *

If the action's run method throws an unchecked + * exception, it will propagate through this method. + * + *

This method preserves the current AccessControlContext's + * DomainCombiner (which may be null) while the action is performed. + * + * @param action the action to be performed. + * + * @return the value returned by the action's run method + * + * @exception PrivilegedActionException if the specified action's + * run method threw a checked exception + * @exception NullPointerException if the action is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + * @see java.security.DomainCombiner + * + * @since 1.6 + */ + public static T doPrivilegedWithCombiner + (PrivilegedExceptionAction action) throws PrivilegedActionException { + return doPrivileged(action); + } + + /** + * Performs the specified PrivilegedExceptionAction with + * privileges enabled and restricted by the specified + * AccessControlContext. The action is performed with the + * intersection of the permissions possessed by the caller's + * protection domain, and those possessed by the domains represented by the + * specified AccessControlContext. + *

+ * If the action's run method throws an unchecked + * exception, it will propagate through this method. + * + * @param action the action to be performed + * @param context an access control context + * representing the restriction to be applied to the + * caller's domain's privileges before performing + * the specified action. If the context is + * null, + * then no additional restriction is applied. + * + * @return the value returned by the action's run method + * + * @exception PrivilegedActionException if the specified action's + * run method + * threw a checked exception + * @exception NullPointerException if the action is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + */ +// public static native T +// doPrivileged(PrivilegedExceptionAction action, +// AccessControlContext context) +// throws PrivilegedActionException; + + /** + * This method takes a "snapshot" of the current calling context, which + * includes the current Thread's inherited AccessControlContext, + * and places it in an AccessControlContext object. This context may then + * be checked at a later point, possibly in another thread. + * + * @see AccessControlContext + * + * @return the AccessControlContext based on the current context. + */ + +// public static AccessControlContext getContext() +// { +// AccessControlContext acc = getStackAccessControlContext(); +// if (acc == null) { +// // all we had was privileged system code. We don't want +// // to return null though, so we construct a real ACC. +// return new AccessControlContext(null, true); +// } else { +// return acc.optimize(); +// } +// } + + /** + * Determines whether the access request indicated by the + * specified permission should be allowed or denied, based on + * the current AccessControlContext and security policy. + * This method quietly returns if the access request + * is permitted, or throws an AccessControlException otherwise. The + * getPermission method of the AccessControlException returns the + * perm Permission object instance. + * + * @param perm the requested permission. + * + * @exception AccessControlException if the specified permission + * is not permitted, based on the current security policy. + * @exception NullPointerException if the specified permission + * is null and is checked based on the + * security policy currently in effect. + */ + +// public static void checkPermission(Permission perm) +// throws AccessControlException +// { +// //System.err.println("checkPermission "+perm); +// //Thread.currentThread().dumpStack(); +// +// if (perm == null) { +// throw new NullPointerException("permission can't be null"); +// } +// +// AccessControlContext stack = getStackAccessControlContext(); +// // if context is null, we had privileged system code on the stack. +// if (stack == null) { +// Debug debug = AccessControlContext.getDebug(); +// boolean dumpDebug = false; +// if (debug != null) { +// dumpDebug = !Debug.isOn("codebase="); +// dumpDebug &= !Debug.isOn("permission=") || +// Debug.isOn("permission=" + perm.getClass().getCanonicalName()); +// } +// +// if (dumpDebug && Debug.isOn("stack")) { +// Thread.currentThread().dumpStack(); +// } +// +// if (dumpDebug && Debug.isOn("domain")) { +// debug.println("domain (context is null)"); +// } +// +// if (dumpDebug) { +// debug.println("access allowed "+perm); +// } +// return; +// } +// +// AccessControlContext acc = stack.optimize(); +// acc.checkPermission(perm); +// } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/security/PrivilegedAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/security/PrivilegedAction.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,56 @@ +/* + * Copyright (c) 1998, 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 java.security; + + +/** + * A computation to be performed with privileges enabled. The computation is + * performed by invoking AccessController.doPrivileged on the + * PrivilegedAction object. This interface is used only for + * computations that do not throw checked exceptions; computations that + * throw checked exceptions must use PrivilegedExceptionAction + * instead. + * + * @see AccessController + * @see AccessController#doPrivileged(PrivilegedAction) + * @see PrivilegedExceptionAction + */ + +public interface PrivilegedAction { + /** + * Performs the computation. This method will be called by + * AccessController.doPrivileged after enabling privileges. + * + * @return a class-dependent value that may represent the results of the + * computation. Each class that implements + * PrivilegedAction + * should document what (if anything) this value represents. + * @see AccessController#doPrivileged(PrivilegedAction) + * @see AccessController#doPrivileged(PrivilegedAction, + * AccessControlContext) + */ + T run(); +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/security/PrivilegedActionException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/security/PrivilegedActionException.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,105 @@ +/* + * Copyright (c) 1998, 2001, 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.security; + +/** + * This exception is thrown by + * doPrivileged(PrivilegedExceptionAction) and + * doPrivileged(PrivilegedExceptionAction, + * AccessControlContext context) to indicate + * that the action being performed threw a checked exception. The exception + * thrown by the action can be obtained by calling the + * getException method. In effect, an + * PrivilegedActionException is a "wrapper" + * for an exception thrown by a privileged action. + * + *

As of release 1.4, this exception has been retrofitted to conform to + * the general purpose exception-chaining mechanism. The "exception thrown + * by the privileged computation" that is provided at construction time and + * accessed via the {@link #getException()} method is now known as the + * cause, and may be accessed via the {@link Throwable#getCause()} + * method, as well as the aforementioned "legacy method." + * + * @see PrivilegedExceptionAction + * @see AccessController#doPrivileged(PrivilegedExceptionAction) + * @see AccessController#doPrivileged(PrivilegedExceptionAction,AccessControlContext) + */ +public class PrivilegedActionException extends Exception { + // use serialVersionUID from JDK 1.2.2 for interoperability + private static final long serialVersionUID = 4724086851538908602L; + + /** + * @serial + */ + private Exception exception; + + /** + * Constructs a new PrivilegedActionException "wrapping" + * the specific Exception. + * + * @param exception The exception thrown + */ + public PrivilegedActionException(Exception exception) { + super((Throwable)null); // Disallow initCause + this.exception = exception; + } + + /** + * Returns the exception thrown by the privileged computation that + * resulted in this PrivilegedActionException. + * + *

This method predates the general-purpose exception chaining facility. + * The {@link Throwable#getCause()} method is now the preferred means of + * obtaining this information. + * + * @return the exception thrown by the privileged computation that + * resulted in this PrivilegedActionException. + * @see PrivilegedExceptionAction + * @see AccessController#doPrivileged(PrivilegedExceptionAction) + * @see AccessController#doPrivileged(PrivilegedExceptionAction, + * AccessControlContext) + */ + public Exception getException() { + return exception; + } + + /** + * Returns the cause of this exception (the exception thrown by + * the privileged computation that resulted in this + * PrivilegedActionException). + * + * @return the cause of this exception. + * @since 1.4 + */ + public Throwable getCause() { + return exception; + } + + public String toString() { + String s = getClass().getName(); + return (exception != null) ? (s + ": " + exception.toString()) : s; + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/security/PrivilegedExceptionAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/security/PrivilegedExceptionAction.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,62 @@ +/* + * Copyright (c) 1998, 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 java.security; + + +/** + * A computation to be performed with privileges enabled, that throws one or + * more checked exceptions. The computation is performed by invoking + * AccessController.doPrivileged on the + * PrivilegedExceptionAction object. This interface is + * used only for computations that throw checked exceptions; + * computations that do not throw + * checked exceptions should use PrivilegedAction instead. + * + * @see AccessController + * @see AccessController#doPrivileged(PrivilegedExceptionAction) + * @see AccessController#doPrivileged(PrivilegedExceptionAction, + * AccessControlContext) + * @see PrivilegedAction + */ + +public interface PrivilegedExceptionAction { + /** + * Performs the computation. This method will be called by + * AccessController.doPrivileged after enabling privileges. + * + * @return a class-dependent value that may represent the results of the + * computation. Each class that implements + * PrivilegedExceptionAction should document what + * (if anything) this value represents. + * @throws Exception an exceptional condition has occurred. Each class + * that implements PrivilegedExceptionAction should + * document the exceptions that its run method can throw. + * @see AccessController#doPrivileged(PrivilegedExceptionAction) + * @see AccessController#doPrivileged(PrivilegedExceptionAction,AccessControlContext) + */ + + T run() throws Exception; +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/Annotation.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/Annotation.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,84 @@ +/* + * Copyright (c) 1997, 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.text; + +/** +* An Annotation object is used as a wrapper for a text attribute value if +* the attribute has annotation characteristics. These characteristics are: +*

    +*
  • The text range that the attribute is applied to is critical to the +* semantics of the range. That means, the attribute cannot be applied to subranges +* of the text range that it applies to, and, if two adjacent text ranges have +* the same value for this attribute, the attribute still cannot be applied to +* the combined range as a whole with this value. +*
  • The attribute or its value usually do no longer apply if the underlying text is +* changed. +*
+* +* An example is grammatical information attached to a sentence: +* For the previous sentence, you can say that "an example" +* is the subject, but you cannot say the same about "an", "example", or "exam". +* When the text is changed, the grammatical information typically becomes invalid. +* Another example is Japanese reading information (yomi). +* +*

+* Wrapping the attribute value into an Annotation object guarantees that +* adjacent text runs don't get merged even if the attribute values are equal, +* and indicates to text containers that the attribute should be discarded if +* the underlying text is modified. +* +* @see AttributedCharacterIterator +* @since 1.2 +*/ + +public class Annotation { + + /** + * Constructs an annotation record with the given value, which + * may be null. + * @param value The value of the attribute + */ + public Annotation(Object value) { + this.value = value; + } + + /** + * Returns the value of the attribute, which may be null. + */ + public Object getValue() { + return value; + } + + /** + * Returns the String representation of this Annotation. + */ + public String toString() { + return getClass().getName() + "[value=" + value + "]"; + } + + private Object value; + +}; diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/AttributedCharacterIterator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/AttributedCharacterIterator.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,254 @@ +/* + * Copyright (c) 1997, 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.text; + +import java.io.InvalidObjectException; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * An {@code AttributedCharacterIterator} allows iteration through both text and + * related attribute information. + * + *

+ * An attribute is a key/value pair, identified by the key. No two + * attributes on a given character can have the same key. + * + *

The values for an attribute are immutable, or must not be mutated + * by clients or storage. They are always passed by reference, and not + * cloned. + * + *

A run with respect to an attribute is a maximum text range for + * which: + *

    + *
  • the attribute is undefined or {@code null} for the entire range, or + *
  • the attribute value is defined and has the same non-{@code null} value for the + * entire range. + *
+ * + *

A run with respect to a set of attributes is a maximum text range for + * which this condition is met for each member attribute. + * + *

When getting a run with no explicit attributes specified (i.e., + * calling {@link #getRunStart()} and {@link #getRunLimit()}), any + * contiguous text segments having the same attributes (the same set + * of attribute/value pairs) are treated as separate runs if the + * attributes have been given to those text segments separately. + * + *

The returned indexes are limited to the range of the iterator. + * + *

The returned attribute information is limited to runs that contain + * the current character. + * + *

+ * Attribute keys are instances of {@link AttributedCharacterIterator.Attribute} and its + * subclasses, such as {@link java.awt.font.TextAttribute}. + * + * @see AttributedCharacterIterator.Attribute + * @see java.awt.font.TextAttribute + * @see AttributedString + * @see Annotation + * @since 1.2 + */ + +public interface AttributedCharacterIterator extends CharacterIterator { + + /** + * Defines attribute keys that are used to identify text attributes. These + * keys are used in {@code AttributedCharacterIterator} and {@code AttributedString}. + * @see AttributedCharacterIterator + * @see AttributedString + * @since 1.2 + */ + + public static class Attribute implements Serializable { + + /** + * The name of this {@code Attribute}. The name is used primarily by {@code readResolve} + * to look up the corresponding predefined instance when deserializing + * an instance. + * @serial + */ + private String name; + + // table of all instances in this class, used by readResolve + private static final Map instanceMap = new HashMap(7); + + /** + * Constructs an {@code Attribute} with the given name. + */ + protected Attribute(String name) { + this.name = name; + if (this.getClass() == Attribute.class) { + instanceMap.put(name, this); + } + } + + /** + * Compares two objects for equality. This version only returns true + * for x.equals(y) if x and y refer + * to the same object, and guarantees this for all subclasses. + */ + public final boolean equals(Object obj) { + return super.equals(obj); + } + + /** + * Returns a hash code value for the object. This version is identical to + * the one in {@code Object}, but is also final. + */ + public final int hashCode() { + return super.hashCode(); + } + + /** + * Returns a string representation of the object. This version returns the + * concatenation of class name, {@code "("}, a name identifying the attribute + * and {@code ")"}. + */ + public String toString() { + return getClass().getName() + "(" + name + ")"; + } + + /** + * Returns the name of the attribute. + */ + protected String getName() { + return name; + } + + /** + * Resolves instances being deserialized to the predefined constants. + */ + protected Object readResolve() throws InvalidObjectException { + if (this.getClass() != Attribute.class) { + throw new InvalidObjectException("subclass didn't correctly implement readResolve"); + } + + Attribute instance = (Attribute) instanceMap.get(getName()); + if (instance != null) { + return instance; + } else { + throw new InvalidObjectException("unknown attribute name"); + } + } + + /** + * Attribute key for the language of some text. + *

Values are instances of {@link java.util.Locale Locale}. + * @see java.util.Locale + */ + public static final Attribute LANGUAGE = new Attribute("language"); + + /** + * Attribute key for the reading of some text. In languages where the written form + * and the pronunciation of a word are only loosely related (such as Japanese), + * it is often necessary to store the reading (pronunciation) along with the + * written form. + *

Values are instances of {@link Annotation} holding instances of {@link String}. + * @see Annotation + * @see java.lang.String + */ + public static final Attribute READING = new Attribute("reading"); + + /** + * Attribute key for input method segments. Input methods often break + * up text into segments, which usually correspond to words. + *

Values are instances of {@link Annotation} holding a {@code null} reference. + * @see Annotation + */ + public static final Attribute INPUT_METHOD_SEGMENT = new Attribute("input_method_segment"); + + // make sure the serial version doesn't change between compiler versions + private static final long serialVersionUID = -9142742483513960612L; + + }; + + /** + * Returns the index of the first character of the run + * with respect to all attributes containing the current character. + * + *

Any contiguous text segments having the same attributes (the + * same set of attribute/value pairs) are treated as separate runs + * if the attributes have been given to those text segments separately. + */ + public int getRunStart(); + + /** + * Returns the index of the first character of the run + * with respect to the given {@code attribute} containing the current character. + */ + public int getRunStart(Attribute attribute); + + /** + * Returns the index of the first character of the run + * with respect to the given {@code attributes} containing the current character. + */ + public int getRunStart(Set attributes); + + /** + * Returns the index of the first character following the run + * with respect to all attributes containing the current character. + * + *

Any contiguous text segments having the same attributes (the + * same set of attribute/value pairs) are treated as separate runs + * if the attributes have been given to those text segments separately. + */ + public int getRunLimit(); + + /** + * Returns the index of the first character following the run + * with respect to the given {@code attribute} containing the current character. + */ + public int getRunLimit(Attribute attribute); + + /** + * Returns the index of the first character following the run + * with respect to the given {@code attributes} containing the current character. + */ + public int getRunLimit(Set attributes); + + /** + * Returns a map with the attributes defined on the current + * character. + */ + public Map getAttributes(); + + /** + * Returns the value of the named {@code attribute} for the current character. + * Returns {@code null} if the {@code attribute} is not defined. + */ + public Object getAttribute(Attribute attribute); + + /** + * Returns the keys of all attributes defined on the + * iterator's text range. The set is empty if no + * attributes are defined. + */ + public Set getAllAttributeKeys(); +}; diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/AttributedString.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/AttributedString.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1120 @@ +/* + * Copyright (c) 1997, 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.text; + +import java.util.*; +import java.text.AttributedCharacterIterator.Attribute; + +/** + * An AttributedString holds text and related attribute information. It + * may be used as the actual data storage in some cases where a text + * reader wants to access attributed text through the AttributedCharacterIterator + * interface. + * + *

+ * An attribute is a key/value pair, identified by the key. No two + * attributes on a given character can have the same key. + * + *

The values for an attribute are immutable, or must not be mutated + * by clients or storage. They are always passed by reference, and not + * cloned. + * + * @see AttributedCharacterIterator + * @see Annotation + * @since 1.2 + */ + +public class AttributedString { + + // since there are no vectors of int, we have to use arrays. + // We allocate them in chunks of 10 elements so we don't have to allocate all the time. + private static final int ARRAY_SIZE_INCREMENT = 10; + + // field holding the text + String text; + + // fields holding run attribute information + // run attributes are organized by run + int runArraySize; // current size of the arrays + int runCount; // actual number of runs, <= runArraySize + int runStarts[]; // start index for each run + Vector runAttributes[]; // vector of attribute keys for each run + Vector runAttributeValues[]; // parallel vector of attribute values for each run + + /** + * Constructs an AttributedString instance with the given + * AttributedCharacterIterators. + * + * @param iterators AttributedCharacterIterators to construct + * AttributedString from. + * @throws NullPointerException if iterators is null + */ + AttributedString(AttributedCharacterIterator[] iterators) { + if (iterators == null) { + throw new NullPointerException("Iterators must not be null"); + } + if (iterators.length == 0) { + text = ""; + } + else { + // Build the String contents + StringBuffer buffer = new StringBuffer(); + for (int counter = 0; counter < iterators.length; counter++) { + appendContents(buffer, iterators[counter]); + } + + text = buffer.toString(); + + if (text.length() > 0) { + // Determine the runs, creating a new run when the attributes + // differ. + int offset = 0; + Map last = null; + + for (int counter = 0; counter < iterators.length; counter++) { + AttributedCharacterIterator iterator = iterators[counter]; + int start = iterator.getBeginIndex(); + int end = iterator.getEndIndex(); + int index = start; + + while (index < end) { + iterator.setIndex(index); + + Map attrs = iterator.getAttributes(); + + if (mapsDiffer(last, attrs)) { + setAttributes(attrs, index - start + offset); + } + last = attrs; + index = iterator.getRunLimit(); + } + offset += (end - start); + } + } + } + } + + /** + * Constructs an AttributedString instance with the given text. + * @param text The text for this attributed string. + * @exception NullPointerException if text is null. + */ + public AttributedString(String text) { + if (text == null) { + throw new NullPointerException(); + } + this.text = text; + } + + /** + * Constructs an AttributedString instance with the given text and attributes. + * @param text The text for this attributed string. + * @param attributes The attributes that apply to the entire string. + * @exception NullPointerException if text or + * attributes is null. + * @exception IllegalArgumentException if the text has length 0 + * and the attributes parameter is not an empty Map (attributes + * cannot be applied to a 0-length range). + */ + public AttributedString(String text, + Map attributes) + { + if (text == null || attributes == null) { + throw new NullPointerException(); + } + this.text = text; + + if (text.length() == 0) { + if (attributes.isEmpty()) + return; + throw new IllegalArgumentException("Can't add attribute to 0-length text"); + } + + int attributeCount = attributes.size(); + if (attributeCount > 0) { + createRunAttributeDataVectors(); + Vector newRunAttributes = new Vector(attributeCount); + Vector newRunAttributeValues = new Vector(attributeCount); + runAttributes[0] = newRunAttributes; + runAttributeValues[0] = newRunAttributeValues; + Iterator iterator = attributes.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + newRunAttributes.addElement(entry.getKey()); + newRunAttributeValues.addElement(entry.getValue()); + } + } + } + + /** + * Constructs an AttributedString instance with the given attributed + * text represented by AttributedCharacterIterator. + * @param text The text for this attributed string. + * @exception NullPointerException if text is null. + */ + public AttributedString(AttributedCharacterIterator text) { + // If performance is critical, this constructor should be + // implemented here rather than invoking the constructor for a + // subrange. We can avoid some range checking in the loops. + this(text, text.getBeginIndex(), text.getEndIndex(), null); + } + + /** + * Constructs an AttributedString instance with the subrange of + * the given attributed text represented by + * AttributedCharacterIterator. If the given range produces an + * empty text, all attributes will be discarded. Note that any + * attributes wrapped by an Annotation object are discarded for a + * subrange of the original attribute range. + * + * @param text The text for this attributed string. + * @param beginIndex Index of the first character of the range. + * @param endIndex Index of the character following the last character + * of the range. + * @exception NullPointerException if text is null. + * @exception IllegalArgumentException if the subrange given by + * beginIndex and endIndex is out of the text range. + * @see java.text.Annotation + */ + public AttributedString(AttributedCharacterIterator text, + int beginIndex, + int endIndex) { + this(text, beginIndex, endIndex, null); + } + + /** + * Constructs an AttributedString instance with the subrange of + * the given attributed text represented by + * AttributedCharacterIterator. Only attributes that match the + * given attributes will be incorporated into the instance. If the + * given range produces an empty text, all attributes will be + * discarded. Note that any attributes wrapped by an Annotation + * object are discarded for a subrange of the original attribute + * range. + * + * @param text The text for this attributed string. + * @param beginIndex Index of the first character of the range. + * @param endIndex Index of the character following the last character + * of the range. + * @param attributes Specifies attributes to be extracted + * from the text. If null is specified, all available attributes will + * be used. + * @exception NullPointerException if text is null. + * @exception IllegalArgumentException if the subrange given by + * beginIndex and endIndex is out of the text range. + * @see java.text.Annotation + */ + public AttributedString(AttributedCharacterIterator text, + int beginIndex, + int endIndex, + Attribute[] attributes) { + if (text == null) { + throw new NullPointerException(); + } + + // Validate the given subrange + int textBeginIndex = text.getBeginIndex(); + int textEndIndex = text.getEndIndex(); + if (beginIndex < textBeginIndex || endIndex > textEndIndex || beginIndex > endIndex) + throw new IllegalArgumentException("Invalid substring range"); + + // Copy the given string + StringBuffer textBuffer = new StringBuffer(); + text.setIndex(beginIndex); + for (char c = text.current(); text.getIndex() < endIndex; c = text.next()) + textBuffer.append(c); + this.text = textBuffer.toString(); + + if (beginIndex == endIndex) + return; + + // Select attribute keys to be taken care of + HashSet keys = new HashSet(); + if (attributes == null) { + keys.addAll(text.getAllAttributeKeys()); + } else { + for (int i = 0; i < attributes.length; i++) + keys.add(attributes[i]); + keys.retainAll(text.getAllAttributeKeys()); + } + if (keys.isEmpty()) + return; + + // Get and set attribute runs for each attribute name. Need to + // scan from the top of the text so that we can discard any + // Annotation that is no longer applied to a subset text segment. + Iterator itr = keys.iterator(); + while (itr.hasNext()) { + Attribute attributeKey = (Attribute)itr.next(); + text.setIndex(textBeginIndex); + while (text.getIndex() < endIndex) { + int start = text.getRunStart(attributeKey); + int limit = text.getRunLimit(attributeKey); + Object value = text.getAttribute(attributeKey); + + if (value != null) { + if (value instanceof Annotation) { + if (start >= beginIndex && limit <= endIndex) { + addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex); + } else { + if (limit > endIndex) + break; + } + } else { + // if the run is beyond the given (subset) range, we + // don't need to process further. + if (start >= endIndex) + break; + if (limit > beginIndex) { + // attribute is applied to any subrange + if (start < beginIndex) + start = beginIndex; + if (limit > endIndex) + limit = endIndex; + if (start != limit) { + addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex); + } + } + } + } + text.setIndex(limit); + } + } + } + + /** + * Adds an attribute to the entire string. + * @param attribute the attribute key + * @param value the value of the attribute; may be null + * @exception NullPointerException if attribute is null. + * @exception IllegalArgumentException if the AttributedString has length 0 + * (attributes cannot be applied to a 0-length range). + */ + public void addAttribute(Attribute attribute, Object value) { + + if (attribute == null) { + throw new NullPointerException(); + } + + int len = length(); + if (len == 0) { + throw new IllegalArgumentException("Can't add attribute to 0-length text"); + } + + addAttributeImpl(attribute, value, 0, len); + } + + /** + * Adds an attribute to a subrange of the string. + * @param attribute the attribute key + * @param value The value of the attribute. May be null. + * @param beginIndex Index of the first character of the range. + * @param endIndex Index of the character following the last character of the range. + * @exception NullPointerException if attribute is null. + * @exception IllegalArgumentException if beginIndex is less then 0, endIndex is + * greater than the length of the string, or beginIndex and endIndex together don't + * define a non-empty subrange of the string. + */ + public void addAttribute(Attribute attribute, Object value, + int beginIndex, int endIndex) { + + if (attribute == null) { + throw new NullPointerException(); + } + + if (beginIndex < 0 || endIndex > length() || beginIndex >= endIndex) { + throw new IllegalArgumentException("Invalid substring range"); + } + + addAttributeImpl(attribute, value, beginIndex, endIndex); + } + + /** + * Adds a set of attributes to a subrange of the string. + * @param attributes The attributes to be added to the string. + * @param beginIndex Index of the first character of the range. + * @param endIndex Index of the character following the last + * character of the range. + * @exception NullPointerException if attributes is null. + * @exception IllegalArgumentException if beginIndex is less then + * 0, endIndex is greater than the length of the string, or + * beginIndex and endIndex together don't define a non-empty + * subrange of the string and the attributes parameter is not an + * empty Map. + */ + public void addAttributes(Map attributes, + int beginIndex, int endIndex) + { + if (attributes == null) { + throw new NullPointerException(); + } + + if (beginIndex < 0 || endIndex > length() || beginIndex > endIndex) { + throw new IllegalArgumentException("Invalid substring range"); + } + if (beginIndex == endIndex) { + if (attributes.isEmpty()) + return; + throw new IllegalArgumentException("Can't add attribute to 0-length text"); + } + + // make sure we have run attribute data vectors + if (runCount == 0) { + createRunAttributeDataVectors(); + } + + // break up runs if necessary + int beginRunIndex = ensureRunBreak(beginIndex); + int endRunIndex = ensureRunBreak(endIndex); + + Iterator iterator = attributes.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + addAttributeRunData((Attribute) entry.getKey(), entry.getValue(), beginRunIndex, endRunIndex); + } + } + + private synchronized void addAttributeImpl(Attribute attribute, Object value, + int beginIndex, int endIndex) { + + // make sure we have run attribute data vectors + if (runCount == 0) { + createRunAttributeDataVectors(); + } + + // break up runs if necessary + int beginRunIndex = ensureRunBreak(beginIndex); + int endRunIndex = ensureRunBreak(endIndex); + + addAttributeRunData(attribute, value, beginRunIndex, endRunIndex); + } + + private final void createRunAttributeDataVectors() { + // use temporary variables so things remain consistent in case of an exception + int newRunStarts[] = new int[ARRAY_SIZE_INCREMENT]; + Vector newRunAttributes[] = new Vector[ARRAY_SIZE_INCREMENT]; + Vector newRunAttributeValues[] = new Vector[ARRAY_SIZE_INCREMENT]; + runStarts = newRunStarts; + runAttributes = newRunAttributes; + runAttributeValues = newRunAttributeValues; + runArraySize = ARRAY_SIZE_INCREMENT; + runCount = 1; // assume initial run starting at index 0 + } + + // ensure there's a run break at offset, return the index of the run + private final int ensureRunBreak(int offset) { + return ensureRunBreak(offset, true); + } + + /** + * Ensures there is a run break at offset, returning the index of + * the run. If this results in splitting a run, two things can happen: + *

    + *
  • If copyAttrs is true, the attributes from the existing run + * will be placed in both of the newly created runs. + *
  • If copyAttrs is false, the attributes from the existing run + * will NOT be copied to the run to the right (>= offset) of the break, + * but will exist on the run to the left (< offset). + *
+ */ + private final int ensureRunBreak(int offset, boolean copyAttrs) { + if (offset == length()) { + return runCount; + } + + // search for the run index where this offset should be + int runIndex = 0; + while (runIndex < runCount && runStarts[runIndex] < offset) { + runIndex++; + } + + // if the offset is at a run start already, we're done + if (runIndex < runCount && runStarts[runIndex] == offset) { + return runIndex; + } + + // we'll have to break up a run + // first, make sure we have enough space in our arrays + if (runCount == runArraySize) { + int newArraySize = runArraySize + ARRAY_SIZE_INCREMENT; + int newRunStarts[] = new int[newArraySize]; + Vector newRunAttributes[] = new Vector[newArraySize]; + Vector newRunAttributeValues[] = new Vector[newArraySize]; + for (int i = 0; i < runArraySize; i++) { + newRunStarts[i] = runStarts[i]; + newRunAttributes[i] = runAttributes[i]; + newRunAttributeValues[i] = runAttributeValues[i]; + } + runStarts = newRunStarts; + runAttributes = newRunAttributes; + runAttributeValues = newRunAttributeValues; + runArraySize = newArraySize; + } + + // make copies of the attribute information of the old run that the new one used to be part of + // use temporary variables so things remain consistent in case of an exception + Vector newRunAttributes = null; + Vector newRunAttributeValues = null; + + if (copyAttrs) { + Vector oldRunAttributes = runAttributes[runIndex - 1]; + Vector oldRunAttributeValues = runAttributeValues[runIndex - 1]; + if (oldRunAttributes != null) { + newRunAttributes = (Vector) oldRunAttributes.clone(); + } + if (oldRunAttributeValues != null) { + newRunAttributeValues = (Vector) oldRunAttributeValues.clone(); + } + } + + // now actually break up the run + runCount++; + for (int i = runCount - 1; i > runIndex; i--) { + runStarts[i] = runStarts[i - 1]; + runAttributes[i] = runAttributes[i - 1]; + runAttributeValues[i] = runAttributeValues[i - 1]; + } + runStarts[runIndex] = offset; + runAttributes[runIndex] = newRunAttributes; + runAttributeValues[runIndex] = newRunAttributeValues; + + return runIndex; + } + + // add the attribute attribute/value to all runs where beginRunIndex <= runIndex < endRunIndex + private void addAttributeRunData(Attribute attribute, Object value, + int beginRunIndex, int endRunIndex) { + + for (int i = beginRunIndex; i < endRunIndex; i++) { + int keyValueIndex = -1; // index of key and value in our vectors; assume we don't have an entry yet + if (runAttributes[i] == null) { + Vector newRunAttributes = new Vector(); + Vector newRunAttributeValues = new Vector(); + runAttributes[i] = newRunAttributes; + runAttributeValues[i] = newRunAttributeValues; + } else { + // check whether we have an entry already + keyValueIndex = runAttributes[i].indexOf(attribute); + } + + if (keyValueIndex == -1) { + // create new entry + int oldSize = runAttributes[i].size(); + runAttributes[i].addElement(attribute); + try { + runAttributeValues[i].addElement(value); + } + catch (Exception e) { + runAttributes[i].setSize(oldSize); + runAttributeValues[i].setSize(oldSize); + } + } else { + // update existing entry + runAttributeValues[i].set(keyValueIndex, value); + } + } + } + + /** + * Creates an AttributedCharacterIterator instance that provides access to the entire contents of + * this string. + * + * @return An iterator providing access to the text and its attributes. + */ + public AttributedCharacterIterator getIterator() { + return getIterator(null, 0, length()); + } + + /** + * Creates an AttributedCharacterIterator instance that provides access to + * selected contents of this string. + * Information about attributes not listed in attributes that the + * implementor may have need not be made accessible through the iterator. + * If the list is null, all available attribute information should be made + * accessible. + * + * @param attributes a list of attributes that the client is interested in + * @return an iterator providing access to the entire text and its selected attributes + */ + public AttributedCharacterIterator getIterator(Attribute[] attributes) { + return getIterator(attributes, 0, length()); + } + + /** + * Creates an AttributedCharacterIterator instance that provides access to + * selected contents of this string. + * Information about attributes not listed in attributes that the + * implementor may have need not be made accessible through the iterator. + * If the list is null, all available attribute information should be made + * accessible. + * + * @param attributes a list of attributes that the client is interested in + * @param beginIndex the index of the first character + * @param endIndex the index of the character following the last character + * @return an iterator providing access to the text and its attributes + * @exception IllegalArgumentException if beginIndex is less then 0, + * endIndex is greater than the length of the string, or beginIndex is + * greater than endIndex. + */ + public AttributedCharacterIterator getIterator(Attribute[] attributes, int beginIndex, int endIndex) { + return new AttributedStringIterator(attributes, beginIndex, endIndex); + } + + // all (with the exception of length) reading operations are private, + // since AttributedString instances are accessed through iterators. + + // length is package private so that CharacterIteratorFieldDelegate can + // access it without creating an AttributedCharacterIterator. + int length() { + return text.length(); + } + + private char charAt(int index) { + return text.charAt(index); + } + + private synchronized Object getAttribute(Attribute attribute, int runIndex) { + Vector currentRunAttributes = runAttributes[runIndex]; + Vector currentRunAttributeValues = runAttributeValues[runIndex]; + if (currentRunAttributes == null) { + return null; + } + int attributeIndex = currentRunAttributes.indexOf(attribute); + if (attributeIndex != -1) { + return currentRunAttributeValues.elementAt(attributeIndex); + } + else { + return null; + } + } + + // gets an attribute value, but returns an annotation only if it's range does not extend outside the range beginIndex..endIndex + private Object getAttributeCheckRange(Attribute attribute, int runIndex, int beginIndex, int endIndex) { + Object value = getAttribute(attribute, runIndex); + if (value instanceof Annotation) { + // need to check whether the annotation's range extends outside the iterator's range + if (beginIndex > 0) { + int currIndex = runIndex; + int runStart = runStarts[currIndex]; + while (runStart >= beginIndex && + valuesMatch(value, getAttribute(attribute, currIndex - 1))) { + currIndex--; + runStart = runStarts[currIndex]; + } + if (runStart < beginIndex) { + // annotation's range starts before iterator's range + return null; + } + } + int textLength = length(); + if (endIndex < textLength) { + int currIndex = runIndex; + int runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength; + while (runLimit <= endIndex && + valuesMatch(value, getAttribute(attribute, currIndex + 1))) { + currIndex++; + runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength; + } + if (runLimit > endIndex) { + // annotation's range ends after iterator's range + return null; + } + } + // annotation's range is subrange of iterator's range, + // so we can return the value + } + return value; + } + + // returns whether all specified attributes have equal values in the runs with the given indices + private boolean attributeValuesMatch(Set attributes, int runIndex1, int runIndex2) { + Iterator iterator = attributes.iterator(); + while (iterator.hasNext()) { + Attribute key = (Attribute) iterator.next(); + if (!valuesMatch(getAttribute(key, runIndex1), getAttribute(key, runIndex2))) { + return false; + } + } + return true; + } + + // returns whether the two objects are either both null or equal + private final static boolean valuesMatch(Object value1, Object value2) { + if (value1 == null) { + return value2 == null; + } else { + return value1.equals(value2); + } + } + + /** + * Appends the contents of the CharacterIterator iterator into the + * StringBuffer buf. + */ + private final void appendContents(StringBuffer buf, + CharacterIterator iterator) { + int index = iterator.getBeginIndex(); + int end = iterator.getEndIndex(); + + while (index < end) { + iterator.setIndex(index++); + buf.append(iterator.current()); + } + } + + /** + * Sets the attributes for the range from offset to the next run break + * (typically the end of the text) to the ones specified in attrs. + * This is only meant to be called from the constructor! + */ + private void setAttributes(Map attrs, int offset) { + if (runCount == 0) { + createRunAttributeDataVectors(); + } + + int index = ensureRunBreak(offset, false); + int size; + + if (attrs != null && (size = attrs.size()) > 0) { + Vector runAttrs = new Vector(size); + Vector runValues = new Vector(size); + Iterator iterator = attrs.entrySet().iterator(); + + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry)iterator.next(); + + runAttrs.add(entry.getKey()); + runValues.add(entry.getValue()); + } + runAttributes[index] = runAttrs; + runAttributeValues[index] = runValues; + } + } + + /** + * Returns true if the attributes specified in last and attrs differ. + */ + private static boolean mapsDiffer(Map last, Map attrs) { + if (last == null) { + return (attrs != null && attrs.size() > 0); + } + return (!last.equals(attrs)); + } + + + // the iterator class associated with this string class + + final private class AttributedStringIterator implements AttributedCharacterIterator { + + // note on synchronization: + // we don't synchronize on the iterator, assuming that an iterator is only used in one thread. + // we do synchronize access to the AttributedString however, since it's more likely to be shared between threads. + + // start and end index for our iteration + private int beginIndex; + private int endIndex; + + // attributes that our client is interested in + private Attribute[] relevantAttributes; + + // the current index for our iteration + // invariant: beginIndex <= currentIndex <= endIndex + private int currentIndex; + + // information about the run that includes currentIndex + private int currentRunIndex; + private int currentRunStart; + private int currentRunLimit; + + // constructor + AttributedStringIterator(Attribute[] attributes, int beginIndex, int endIndex) { + + if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) { + throw new IllegalArgumentException("Invalid substring range"); + } + + this.beginIndex = beginIndex; + this.endIndex = endIndex; + this.currentIndex = beginIndex; + updateRunInfo(); + if (attributes != null) { + relevantAttributes = (Attribute[]) attributes.clone(); + } + } + + // Object methods. See documentation in that class. + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AttributedStringIterator)) { + return false; + } + + AttributedStringIterator that = (AttributedStringIterator) obj; + + if (AttributedString.this != that.getString()) + return false; + if (currentIndex != that.currentIndex || beginIndex != that.beginIndex || endIndex != that.endIndex) + return false; + return true; + } + + public int hashCode() { + return text.hashCode() ^ currentIndex ^ beginIndex ^ endIndex; + } + + public Object clone() { + try { + AttributedStringIterator other = (AttributedStringIterator) super.clone(); + return other; + } + catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + // CharacterIterator methods. See documentation in that interface. + + public char first() { + return internalSetIndex(beginIndex); + } + + public char last() { + if (endIndex == beginIndex) { + return internalSetIndex(endIndex); + } else { + return internalSetIndex(endIndex - 1); + } + } + + public char current() { + if (currentIndex == endIndex) { + return DONE; + } else { + return charAt(currentIndex); + } + } + + public char next() { + if (currentIndex < endIndex) { + return internalSetIndex(currentIndex + 1); + } + else { + return DONE; + } + } + + public char previous() { + if (currentIndex > beginIndex) { + return internalSetIndex(currentIndex - 1); + } + else { + return DONE; + } + } + + public char setIndex(int position) { + if (position < beginIndex || position > endIndex) + throw new IllegalArgumentException("Invalid index"); + return internalSetIndex(position); + } + + public int getBeginIndex() { + return beginIndex; + } + + public int getEndIndex() { + return endIndex; + } + + public int getIndex() { + return currentIndex; + } + + // AttributedCharacterIterator methods. See documentation in that interface. + + public int getRunStart() { + return currentRunStart; + } + + public int getRunStart(Attribute attribute) { + if (currentRunStart == beginIndex || currentRunIndex == -1) { + return currentRunStart; + } else { + Object value = getAttribute(attribute); + int runStart = currentRunStart; + int runIndex = currentRunIndex; + while (runStart > beginIndex && + valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex - 1))) { + runIndex--; + runStart = runStarts[runIndex]; + } + if (runStart < beginIndex) { + runStart = beginIndex; + } + return runStart; + } + } + + public int getRunStart(Set attributes) { + if (currentRunStart == beginIndex || currentRunIndex == -1) { + return currentRunStart; + } else { + int runStart = currentRunStart; + int runIndex = currentRunIndex; + while (runStart > beginIndex && + AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex - 1)) { + runIndex--; + runStart = runStarts[runIndex]; + } + if (runStart < beginIndex) { + runStart = beginIndex; + } + return runStart; + } + } + + public int getRunLimit() { + return currentRunLimit; + } + + public int getRunLimit(Attribute attribute) { + if (currentRunLimit == endIndex || currentRunIndex == -1) { + return currentRunLimit; + } else { + Object value = getAttribute(attribute); + int runLimit = currentRunLimit; + int runIndex = currentRunIndex; + while (runLimit < endIndex && + valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex + 1))) { + runIndex++; + runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex; + } + if (runLimit > endIndex) { + runLimit = endIndex; + } + return runLimit; + } + } + + public int getRunLimit(Set attributes) { + if (currentRunLimit == endIndex || currentRunIndex == -1) { + return currentRunLimit; + } else { + int runLimit = currentRunLimit; + int runIndex = currentRunIndex; + while (runLimit < endIndex && + AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex + 1)) { + runIndex++; + runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex; + } + if (runLimit > endIndex) { + runLimit = endIndex; + } + return runLimit; + } + } + + public Map getAttributes() { + if (runAttributes == null || currentRunIndex == -1 || runAttributes[currentRunIndex] == null) { + // ??? would be nice to return null, but current spec doesn't allow it + // returning Hashtable saves AttributeMap from dealing with emptiness + return new Hashtable(); + } + return new AttributeMap(currentRunIndex, beginIndex, endIndex); + } + + public Set getAllAttributeKeys() { + // ??? This should screen out attribute keys that aren't relevant to the client + if (runAttributes == null) { + // ??? would be nice to return null, but current spec doesn't allow it + // returning HashSet saves us from dealing with emptiness + return new HashSet(); + } + synchronized (AttributedString.this) { + // ??? should try to create this only once, then update if necessary, + // and give callers read-only view + Set keys = new HashSet(); + int i = 0; + while (i < runCount) { + if (runStarts[i] < endIndex && (i == runCount - 1 || runStarts[i + 1] > beginIndex)) { + Vector currentRunAttributes = runAttributes[i]; + if (currentRunAttributes != null) { + int j = currentRunAttributes.size(); + while (j-- > 0) { + keys.add(currentRunAttributes.get(j)); + } + } + } + i++; + } + return keys; + } + } + + public Object getAttribute(Attribute attribute) { + int runIndex = currentRunIndex; + if (runIndex < 0) { + return null; + } + return AttributedString.this.getAttributeCheckRange(attribute, runIndex, beginIndex, endIndex); + } + + // internally used methods + + private AttributedString getString() { + return AttributedString.this; + } + + // set the current index, update information about the current run if necessary, + // return the character at the current index + private char internalSetIndex(int position) { + currentIndex = position; + if (position < currentRunStart || position >= currentRunLimit) { + updateRunInfo(); + } + if (currentIndex == endIndex) { + return DONE; + } else { + return charAt(position); + } + } + + // update the information about the current run + private void updateRunInfo() { + if (currentIndex == endIndex) { + currentRunStart = currentRunLimit = endIndex; + currentRunIndex = -1; + } else { + synchronized (AttributedString.this) { + int runIndex = -1; + while (runIndex < runCount - 1 && runStarts[runIndex + 1] <= currentIndex) + runIndex++; + currentRunIndex = runIndex; + if (runIndex >= 0) { + currentRunStart = runStarts[runIndex]; + if (currentRunStart < beginIndex) + currentRunStart = beginIndex; + } + else { + currentRunStart = beginIndex; + } + if (runIndex < runCount - 1) { + currentRunLimit = runStarts[runIndex + 1]; + if (currentRunLimit > endIndex) + currentRunLimit = endIndex; + } + else { + currentRunLimit = endIndex; + } + } + } + } + + } + + // the map class associated with this string class, giving access to the attributes of one run + + final private class AttributeMap extends AbstractMap { + + int runIndex; + int beginIndex; + int endIndex; + + AttributeMap(int runIndex, int beginIndex, int endIndex) { + this.runIndex = runIndex; + this.beginIndex = beginIndex; + this.endIndex = endIndex; + } + + public Set entrySet() { + HashSet set = new HashSet(); + synchronized (AttributedString.this) { + int size = runAttributes[runIndex].size(); + for (int i = 0; i < size; i++) { + Attribute key = (Attribute) runAttributes[runIndex].get(i); + Object value = runAttributeValues[runIndex].get(i); + if (value instanceof Annotation) { + value = AttributedString.this.getAttributeCheckRange(key, + runIndex, beginIndex, endIndex); + if (value == null) { + continue; + } + } + Map.Entry entry = new AttributeEntry(key, value); + set.add(entry); + } + } + return set; + } + + public Object get(Object key) { + return AttributedString.this.getAttributeCheckRange((Attribute) key, runIndex, beginIndex, endIndex); + } + } +} + +class AttributeEntry implements Map.Entry { + + private Attribute key; + private Object value; + + AttributeEntry(Attribute key, Object value) { + this.key = key; + this.value = value; + } + + public boolean equals(Object o) { + if (!(o instanceof AttributeEntry)) { + return false; + } + AttributeEntry other = (AttributeEntry) o; + return other.key.equals(key) && + (value == null ? other.value == null : other.value.equals(value)); + } + + public Object getKey() { + return key; + } + + public Object getValue() { + return value; + } + + public Object setValue(Object newValue) { + throw new UnsupportedOperationException(); + } + + public int hashCode() { + return key.hashCode() ^ (value==null ? 0 : value.hashCode()); + } + + public String toString() { + return key.toString()+"="+value.toString(); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/CalendarBuilder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/CalendarBuilder.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,170 @@ +/* + * Copyright (c) 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.text; + +import java.util.Calendar; +import static java.util.Calendar.*; + +/** + * {@code CalendarBuilder} keeps field-value pairs for setting + * the calendar fields of the given {@code Calendar}. It has the + * {@link Calendar#FIELD_COUNT FIELD_COUNT}-th field for the week year + * support. Also {@code ISO_DAY_OF_WEEK} is used to specify + * {@code DAY_OF_WEEK} in the ISO day of week numbering. + * + *

{@code CalendarBuilder} retains the semantic of the pseudo + * timestamp for fields. {@code CalendarBuilder} uses a single + * int array combining fields[] and stamp[] of {@code Calendar}. + * + * @author Masayoshi Okutsu + */ +class CalendarBuilder { + /* + * Pseudo time stamp constants used in java.util.Calendar + */ + private static final int UNSET = 0; + private static final int COMPUTED = 1; + private static final int MINIMUM_USER_STAMP = 2; + + private static final int MAX_FIELD = FIELD_COUNT + 1; + + public static final int WEEK_YEAR = FIELD_COUNT; + public static final int ISO_DAY_OF_WEEK = 1000; // pseudo field index + + // stamp[] (lower half) and field[] (upper half) combined + private final int[] field; + private int nextStamp; + private int maxFieldIndex; + + CalendarBuilder() { + field = new int[MAX_FIELD * 2]; + nextStamp = MINIMUM_USER_STAMP; + maxFieldIndex = -1; + } + + CalendarBuilder set(int index, int value) { + if (index == ISO_DAY_OF_WEEK) { + index = DAY_OF_WEEK; + value = toCalendarDayOfWeek(value); + } + field[index] = nextStamp++; + field[MAX_FIELD + index] = value; + if (index > maxFieldIndex && index < FIELD_COUNT) { + maxFieldIndex = index; + } + return this; + } + + CalendarBuilder addYear(int value) { + field[MAX_FIELD + YEAR] += value; + field[MAX_FIELD + WEEK_YEAR] += value; + return this; + } + + boolean isSet(int index) { + if (index == ISO_DAY_OF_WEEK) { + index = DAY_OF_WEEK; + } + return field[index] > UNSET; + } + + Calendar establish(Calendar cal) { + boolean weekDate = isSet(WEEK_YEAR) + && field[WEEK_YEAR] > field[YEAR]; + if (weekDate && !cal.isWeekDateSupported()) { + // Use YEAR instead + if (!isSet(YEAR)) { + set(YEAR, field[MAX_FIELD + WEEK_YEAR]); + } + weekDate = false; + } + + cal.clear(); + // Set the fields from the min stamp to the max stamp so that + // the field resolution works in the Calendar. + for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) { + for (int index = 0; index <= maxFieldIndex; index++) { + if (field[index] == stamp) { + cal.set(index, field[MAX_FIELD + index]); + break; + } + } + } + + if (weekDate) { + int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1; + int dayOfWeek = isSet(DAY_OF_WEEK) ? + field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek(); + if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) { + if (dayOfWeek >= 8) { + dayOfWeek--; + weekOfYear += dayOfWeek / 7; + dayOfWeek = (dayOfWeek % 7) + 1; + } else { + while (dayOfWeek <= 0) { + dayOfWeek += 7; + weekOfYear--; + } + } + dayOfWeek = toCalendarDayOfWeek(dayOfWeek); + } + cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek); + } + return cal; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("CalendarBuilder:["); + for (int i = 0; i < field.length; i++) { + if (isSet(i)) { + sb.append(i).append('=').append(field[MAX_FIELD + i]).append(','); + } + } + int lastIndex = sb.length() - 1; + if (sb.charAt(lastIndex) == ',') { + sb.setLength(lastIndex); + } + sb.append(']'); + return sb.toString(); + } + + static int toISODayOfWeek(int calendarDayOfWeek) { + return calendarDayOfWeek == SUNDAY ? 7 : calendarDayOfWeek - 1; + } + + static int toCalendarDayOfWeek(int isoDayOfWeek) { + if (!isValidDayOfWeek(isoDayOfWeek)) { + // adjust later for lenient mode + return isoDayOfWeek; + } + return isoDayOfWeek == 7 ? SUNDAY : isoDayOfWeek + 1; + } + + static boolean isValidDayOfWeek(int dayOfWeek) { + return dayOfWeek > 0 && dayOfWeek <= 7; + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/CharacterIterator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/CharacterIterator.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,193 @@ +/* + * Copyright (c) 1996, 2000, 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation + * is copyrighted and owned by Taligent, Inc., a wholly-owned + * subsidiary of IBM. These materials are provided under terms + * of a License Agreement between Taligent and Sun. This technology + * is protected by multiple US and International patents. + * + * This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + + +/** + * This interface defines a protocol for bidirectional iteration over text. + * The iterator iterates over a bounded sequence of characters. Characters + * are indexed with values beginning with the value returned by getBeginIndex() and + * continuing through the value returned by getEndIndex()-1. + *

+ * Iterators maintain a current character index, whose valid range is from + * getBeginIndex() to getEndIndex(); the value getEndIndex() is included to allow + * handling of zero-length text ranges and for historical reasons. + * The current index can be retrieved by calling getIndex() and set directly + * by calling setIndex(), first(), and last(). + *

+ * The methods previous() and next() are used for iteration. They return DONE if + * they would move outside the range from getBeginIndex() to getEndIndex() -1, + * signaling that the iterator has reached the end of the sequence. DONE is + * also returned by other methods to indicate that the current index is + * outside this range. + * + *

Examples:

+ * + * Traverse the text from start to finish + *

+ * public void traverseForward(CharacterIterator iter) {
+ *     for(char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
+ *         processChar(c);
+ *     }
+ * }
+ * 
+ * + * Traverse the text backwards, from end to start + *
+ * public void traverseBackward(CharacterIterator iter) {
+ *     for(char c = iter.last(); c != CharacterIterator.DONE; c = iter.previous()) {
+ *         processChar(c);
+ *     }
+ * }
+ * 
+ * + * Traverse both forward and backward from a given position in the text. + * Calls to notBoundary() in this example represents some + * additional stopping criteria. + *
+ * public void traverseOut(CharacterIterator iter, int pos) {
+ *     for (char c = iter.setIndex(pos);
+ *              c != CharacterIterator.DONE && notBoundary(c);
+ *              c = iter.next()) {
+ *     }
+ *     int end = iter.getIndex();
+ *     for (char c = iter.setIndex(pos);
+ *             c != CharacterIterator.DONE && notBoundary(c);
+ *             c = iter.previous()) {
+ *     }
+ *     int start = iter.getIndex();
+ *     processSection(start, end);
+ * }
+ * 
+ * + * @see StringCharacterIterator + * @see AttributedCharacterIterator + */ + +public interface CharacterIterator extends Cloneable +{ + + /** + * Constant that is returned when the iterator has reached either the end + * or the beginning of the text. The value is '\\uFFFF', the "not a + * character" value which should not occur in any valid Unicode string. + */ + public static final char DONE = '\uFFFF'; + + /** + * Sets the position to getBeginIndex() and returns the character at that + * position. + * @return the first character in the text, or DONE if the text is empty + * @see #getBeginIndex() + */ + public char first(); + + /** + * Sets the position to getEndIndex()-1 (getEndIndex() if the text is empty) + * and returns the character at that position. + * @return the last character in the text, or DONE if the text is empty + * @see #getEndIndex() + */ + public char last(); + + /** + * Gets the character at the current position (as returned by getIndex()). + * @return the character at the current position or DONE if the current + * position is off the end of the text. + * @see #getIndex() + */ + public char current(); + + /** + * Increments the iterator's index by one and returns the character + * at the new index. If the resulting index is greater or equal + * to getEndIndex(), the current index is reset to getEndIndex() and + * a value of DONE is returned. + * @return the character at the new position or DONE if the new + * position is off the end of the text range. + */ + public char next(); + + /** + * Decrements the iterator's index by one and returns the character + * at the new index. If the current index is getBeginIndex(), the index + * remains at getBeginIndex() and a value of DONE is returned. + * @return the character at the new position or DONE if the current + * position is equal to getBeginIndex(). + */ + public char previous(); + + /** + * Sets the position to the specified position in the text and returns that + * character. + * @param position the position within the text. Valid values range from + * getBeginIndex() to getEndIndex(). An IllegalArgumentException is thrown + * if an invalid value is supplied. + * @return the character at the specified position or DONE if the specified position is equal to getEndIndex() + */ + public char setIndex(int position); + + /** + * Returns the start index of the text. + * @return the index at which the text begins. + */ + public int getBeginIndex(); + + /** + * Returns the end index of the text. This index is the index of the first + * character following the end of the text. + * @return the index after the last character in the text + */ + public int getEndIndex(); + + /** + * Returns the current index. + * @return the current index. + */ + public int getIndex(); + + /** + * Create a copy of this iterator + * @return A copy of this + */ + public Object clone(); + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/CharacterIteratorFieldDelegate.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/CharacterIteratorFieldDelegate.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2000, 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.text; + +import java.util.ArrayList; + +/** + * CharacterIteratorFieldDelegate combines the notifications from a Format + * into a resulting AttributedCharacterIterator. The resulting + * AttributedCharacterIterator can be retrieved by way of + * the getIterator method. + * + */ +class CharacterIteratorFieldDelegate implements Format.FieldDelegate { + /** + * Array of AttributeStrings. Whenever formatted is invoked + * for a region > size, a new instance of AttributedString is added to + * attributedStrings. Subsequent invocations of formatted + * for existing regions result in invoking addAttribute on the existing + * AttributedStrings. + */ + private ArrayList attributedStrings; + /** + * Running count of the number of characters that have + * been encountered. + */ + private int size; + + + CharacterIteratorFieldDelegate() { + attributedStrings = new ArrayList(); + } + + public void formatted(Format.Field attr, Object value, int start, int end, + StringBuffer buffer) { + if (start != end) { + if (start < size) { + // Adjust attributes of existing runs + int index = size; + int asIndex = attributedStrings.size() - 1; + + while (start < index) { + AttributedString as = (AttributedString)attributedStrings. + get(asIndex--); + int newIndex = index - as.length(); + int aStart = Math.max(0, start - newIndex); + + as.addAttribute(attr, value, aStart, Math.min( + end - start, as.length() - aStart) + + aStart); + index = newIndex; + } + } + if (size < start) { + // Pad attributes + attributedStrings.add(new AttributedString( + buffer.substring(size, start))); + size = start; + } + if (size < end) { + // Add new string + int aStart = Math.max(start, size); + AttributedString string = new AttributedString( + buffer.substring(aStart, end)); + + string.addAttribute(attr, value); + attributedStrings.add(string); + size = end; + } + } + } + + public void formatted(int fieldID, Format.Field attr, Object value, + int start, int end, StringBuffer buffer) { + formatted(attr, value, start, end, buffer); + } + + /** + * Returns an AttributedCharacterIterator that can be used + * to iterate over the resulting formatted String. + * + * @pararm string Result of formatting. + */ + public AttributedCharacterIterator getIterator(String string) { + // Add the last AttributedCharacterIterator if necessary + // assert(size <= string.length()); + if (string.length() > size) { + attributedStrings.add(new AttributedString( + string.substring(size))); + size = string.length(); + } + int iCount = attributedStrings.size(); + AttributedCharacterIterator iterators[] = new + AttributedCharacterIterator[iCount]; + + for (int counter = 0; counter < iCount; counter++) { + iterators[counter] = ((AttributedString)attributedStrings. + get(counter)).getIterator(); + } + return new AttributedString(iterators).getIterator(); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/ChoiceFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/ChoiceFormat.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,619 @@ +/* + * 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.InvalidObjectException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.Arrays; + +/** + * A ChoiceFormat allows you to attach a format to a range of numbers. + * It is generally used in a MessageFormat for handling plurals. + * The choice is specified with an ascending list of doubles, where each item + * specifies a half-open interval up to the next item: + *
+ *
+ * X matches j if and only if limit[j] <= X < limit[j+1]
+ * 
+ *
+ * If there is no match, then either the first or last index is used, depending + * on whether the number (X) is too low or too high. If the limit array is not + * in ascending order, the results of formatting will be incorrect. ChoiceFormat + * also accepts \u221E as equivalent to infinity(INF). + * + *

+ * Note: + * ChoiceFormat differs from the other Format + * classes in that you create a ChoiceFormat object with a + * constructor (not with a getInstance style factory + * method). The factory methods aren't necessary because ChoiceFormat + * doesn't require any complex setup for a given locale. In fact, + * ChoiceFormat doesn't implement any locale specific behavior. + * + *

+ * When creating a ChoiceFormat, you must specify an array of formats + * and an array of limits. The length of these arrays must be the same. + * For example, + *

    + *
  • + * limits = {1,2,3,4,5,6,7}
    + * formats = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"} + *
  • + * limits = {0, 1, ChoiceFormat.nextDouble(1)}
    + * formats = {"no files", "one file", "many files"}
    + * (nextDouble can be used to get the next higher double, to + * make the half-open interval.) + *
+ * + *

+ * Here is a simple example that shows formatting and parsing: + *

+ *
+ * double[] limits = {1,2,3,4,5,6,7};
+ * String[] dayOfWeekNames = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"};
+ * ChoiceFormat form = new ChoiceFormat(limits, dayOfWeekNames);
+ * ParsePosition status = new ParsePosition(0);
+ * for (double i = 0.0; i <= 8.0; ++i) {
+ *     status.setIndex(0);
+ *     System.out.println(i + " -> " + form.format(i) + " -> "
+ *                              + form.parse(form.format(i),status));
+ * }
+ * 
+ *
+ * Here is a more complex example, with a pattern format: + *
+ *
+ * double[] filelimits = {0,1,2};
+ * String[] filepart = {"are no files","is one file","are {2} files"};
+ * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
+ * Format[] testFormats = {fileform, null, NumberFormat.getInstance()};
+ * MessageFormat pattform = new MessageFormat("There {0} on {1}");
+ * pattform.setFormats(testFormats);
+ * Object[] testArgs = {null, "ADisk", null};
+ * for (int i = 0; i < 4; ++i) {
+ *     testArgs[0] = new Integer(i);
+ *     testArgs[2] = testArgs[0];
+ *     System.out.println(pattform.format(testArgs));
+ * }
+ * 
+ *
+ *

+ * Specifying a pattern for ChoiceFormat objects is fairly straightforward. + * For example: + *

+ *
+ * ChoiceFormat fmt = new ChoiceFormat(
+ *      "-1#is negative| 0#is zero or fraction | 1#is one |1.0<is 1+ |2#is two |2<is more than 2.");
+ * System.out.println("Formatter Pattern : " + fmt.toPattern());
+ *
+ * System.out.println("Format with -INF : " + fmt.format(Double.NEGATIVE_INFINITY));
+ * System.out.println("Format with -1.0 : " + fmt.format(-1.0));
+ * System.out.println("Format with 0 : " + fmt.format(0));
+ * System.out.println("Format with 0.9 : " + fmt.format(0.9));
+ * System.out.println("Format with 1.0 : " + fmt.format(1));
+ * System.out.println("Format with 1.5 : " + fmt.format(1.5));
+ * System.out.println("Format with 2 : " + fmt.format(2));
+ * System.out.println("Format with 2.1 : " + fmt.format(2.1));
+ * System.out.println("Format with NaN : " + fmt.format(Double.NaN));
+ * System.out.println("Format with +INF : " + fmt.format(Double.POSITIVE_INFINITY));
+ * 
+ *
+ * And the output result would be like the following: + *
+ *
+ *   Format with -INF : is negative
+ *   Format with -1.0 : is negative
+ *   Format with 0 : is zero or fraction
+ *   Format with 0.9 : is zero or fraction
+ *   Format with 1.0 : is one
+ *   Format with 1.5 : is 1+
+ *   Format with 2 : is two
+ *   Format with 2.1 : is more than 2.
+ *   Format with NaN : is negative
+ *   Format with +INF : is more than 2.
+ * 
+ *
+ * + *

Synchronization

+ * + *

+ * Choice formats are not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + * + * @see DecimalFormat + * @see MessageFormat + * @author Mark Davis + */ +public class ChoiceFormat extends NumberFormat { + + // Proclaim serial compatibility with 1.1 FCS + private static final long serialVersionUID = 1795184449645032964L; + + /** + * Sets the pattern. + * @param newPattern See the class description. + */ + public void applyPattern(String newPattern) { + StringBuffer[] segments = new StringBuffer[2]; + for (int i = 0; i < segments.length; ++i) { + segments[i] = new StringBuffer(); + } + double[] newChoiceLimits = new double[30]; + String[] newChoiceFormats = new String[30]; + int count = 0; + int part = 0; + double startValue = 0; + double oldStartValue = Double.NaN; + boolean inQuote = false; + for (int i = 0; i < newPattern.length(); ++i) { + char ch = newPattern.charAt(i); + if (ch=='\'') { + // Check for "''" indicating a literal quote + if ((i+1)= 0 + || text.indexOf('#') >= 0 + || text.indexOf('\u2264') >= 0 + || text.indexOf('|') >= 0; + if (needQuote) result.append('\''); + if (text.indexOf('\'') < 0) result.append(text); + else { + for (int j=0; jformat(double, StringBuffer, FieldPosition) + * thus the range of longs that are supported is only equal to + * the range that can be stored by double. This will never be + * a practical limitation. + */ + public StringBuffer format(long number, StringBuffer toAppendTo, + FieldPosition status) { + return format((double)number, toAppendTo, status); + } + + /** + * Returns pattern with formatted double. + * @param number number to be formatted & substituted. + * @param toAppendTo where text is appended. + * @param status ignore no useful status is returned. + */ + public StringBuffer format(double number, StringBuffer toAppendTo, + FieldPosition status) { + // find the number + int i; + for (i = 0; i < choiceLimits.length; ++i) { + if (!(number >= choiceLimits[i])) { + // same as number < choiceLimits, except catchs NaN + break; + } + } + --i; + if (i < 0) i = 0; + // return either a formatted number, or a string + return toAppendTo.append(choiceFormats[i]); + } + + /** + * Parses a Number from the input text. + * @param text the source text. + * @param status an input-output parameter. On input, the + * status.index field indicates the first character of the + * source text that should be parsed. On exit, if no error + * occured, status.index is set to the first unparsed character + * in the source text. On exit, if an error did occur, + * status.index is unchanged and status.errorIndex is set to the + * first index of the character that caused the parse to fail. + * @return A Number representing the value of the number parsed. + */ + public Number parse(String text, ParsePosition status) { + // find the best number (defined as the one with the longest parse) + int start = status.index; + int furthest = start; + double bestNumber = Double.NaN; + double tempNumber = 0.0; + for (int i = 0; i < choiceFormats.length; ++i) { + String tempString = choiceFormats[i]; + if (text.regionMatches(start, tempString, 0, tempString.length())) { + status.index = start + tempString.length(); + tempNumber = choiceLimits[i]; + if (status.index > furthest) { + furthest = status.index; + bestNumber = tempNumber; + if (furthest == text.length()) break; + } + } + } + status.index = furthest; + if (status.index == start) { + status.errorIndex = furthest; + } + return new Double(bestNumber); + } + + /** + * Finds the least double greater than d. + * If NaN, returns same value. + *

Used to make half-open intervals. + * @see #previousDouble + */ + public static final double nextDouble (double d) { + return nextDouble(d,true); + } + + /** + * Finds the greatest double less than d. + * If NaN, returns same value. + * @see #nextDouble + */ + public static final double previousDouble (double d) { + return nextDouble(d,false); + } + + /** + * Overrides Cloneable + */ + public Object clone() + { + ChoiceFormat other = (ChoiceFormat) super.clone(); + // for primitives or immutables, shallow clone is enough + other.choiceLimits = (double[]) choiceLimits.clone(); + other.choiceFormats = (String[]) choiceFormats.clone(); + return other; + } + + /** + * Generates a hash code for the message format object. + */ + public int hashCode() { + int result = choiceLimits.length; + if (choiceFormats.length > 0) { + // enough for reasonable distribution + result ^= choiceFormats[choiceFormats.length-1].hashCode(); + } + return result; + } + + /** + * Equality comparision between two + */ + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) // quick check + return true; + if (getClass() != obj.getClass()) + return false; + ChoiceFormat other = (ChoiceFormat) obj; + return (Arrays.equals(choiceLimits, other.choiceLimits) + && Arrays.equals(choiceFormats, other.choiceFormats)); + } + + /** + * After reading an object from the input stream, do a simple verification + * to maintain class invariants. + * @throws InvalidObjectException if the objects read from the stream is invalid. + */ + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + if (choiceLimits.length != choiceFormats.length) { + throw new InvalidObjectException( + "limits and format arrays of different length."); + } + } + + // ===============privates=========================== + + /** + * A list of lower bounds for the choices. The formatter will return + * choiceFormats[i] if the number being formatted is greater than or equal to + * choiceLimits[i] and less than choiceLimits[i+1]. + * @serial + */ + private double[] choiceLimits; + + /** + * A list of choice strings. The formatter will return + * choiceFormats[i] if the number being formatted is greater than or equal to + * choiceLimits[i] and less than choiceLimits[i+1]. + * @serial + */ + private String[] choiceFormats; + + /* + static final long SIGN = 0x8000000000000000L; + static final long EXPONENT = 0x7FF0000000000000L; + static final long SIGNIFICAND = 0x000FFFFFFFFFFFFFL; + + private static double nextDouble (double d, boolean positive) { + if (Double.isNaN(d) || Double.isInfinite(d)) { + return d; + } + long bits = Double.doubleToLongBits(d); + long significand = bits & SIGNIFICAND; + if (bits < 0) { + significand |= (SIGN | EXPONENT); + } + long exponent = bits & EXPONENT; + if (positive) { + significand += 1; + // FIXME fix overflow & underflow + } else { + significand -= 1; + // FIXME fix overflow & underflow + } + bits = exponent | (significand & ~EXPONENT); + return Double.longBitsToDouble(bits); + } + */ + + static final long SIGN = 0x8000000000000000L; + static final long EXPONENT = 0x7FF0000000000000L; + static final long POSITIVEINFINITY = 0x7FF0000000000000L; + + /** + * Finds the least double greater than d (if positive == true), + * or the greatest double less than d (if positive == false). + * If NaN, returns same value. + * + * Does not affect floating-point flags, + * provided these member functions do not: + * Double.longBitsToDouble(long) + * Double.doubleToLongBits(double) + * Double.isNaN(double) + */ + public static double nextDouble (double d, boolean positive) { + + /* filter out NaN's */ + if (Double.isNaN(d)) { + return d; + } + + /* zero's are also a special case */ + if (d == 0.0) { + double smallestPositiveDouble = Double.longBitsToDouble(1L); + if (positive) { + return smallestPositiveDouble; + } else { + return -smallestPositiveDouble; + } + } + + /* if entering here, d is a nonzero value */ + + /* hold all bits in a long for later use */ + long bits = Double.doubleToLongBits(d); + + /* strip off the sign bit */ + long magnitude = bits & ~SIGN; + + /* if next double away from zero, increase magnitude */ + if ((bits > 0) == positive) { + if (magnitude != POSITIVEINFINITY) { + magnitude += 1; + } + } + /* else decrease magnitude */ + else { + magnitude -= 1; + } + + /* restore sign bit and return */ + long signbit = bits & SIGN; + return Double.longBitsToDouble (magnitude | signbit); + } + + private static double[] doubleArraySize(double[] array) { + int oldSize = array.length; + double[] newArray = new double[oldSize * 2]; + System.arraycopy(array, 0, newArray, 0, oldSize); + return newArray; + } + + private String[] doubleArraySize(String[] array) { + int oldSize = array.length; + String[] newArray = new String[oldSize * 2]; + System.arraycopy(array, 0, newArray, 0, oldSize); + return newArray; + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/DateFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/DateFormat.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1025 @@ +/* + * Copyright (c) 1996, 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.InvalidObjectException; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.TimeZone; + +/** + * {@code DateFormat} is an abstract class for date/time formatting subclasses which + * formats and parses dates or time in a language-independent manner. + * The date/time formatting subclass, such as {@link SimpleDateFormat}, allows for + * formatting (i.e., date -> text), parsing (text -> date), and + * normalization. The date is represented as a Date object or + * as the milliseconds since January 1, 1970, 00:00:00 GMT. + * + *

{@code DateFormat} provides many class methods for obtaining default date/time + * formatters based on the default or a given locale and a number of formatting + * styles. The formatting styles include {@link #FULL}, {@link #LONG}, {@link #MEDIUM}, and {@link #SHORT}. More + * detail and examples of using these styles are provided in the method + * descriptions. + * + *

{@code DateFormat} helps you to format and parse dates for any locale. + * Your code can be completely independent of the locale conventions for + * months, days of the week, or even the calendar format: lunar vs. solar. + * + *

To format a date for the current Locale, use one of the + * static factory methods: + *

+ *  myString = DateFormat.getDateInstance().format(myDate);
+ * 
+ *

If you are formatting multiple dates, it is + * more efficient to get the format and use it multiple times so that + * the system doesn't have to fetch the information about the local + * language and country conventions multiple times. + *

+ *  DateFormat df = DateFormat.getDateInstance();
+ *  for (int i = 0; i < myDate.length; ++i) {
+ *    output.println(df.format(myDate[i]) + "; ");
+ *  }
+ * 
+ *

To format a date for a different Locale, specify it in the + * call to {@link #getDateInstance(int, Locale) getDateInstance()}. + *

+ *  DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE);
+ * 
+ *

You can use a DateFormat to parse also. + *

+ *  myDate = df.parse(myString);
+ * 
+ *

Use {@code getDateInstance} to get the normal date format for that country. + * There are other static factory methods available. + * Use {@code getTimeInstance} to get the time format for that country. + * Use {@code getDateTimeInstance} to get a date and time format. You can pass in + * different options to these factory methods to control the length of the + * result; from {@link #SHORT} to {@link #MEDIUM} to {@link #LONG} to {@link #FULL}. The exact result depends + * on the locale, but generally: + *

  • {@link #SHORT} is completely numeric, such as {@code 12.13.52} or {@code 3:30pm} + *
  • {@link #MEDIUM} is longer, such as {@code Jan 12, 1952} + *
  • {@link #LONG} is longer, such as {@code January 12, 1952} or {@code 3:30:32pm} + *
  • {@link #FULL} is pretty completely specified, such as + * {@code Tuesday, April 12, 1952 AD or 3:30:42pm PST}. + *
+ * + *

You can also set the time zone on the format if you wish. + * If you want even more control over the format or parsing, + * (or want to give your users more control), + * you can try casting the {@code DateFormat} you get from the factory methods + * to a {@link SimpleDateFormat}. This will work for the majority + * of countries; just remember to put it in a {@code try} block in case you + * encounter an unusual one. + * + *

You can also use forms of the parse and format methods with + * {@link ParsePosition} and {@link FieldPosition} to + * allow you to + *

  • progressively parse through pieces of a string. + *
  • align any particular field, or find out where it is for selection + * on the screen. + *
+ * + *

Synchronization

+ * + *

+ * Date formats are not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + * @see Format + * @see NumberFormat + * @see SimpleDateFormat + * @see java.util.Calendar + * @see java.util.GregorianCalendar + * @see java.util.TimeZone + * @author Mark Davis, Chen-Lieh Huang, Alan Liu + */ +public abstract class DateFormat extends Format { + + /** + * The {@link Calendar} instance used for calculating the date-time fields + * and the instant of time. This field is used for both formatting and + * parsing. + * + *

Subclasses should initialize this field to a {@link Calendar} + * appropriate for the {@link Locale} associated with this + * DateFormat. + * @serial + */ + protected Calendar calendar; + + /** + * The number formatter that DateFormat uses to format numbers + * in dates and times. Subclasses should initialize this to a number format + * appropriate for the locale associated with this DateFormat. + * @serial + */ + protected NumberFormat numberFormat; + + /** + * Useful constant for ERA field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int ERA_FIELD = 0; + /** + * Useful constant for YEAR field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int YEAR_FIELD = 1; + /** + * Useful constant for MONTH field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int MONTH_FIELD = 2; + /** + * Useful constant for DATE field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int DATE_FIELD = 3; + /** + * Useful constant for one-based HOUR_OF_DAY field alignment. + * Used in FieldPosition of date/time formatting. + * HOUR_OF_DAY1_FIELD is used for the one-based 24-hour clock. + * For example, 23:59 + 01:00 results in 24:59. + */ + public final static int HOUR_OF_DAY1_FIELD = 4; + /** + * Useful constant for zero-based HOUR_OF_DAY field alignment. + * Used in FieldPosition of date/time formatting. + * HOUR_OF_DAY0_FIELD is used for the zero-based 24-hour clock. + * For example, 23:59 + 01:00 results in 00:59. + */ + public final static int HOUR_OF_DAY0_FIELD = 5; + /** + * Useful constant for MINUTE field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int MINUTE_FIELD = 6; + /** + * Useful constant for SECOND field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int SECOND_FIELD = 7; + /** + * Useful constant for MILLISECOND field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int MILLISECOND_FIELD = 8; + /** + * Useful constant for DAY_OF_WEEK field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int DAY_OF_WEEK_FIELD = 9; + /** + * Useful constant for DAY_OF_YEAR field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int DAY_OF_YEAR_FIELD = 10; + /** + * Useful constant for DAY_OF_WEEK_IN_MONTH field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int DAY_OF_WEEK_IN_MONTH_FIELD = 11; + /** + * Useful constant for WEEK_OF_YEAR field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int WEEK_OF_YEAR_FIELD = 12; + /** + * Useful constant for WEEK_OF_MONTH field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int WEEK_OF_MONTH_FIELD = 13; + /** + * Useful constant for AM_PM field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int AM_PM_FIELD = 14; + /** + * Useful constant for one-based HOUR field alignment. + * Used in FieldPosition of date/time formatting. + * HOUR1_FIELD is used for the one-based 12-hour clock. + * For example, 11:30 PM + 1 hour results in 12:30 AM. + */ + public final static int HOUR1_FIELD = 15; + /** + * Useful constant for zero-based HOUR field alignment. + * Used in FieldPosition of date/time formatting. + * HOUR0_FIELD is used for the zero-based 12-hour clock. + * For example, 11:30 PM + 1 hour results in 00:30 AM. + */ + public final static int HOUR0_FIELD = 16; + /** + * Useful constant for TIMEZONE field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int TIMEZONE_FIELD = 17; + + // Proclaim serial compatibility with 1.1 FCS + private static final long serialVersionUID = 7218322306649953788L; + + /** + * Overrides Format. + * Formats a time object into a time string. Examples of time objects + * are a time value expressed in milliseconds and a Date object. + * @param obj must be a Number or a Date. + * @param toAppendTo the string buffer for the returning time string. + * @return the string buffer passed in as toAppendTo, with formatted text appended. + * @param fieldPosition keeps track of the position of the field + * within the returned string. + * On input: an alignment field, + * if desired. On output: the offsets of the alignment field. For + * example, given a time text "1996.07.10 AD at 15:08:56 PDT", + * if the given fieldPosition is DateFormat.YEAR_FIELD, the + * begin index and end index of fieldPosition will be set to + * 0 and 4, respectively. + * Notice that if the same time field appears + * more than once in a pattern, the fieldPosition will be set for the first + * occurrence of that time field. For instance, formatting a Date to + * the time string "1 PM PDT (Pacific Daylight Time)" using the pattern + * "h a z (zzzz)" and the alignment field DateFormat.TIMEZONE_FIELD, + * the begin index and end index of fieldPosition will be set to + * 5 and 8, respectively, for the first occurrence of the timezone + * pattern character 'z'. + * @see java.text.Format + */ + public final StringBuffer format(Object obj, StringBuffer toAppendTo, + FieldPosition fieldPosition) + { + if (obj instanceof Date) + return format( (Date)obj, toAppendTo, fieldPosition ); + else if (obj instanceof Number) + return format( new Date(((Number)obj).longValue()), + toAppendTo, fieldPosition ); + else + throw new IllegalArgumentException("Cannot format given Object as a Date"); + } + + /** + * Formats a Date into a date/time string. + * @param date a Date to be formatted into a date/time string. + * @param toAppendTo the string buffer for the returning date/time string. + * @param fieldPosition keeps track of the position of the field + * within the returned string. + * On input: an alignment field, + * if desired. On output: the offsets of the alignment field. For + * example, given a time text "1996.07.10 AD at 15:08:56 PDT", + * if the given fieldPosition is DateFormat.YEAR_FIELD, the + * begin index and end index of fieldPosition will be set to + * 0 and 4, respectively. + * Notice that if the same time field appears + * more than once in a pattern, the fieldPosition will be set for the first + * occurrence of that time field. For instance, formatting a Date to + * the time string "1 PM PDT (Pacific Daylight Time)" using the pattern + * "h a z (zzzz)" and the alignment field DateFormat.TIMEZONE_FIELD, + * the begin index and end index of fieldPosition will be set to + * 5 and 8, respectively, for the first occurrence of the timezone + * pattern character 'z'. + * @return the string buffer passed in as toAppendTo, with formatted text appended. + */ + public abstract StringBuffer format(Date date, StringBuffer toAppendTo, + FieldPosition fieldPosition); + + /** + * Formats a Date into a date/time string. + * @param date the time value to be formatted into a time string. + * @return the formatted time string. + */ + public final String format(Date date) + { + return format(date, new StringBuffer(), + DontCareFieldPosition.INSTANCE).toString(); + } + + /** + * Parses text from the beginning of the given string to produce a date. + * The method may not use the entire text of the given string. + *

+ * See the {@link #parse(String, ParsePosition)} method for more information + * on date parsing. + * + * @param source A String whose beginning should be parsed. + * @return A Date parsed from the string. + * @exception ParseException if the beginning of the specified string + * cannot be parsed. + */ + public Date parse(String source) throws ParseException + { + ParsePosition pos = new ParsePosition(0); + Date result = parse(source, pos); + if (pos.index == 0) + throw new ParseException("Unparseable date: \"" + source + "\"" , + pos.errorIndex); + return result; + } + + /** + * Parse a date/time string according to the given parse position. For + * example, a time text {@code "07/10/96 4:5 PM, PDT"} will be parsed into a {@code Date} + * that is equivalent to {@code Date(837039900000L)}. + * + *

By default, parsing is lenient: If the input is not in the form used + * by this object's format method but can still be parsed as a date, then + * the parse succeeds. Clients may insist on strict adherence to the + * format by calling {@link #setLenient(boolean) setLenient(false)}. + * + *

This parsing operation uses the {@link #calendar} to produce + * a {@code Date}. As a result, the {@code calendar}'s date-time + * fields and the {@code TimeZone} value may have been + * overwritten, depending on subclass implementations. Any {@code + * TimeZone} value that has previously been set by a call to + * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need + * to be restored for further operations. + * + * @param source The date/time string to be parsed + * + * @param pos On input, the position at which to start parsing; on + * output, the position at which parsing terminated, or the + * start position if the parse failed. + * + * @return A {@code Date}, or {@code null} if the input could not be parsed + */ + public abstract Date parse(String source, ParsePosition pos); + + /** + * Parses text from a string to produce a Date. + *

+ * The method attempts to parse text starting at the index given by + * pos. + * If parsing succeeds, then the index of pos is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * date is returned. The updated pos can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of pos is not + * changed, the error index of pos is set to the index of + * the character where the error occurred, and null is returned. + *

+ * See the {@link #parse(String, ParsePosition)} method for more information + * on date parsing. + * + * @param source A String, part of which should be parsed. + * @param pos A ParsePosition object with index and error + * index information as described above. + * @return A Date parsed from the string. In case of + * error, returns null. + * @exception NullPointerException if pos is null. + */ + public Object parseObject(String source, ParsePosition pos) { + return parse(source, pos); + } + + /** + * Constant for full style pattern. + */ + public static final int FULL = 0; + /** + * Constant for long style pattern. + */ + public static final int LONG = 1; + /** + * Constant for medium style pattern. + */ + public static final int MEDIUM = 2; + /** + * Constant for short style pattern. + */ + public static final int SHORT = 3; + /** + * Constant for default style pattern. Its value is MEDIUM. + */ + public static final int DEFAULT = MEDIUM; + + /** + * Gets the time formatter with the default formatting style + * for the default locale. + * @return a time formatter. + */ + public final static DateFormat getTimeInstance() + { + return get(DEFAULT, 0, 1, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the time formatter with the given formatting style + * for the default locale. + * @param style the given formatting style. For example, + * SHORT for "h:mm a" in the US locale. + * @return a time formatter. + */ + public final static DateFormat getTimeInstance(int style) + { + return get(style, 0, 1, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the time formatter with the given formatting style + * for the given locale. + * @param style the given formatting style. For example, + * SHORT for "h:mm a" in the US locale. + * @param aLocale the given locale. + * @return a time formatter. + */ + public final static DateFormat getTimeInstance(int style, + Locale aLocale) + { + return get(style, 0, 1, aLocale); + } + + /** + * Gets the date formatter with the default formatting style + * for the default locale. + * @return a date formatter. + */ + public final static DateFormat getDateInstance() + { + return get(0, DEFAULT, 2, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the date formatter with the given formatting style + * for the default locale. + * @param style the given formatting style. For example, + * SHORT for "M/d/yy" in the US locale. + * @return a date formatter. + */ + public final static DateFormat getDateInstance(int style) + { + return get(0, style, 2, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the date formatter with the given formatting style + * for the given locale. + * @param style the given formatting style. For example, + * SHORT for "M/d/yy" in the US locale. + * @param aLocale the given locale. + * @return a date formatter. + */ + public final static DateFormat getDateInstance(int style, + Locale aLocale) + { + return get(0, style, 2, aLocale); + } + + /** + * Gets the date/time formatter with the default formatting style + * for the default locale. + * @return a date/time formatter. + */ + public final static DateFormat getDateTimeInstance() + { + return get(DEFAULT, DEFAULT, 3, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the date/time formatter with the given date and time + * formatting styles for the default locale. + * @param dateStyle the given date formatting style. For example, + * SHORT for "M/d/yy" in the US locale. + * @param timeStyle the given time formatting style. For example, + * SHORT for "h:mm a" in the US locale. + * @return a date/time formatter. + */ + public final static DateFormat getDateTimeInstance(int dateStyle, + int timeStyle) + { + return get(timeStyle, dateStyle, 3, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the date/time formatter with the given formatting styles + * for the given locale. + * @param dateStyle the given date formatting style. + * @param timeStyle the given time formatting style. + * @param aLocale the given locale. + * @return a date/time formatter. + */ + public final static DateFormat + getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale) + { + return get(timeStyle, dateStyle, 3, aLocale); + } + + /** + * Get a default date/time formatter that uses the SHORT style for both the + * date and the time. + */ + public final static DateFormat getInstance() { + return getDateTimeInstance(SHORT, SHORT); + } + + /** + * Returns an array of all locales for which the + * get*Instance methods of this class can return + * localized instances. + * The returned array represents the union of locales supported by the Java + * runtime and by installed + * {@link java.text.spi.DateFormatProvider DateFormatProvider} implementations. + * It must contain at least a Locale instance equal to + * {@link java.util.Locale#US Locale.US}. + * + * @return An array of locales for which localized + * DateFormat instances are available. + */ + public static Locale[] getAvailableLocales() + { + return new Locale[] { Locale.US }; + } + + /** + * Set the calendar to be used by this date format. Initially, the default + * calendar for the specified or default locale is used. + * + *

Any {@link java.util.TimeZone TimeZone} and {@linkplain + * #isLenient() leniency} values that have previously been set are + * overwritten by {@code newCalendar}'s values. + * + * @param newCalendar the new {@code Calendar} to be used by the date format + */ + public void setCalendar(Calendar newCalendar) + { + this.calendar = newCalendar; + } + + /** + * Gets the calendar associated with this date/time formatter. + * + * @return the calendar associated with this date/time formatter. + */ + public Calendar getCalendar() + { + return calendar; + } + + /** + * Allows you to set the number formatter. + * @param newNumberFormat the given new NumberFormat. + */ + public void setNumberFormat(NumberFormat newNumberFormat) + { + this.numberFormat = newNumberFormat; + } + + /** + * Gets the number formatter which this date/time formatter uses to + * format and parse a time. + * @return the number formatter which this date/time formatter uses. + */ + public NumberFormat getNumberFormat() + { + return numberFormat; + } + + /** + * Sets the time zone for the calendar of this {@code DateFormat} object. + * This method is equivalent to the following call. + *

+     *  getCalendar().setTimeZone(zone)
+     * 
+ * + *

The {@code TimeZone} set by this method is overwritten by a + * {@link #setCalendar(java.util.Calendar) setCalendar} call. + * + *

The {@code TimeZone} set by this method may be overwritten as + * a result of a call to the parse method. + * + * @param zone the given new time zone. + */ + public void setTimeZone(TimeZone zone) + { + calendar.setTimeZone(zone); + } + + /** + * Gets the time zone. + * This method is equivalent to the following call. + *

+     *  getCalendar().getTimeZone()
+     * 
+ * + * @return the time zone associated with the calendar of DateFormat. + */ + public TimeZone getTimeZone() + { + return calendar.getTimeZone(); + } + + /** + * Specify whether or not date/time parsing is to be lenient. With + * lenient parsing, the parser may use heuristics to interpret inputs that + * do not precisely match this object's format. With strict parsing, + * inputs must match this object's format. + * + *

This method is equivalent to the following call. + *

+     *  getCalendar().setLenient(lenient)
+     * 
+ * + *

This leniency value is overwritten by a call to {@link + * #setCalendar(java.util.Calendar) setCalendar()}. + * + * @param lenient when {@code true}, parsing is lenient + * @see java.util.Calendar#setLenient(boolean) + */ + public void setLenient(boolean lenient) + { + calendar.setLenient(lenient); + } + + /** + * Tell whether date/time parsing is to be lenient. + * This method is equivalent to the following call. + *

+     *  getCalendar().isLenient()
+     * 
+ * + * @return {@code true} if the {@link #calendar} is lenient; + * {@code false} otherwise. + * @see java.util.Calendar#isLenient() + */ + public boolean isLenient() + { + return calendar.isLenient(); + } + + /** + * Overrides hashCode + */ + public int hashCode() { + return numberFormat.hashCode(); + // just enough fields for a reasonable distribution + } + + /** + * Overrides equals + */ + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + DateFormat other = (DateFormat) obj; + return (// calendar.equivalentTo(other.calendar) // THIS API DOESN'T EXIST YET! + calendar.getFirstDayOfWeek() == other.calendar.getFirstDayOfWeek() && + calendar.getMinimalDaysInFirstWeek() == other.calendar.getMinimalDaysInFirstWeek() && + calendar.isLenient() == other.calendar.isLenient() && + calendar.getTimeZone().equals(other.calendar.getTimeZone()) && + numberFormat.equals(other.numberFormat)); + } + + /** + * Overrides Cloneable + */ + public Object clone() + { + DateFormat other = (DateFormat) super.clone(); + other.calendar = (Calendar) calendar.clone(); + other.numberFormat = (NumberFormat) numberFormat.clone(); + return other; + } + + /** + * Creates a DateFormat with the given time and/or date style in the given + * locale. + * @param timeStyle a value from 0 to 3 indicating the time format, + * ignored if flags is 2 + * @param dateStyle a value from 0 to 3 indicating the time format, + * ignored if flags is 1 + * @param flags either 1 for a time format, 2 for a date format, + * or 3 for a date/time format + * @param loc the locale for the format + */ + private static DateFormat get(int timeStyle, int dateStyle, + int flags, Locale loc) { + if ((flags & 1) != 0) { + if (timeStyle < 0 || timeStyle > 3) { + throw new IllegalArgumentException("Illegal time style " + timeStyle); + } + } else { + timeStyle = -1; + } + if ((flags & 2) != 0) { + if (dateStyle < 0 || dateStyle > 3) { + throw new IllegalArgumentException("Illegal date style " + dateStyle); + } + } else { + dateStyle = -1; + } + try { + // Check whether a provider can provide an implementation that's closer + // to the requested locale than what the Java runtime itself can provide. + /* + LocaleServiceProviderPool pool = + LocaleServiceProviderPool.getPool(DateFormatProvider.class); + if (pool.hasProviders()) { + DateFormat providersInstance = pool.getLocalizedObject( + DateFormatGetter.INSTANCE, + loc, + timeStyle, + dateStyle, + flags); + if (providersInstance != null) { + return providersInstance; + } + } + */ + + return new SimpleDateFormat(timeStyle, dateStyle, loc); + } catch (MissingResourceException e) { + return new SimpleDateFormat("M/d/yy h:mm a"); + } + } + + /** + * Create a new date format. + */ + protected DateFormat() {} + + /** + * Defines constants that are used as attribute keys in the + * AttributedCharacterIterator returned + * from DateFormat.formatToCharacterIterator and as + * field identifiers in FieldPosition. + *

+ * The class also provides two methods to map + * between its constants and the corresponding Calendar constants. + * + * @since 1.4 + * @see java.util.Calendar + */ + public static class Field extends Format.Field { + + // Proclaim serial compatibility with 1.4 FCS + private static final long serialVersionUID = 7441350119349544720L; + + // table of all instances in this class, used by readResolve + private static final Map instanceMap = new HashMap(18); + // Maps from Calendar constant (such as Calendar.ERA) to Field + // constant (such as Field.ERA). + private static final Field[] calendarToFieldMapping = + new Field[Calendar.FIELD_COUNT]; + + /** Calendar field. */ + private int calendarField; + + /** + * Returns the Field constant that corresponds to + * the Calendar constant calendarField. + * If there is no direct mapping between the Calendar + * constant and a Field, null is returned. + * + * @throws IllegalArgumentException if calendarField is + * not the value of a Calendar field constant. + * @param calendarField Calendar field constant + * @return Field instance representing calendarField. + * @see java.util.Calendar + */ + public static Field ofCalendarField(int calendarField) { + if (calendarField < 0 || calendarField >= + calendarToFieldMapping.length) { + throw new IllegalArgumentException("Unknown Calendar constant " + + calendarField); + } + return calendarToFieldMapping[calendarField]; + } + + /** + * Creates a Field. + * + * @param name the name of the Field + * @param calendarField the Calendar constant this + * Field corresponds to; any value, even one + * outside the range of legal Calendar values may + * be used, but -1 should be used for values + * that don't correspond to legal Calendar values + */ + protected Field(String name, int calendarField) { + super(name); + this.calendarField = calendarField; + if (this.getClass() == DateFormat.Field.class) { + instanceMap.put(name, this); + if (calendarField >= 0) { + // assert(calendarField < Calendar.FIELD_COUNT); + calendarToFieldMapping[calendarField] = this; + } + } + } + + /** + * Returns the Calendar field associated with this + * attribute. For example, if this represents the hours field of + * a Calendar, this would return + * Calendar.HOUR. If there is no corresponding + * Calendar constant, this will return -1. + * + * @return Calendar constant for this field + * @see java.util.Calendar + */ + public int getCalendarField() { + return calendarField; + } + + /** + * Resolves instances being deserialized to the predefined constants. + * + * @throws InvalidObjectException if the constant could not be + * resolved. + * @return resolved DateFormat.Field constant + */ + protected Object readResolve() throws InvalidObjectException { + if (this.getClass() != DateFormat.Field.class) { + throw new InvalidObjectException("subclass didn't correctly implement readResolve"); + } + + Object instance = instanceMap.get(getName()); + if (instance != null) { + return instance; + } else { + throw new InvalidObjectException("unknown attribute name"); + } + } + + // + // The constants + // + + /** + * Constant identifying the era field. + */ + public final static Field ERA = new Field("era", Calendar.ERA); + + /** + * Constant identifying the year field. + */ + public final static Field YEAR = new Field("year", Calendar.YEAR); + + /** + * Constant identifying the month field. + */ + public final static Field MONTH = new Field("month", Calendar.MONTH); + + /** + * Constant identifying the day of month field. + */ + public final static Field DAY_OF_MONTH = new + Field("day of month", Calendar.DAY_OF_MONTH); + + /** + * Constant identifying the hour of day field, where the legal values + * are 1 to 24. + */ + public final static Field HOUR_OF_DAY1 = new Field("hour of day 1",-1); + + /** + * Constant identifying the hour of day field, where the legal values + * are 0 to 23. + */ + public final static Field HOUR_OF_DAY0 = new + Field("hour of day", Calendar.HOUR_OF_DAY); + + /** + * Constant identifying the minute field. + */ + public final static Field MINUTE =new Field("minute", Calendar.MINUTE); + + /** + * Constant identifying the second field. + */ + public final static Field SECOND =new Field("second", Calendar.SECOND); + + /** + * Constant identifying the millisecond field. + */ + public final static Field MILLISECOND = new + Field("millisecond", Calendar.MILLISECOND); + + /** + * Constant identifying the day of week field. + */ + public final static Field DAY_OF_WEEK = new + Field("day of week", Calendar.DAY_OF_WEEK); + + /** + * Constant identifying the day of year field. + */ + public final static Field DAY_OF_YEAR = new + Field("day of year", Calendar.DAY_OF_YEAR); + + /** + * Constant identifying the day of week field. + */ + public final static Field DAY_OF_WEEK_IN_MONTH = + new Field("day of week in month", + Calendar.DAY_OF_WEEK_IN_MONTH); + + /** + * Constant identifying the week of year field. + */ + public final static Field WEEK_OF_YEAR = new + Field("week of year", Calendar.WEEK_OF_YEAR); + + /** + * Constant identifying the week of month field. + */ + public final static Field WEEK_OF_MONTH = new + Field("week of month", Calendar.WEEK_OF_MONTH); + + /** + * Constant identifying the time of day indicator + * (e.g. "a.m." or "p.m.") field. + */ + public final static Field AM_PM = new + Field("am pm", Calendar.AM_PM); + + /** + * Constant identifying the hour field, where the legal values are + * 1 to 12. + */ + public final static Field HOUR1 = new Field("hour 1", -1); + + /** + * Constant identifying the hour field, where the legal values are + * 0 to 11. + */ + public final static Field HOUR0 = new + Field("hour", Calendar.HOUR); + + /** + * Constant identifying the time zone field. + */ + public final static Field TIME_ZONE = new Field("time zone", -1); + } + + /** + * Obtains a DateFormat instance from a DateFormatProvider + * implementation. + private static class DateFormatGetter + implements LocaleServiceProviderPool.LocalizedObjectGetter { + private static final DateFormatGetter INSTANCE = new DateFormatGetter(); + + public DateFormat getObject(DateFormatProvider dateFormatProvider, + Locale locale, + String key, + Object... params) { + assert params.length == 3; + + int timeStyle = (Integer)params[0]; + int dateStyle = (Integer)params[1]; + int flags = (Integer)params[2]; + + switch (flags) { + case 1: + return dateFormatProvider.getTimeInstance(timeStyle, locale); + case 2: + return dateFormatProvider.getDateInstance(dateStyle, locale); + case 3: + return dateFormatProvider.getDateTimeInstance(dateStyle, timeStyle, locale); + default: + assert false : "should not happen"; + } + + return null; + } + } + */ +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/DateFormatSymbols.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/DateFormatSymbols.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,786 @@ +/* + * Copyright (c) 1996, 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.ref.SoftReference; +import java.util.Arrays; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * DateFormatSymbols is a public class for encapsulating + * localizable date-time formatting data, such as the names of the + * months, the names of the days of the week, and the time zone data. + * DateFormat and SimpleDateFormat both use + * DateFormatSymbols to encapsulate this information. + * + *

+ * Typically you shouldn't use DateFormatSymbols directly. + * Rather, you are encouraged to create a date-time formatter with the + * DateFormat class's factory methods: getTimeInstance, + * getDateInstance, or getDateTimeInstance. + * These methods automatically create a DateFormatSymbols for + * the formatter so that you don't have to. After the + * formatter is created, you may modify its format pattern using the + * setPattern method. For more information about + * creating formatters using DateFormat's factory methods, + * see {@link DateFormat}. + * + *

+ * If you decide to create a date-time formatter with a specific + * format pattern for a specific locale, you can do so with: + *

+ *
+ * new SimpleDateFormat(aPattern, DateFormatSymbols.getInstance(aLocale)).
+ * 
+ *
+ * + *

+ * DateFormatSymbols objects are cloneable. When you obtain + * a DateFormatSymbols object, feel free to modify the + * date-time formatting data. For instance, you can replace the localized + * date-time format pattern characters with the ones that you feel easy + * to remember. Or you can change the representative cities + * to your favorite ones. + * + *

+ * New DateFormatSymbols subclasses may be added to support + * SimpleDateFormat for date-time formatting for additional locales. + + * @see DateFormat + * @see SimpleDateFormat + * @see java.util.SimpleTimeZone + * @author Chen-Lieh Huang + */ +public class DateFormatSymbols implements Serializable, Cloneable { + + /** + * Construct a DateFormatSymbols object by loading format data from + * resources for the default locale. This constructor can only + * construct instances for the locales supported by the Java + * runtime environment, not for those supported by installed + * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} + * implementations. For full locale coverage, use the + * {@link #getInstance(Locale) getInstance} method. + * + * @see #getInstance() + * @exception java.util.MissingResourceException + * if the resources for the default locale cannot be + * found or cannot be loaded. + */ + public DateFormatSymbols() + { + initializeData(Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Construct a DateFormatSymbols object by loading format data from + * resources for the given locale. This constructor can only + * construct instances for the locales supported by the Java + * runtime environment, not for those supported by installed + * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} + * implementations. For full locale coverage, use the + * {@link #getInstance(Locale) getInstance} method. + * + * @see #getInstance(Locale) + * @exception java.util.MissingResourceException + * if the resources for the specified locale cannot be + * found or cannot be loaded. + */ + public DateFormatSymbols(Locale locale) + { + initializeData(locale); + } + + /** + * Era strings. For example: "AD" and "BC". An array of 2 strings, + * indexed by Calendar.BC and Calendar.AD. + * @serial + */ + String eras[] = null; + + /** + * Month strings. For example: "January", "February", etc. An array + * of 13 strings (some calendars have 13 months), indexed by + * Calendar.JANUARY, Calendar.FEBRUARY, etc. + * @serial + */ + String months[] = null; + + /** + * Short month strings. For example: "Jan", "Feb", etc. An array of + * 13 strings (some calendars have 13 months), indexed by + * Calendar.JANUARY, Calendar.FEBRUARY, etc. + + * @serial + */ + String shortMonths[] = null; + + /** + * Weekday strings. For example: "Sunday", "Monday", etc. An array + * of 8 strings, indexed by Calendar.SUNDAY, + * Calendar.MONDAY, etc. + * The element weekdays[0] is ignored. + * @serial + */ + String weekdays[] = null; + + /** + * Short weekday strings. For example: "Sun", "Mon", etc. An array + * of 8 strings, indexed by Calendar.SUNDAY, + * Calendar.MONDAY, etc. + * The element shortWeekdays[0] is ignored. + * @serial + */ + String shortWeekdays[] = null; + + /** + * AM and PM strings. For example: "AM" and "PM". An array of + * 2 strings, indexed by Calendar.AM and + * Calendar.PM. + * @serial + */ + String ampms[] = null; + + /** + * Localized names of time zones in this locale. This is a + * two-dimensional array of strings of size n by m, + * where m is at least 5. Each of the n rows is an + * entry containing the localized names for a single TimeZone. + * Each such row contains (with i ranging from + * 0..n-1): + *

    + *
  • zoneStrings[i][0] - time zone ID
  • + *
  • zoneStrings[i][1] - long name of zone in standard + * time
  • + *
  • zoneStrings[i][2] - short name of zone in + * standard time
  • + *
  • zoneStrings[i][3] - long name of zone in daylight + * saving time
  • + *
  • zoneStrings[i][4] - short name of zone in daylight + * saving time
  • + *
+ * The zone ID is not localized; it's one of the valid IDs of + * the {@link java.util.TimeZone TimeZone} class that are not + * custom IDs. + * All other entries are localized names. + * @see java.util.TimeZone + * @serial + */ + String zoneStrings[][] = null; + + /** + * Indicates that zoneStrings is set externally with setZoneStrings() method. + */ + transient boolean isZoneStringsSet = false; + + /** + * Unlocalized date-time pattern characters. For example: 'y', 'd', etc. + * All locales use the same these unlocalized pattern characters. + */ + static final String patternChars = "GyMdkHmsSEDFwWahKzZYuX"; + + static final int PATTERN_ERA = 0; // G + static final int PATTERN_YEAR = 1; // y + static final int PATTERN_MONTH = 2; // M + static final int PATTERN_DAY_OF_MONTH = 3; // d + static final int PATTERN_HOUR_OF_DAY1 = 4; // k + static final int PATTERN_HOUR_OF_DAY0 = 5; // H + static final int PATTERN_MINUTE = 6; // m + static final int PATTERN_SECOND = 7; // s + static final int PATTERN_MILLISECOND = 8; // S + static final int PATTERN_DAY_OF_WEEK = 9; // E + static final int PATTERN_DAY_OF_YEAR = 10; // D + static final int PATTERN_DAY_OF_WEEK_IN_MONTH = 11; // F + static final int PATTERN_WEEK_OF_YEAR = 12; // w + static final int PATTERN_WEEK_OF_MONTH = 13; // W + static final int PATTERN_AM_PM = 14; // a + static final int PATTERN_HOUR1 = 15; // h + static final int PATTERN_HOUR0 = 16; // K + static final int PATTERN_ZONE_NAME = 17; // z + static final int PATTERN_ZONE_VALUE = 18; // Z + static final int PATTERN_WEEK_YEAR = 19; // Y + static final int PATTERN_ISO_DAY_OF_WEEK = 20; // u + static final int PATTERN_ISO_ZONE = 21; // X + + /** + * Localized date-time pattern characters. For example, a locale may + * wish to use 'u' rather than 'y' to represent years in its date format + * pattern strings. + * This string must be exactly 18 characters long, with the index of + * the characters described by DateFormat.ERA_FIELD, + * DateFormat.YEAR_FIELD, etc. Thus, if the string were + * "Xz...", then localized patterns would use 'X' for era and 'z' for year. + * @serial + */ + String localPatternChars = null; + + /** + * The locale which is used for initializing this DateFormatSymbols object. + * + * @since 1.6 + * @serial + */ + Locale locale = null; + + /* use serialVersionUID from JDK 1.1.4 for interoperability */ + static final long serialVersionUID = -5987973545549424702L; + + /** + * Returns an array of all locales for which the + * getInstance methods of this class can return + * localized instances. + * The returned array represents the union of locales supported by the + * Java runtime and by installed + * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} + * implementations. It must contain at least a Locale + * instance equal to {@link java.util.Locale#US Locale.US}. + * + * @return An array of locales for which localized + * DateFormatSymbols instances are available. + * @since 1.6 + */ + public static Locale[] getAvailableLocales() { + return new Locale[] { Locale.US }; +// LocaleServiceProviderPool pool= +// LocaleServiceProviderPool.getPool(DateFormatSymbolsProvider.class); +// return pool.getAvailableLocales(); + } + + /** + * Gets the DateFormatSymbols instance for the default + * locale. This method provides access to DateFormatSymbols + * instances for locales supported by the Java runtime itself as well + * as for those supported by installed + * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} + * implementations. + * @return a DateFormatSymbols instance. + * @since 1.6 + */ + public static final DateFormatSymbols getInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the DateFormatSymbols instance for the specified + * locale. This method provides access to DateFormatSymbols + * instances for locales supported by the Java runtime itself as well + * as for those supported by installed + * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} + * implementations. + * @param locale the given locale. + * @return a DateFormatSymbols instance. + * @exception NullPointerException if locale is null + * @since 1.6 + */ + public static final DateFormatSymbols getInstance(Locale locale) { + DateFormatSymbols dfs = getProviderInstance(locale); + if (dfs != null) { + return dfs; + } + return (DateFormatSymbols) getCachedInstance(locale).clone(); + } + + /** + * Returns a DateFormatSymbols provided by a provider or found in + * the cache. Note that this method returns a cached instance, + * not its clone. Therefore, the instance should never be given to + * an application. + */ + static final DateFormatSymbols getInstanceRef(Locale locale) { + DateFormatSymbols dfs = getProviderInstance(locale); + if (dfs != null) { + return dfs; + } + return getCachedInstance(locale); + } + + private static DateFormatSymbols getProviderInstance(Locale locale) { + DateFormatSymbols providersInstance = null; + + // Check whether a provider can provide an implementation that's closer + // to the requested locale than what the Java runtime itself can provide. +// LocaleServiceProviderPool pool = +// LocaleServiceProviderPool.getPool(DateFormatSymbolsProvider.class); +// if (pool.hasProviders()) { +// providersInstance = pool.getLocalizedObject( +// DateFormatSymbolsGetter.INSTANCE, locale); +// } + return providersInstance; + } + + /** + * Returns a cached DateFormatSymbols if it's found in the + * cache. Otherwise, this method returns a newly cached instance + * for the given locale. + */ + private static DateFormatSymbols getCachedInstance(Locale locale) { + SoftReference ref = cachedInstances.get(locale); + DateFormatSymbols dfs = null; + if (ref == null || (dfs = ref.get()) == null) { + dfs = new DateFormatSymbols(locale); + ref = new SoftReference(dfs); + SoftReference x = cachedInstances.putIfAbsent(locale, ref); + if (x != null) { + DateFormatSymbols y = x.get(); + if (y != null) { + dfs = y; + } else { + // Replace the empty SoftReference with ref. + cachedInstances.put(locale, ref); + } + } + } + return dfs; + } + + /** + * Gets era strings. For example: "AD" and "BC". + * @return the era strings. + */ + public String[] getEras() { + return Arrays.copyOf(eras, eras.length); + } + + /** + * Sets era strings. For example: "AD" and "BC". + * @param newEras the new era strings. + */ + public void setEras(String[] newEras) { + eras = Arrays.copyOf(newEras, newEras.length); + } + + /** + * Gets month strings. For example: "January", "February", etc. + * @return the month strings. + */ + public String[] getMonths() { + return Arrays.copyOf(months, months.length); + } + + /** + * Sets month strings. For example: "January", "February", etc. + * @param newMonths the new month strings. + */ + public void setMonths(String[] newMonths) { + months = Arrays.copyOf(newMonths, newMonths.length); + } + + /** + * Gets short month strings. For example: "Jan", "Feb", etc. + * @return the short month strings. + */ + public String[] getShortMonths() { + return Arrays.copyOf(shortMonths, shortMonths.length); + } + + /** + * Sets short month strings. For example: "Jan", "Feb", etc. + * @param newShortMonths the new short month strings. + */ + public void setShortMonths(String[] newShortMonths) { + shortMonths = Arrays.copyOf(newShortMonths, newShortMonths.length); + } + + /** + * Gets weekday strings. For example: "Sunday", "Monday", etc. + * @return the weekday strings. Use Calendar.SUNDAY, + * Calendar.MONDAY, etc. to index the result array. + */ + public String[] getWeekdays() { + return Arrays.copyOf(weekdays, weekdays.length); + } + + /** + * Sets weekday strings. For example: "Sunday", "Monday", etc. + * @param newWeekdays the new weekday strings. The array should + * be indexed by Calendar.SUNDAY, + * Calendar.MONDAY, etc. + */ + public void setWeekdays(String[] newWeekdays) { + weekdays = Arrays.copyOf(newWeekdays, newWeekdays.length); + } + + /** + * Gets short weekday strings. For example: "Sun", "Mon", etc. + * @return the short weekday strings. Use Calendar.SUNDAY, + * Calendar.MONDAY, etc. to index the result array. + */ + public String[] getShortWeekdays() { + return Arrays.copyOf(shortWeekdays, shortWeekdays.length); + } + + /** + * Sets short weekday strings. For example: "Sun", "Mon", etc. + * @param newShortWeekdays the new short weekday strings. The array should + * be indexed by Calendar.SUNDAY, + * Calendar.MONDAY, etc. + */ + public void setShortWeekdays(String[] newShortWeekdays) { + shortWeekdays = Arrays.copyOf(newShortWeekdays, newShortWeekdays.length); + } + + /** + * Gets ampm strings. For example: "AM" and "PM". + * @return the ampm strings. + */ + public String[] getAmPmStrings() { + return Arrays.copyOf(ampms, ampms.length); + } + + /** + * Sets ampm strings. For example: "AM" and "PM". + * @param newAmpms the new ampm strings. + */ + public void setAmPmStrings(String[] newAmpms) { + ampms = Arrays.copyOf(newAmpms, newAmpms.length); + } + + /** + * Gets time zone strings. Use of this method is discouraged; use + * {@link java.util.TimeZone#getDisplayName() TimeZone.getDisplayName()} + * instead. + *

+ * The value returned is a + * two-dimensional array of strings of size n by m, + * where m is at least 5. Each of the n rows is an + * entry containing the localized names for a single TimeZone. + * Each such row contains (with i ranging from + * 0..n-1): + *

    + *
  • zoneStrings[i][0] - time zone ID
  • + *
  • zoneStrings[i][1] - long name of zone in standard + * time
  • + *
  • zoneStrings[i][2] - short name of zone in + * standard time
  • + *
  • zoneStrings[i][3] - long name of zone in daylight + * saving time
  • + *
  • zoneStrings[i][4] - short name of zone in daylight + * saving time
  • + *
+ * The zone ID is not localized; it's one of the valid IDs of + * the {@link java.util.TimeZone TimeZone} class that are not + * custom IDs. + * All other entries are localized names. If a zone does not implement + * daylight saving time, the daylight saving time names should not be used. + *

+ * If {@link #setZoneStrings(String[][]) setZoneStrings} has been called + * on this DateFormatSymbols instance, then the strings + * provided by that call are returned. Otherwise, the returned array + * contains names provided by the Java runtime and by installed + * {@link java.util.spi.TimeZoneNameProvider TimeZoneNameProvider} + * implementations. + * + * @return the time zone strings. + * @see #setZoneStrings(String[][]) + */ + public String[][] getZoneStrings() { + return getZoneStringsImpl(true); + } + + /** + * Sets time zone strings. The argument must be a + * two-dimensional array of strings of size n by m, + * where m is at least 5. Each of the n rows is an + * entry containing the localized names for a single TimeZone. + * Each such row contains (with i ranging from + * 0..n-1): + *

    + *
  • zoneStrings[i][0] - time zone ID
  • + *
  • zoneStrings[i][1] - long name of zone in standard + * time
  • + *
  • zoneStrings[i][2] - short name of zone in + * standard time
  • + *
  • zoneStrings[i][3] - long name of zone in daylight + * saving time
  • + *
  • zoneStrings[i][4] - short name of zone in daylight + * saving time
  • + *
+ * The zone ID is not localized; it's one of the valid IDs of + * the {@link java.util.TimeZone TimeZone} class that are not + * custom IDs. + * All other entries are localized names. + * + * @param newZoneStrings the new time zone strings. + * @exception IllegalArgumentException if the length of any row in + * newZoneStrings is less than 5 + * @exception NullPointerException if newZoneStrings is null + * @see #getZoneStrings() + */ + public void setZoneStrings(String[][] newZoneStrings) { + String[][] aCopy = new String[newZoneStrings.length][]; + for (int i = 0; i < newZoneStrings.length; ++i) { + int len = newZoneStrings[i].length; + if (len < 5) { + throw new IllegalArgumentException(); + } + aCopy[i] = Arrays.copyOf(newZoneStrings[i], len); + } + zoneStrings = aCopy; + isZoneStringsSet = true; + } + + /** + * Gets localized date-time pattern characters. For example: 'u', 't', etc. + * @return the localized date-time pattern characters. + */ + public String getLocalPatternChars() { + return localPatternChars; + } + + /** + * Sets localized date-time pattern characters. For example: 'u', 't', etc. + * @param newLocalPatternChars the new localized date-time + * pattern characters. + */ + public void setLocalPatternChars(String newLocalPatternChars) { + // Call toString() to throw an NPE in case the argument is null + localPatternChars = newLocalPatternChars.toString(); + } + + /** + * Overrides Cloneable + */ + public Object clone() + { + try + { + DateFormatSymbols other = (DateFormatSymbols)super.clone(); + copyMembers(this, other); + return other; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * Override hashCode. + * Generates a hash code for the DateFormatSymbols object. + */ + public int hashCode() { + int hashcode = 0; + String[][] zoneStrings = getZoneStringsWrapper(); + for (int index = 0; index < zoneStrings[0].length; ++index) + hashcode ^= zoneStrings[0][index].hashCode(); + return hashcode; + } + + /** + * Override equals + */ + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + DateFormatSymbols that = (DateFormatSymbols) obj; + return (Arrays.equals(eras, that.eras) + && Arrays.equals(months, that.months) + && Arrays.equals(shortMonths, that.shortMonths) + && Arrays.equals(weekdays, that.weekdays) + && Arrays.equals(shortWeekdays, that.shortWeekdays) + && Arrays.equals(ampms, that.ampms) + && Arrays.deepEquals(getZoneStringsWrapper(), that.getZoneStringsWrapper()) + && ((localPatternChars != null + && localPatternChars.equals(that.localPatternChars)) + || (localPatternChars == null + && that.localPatternChars == null))); + } + + // =======================privates=============================== + + /** + * Useful constant for defining time zone offsets. + */ + static final int millisPerHour = 60*60*1000; + + /** + * Cache to hold DateFormatSymbols instances per Locale. + */ + private static final ConcurrentMap> cachedInstances + = new ConcurrentHashMap>(3); + + private void initializeData(Locale desiredLocale) { + locale = desiredLocale; + + // Copy values of a cached instance if any. + SoftReference ref = cachedInstances.get(locale); + DateFormatSymbols dfs; + if (ref != null && (dfs = ref.get()) != null) { + copyMembers(dfs, this); + return; + } + + // Initialize the fields from the ResourceBundle for locale. +// ResourceBundle resource = LocaleData.getDateFormatData(locale); +// +// eras = resource.getStringArray("Eras"); +// months = resource.getStringArray("MonthNames"); +// shortMonths = resource.getStringArray("MonthAbbreviations"); +// ampms = resource.getStringArray("AmPmMarkers"); +// localPatternChars = resource.getString("DateTimePatternChars"); +// +// // Day of week names are stored in a 1-based array. +// weekdays = toOneBasedArray(resource.getStringArray("DayNames")); +// shortWeekdays = toOneBasedArray(resource.getStringArray("DayAbbreviations")); + } + + private static String[] toOneBasedArray(String[] src) { + int len = src.length; + String[] dst = new String[len + 1]; + dst[0] = ""; + for (int i = 0; i < len; i++) { + dst[i + 1] = src[i]; + } + return dst; + } + + /** + * Package private: used by SimpleDateFormat + * Gets the index for the given time zone ID to obtain the time zone + * strings for formatting. The time zone ID is just for programmatic + * lookup. NOT LOCALIZED!!! + * @param ID the given time zone ID. + * @return the index of the given time zone ID. Returns -1 if + * the given time zone ID can't be located in the DateFormatSymbols object. + * @see java.util.SimpleTimeZone + */ + final int getZoneIndex(String ID) + { + String[][] zoneStrings = getZoneStringsWrapper(); + for (int index=0; indexzoneStrings field is initialized in order to make + * sure the backward compatibility. + * + * @since 1.6 + private void writeObject(ObjectOutputStream stream) throws IOException { + if (zoneStrings == null) { + zoneStrings = TimeZoneNameUtility.getZoneStrings(locale); + } + stream.defaultWriteObject(); + } + + /** + * Obtains a DateFormatSymbols instance from a DateFormatSymbolsProvider + * implementation. + private static class DateFormatSymbolsGetter + implements LocaleServiceProviderPool.LocalizedObjectGetter { + private static final DateFormatSymbolsGetter INSTANCE = + new DateFormatSymbolsGetter(); + + public DateFormatSymbols getObject(DateFormatSymbolsProvider dateFormatSymbolsProvider, + Locale locale, + String key, + Object... params) { + assert params.length == 0; + return dateFormatSymbolsProvider.getInstance(locale); + } + } + */ +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/DecimalFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/DecimalFormat.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,3277 @@ +/* + * Copyright (c) 1996, 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.InvalidObjectException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Currency; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * DecimalFormat is a concrete subclass of + * NumberFormat that formats decimal numbers. It has a variety of + * features designed to make it possible to parse and format numbers in any + * locale, including support for Western, Arabic, and Indic digits. It also + * supports different kinds of numbers, including integers (123), fixed-point + * numbers (123.4), scientific notation (1.23E4), percentages (12%), and + * currency amounts ($123). All of these can be localized. + * + *

To obtain a NumberFormat for a specific locale, including the + * default locale, call one of NumberFormat's factory methods, such + * as getInstance(). In general, do not call the + * DecimalFormat constructors directly, since the + * NumberFormat factory methods may return subclasses other than + * DecimalFormat. If you need to customize the format object, do + * something like this: + * + *

+ * NumberFormat f = NumberFormat.getInstance(loc);
+ * if (f instanceof DecimalFormat) {
+ *     ((DecimalFormat) f).setDecimalSeparatorAlwaysShown(true);
+ * }
+ * 
+ * + *

A DecimalFormat comprises a pattern and a set of + * symbols. The pattern may be set directly using + * applyPattern(), or indirectly using the API methods. The + * symbols are stored in a DecimalFormatSymbols object. When using + * the NumberFormat factory methods, the pattern and symbols are + * read from localized ResourceBundles. + * + *

Patterns

+ * + * DecimalFormat patterns have the following syntax: + *
+ * Pattern:
+ *         PositivePattern
+ *         PositivePattern ; NegativePattern
+ * PositivePattern:
+ *         Prefixopt Number Suffixopt
+ * NegativePattern:
+ *         Prefixopt Number Suffixopt
+ * Prefix:
+ *         any Unicode characters except \uFFFE, \uFFFF, and special characters
+ * Suffix:
+ *         any Unicode characters except \uFFFE, \uFFFF, and special characters
+ * Number:
+ *         Integer Exponentopt
+ *         Integer . Fraction Exponentopt
+ * Integer:
+ *         MinimumInteger
+ *         #
+ *         # Integer
+ *         # , Integer
+ * MinimumInteger:
+ *         0
+ *         0 MinimumInteger
+ *         0 , MinimumInteger
+ * Fraction:
+ *         MinimumFractionopt OptionalFractionopt
+ * MinimumFraction:
+ *         0 MinimumFractionopt
+ * OptionalFraction:
+ *         # OptionalFractionopt
+ * Exponent:
+ *         E MinimumExponent
+ * MinimumExponent:
+ *         0 MinimumExponentopt
+ * 
+ * + *

A DecimalFormat pattern contains a positive and negative + * subpattern, for example, "#,##0.00;(#,##0.00)". Each + * subpattern has a prefix, numeric part, and suffix. The negative subpattern + * is optional; if absent, then the positive subpattern prefixed with the + * localized minus sign ('-' in most locales) is used as the + * negative subpattern. That is, "0.00" alone is equivalent to + * "0.00;-0.00". If there is an explicit negative subpattern, it + * serves only to specify the negative prefix and suffix; the number of digits, + * minimal digits, and other characteristics are all the same as the positive + * pattern. That means that "#,##0.0#;(#)" produces precisely + * the same behavior as "#,##0.0#;(#,##0.0#)". + * + *

The prefixes, suffixes, and various symbols used for infinity, digits, + * thousands separators, decimal separators, etc. may be set to arbitrary + * values, and they will appear properly during formatting. However, care must + * be taken that the symbols and strings do not conflict, or parsing will be + * unreliable. For example, either the positive and negative prefixes or the + * suffixes must be distinct for DecimalFormat.parse() to be able + * to distinguish positive from negative values. (If they are identical, then + * DecimalFormat will behave as if no negative subpattern was + * specified.) Another example is that the decimal separator and thousands + * separator should be distinct characters, or parsing will be impossible. + * + *

The grouping separator is commonly used for thousands, but in some + * countries it separates ten-thousands. The grouping size is a constant number + * of digits between the grouping characters, such as 3 for 100,000,000 or 4 for + * 1,0000,0000. If you supply a pattern with multiple grouping characters, the + * interval between the last one and the end of the integer is the one that is + * used. So "#,##,###,####" == "######,####" == + * "##,####,####". + * + *

Special Pattern Characters

+ * + *

Many characters in a pattern are taken literally; they are matched during + * parsing and output unchanged during formatting. Special characters, on the + * other hand, stand for other characters, strings, or classes of characters. + * They must be quoted, unless noted otherwise, if they are to appear in the + * prefix or suffix as literals. + * + *

The characters listed here are used in non-localized patterns. Localized + * patterns use the corresponding characters taken from this formatter's + * DecimalFormatSymbols object instead, and these characters lose + * their special status. Two exceptions are the currency sign and quote, which + * are not localized. + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + *
Symbol + * Location + * Localized? + * Meaning + *
0 + * Number + * Yes + * Digit + *
# + * Number + * Yes + * Digit, zero shows as absent + *
. + * Number + * Yes + * Decimal separator or monetary decimal separator + *
- + * Number + * Yes + * Minus sign + *
, + * Number + * Yes + * Grouping separator + *
E + * Number + * Yes + * Separates mantissa and exponent in scientific notation. + * Need not be quoted in prefix or suffix. + *
; + * Subpattern boundary + * Yes + * Separates positive and negative subpatterns + *
% + * Prefix or suffix + * Yes + * Multiply by 100 and show as percentage + *
\u2030 + * Prefix or suffix + * Yes + * Multiply by 1000 and show as per mille value + *
¤ (\u00A4) + * Prefix or suffix + * No + * Currency sign, replaced by currency symbol. If + * doubled, replaced by international currency symbol. + * If present in a pattern, the monetary decimal separator + * is used instead of the decimal separator. + *
' + * Prefix or suffix + * No + * Used to quote special characters in a prefix or suffix, + * for example, "'#'#" formats 123 to + * "#123". To create a single quote + * itself, use two in a row: "# o''clock". + *
+ *
+ * + *

Scientific Notation

+ * + *

Numbers in scientific notation are expressed as the product of a mantissa + * and a power of ten, for example, 1234 can be expressed as 1.234 x 10^3. The + * mantissa is often in the range 1.0 <= x < 10.0, but it need not be. + * DecimalFormat can be instructed to format and parse scientific + * notation only via a pattern; there is currently no factory method + * that creates a scientific notation format. In a pattern, the exponent + * character immediately followed by one or more digit characters indicates + * scientific notation. Example: "0.###E0" formats the number + * 1234 as "1.234E3". + * + *

    + *
  • The number of digit characters after the exponent character gives the + * minimum exponent digit count. There is no maximum. Negative exponents are + * formatted using the localized minus sign, not the prefix and suffix + * from the pattern. This allows patterns such as "0.###E0 m/s". + * + *
  • The minimum and maximum number of integer digits are interpreted + * together: + * + *
      + *
    • If the maximum number of integer digits is greater than their minimum number + * and greater than 1, it forces the exponent to be a multiple of the maximum + * number of integer digits, and the minimum number of integer digits to be + * interpreted as 1. The most common use of this is to generate + * engineering notation, in which the exponent is a multiple of three, + * e.g., "##0.#####E0". Using this pattern, the number 12345 + * formats to "12.345E3", and 123456 formats to + * "123.456E3". + * + *
    • Otherwise, the minimum number of integer digits is achieved by adjusting the + * exponent. Example: 0.00123 formatted with "00.###E0" yields + * "12.3E-4". + *
    + * + *
  • The number of significant digits in the mantissa is the sum of the + * minimum integer and maximum fraction digits, and is + * unaffected by the maximum integer digits. For example, 12345 formatted with + * "##0.##E0" is "12.3E3". To show all digits, set + * the significant digits count to zero. The number of significant digits + * does not affect parsing. + * + *
  • Exponential patterns may not contain grouping separators. + *
+ * + *

Rounding

+ * + * DecimalFormat provides rounding modes defined in + * {@link java.math.RoundingMode} for formatting. By default, it uses + * {@link java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}. + * + *

Digits

+ * + * For formatting, DecimalFormat uses the ten consecutive + * characters starting with the localized zero digit defined in the + * DecimalFormatSymbols object as digits. For parsing, these + * digits as well as all Unicode decimal digits, as defined by + * {@link Character#digit Character.digit}, are recognized. + * + *

Special Values

+ * + *

NaN is formatted as a string, which typically has a single character + * \uFFFD. This string is determined by the + * DecimalFormatSymbols object. This is the only value for which + * the prefixes and suffixes are not used. + * + *

Infinity is formatted as a string, which typically has a single character + * \u221E, with the positive or negative prefixes and suffixes + * applied. The infinity string is determined by the + * DecimalFormatSymbols object. + * + *

Negative zero ("-0") parses to + *

    + *
  • BigDecimal(0) if isParseBigDecimal() is + * true, + *
  • Long(0) if isParseBigDecimal() is false + * and isParseIntegerOnly() is true, + *
  • Double(-0.0) if both isParseBigDecimal() + * and isParseIntegerOnly() are false. + *
+ * + *

Synchronization

+ * + *

+ * Decimal formats are generally not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + *

Example

+ * + *
+ * // Print out a number using the localized number, integer, currency,
+ * // and percent format for each locale
+ * Locale[] locales = NumberFormat.getAvailableLocales();
+ * double myNumber = -1234.56;
+ * NumberFormat form;
+ * for (int j=0; j<4; ++j) {
+ *     System.out.println("FORMAT");
+ *     for (int i = 0; i < locales.length; ++i) {
+ *         if (locales[i].getCountry().length() == 0) {
+ *            continue; // Skip language-only locales
+ *         }
+ *         System.out.print(locales[i].getDisplayName());
+ *         switch (j) {
+ *         case 0:
+ *             form = NumberFormat.getInstance(locales[i]); break;
+ *         case 1:
+ *             form = NumberFormat.getIntegerInstance(locales[i]); break;
+ *         case 2:
+ *             form = NumberFormat.getCurrencyInstance(locales[i]); break;
+ *         default:
+ *             form = NumberFormat.getPercentInstance(locales[i]); break;
+ *         }
+ *         if (form instanceof DecimalFormat) {
+ *             System.out.print(": " + ((DecimalFormat) form).toPattern());
+ *         }
+ *         System.out.print(" -> " + form.format(myNumber));
+ *         try {
+ *             System.out.println(" -> " + form.parse(form.format(myNumber)));
+ *         } catch (ParseException e) {}
+ *     }
+ * }
+ * 
+ * + * @see Java Tutorial + * @see NumberFormat + * @see DecimalFormatSymbols + * @see ParsePosition + * @author Mark Davis + * @author Alan Liu + */ +public class DecimalFormat extends NumberFormat { + + /** + * Creates a DecimalFormat using the default pattern and symbols + * for the default locale. This is a convenient way to obtain a + * DecimalFormat when internationalization is not the main concern. + *

+ * To obtain standard formats for a given locale, use the factory methods + * on NumberFormat such as getNumberInstance. These factories will + * return the most appropriate sub-class of NumberFormat for a given + * locale. + * + * @see java.text.NumberFormat#getInstance + * @see java.text.NumberFormat#getNumberInstance + * @see java.text.NumberFormat#getCurrencyInstance + * @see java.text.NumberFormat#getPercentInstance + */ + public DecimalFormat() { + Locale def = Locale.getDefault(Locale.Category.FORMAT); + // try to get the pattern from the cache + String pattern = cachedLocaleData.get(def); + if (pattern == null) { /* cache miss */ + // Get the pattern for the default locale. +// ResourceBundle rb = LocaleData.getNumberFormatData(def); +// String[] all = rb.getStringArray("NumberPatterns"); +// pattern = all[0]; +// /* update cache */ +// cachedLocaleData.putIfAbsent(def, pattern); + } + + // Always applyPattern after the symbols are set + this.symbols = new DecimalFormatSymbols(def); + applyPattern(pattern, false); + } + + + /** + * Creates a DecimalFormat using the given pattern and the symbols + * for the default locale. This is a convenient way to obtain a + * DecimalFormat when internationalization is not the main concern. + *

+ * To obtain standard formats for a given locale, use the factory methods + * on NumberFormat such as getNumberInstance. These factories will + * return the most appropriate sub-class of NumberFormat for a given + * locale. + * + * @param pattern A non-localized pattern string. + * @exception NullPointerException if pattern is null + * @exception IllegalArgumentException if the given pattern is invalid. + * @see java.text.NumberFormat#getInstance + * @see java.text.NumberFormat#getNumberInstance + * @see java.text.NumberFormat#getCurrencyInstance + * @see java.text.NumberFormat#getPercentInstance + */ + public DecimalFormat(String pattern) { + // Always applyPattern after the symbols are set + this.symbols = new DecimalFormatSymbols(Locale.getDefault(Locale.Category.FORMAT)); + applyPattern(pattern, false); + } + + + /** + * Creates a DecimalFormat using the given pattern and symbols. + * Use this constructor when you need to completely customize the + * behavior of the format. + *

+ * To obtain standard formats for a given + * locale, use the factory methods on NumberFormat such as + * getInstance or getCurrencyInstance. If you need only minor adjustments + * to a standard format, you can modify the format returned by + * a NumberFormat factory method. + * + * @param pattern a non-localized pattern string + * @param symbols the set of symbols to be used + * @exception NullPointerException if any of the given arguments is null + * @exception IllegalArgumentException if the given pattern is invalid + * @see java.text.NumberFormat#getInstance + * @see java.text.NumberFormat#getNumberInstance + * @see java.text.NumberFormat#getCurrencyInstance + * @see java.text.NumberFormat#getPercentInstance + * @see java.text.DecimalFormatSymbols + */ + public DecimalFormat (String pattern, DecimalFormatSymbols symbols) { + // Always applyPattern after the symbols are set + this.symbols = (DecimalFormatSymbols)symbols.clone(); + applyPattern(pattern, false); + } + + + // Overrides + /** + * Formats a number and appends the resulting text to the given string + * buffer. + * The number can be of any subclass of {@link java.lang.Number}. + *

+ * This implementation uses the maximum precision permitted. + * @param number the number to format + * @param toAppendTo the StringBuffer to which the formatted + * text is to be appended + * @param pos On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @return the value passed in as toAppendTo + * @exception IllegalArgumentException if number is + * null or not an instance of Number. + * @exception NullPointerException if toAppendTo or + * pos is null + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + public final StringBuffer format(Object number, + StringBuffer toAppendTo, + FieldPosition pos) { + if (number instanceof Long || number instanceof Integer || + number instanceof Short || number instanceof Byte || + number instanceof AtomicInteger || + number instanceof AtomicLong || + (number instanceof BigInteger && + ((BigInteger)number).bitLength () < 64)) { + return format(((Number)number).longValue(), toAppendTo, pos); + } else if (number instanceof BigDecimal) { + return format((BigDecimal)number, toAppendTo, pos); + } else if (number instanceof BigInteger) { + return format((BigInteger)number, toAppendTo, pos); + } else if (number instanceof Number) { + return format(((Number)number).doubleValue(), toAppendTo, pos); + } else { + throw new IllegalArgumentException("Cannot format given Object as a Number"); + } + } + + /** + * Formats a double to produce a string. + * @param number The double to format + * @param result where the text is to be appended + * @param fieldPosition On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return The formatted number string + * @see java.text.FieldPosition + */ + public StringBuffer format(double number, StringBuffer result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + + return format(number, result, fieldPosition.getFieldDelegate()); + } + + /** + * Formats a double to produce a string. + * @param number The double to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return The formatted number string + */ + private StringBuffer format(double number, StringBuffer result, + FieldDelegate delegate) { + if (Double.isNaN(number) || + (Double.isInfinite(number) && multiplier == 0)) { + int iFieldStart = result.length(); + result.append(symbols.getNaN()); + delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, + iFieldStart, result.length(), result); + return result; + } + + /* Detecting whether a double is negative is easy with the exception of + * the value -0.0. This is a double which has a zero mantissa (and + * exponent), but a negative sign bit. It is semantically distinct from + * a zero with a positive sign bit, and this distinction is important + * to certain kinds of computations. However, it's a little tricky to + * detect, since (-0.0 == 0.0) and !(-0.0 < 0.0). How then, you may + * ask, does it behave distinctly from +0.0? Well, 1/(-0.0) == + * -Infinity. Proper detection of -0.0 is needed to deal with the + * issues raised by bugs 4106658, 4106667, and 4147706. Liu 7/6/98. + */ + boolean isNegative = ((number < 0.0) || (number == 0.0 && 1/number < 0.0)) ^ (multiplier < 0); + + if (multiplier != 1) { + number *= multiplier; + } + + if (Double.isInfinite(number)) { + if (isNegative) { + append(result, negativePrefix, delegate, + getNegativePrefixFieldPositions(), Field.SIGN); + } else { + append(result, positivePrefix, delegate, + getPositivePrefixFieldPositions(), Field.SIGN); + } + + int iFieldStart = result.length(); + result.append(symbols.getInfinity()); + delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, + iFieldStart, result.length(), result); + + if (isNegative) { + append(result, negativeSuffix, delegate, + getNegativeSuffixFieldPositions(), Field.SIGN); + } else { + append(result, positiveSuffix, delegate, + getPositiveSuffixFieldPositions(), Field.SIGN); + } + + return result; + } + + if (isNegative) { + number = -number; + } + + // at this point we are guaranteed a nonnegative finite number. + assert(number >= 0 && !Double.isInfinite(number)); + + synchronized(digitList) { + int maxIntDigits = super.getMaximumIntegerDigits(); + int minIntDigits = super.getMinimumIntegerDigits(); + int maxFraDigits = super.getMaximumFractionDigits(); + int minFraDigits = super.getMinimumFractionDigits(); + + digitList.set(isNegative, number, useExponentialNotation ? + maxIntDigits + maxFraDigits : maxFraDigits, + !useExponentialNotation); + return subformat(result, delegate, isNegative, false, + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + } + } + + /** + * Format a long to produce a string. + * @param number The long to format + * @param result where the text is to be appended + * @param fieldPosition On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return The formatted number string + * @see java.text.FieldPosition + */ + public StringBuffer format(long number, StringBuffer result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + + return format(number, result, fieldPosition.getFieldDelegate()); + } + + /** + * Format a long to produce a string. + * @param number The long to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @return The formatted number string + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + private StringBuffer format(long number, StringBuffer result, + FieldDelegate delegate) { + boolean isNegative = (number < 0); + if (isNegative) { + number = -number; + } + + // In general, long values always represent real finite numbers, so + // we don't have to check for +/- Infinity or NaN. However, there + // is one case we have to be careful of: The multiplier can push + // a number near MIN_VALUE or MAX_VALUE outside the legal range. We + // check for this before multiplying, and if it happens we use + // BigInteger instead. + boolean useBigInteger = false; + if (number < 0) { // This can only happen if number == Long.MIN_VALUE. + if (multiplier != 0) { + useBigInteger = true; + } + } else if (multiplier != 1 && multiplier != 0) { + long cutoff = Long.MAX_VALUE / multiplier; + if (cutoff < 0) { + cutoff = -cutoff; + } + useBigInteger = (number > cutoff); + } + + if (useBigInteger) { + if (isNegative) { + number = -number; + } + BigInteger bigIntegerValue = BigInteger.valueOf(number); + return format(bigIntegerValue, result, delegate, true); + } + + number *= multiplier; + if (number == 0) { + isNegative = false; + } else { + if (multiplier < 0) { + number = -number; + isNegative = !isNegative; + } + } + + synchronized(digitList) { + int maxIntDigits = super.getMaximumIntegerDigits(); + int minIntDigits = super.getMinimumIntegerDigits(); + int maxFraDigits = super.getMaximumFractionDigits(); + int minFraDigits = super.getMinimumFractionDigits(); + + digitList.set(isNegative, number, + useExponentialNotation ? maxIntDigits + maxFraDigits : 0); + + return subformat(result, delegate, isNegative, true, + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + } + } + + /** + * Formats a BigDecimal to produce a string. + * @param number The BigDecimal to format + * @param result where the text is to be appended + * @param fieldPosition On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @return The formatted number string + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + private StringBuffer format(BigDecimal number, StringBuffer result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + return format(number, result, fieldPosition.getFieldDelegate()); + } + + /** + * Formats a BigDecimal to produce a string. + * @param number The BigDecimal to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return The formatted number string + */ + private StringBuffer format(BigDecimal number, StringBuffer result, + FieldDelegate delegate) { + if (multiplier != 1) { + number = number.multiply(getBigDecimalMultiplier()); + } + boolean isNegative = number.signum() == -1; + if (isNegative) { + number = number.negate(); + } + + synchronized(digitList) { + int maxIntDigits = getMaximumIntegerDigits(); + int minIntDigits = getMinimumIntegerDigits(); + int maxFraDigits = getMaximumFractionDigits(); + int minFraDigits = getMinimumFractionDigits(); + int maximumDigits = maxIntDigits + maxFraDigits; + + digitList.set(isNegative, number, useExponentialNotation ? + ((maximumDigits < 0) ? Integer.MAX_VALUE : maximumDigits) : + maxFraDigits, !useExponentialNotation); + + return subformat(result, delegate, isNegative, false, + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + } + } + + /** + * Format a BigInteger to produce a string. + * @param number The BigInteger to format + * @param result where the text is to be appended + * @param fieldPosition On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @return The formatted number string + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + private StringBuffer format(BigInteger number, StringBuffer result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + + return format(number, result, fieldPosition.getFieldDelegate(), false); + } + + /** + * Format a BigInteger to produce a string. + * @param number The BigInteger to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @return The formatted number string + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + private StringBuffer format(BigInteger number, StringBuffer result, + FieldDelegate delegate, boolean formatLong) { + if (multiplier != 1) { + number = number.multiply(getBigIntegerMultiplier()); + } + boolean isNegative = number.signum() == -1; + if (isNegative) { + number = number.negate(); + } + + synchronized(digitList) { + int maxIntDigits, minIntDigits, maxFraDigits, minFraDigits, maximumDigits; + if (formatLong) { + maxIntDigits = super.getMaximumIntegerDigits(); + minIntDigits = super.getMinimumIntegerDigits(); + maxFraDigits = super.getMaximumFractionDigits(); + minFraDigits = super.getMinimumFractionDigits(); + maximumDigits = maxIntDigits + maxFraDigits; + } else { + maxIntDigits = getMaximumIntegerDigits(); + minIntDigits = getMinimumIntegerDigits(); + maxFraDigits = getMaximumFractionDigits(); + minFraDigits = getMinimumFractionDigits(); + maximumDigits = maxIntDigits + maxFraDigits; + if (maximumDigits < 0) { + maximumDigits = Integer.MAX_VALUE; + } + } + + digitList.set(isNegative, number, + useExponentialNotation ? maximumDigits : 0); + + return subformat(result, delegate, isNegative, true, + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + } + } + + /** + * Formats an Object producing an AttributedCharacterIterator. + * You can use the returned AttributedCharacterIterator + * to build the resulting String, as well as to determine information + * about the resulting String. + *

+ * Each attribute key of the AttributedCharacterIterator will be of type + * NumberFormat.Field, with the attribute value being the + * same as the attribute key. + * + * @exception NullPointerException if obj is null. + * @exception IllegalArgumentException when the Format cannot format the + * given object. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @param obj The object to format + * @return AttributedCharacterIterator describing the formatted value. + * @since 1.4 + */ + public AttributedCharacterIterator formatToCharacterIterator(Object obj) { + CharacterIteratorFieldDelegate delegate = + new CharacterIteratorFieldDelegate(); + StringBuffer sb = new StringBuffer(); + + if (obj instanceof Double || obj instanceof Float) { + format(((Number)obj).doubleValue(), sb, delegate); + } else if (obj instanceof Long || obj instanceof Integer || + obj instanceof Short || obj instanceof Byte || + obj instanceof AtomicInteger || obj instanceof AtomicLong) { + format(((Number)obj).longValue(), sb, delegate); + } else if (obj instanceof BigDecimal) { + format((BigDecimal)obj, sb, delegate); + } else if (obj instanceof BigInteger) { + format((BigInteger)obj, sb, delegate, false); + } else if (obj == null) { + throw new NullPointerException( + "formatToCharacterIterator must be passed non-null object"); + } else { + throw new IllegalArgumentException( + "Cannot format given Object as a Number"); + } + return delegate.getIterator(sb.toString()); + } + + /** + * Complete the formatting of a finite number. On entry, the digitList must + * be filled in with the correct digits. + */ + private StringBuffer subformat(StringBuffer result, FieldDelegate delegate, + boolean isNegative, boolean isInteger, + int maxIntDigits, int minIntDigits, + int maxFraDigits, int minFraDigits) { + // NOTE: This isn't required anymore because DigitList takes care of this. + // + // // The negative of the exponent represents the number of leading + // // zeros between the decimal and the first non-zero digit, for + // // a value < 0.1 (e.g., for 0.00123, -fExponent == 2). If this + // // is more than the maximum fraction digits, then we have an underflow + // // for the printed representation. We recognize this here and set + // // the DigitList representation to zero in this situation. + // + // if (-digitList.decimalAt >= getMaximumFractionDigits()) + // { + // digitList.count = 0; + // } + + char zero = symbols.getZeroDigit(); + int zeroDelta = zero - '0'; // '0' is the DigitList representation of zero + char grouping = symbols.getGroupingSeparator(); + char decimal = isCurrencyFormat ? + symbols.getMonetaryDecimalSeparator() : + symbols.getDecimalSeparator(); + + /* Per bug 4147706, DecimalFormat must respect the sign of numbers which + * format as zero. This allows sensible computations and preserves + * relations such as signum(1/x) = signum(x), where x is +Infinity or + * -Infinity. Prior to this fix, we always formatted zero values as if + * they were positive. Liu 7/6/98. + */ + if (digitList.isZero()) { + digitList.decimalAt = 0; // Normalize + } + + if (isNegative) { + append(result, negativePrefix, delegate, + getNegativePrefixFieldPositions(), Field.SIGN); + } else { + append(result, positivePrefix, delegate, + getPositivePrefixFieldPositions(), Field.SIGN); + } + + if (useExponentialNotation) { + int iFieldStart = result.length(); + int iFieldEnd = -1; + int fFieldStart = -1; + + // Minimum integer digits are handled in exponential format by + // adjusting the exponent. For example, 0.01234 with 3 minimum + // integer digits is "123.4E-4". + + // Maximum integer digits are interpreted as indicating the + // repeating range. This is useful for engineering notation, in + // which the exponent is restricted to a multiple of 3. For + // example, 0.01234 with 3 maximum integer digits is "12.34e-3". + // If maximum integer digits are > 1 and are larger than + // minimum integer digits, then minimum integer digits are + // ignored. + int exponent = digitList.decimalAt; + int repeat = maxIntDigits; + int minimumIntegerDigits = minIntDigits; + if (repeat > 1 && repeat > minIntDigits) { + // A repeating range is defined; adjust to it as follows. + // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3; + // -3,-4,-5=>-6, etc. This takes into account that the + // exponent we have here is off by one from what we expect; + // it is for the format 0.MMMMMx10^n. + if (exponent >= 1) { + exponent = ((exponent - 1) / repeat) * repeat; + } else { + // integer division rounds towards 0 + exponent = ((exponent - repeat) / repeat) * repeat; + } + minimumIntegerDigits = 1; + } else { + // No repeating range is defined; use minimum integer digits. + exponent -= minimumIntegerDigits; + } + + // We now output a minimum number of digits, and more if there + // are more digits, up to the maximum number of digits. We + // place the decimal point after the "integer" digits, which + // are the first (decimalAt - exponent) digits. + int minimumDigits = minIntDigits + minFraDigits; + if (minimumDigits < 0) { // overflow? + minimumDigits = Integer.MAX_VALUE; + } + + // The number of integer digits is handled specially if the number + // is zero, since then there may be no digits. + int integerDigits = digitList.isZero() ? minimumIntegerDigits : + digitList.decimalAt - exponent; + if (minimumDigits < integerDigits) { + minimumDigits = integerDigits; + } + int totalDigits = digitList.count; + if (minimumDigits > totalDigits) { + totalDigits = minimumDigits; + } + boolean addedDecimalSeparator = false; + + for (int i=0; i 0 && count < digitList.decimalAt) { + count = digitList.decimalAt; + } + + // Handle the case where getMaximumIntegerDigits() is smaller + // than the real number of integer digits. If this is so, we + // output the least significant max integer digits. For example, + // the value 1997 printed with 2 max integer digits is just "97". + if (count > maxIntDigits) { + count = maxIntDigits; + digitIndex = digitList.decimalAt - count; + } + + int sizeBeforeIntegerPart = result.length(); + for (int i=count-1; i>=0; --i) { + if (i < digitList.decimalAt && digitIndex < digitList.count) { + // Output a real digit + result.append((char)(digitList.digits[digitIndex++] + zeroDelta)); + } else { + // Output a leading zero + result.append(zero); + } + + // Output grouping separator if necessary. Don't output a + // grouping separator if i==0 though; that's at the end of + // the integer part. + if (isGroupingUsed() && i>0 && (groupingSize != 0) && + (i % groupingSize == 0)) { + int gStart = result.length(); + result.append(grouping); + delegate.formatted(Field.GROUPING_SEPARATOR, + Field.GROUPING_SEPARATOR, gStart, + result.length(), result); + } + } + + // Determine whether or not there are any printable fractional + // digits. If we've used up the digits we know there aren't. + boolean fractionPresent = (minFraDigits > 0) || + (!isInteger && digitIndex < digitList.count); + + // If there is no fraction present, and we haven't printed any + // integer digits, then print a zero. Otherwise we won't print + // _any_ digits, and we won't be able to parse this string. + if (!fractionPresent && result.length() == sizeBeforeIntegerPart) { + result.append(zero); + } + + delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, + iFieldStart, result.length(), result); + + // Output the decimal separator if we always do so. + int sStart = result.length(); + if (decimalSeparatorAlwaysShown || fractionPresent) { + result.append(decimal); + } + + if (sStart != result.length()) { + delegate.formatted(Field.DECIMAL_SEPARATOR, + Field.DECIMAL_SEPARATOR, + sStart, result.length(), result); + } + int fFieldStart = result.length(); + + for (int i=0; i < maxFraDigits; ++i) { + // Here is where we escape from the loop. We escape if we've + // output the maximum fraction digits (specified in the for + // expression above). + // We also stop when we've output the minimum digits and either: + // we have an integer, so there is no fractional stuff to + // display, or we're out of significant digits. + if (i >= minFraDigits && + (isInteger || digitIndex >= digitList.count)) { + break; + } + + // Output leading fractional zeros. These are zeros that come + // after the decimal but before any significant digits. These + // are only output if abs(number being formatted) < 1.0. + if (-1-i > (digitList.decimalAt-1)) { + result.append(zero); + continue; + } + + // Output a digit, if we have any precision left, or a + // zero if we don't. We don't want to output noise digits. + if (!isInteger && digitIndex < digitList.count) { + result.append((char)(digitList.digits[digitIndex++] + zeroDelta)); + } else { + result.append(zero); + } + } + + // Record field information for caller. + delegate.formatted(FRACTION_FIELD, Field.FRACTION, Field.FRACTION, + fFieldStart, result.length(), result); + } + + if (isNegative) { + append(result, negativeSuffix, delegate, + getNegativeSuffixFieldPositions(), Field.SIGN); + } + else { + append(result, positiveSuffix, delegate, + getPositiveSuffixFieldPositions(), Field.SIGN); + } + + return result; + } + + /** + * Appends the String string to result. + * delegate is notified of all the + * FieldPositions in positions. + *

+ * If one of the FieldPositions in positions + * identifies a SIGN attribute, it is mapped to + * signAttribute. This is used + * to map the SIGN attribute to the EXPONENT + * attribute as necessary. + *

+ * This is used by subformat to add the prefix/suffix. + */ + private void append(StringBuffer result, String string, + FieldDelegate delegate, + FieldPosition[] positions, + Format.Field signAttribute) { + int start = result.length(); + + if (string.length() > 0) { + result.append(string); + for (int counter = 0, max = positions.length; counter < max; + counter++) { + FieldPosition fp = positions[counter]; + Format.Field attribute = fp.getFieldAttribute(); + + if (attribute == Field.SIGN) { + attribute = signAttribute; + } + delegate.formatted(attribute, attribute, + start + fp.getBeginIndex(), + start + fp.getEndIndex(), result); + } + } + } + + /** + * Parses text from a string to produce a Number. + *

+ * The method attempts to parse text starting at the index given by + * pos. + * If parsing succeeds, then the index of pos is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * number is returned. The updated pos can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of pos is not + * changed, the error index of pos is set to the index of + * the character where the error occurred, and null is returned. + *

+ * The subclass returned depends on the value of {@link #isParseBigDecimal} + * as well as on the string being parsed. + *

    + *
  • If isParseBigDecimal() is false (the default), + * most integer values are returned as Long + * objects, no matter how they are written: "17" and + * "17.000" both parse to Long(17). + * Values that cannot fit into a Long are returned as + * Doubles. This includes values with a fractional part, + * infinite values, NaN, and the value -0.0. + * DecimalFormat does not decide whether to + * return a Double or a Long based on the + * presence of a decimal separator in the source string. Doing so + * would prevent integers that overflow the mantissa of a double, + * such as "-9,223,372,036,854,775,808.00", from being + * parsed accurately. + *

    + * Callers may use the Number methods + * doubleValue, longValue, etc., to obtain + * the type they want. + *

  • If isParseBigDecimal() is true, values are returned + * as BigDecimal objects. The values are the ones + * constructed by {@link java.math.BigDecimal#BigDecimal(String)} + * for corresponding strings in locale-independent format. The + * special cases negative and positive infinity and NaN are returned + * as Double instances holding the values of the + * corresponding Double constants. + *
+ *

+ * DecimalFormat parses all Unicode characters that represent + * decimal digits, as defined by Character.digit(). In + * addition, DecimalFormat also recognizes as digits the ten + * consecutive characters starting with the localized zero digit defined in + * the DecimalFormatSymbols object. + * + * @param text the string to be parsed + * @param pos A ParsePosition object with index and error + * index information as described above. + * @return the parsed value, or null if the parse fails + * @exception NullPointerException if text or + * pos is null. + */ + public Number parse(String text, ParsePosition pos) { + // special case NaN + if (text.regionMatches(pos.index, symbols.getNaN(), 0, symbols.getNaN().length())) { + pos.index = pos.index + symbols.getNaN().length(); + return new Double(Double.NaN); + } + + boolean[] status = new boolean[STATUS_LENGTH]; + if (!subparse(text, pos, positivePrefix, negativePrefix, digitList, false, status)) { + return null; + } + + // special case INFINITY + if (status[STATUS_INFINITE]) { + if (status[STATUS_POSITIVE] == (multiplier >= 0)) { + return new Double(Double.POSITIVE_INFINITY); + } else { + return new Double(Double.NEGATIVE_INFINITY); + } + } + + if (multiplier == 0) { + if (digitList.isZero()) { + return new Double(Double.NaN); + } else if (status[STATUS_POSITIVE]) { + return new Double(Double.POSITIVE_INFINITY); + } else { + return new Double(Double.NEGATIVE_INFINITY); + } + } + + if (isParseBigDecimal()) { + BigDecimal bigDecimalResult = digitList.getBigDecimal(); + + if (multiplier != 1) { + try { + bigDecimalResult = bigDecimalResult.divide(getBigDecimalMultiplier()); + } + catch (ArithmeticException e) { // non-terminating decimal expansion + bigDecimalResult = bigDecimalResult.divide(getBigDecimalMultiplier(), roundingMode); + } + } + + if (!status[STATUS_POSITIVE]) { + bigDecimalResult = bigDecimalResult.negate(); + } + return bigDecimalResult; + } else { + boolean gotDouble = true; + boolean gotLongMinimum = false; + double doubleResult = 0.0; + long longResult = 0; + + // Finally, have DigitList parse the digits into a value. + if (digitList.fitsIntoLong(status[STATUS_POSITIVE], isParseIntegerOnly())) { + gotDouble = false; + longResult = digitList.getLong(); + if (longResult < 0) { // got Long.MIN_VALUE + gotLongMinimum = true; + } + } else { + doubleResult = digitList.getDouble(); + } + + // Divide by multiplier. We have to be careful here not to do + // unneeded conversions between double and long. + if (multiplier != 1) { + if (gotDouble) { + doubleResult /= multiplier; + } else { + // Avoid converting to double if we can + if (longResult % multiplier == 0) { + longResult /= multiplier; + } else { + doubleResult = ((double)longResult) / multiplier; + gotDouble = true; + } + } + } + + if (!status[STATUS_POSITIVE] && !gotLongMinimum) { + doubleResult = -doubleResult; + longResult = -longResult; + } + + // At this point, if we divided the result by the multiplier, the + // result may fit into a long. We check for this case and return + // a long if possible. + // We must do this AFTER applying the negative (if appropriate) + // in order to handle the case of LONG_MIN; otherwise, if we do + // this with a positive value -LONG_MIN, the double is > 0, but + // the long is < 0. We also must retain a double in the case of + // -0.0, which will compare as == to a long 0 cast to a double + // (bug 4162852). + if (multiplier != 1 && gotDouble) { + longResult = (long)doubleResult; + gotDouble = ((doubleResult != (double)longResult) || + (doubleResult == 0.0 && 1/doubleResult < 0.0)) && + !isParseIntegerOnly(); + } + + return gotDouble ? + (Number)new Double(doubleResult) : (Number)new Long(longResult); + } + } + + /** + * Return a BigInteger multiplier. + */ + private BigInteger getBigIntegerMultiplier() { + if (bigIntegerMultiplier == null) { + bigIntegerMultiplier = BigInteger.valueOf(multiplier); + } + return bigIntegerMultiplier; + } + private transient BigInteger bigIntegerMultiplier; + + /** + * Return a BigDecimal multiplier. + */ + private BigDecimal getBigDecimalMultiplier() { + if (bigDecimalMultiplier == null) { + bigDecimalMultiplier = new BigDecimal(multiplier); + } + return bigDecimalMultiplier; + } + private transient BigDecimal bigDecimalMultiplier; + + private static final int STATUS_INFINITE = 0; + private static final int STATUS_POSITIVE = 1; + private static final int STATUS_LENGTH = 2; + + /** + * Parse the given text into a number. The text is parsed beginning at + * parsePosition, until an unparseable character is seen. + * @param text The string to parse. + * @param parsePosition The position at which to being parsing. Upon + * return, the first unparseable character. + * @param digits The DigitList to set to the parsed value. + * @param isExponent If true, parse an exponent. This means no + * infinite values and integer only. + * @param status Upon return contains boolean status flags indicating + * whether the value was infinite and whether it was positive. + */ + private final boolean subparse(String text, ParsePosition parsePosition, + String positivePrefix, String negativePrefix, + DigitList digits, boolean isExponent, + boolean status[]) { + int position = parsePosition.index; + int oldStart = parsePosition.index; + int backup; + boolean gotPositive, gotNegative; + + // check for positivePrefix; take longest + gotPositive = text.regionMatches(position, positivePrefix, 0, + positivePrefix.length()); + gotNegative = text.regionMatches(position, negativePrefix, 0, + negativePrefix.length()); + + if (gotPositive && gotNegative) { + if (positivePrefix.length() > negativePrefix.length()) { + gotNegative = false; + } else if (positivePrefix.length() < negativePrefix.length()) { + gotPositive = false; + } + } + + if (gotPositive) { + position += positivePrefix.length(); + } else if (gotNegative) { + position += negativePrefix.length(); + } else { + parsePosition.errorIndex = position; + return false; + } + + // process digits or Inf, find decimal position + status[STATUS_INFINITE] = false; + if (!isExponent && text.regionMatches(position,symbols.getInfinity(),0, + symbols.getInfinity().length())) { + position += symbols.getInfinity().length(); + status[STATUS_INFINITE] = true; + } else { + // We now have a string of digits, possibly with grouping symbols, + // and decimal points. We want to process these into a DigitList. + // We don't want to put a bunch of leading zeros into the DigitList + // though, so we keep track of the location of the decimal point, + // put only significant digits into the DigitList, and adjust the + // exponent as needed. + + digits.decimalAt = digits.count = 0; + char zero = symbols.getZeroDigit(); + char decimal = isCurrencyFormat ? + symbols.getMonetaryDecimalSeparator() : + symbols.getDecimalSeparator(); + char grouping = symbols.getGroupingSeparator(); + String exponentString = symbols.getExponentSeparator(); + boolean sawDecimal = false; + boolean sawExponent = false; + boolean sawDigit = false; + int exponent = 0; // Set to the exponent value, if any + + // We have to track digitCount ourselves, because digits.count will + // pin when the maximum allowable digits is reached. + int digitCount = 0; + + backup = -1; + for (; position < text.length(); ++position) { + char ch = text.charAt(position); + + /* We recognize all digit ranges, not only the Latin digit range + * '0'..'9'. We do so by using the Character.digit() method, + * which converts a valid Unicode digit to the range 0..9. + * + * The character 'ch' may be a digit. If so, place its value + * from 0 to 9 in 'digit'. First try using the locale digit, + * which may or MAY NOT be a standard Unicode digit range. If + * this fails, try using the standard Unicode digit ranges by + * calling Character.digit(). If this also fails, digit will + * have a value outside the range 0..9. + */ + int digit = ch - zero; + if (digit < 0 || digit > 9) { + digit = Character.digit(ch, 10); + } + + if (digit == 0) { + // Cancel out backup setting (see grouping handler below) + backup = -1; // Do this BEFORE continue statement below!!! + sawDigit = true; + + // Handle leading zeros + if (digits.count == 0) { + // Ignore leading zeros in integer part of number. + if (!sawDecimal) { + continue; + } + + // If we have seen the decimal, but no significant + // digits yet, then we account for leading zeros by + // decrementing the digits.decimalAt into negative + // values. + --digits.decimalAt; + } else { + ++digitCount; + digits.append((char)(digit + '0')); + } + } else if (digit > 0 && digit <= 9) { // [sic] digit==0 handled above + sawDigit = true; + ++digitCount; + digits.append((char)(digit + '0')); + + // Cancel out backup setting (see grouping handler below) + backup = -1; + } else if (!isExponent && ch == decimal) { + // If we're only parsing integers, or if we ALREADY saw the + // decimal, then don't parse this one. + if (isParseIntegerOnly() || sawDecimal) { + break; + } + digits.decimalAt = digitCount; // Not digits.count! + sawDecimal = true; + } else if (!isExponent && ch == grouping && isGroupingUsed()) { + if (sawDecimal) { + break; + } + // Ignore grouping characters, if we are using them, but + // require that they be followed by a digit. Otherwise + // we backup and reprocess them. + backup = position; + } else if (!isExponent && text.regionMatches(position, exponentString, 0, exponentString.length()) + && !sawExponent) { + // Process the exponent by recursively calling this method. + ParsePosition pos = new ParsePosition(position + exponentString.length()); + boolean[] stat = new boolean[STATUS_LENGTH]; + DigitList exponentDigits = new DigitList(); + + if (subparse(text, pos, "", Character.toString(symbols.getMinusSign()), exponentDigits, true, stat) && + exponentDigits.fitsIntoLong(stat[STATUS_POSITIVE], true)) { + position = pos.index; // Advance past the exponent + exponent = (int)exponentDigits.getLong(); + if (!stat[STATUS_POSITIVE]) { + exponent = -exponent; + } + sawExponent = true; + } + break; // Whether we fail or succeed, we exit this loop + } + else { + break; + } + } + + if (backup != -1) { + position = backup; + } + + // If there was no decimal point we have an integer + if (!sawDecimal) { + digits.decimalAt = digitCount; // Not digits.count! + } + + // Adjust for exponent, if any + digits.decimalAt += exponent; + + // If none of the text string was recognized. For example, parse + // "x" with pattern "#0.00" (return index and error index both 0) + // parse "$" with pattern "$#0.00". (return index 0 and error + // index 1). + if (!sawDigit && digitCount == 0) { + parsePosition.index = oldStart; + parsePosition.errorIndex = oldStart; + return false; + } + } + + // check for suffix + if (!isExponent) { + if (gotPositive) { + gotPositive = text.regionMatches(position,positiveSuffix,0, + positiveSuffix.length()); + } + if (gotNegative) { + gotNegative = text.regionMatches(position,negativeSuffix,0, + negativeSuffix.length()); + } + + // if both match, take longest + if (gotPositive && gotNegative) { + if (positiveSuffix.length() > negativeSuffix.length()) { + gotNegative = false; + } else if (positiveSuffix.length() < negativeSuffix.length()) { + gotPositive = false; + } + } + + // fail if neither or both + if (gotPositive == gotNegative) { + parsePosition.errorIndex = position; + return false; + } + + parsePosition.index = position + + (gotPositive ? positiveSuffix.length() : negativeSuffix.length()); // mark success! + } else { + parsePosition.index = position; + } + + status[STATUS_POSITIVE] = gotPositive; + if (parsePosition.index == oldStart) { + parsePosition.errorIndex = position; + return false; + } + return true; + } + + /** + * Returns a copy of the decimal format symbols, which is generally not + * changed by the programmer or user. + * @return a copy of the desired DecimalFormatSymbols + * @see java.text.DecimalFormatSymbols + */ + public DecimalFormatSymbols getDecimalFormatSymbols() { + try { + // don't allow multiple references + return (DecimalFormatSymbols) symbols.clone(); + } catch (Exception foo) { + return null; // should never happen + } + } + + + /** + * Sets the decimal format symbols, which is generally not changed + * by the programmer or user. + * @param newSymbols desired DecimalFormatSymbols + * @see java.text.DecimalFormatSymbols + */ + public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) { + try { + // don't allow multiple references + symbols = (DecimalFormatSymbols) newSymbols.clone(); + expandAffixes(); + } catch (Exception foo) { + // should never happen + } + } + + /** + * Get the positive prefix. + *

Examples: +123, $123, sFr123 + */ + public String getPositivePrefix () { + return positivePrefix; + } + + /** + * Set the positive prefix. + *

Examples: +123, $123, sFr123 + */ + public void setPositivePrefix (String newValue) { + positivePrefix = newValue; + posPrefixPattern = null; + positivePrefixFieldPositions = null; + } + + /** + * Returns the FieldPositions of the fields in the prefix used for + * positive numbers. This is not used if the user has explicitly set + * a positive prefix via setPositivePrefix. This is + * lazily created. + * + * @return FieldPositions in positive prefix + */ + private FieldPosition[] getPositivePrefixFieldPositions() { + if (positivePrefixFieldPositions == null) { + if (posPrefixPattern != null) { + positivePrefixFieldPositions = expandAffix(posPrefixPattern); + } + else { + positivePrefixFieldPositions = EmptyFieldPositionArray; + } + } + return positivePrefixFieldPositions; + } + + /** + * Get the negative prefix. + *

Examples: -123, ($123) (with negative suffix), sFr-123 + */ + public String getNegativePrefix () { + return negativePrefix; + } + + /** + * Set the negative prefix. + *

Examples: -123, ($123) (with negative suffix), sFr-123 + */ + public void setNegativePrefix (String newValue) { + negativePrefix = newValue; + negPrefixPattern = null; + } + + /** + * Returns the FieldPositions of the fields in the prefix used for + * negative numbers. This is not used if the user has explicitly set + * a negative prefix via setNegativePrefix. This is + * lazily created. + * + * @return FieldPositions in positive prefix + */ + private FieldPosition[] getNegativePrefixFieldPositions() { + if (negativePrefixFieldPositions == null) { + if (negPrefixPattern != null) { + negativePrefixFieldPositions = expandAffix(negPrefixPattern); + } + else { + negativePrefixFieldPositions = EmptyFieldPositionArray; + } + } + return negativePrefixFieldPositions; + } + + /** + * Get the positive suffix. + *

Example: 123% + */ + public String getPositiveSuffix () { + return positiveSuffix; + } + + /** + * Set the positive suffix. + *

Example: 123% + */ + public void setPositiveSuffix (String newValue) { + positiveSuffix = newValue; + posSuffixPattern = null; + } + + /** + * Returns the FieldPositions of the fields in the suffix used for + * positive numbers. This is not used if the user has explicitly set + * a positive suffix via setPositiveSuffix. This is + * lazily created. + * + * @return FieldPositions in positive prefix + */ + private FieldPosition[] getPositiveSuffixFieldPositions() { + if (positiveSuffixFieldPositions == null) { + if (posSuffixPattern != null) { + positiveSuffixFieldPositions = expandAffix(posSuffixPattern); + } + else { + positiveSuffixFieldPositions = EmptyFieldPositionArray; + } + } + return positiveSuffixFieldPositions; + } + + /** + * Get the negative suffix. + *

Examples: -123%, ($123) (with positive suffixes) + */ + public String getNegativeSuffix () { + return negativeSuffix; + } + + /** + * Set the negative suffix. + *

Examples: 123% + */ + public void setNegativeSuffix (String newValue) { + negativeSuffix = newValue; + negSuffixPattern = null; + } + + /** + * Returns the FieldPositions of the fields in the suffix used for + * negative numbers. This is not used if the user has explicitly set + * a negative suffix via setNegativeSuffix. This is + * lazily created. + * + * @return FieldPositions in positive prefix + */ + private FieldPosition[] getNegativeSuffixFieldPositions() { + if (negativeSuffixFieldPositions == null) { + if (negSuffixPattern != null) { + negativeSuffixFieldPositions = expandAffix(negSuffixPattern); + } + else { + negativeSuffixFieldPositions = EmptyFieldPositionArray; + } + } + return negativeSuffixFieldPositions; + } + + /** + * Gets the multiplier for use in percent, per mille, and similar + * formats. + * + * @see #setMultiplier(int) + */ + public int getMultiplier () { + return multiplier; + } + + /** + * Sets the multiplier for use in percent, per mille, and similar + * formats. + * For a percent format, set the multiplier to 100 and the suffixes to + * have '%' (for Arabic, use the Arabic percent sign). + * For a per mille format, set the multiplier to 1000 and the suffixes to + * have '\u2030'. + * + *

Example: with multiplier 100, 1.23 is formatted as "123", and + * "123" is parsed into 1.23. + * + * @see #getMultiplier + */ + public void setMultiplier (int newValue) { + multiplier = newValue; + bigDecimalMultiplier = null; + bigIntegerMultiplier = null; + } + + /** + * Return the grouping size. Grouping size is the number of digits between + * grouping separators in the integer portion of a number. For example, + * in the number "123,456.78", the grouping size is 3. + * @see #setGroupingSize + * @see java.text.NumberFormat#isGroupingUsed + * @see java.text.DecimalFormatSymbols#getGroupingSeparator + */ + public int getGroupingSize () { + return groupingSize; + } + + /** + * Set the grouping size. Grouping size is the number of digits between + * grouping separators in the integer portion of a number. For example, + * in the number "123,456.78", the grouping size is 3. + *
+ * The value passed in is converted to a byte, which may lose information. + * @see #getGroupingSize + * @see java.text.NumberFormat#setGroupingUsed + * @see java.text.DecimalFormatSymbols#setGroupingSeparator + */ + public void setGroupingSize (int newValue) { + groupingSize = (byte)newValue; + } + + /** + * Allows you to get the behavior of the decimal separator with integers. + * (The decimal separator will always appear with decimals.) + *

Example: Decimal ON: 12345 -> 12345.; OFF: 12345 -> 12345 + */ + public boolean isDecimalSeparatorAlwaysShown() { + return decimalSeparatorAlwaysShown; + } + + /** + * Allows you to set the behavior of the decimal separator with integers. + * (The decimal separator will always appear with decimals.) + *

Example: Decimal ON: 12345 -> 12345.; OFF: 12345 -> 12345 + */ + public void setDecimalSeparatorAlwaysShown(boolean newValue) { + decimalSeparatorAlwaysShown = newValue; + } + + /** + * Returns whether the {@link #parse(java.lang.String, java.text.ParsePosition)} + * method returns BigDecimal. The default value is false. + * @see #setParseBigDecimal + * @since 1.5 + */ + public boolean isParseBigDecimal() { + return parseBigDecimal; + } + + /** + * Sets whether the {@link #parse(java.lang.String, java.text.ParsePosition)} + * method returns BigDecimal. + * @see #isParseBigDecimal + * @since 1.5 + */ + public void setParseBigDecimal(boolean newValue) { + parseBigDecimal = newValue; + } + + /** + * Standard override; no change in semantics. + */ + public Object clone() { + try { + DecimalFormat other = (DecimalFormat) super.clone(); + other.symbols = (DecimalFormatSymbols) symbols.clone(); + other.digitList = (DigitList) digitList.clone(); + return other; + } catch (Exception e) { + throw new InternalError(); + } + } + + /** + * Overrides equals + */ + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!super.equals(obj)) return false; // super does class check + DecimalFormat other = (DecimalFormat) obj; + return ((posPrefixPattern == other.posPrefixPattern && + positivePrefix.equals(other.positivePrefix)) + || (posPrefixPattern != null && + posPrefixPattern.equals(other.posPrefixPattern))) + && ((posSuffixPattern == other.posSuffixPattern && + positiveSuffix.equals(other.positiveSuffix)) + || (posSuffixPattern != null && + posSuffixPattern.equals(other.posSuffixPattern))) + && ((negPrefixPattern == other.negPrefixPattern && + negativePrefix.equals(other.negativePrefix)) + || (negPrefixPattern != null && + negPrefixPattern.equals(other.negPrefixPattern))) + && ((negSuffixPattern == other.negSuffixPattern && + negativeSuffix.equals(other.negativeSuffix)) + || (negSuffixPattern != null && + negSuffixPattern.equals(other.negSuffixPattern))) + && multiplier == other.multiplier + && groupingSize == other.groupingSize + && decimalSeparatorAlwaysShown == other.decimalSeparatorAlwaysShown + && parseBigDecimal == other.parseBigDecimal + && useExponentialNotation == other.useExponentialNotation + && (!useExponentialNotation || + minExponentDigits == other.minExponentDigits) + && maximumIntegerDigits == other.maximumIntegerDigits + && minimumIntegerDigits == other.minimumIntegerDigits + && maximumFractionDigits == other.maximumFractionDigits + && minimumFractionDigits == other.minimumFractionDigits + && roundingMode == other.roundingMode + && symbols.equals(other.symbols); + } + + /** + * Overrides hashCode + */ + public int hashCode() { + return super.hashCode() * 37 + positivePrefix.hashCode(); + // just enough fields for a reasonable distribution + } + + /** + * Synthesizes a pattern string that represents the current state + * of this Format object. + * @see #applyPattern + */ + public String toPattern() { + return toPattern( false ); + } + + /** + * Synthesizes a localized pattern string that represents the current + * state of this Format object. + * @see #applyPattern + */ + public String toLocalizedPattern() { + return toPattern( true ); + } + + /** + * Expand the affix pattern strings into the expanded affix strings. If any + * affix pattern string is null, do not expand it. This method should be + * called any time the symbols or the affix patterns change in order to keep + * the expanded affix strings up to date. + */ + private void expandAffixes() { + // Reuse one StringBuffer for better performance + StringBuffer buffer = new StringBuffer(); + if (posPrefixPattern != null) { + positivePrefix = expandAffix(posPrefixPattern, buffer); + positivePrefixFieldPositions = null; + } + if (posSuffixPattern != null) { + positiveSuffix = expandAffix(posSuffixPattern, buffer); + positiveSuffixFieldPositions = null; + } + if (negPrefixPattern != null) { + negativePrefix = expandAffix(negPrefixPattern, buffer); + negativePrefixFieldPositions = null; + } + if (negSuffixPattern != null) { + negativeSuffix = expandAffix(negSuffixPattern, buffer); + negativeSuffixFieldPositions = null; + } + } + + /** + * Expand an affix pattern into an affix string. All characters in the + * pattern are literal unless prefixed by QUOTE. The following characters + * after QUOTE are recognized: PATTERN_PERCENT, PATTERN_PER_MILLE, + * PATTERN_MINUS, and CURRENCY_SIGN. If CURRENCY_SIGN is doubled (QUOTE + + * CURRENCY_SIGN + CURRENCY_SIGN), it is interpreted as an ISO 4217 + * currency code. Any other character after a QUOTE represents itself. + * QUOTE must be followed by another character; QUOTE may not occur by + * itself at the end of the pattern. + * + * @param pattern the non-null, possibly empty pattern + * @param buffer a scratch StringBuffer; its contents will be lost + * @return the expanded equivalent of pattern + */ + private String expandAffix(String pattern, StringBuffer buffer) { + buffer.setLength(0); + for (int i=0; i 0) { + if (positions == null) { + positions = new ArrayList(2); + } + FieldPosition fp = new FieldPosition(Field.CURRENCY); + fp.setBeginIndex(stringIndex); + fp.setEndIndex(stringIndex + string.length()); + positions.add(fp); + stringIndex += string.length(); + } + continue; + case PATTERN_PERCENT: + c = symbols.getPercent(); + field = -1; + fieldID = Field.PERCENT; + break; + case PATTERN_PER_MILLE: + c = symbols.getPerMill(); + field = -1; + fieldID = Field.PERMILLE; + break; + case PATTERN_MINUS: + c = symbols.getMinusSign(); + field = -1; + fieldID = Field.SIGN; + break; + } + if (fieldID != null) { + if (positions == null) { + positions = new ArrayList(2); + } + FieldPosition fp = new FieldPosition(fieldID, field); + fp.setBeginIndex(stringIndex); + fp.setEndIndex(stringIndex + 1); + positions.add(fp); + } + } + stringIndex++; + } + if (positions != null) { + return (FieldPosition[])positions.toArray(EmptyFieldPositionArray); + } + return EmptyFieldPositionArray; + } + + /** + * Appends an affix pattern to the given StringBuffer, quoting special + * characters as needed. Uses the internal affix pattern, if that exists, + * or the literal affix, if the internal affix pattern is null. The + * appended string will generate the same affix pattern (or literal affix) + * when passed to toPattern(). + * + * @param buffer the affix string is appended to this + * @param affixPattern a pattern such as posPrefixPattern; may be null + * @param expAffix a corresponding expanded affix, such as positivePrefix. + * Ignored unless affixPattern is null. If affixPattern is null, then + * expAffix is appended as a literal affix. + * @param localized true if the appended pattern should contain localized + * pattern characters; otherwise, non-localized pattern chars are appended + */ + private void appendAffix(StringBuffer buffer, String affixPattern, + String expAffix, boolean localized) { + if (affixPattern == null) { + appendAffix(buffer, expAffix, localized); + } else { + int i; + for (int pos=0; pos pos) { + appendAffix(buffer, affixPattern.substring(pos, i), localized); + } + char c = affixPattern.charAt(++i); + ++i; + if (c == QUOTE) { + buffer.append(c); + // Fall through and append another QUOTE below + } else if (c == CURRENCY_SIGN && + i= 0 + || affix.indexOf(symbols.getGroupingSeparator()) >= 0 + || affix.indexOf(symbols.getDecimalSeparator()) >= 0 + || affix.indexOf(symbols.getPercent()) >= 0 + || affix.indexOf(symbols.getPerMill()) >= 0 + || affix.indexOf(symbols.getDigit()) >= 0 + || affix.indexOf(symbols.getPatternSeparator()) >= 0 + || affix.indexOf(symbols.getMinusSign()) >= 0 + || affix.indexOf(CURRENCY_SIGN) >= 0; + } + else { + needQuote = affix.indexOf(PATTERN_ZERO_DIGIT) >= 0 + || affix.indexOf(PATTERN_GROUPING_SEPARATOR) >= 0 + || affix.indexOf(PATTERN_DECIMAL_SEPARATOR) >= 0 + || affix.indexOf(PATTERN_PERCENT) >= 0 + || affix.indexOf(PATTERN_PER_MILLE) >= 0 + || affix.indexOf(PATTERN_DIGIT) >= 0 + || affix.indexOf(PATTERN_SEPARATOR) >= 0 + || affix.indexOf(PATTERN_MINUS) >= 0 + || affix.indexOf(CURRENCY_SIGN) >= 0; + } + if (needQuote) buffer.append('\''); + if (affix.indexOf('\'') < 0) buffer.append(affix); + else { + for (int j=0; j= 0; --j) { + if (j == 1) + appendAffix(result, posPrefixPattern, positivePrefix, localized); + else appendAffix(result, negPrefixPattern, negativePrefix, localized); + int i; + int digitCount = useExponentialNotation + ? getMaximumIntegerDigits() + : Math.max(groupingSize, getMinimumIntegerDigits())+1; + for (i = digitCount; i > 0; --i) { + if (i != digitCount && isGroupingUsed() && groupingSize != 0 && + i % groupingSize == 0) { + result.append(localized ? symbols.getGroupingSeparator() : + PATTERN_GROUPING_SEPARATOR); + } + result.append(i <= getMinimumIntegerDigits() + ? (localized ? symbols.getZeroDigit() : PATTERN_ZERO_DIGIT) + : (localized ? symbols.getDigit() : PATTERN_DIGIT)); + } + if (getMaximumFractionDigits() > 0 || decimalSeparatorAlwaysShown) + result.append(localized ? symbols.getDecimalSeparator() : + PATTERN_DECIMAL_SEPARATOR); + for (i = 0; i < getMaximumFractionDigits(); ++i) { + if (i < getMinimumFractionDigits()) { + result.append(localized ? symbols.getZeroDigit() : + PATTERN_ZERO_DIGIT); + } else { + result.append(localized ? symbols.getDigit() : + PATTERN_DIGIT); + } + } + if (useExponentialNotation) + { + result.append(localized ? symbols.getExponentSeparator() : + PATTERN_EXPONENT); + for (i=0; i + * There is no limit to integer digits set + * by this routine, since that is the typical end-user desire; + * use setMaximumInteger if you want to set a real value. + * For negative numbers, use a second pattern, separated by a semicolon + *

Example "#,#00.0#" -> 1,234.56 + *

This means a minimum of 2 integer digits, 1 fraction digit, and + * a maximum of 2 fraction digits. + *

Example: "#,#00.0#;(#,#00.0#)" for negatives in + * parentheses. + *

In negative patterns, the minimum and maximum counts are ignored; + * these are presumed to be set in the positive pattern. + * + * @exception NullPointerException if pattern is null + * @exception IllegalArgumentException if the given pattern is invalid. + */ + public void applyPattern(String pattern) { + applyPattern(pattern, false); + } + + /** + * Apply the given pattern to this Format object. The pattern + * is assumed to be in a localized notation. A pattern is a + * short-hand specification for the various formatting properties. + * These properties can also be changed individually through the + * various setter methods. + *

+ * There is no limit to integer digits set + * by this routine, since that is the typical end-user desire; + * use setMaximumInteger if you want to set a real value. + * For negative numbers, use a second pattern, separated by a semicolon + *

Example "#,#00.0#" -> 1,234.56 + *

This means a minimum of 2 integer digits, 1 fraction digit, and + * a maximum of 2 fraction digits. + *

Example: "#,#00.0#;(#,#00.0#)" for negatives in + * parentheses. + *

In negative patterns, the minimum and maximum counts are ignored; + * these are presumed to be set in the positive pattern. + * + * @exception NullPointerException if pattern is null + * @exception IllegalArgumentException if the given pattern is invalid. + */ + public void applyLocalizedPattern(String pattern) { + applyPattern(pattern, true); + } + + /** + * Does the real work of applying a pattern. + */ + private void applyPattern(String pattern, boolean localized) { + char zeroDigit = PATTERN_ZERO_DIGIT; + char groupingSeparator = PATTERN_GROUPING_SEPARATOR; + char decimalSeparator = PATTERN_DECIMAL_SEPARATOR; + char percent = PATTERN_PERCENT; + char perMill = PATTERN_PER_MILLE; + char digit = PATTERN_DIGIT; + char separator = PATTERN_SEPARATOR; + String exponent = PATTERN_EXPONENT; + char minus = PATTERN_MINUS; + if (localized) { + zeroDigit = symbols.getZeroDigit(); + groupingSeparator = symbols.getGroupingSeparator(); + decimalSeparator = symbols.getDecimalSeparator(); + percent = symbols.getPercent(); + perMill = symbols.getPerMill(); + digit = symbols.getDigit(); + separator = symbols.getPatternSeparator(); + exponent = symbols.getExponentSeparator(); + minus = symbols.getMinusSign(); + } + boolean gotNegative = false; + decimalSeparatorAlwaysShown = false; + isCurrencyFormat = false; + useExponentialNotation = false; + + // Two variables are used to record the subrange of the pattern + // occupied by phase 1. This is used during the processing of the + // second pattern (the one representing negative numbers) to ensure + // that no deviation exists in phase 1 between the two patterns. + int phaseOneStart = 0; + int phaseOneLength = 0; + + int start = 0; + for (int j = 1; j >= 0 && start < pattern.length(); --j) { + boolean inQuote = false; + StringBuffer prefix = new StringBuffer(); + StringBuffer suffix = new StringBuffer(); + int decimalPos = -1; + int multiplier = 1; + int digitLeftCount = 0, zeroDigitCount = 0, digitRightCount = 0; + byte groupingCount = -1; + + // The phase ranges from 0 to 2. Phase 0 is the prefix. Phase 1 is + // the section of the pattern with digits, decimal separator, + // grouping characters. Phase 2 is the suffix. In phases 0 and 2, + // percent, per mille, and currency symbols are recognized and + // translated. The separation of the characters into phases is + // strictly enforced; if phase 1 characters are to appear in the + // suffix, for example, they must be quoted. + int phase = 0; + + // The affix is either the prefix or the suffix. + StringBuffer affix = prefix; + + for (int pos = start; pos < pattern.length(); ++pos) { + char ch = pattern.charAt(pos); + switch (phase) { + case 0: + case 2: + // Process the prefix / suffix characters + if (inQuote) { + // A quote within quotes indicates either the closing + // quote or two quotes, which is a quote literal. That + // is, we have the second quote in 'do' or 'don''t'. + if (ch == QUOTE) { + if ((pos+1) < pattern.length() && + pattern.charAt(pos+1) == QUOTE) { + ++pos; + affix.append("''"); // 'don''t' + } else { + inQuote = false; // 'do' + } + continue; + } + } else { + // Process unquoted characters seen in prefix or suffix + // phase. + if (ch == digit || + ch == zeroDigit || + ch == groupingSeparator || + ch == decimalSeparator) { + phase = 1; + if (j == 1) { + phaseOneStart = pos; + } + --pos; // Reprocess this character + continue; + } else if (ch == CURRENCY_SIGN) { + // Use lookahead to determine if the currency sign + // is doubled or not. + boolean doubled = (pos + 1) < pattern.length() && + pattern.charAt(pos + 1) == CURRENCY_SIGN; + if (doubled) { // Skip over the doubled character + ++pos; + } + isCurrencyFormat = true; + affix.append(doubled ? "'\u00A4\u00A4" : "'\u00A4"); + continue; + } else if (ch == QUOTE) { + // A quote outside quotes indicates either the + // opening quote or two quotes, which is a quote + // literal. That is, we have the first quote in 'do' + // or o''clock. + if (ch == QUOTE) { + if ((pos+1) < pattern.length() && + pattern.charAt(pos+1) == QUOTE) { + ++pos; + affix.append("''"); // o''clock + } else { + inQuote = true; // 'do' + } + continue; + } + } else if (ch == separator) { + // Don't allow separators before we see digit + // characters of phase 1, and don't allow separators + // in the second pattern (j == 0). + if (phase == 0 || j == 0) { + throw new IllegalArgumentException("Unquoted special character '" + + ch + "' in pattern \"" + pattern + '"'); + } + start = pos + 1; + pos = pattern.length(); + continue; + } + + // Next handle characters which are appended directly. + else if (ch == percent) { + if (multiplier != 1) { + throw new IllegalArgumentException("Too many percent/per mille characters in pattern \"" + + pattern + '"'); + } + multiplier = 100; + affix.append("'%"); + continue; + } else if (ch == perMill) { + if (multiplier != 1) { + throw new IllegalArgumentException("Too many percent/per mille characters in pattern \"" + + pattern + '"'); + } + multiplier = 1000; + affix.append("'\u2030"); + continue; + } else if (ch == minus) { + affix.append("'-"); + continue; + } + } + // Note that if we are within quotes, or if this is an + // unquoted, non-special character, then we usually fall + // through to here. + affix.append(ch); + break; + + case 1: + // Phase one must be identical in the two sub-patterns. We + // enforce this by doing a direct comparison. While + // processing the first sub-pattern, we just record its + // length. While processing the second, we compare + // characters. + if (j == 1) { + ++phaseOneLength; + } else { + if (--phaseOneLength == 0) { + phase = 2; + affix = suffix; + } + continue; + } + + // Process the digits, decimal, and grouping characters. We + // record five pieces of information. We expect the digits + // to occur in the pattern ####0000.####, and we record the + // number of left digits, zero (central) digits, and right + // digits. The position of the last grouping character is + // recorded (should be somewhere within the first two blocks + // of characters), as is the position of the decimal point, + // if any (should be in the zero digits). If there is no + // decimal point, then there should be no right digits. + if (ch == digit) { + if (zeroDigitCount > 0) { + ++digitRightCount; + } else { + ++digitLeftCount; + } + if (groupingCount >= 0 && decimalPos < 0) { + ++groupingCount; + } + } else if (ch == zeroDigit) { + if (digitRightCount > 0) { + throw new IllegalArgumentException("Unexpected '0' in pattern \"" + + pattern + '"'); + } + ++zeroDigitCount; + if (groupingCount >= 0 && decimalPos < 0) { + ++groupingCount; + } + } else if (ch == groupingSeparator) { + groupingCount = 0; + } else if (ch == decimalSeparator) { + if (decimalPos >= 0) { + throw new IllegalArgumentException("Multiple decimal separators in pattern \"" + + pattern + '"'); + } + decimalPos = digitLeftCount + zeroDigitCount + digitRightCount; + } else if (pattern.regionMatches(pos, exponent, 0, exponent.length())){ + if (useExponentialNotation) { + throw new IllegalArgumentException("Multiple exponential " + + "symbols in pattern \"" + pattern + '"'); + } + useExponentialNotation = true; + minExponentDigits = 0; + + // Use lookahead to parse out the exponential part + // of the pattern, then jump into phase 2. + pos = pos+exponent.length(); + while (pos < pattern.length() && + pattern.charAt(pos) == zeroDigit) { + ++minExponentDigits; + ++phaseOneLength; + ++pos; + } + + if ((digitLeftCount + zeroDigitCount) < 1 || + minExponentDigits < 1) { + throw new IllegalArgumentException("Malformed exponential " + + "pattern \"" + pattern + '"'); + } + + // Transition to phase 2 + phase = 2; + affix = suffix; + --pos; + continue; + } else { + phase = 2; + affix = suffix; + --pos; + --phaseOneLength; + continue; + } + break; + } + } + + // Handle patterns with no '0' pattern character. These patterns + // are legal, but must be interpreted. "##.###" -> "#0.###". + // ".###" -> ".0##". + /* We allow patterns of the form "####" to produce a zeroDigitCount + * of zero (got that?); although this seems like it might make it + * possible for format() to produce empty strings, format() checks + * for this condition and outputs a zero digit in this situation. + * Having a zeroDigitCount of zero yields a minimum integer digits + * of zero, which allows proper round-trip patterns. That is, we + * don't want "#" to become "#0" when toPattern() is called (even + * though that's what it really is, semantically). + */ + if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) { + // Handle "###.###" and "###." and ".###" + int n = decimalPos; + if (n == 0) { // Handle ".###" + ++n; + } + digitRightCount = digitLeftCount - n; + digitLeftCount = n - 1; + zeroDigitCount = 1; + } + + // Do syntax checking on the digits. + if ((decimalPos < 0 && digitRightCount > 0) || + (decimalPos >= 0 && (decimalPos < digitLeftCount || + decimalPos > (digitLeftCount + zeroDigitCount))) || + groupingCount == 0 || inQuote) { + throw new IllegalArgumentException("Malformed pattern \"" + + pattern + '"'); + } + + if (j == 1) { + posPrefixPattern = prefix.toString(); + posSuffixPattern = suffix.toString(); + negPrefixPattern = posPrefixPattern; // assume these for now + negSuffixPattern = posSuffixPattern; + int digitTotalCount = digitLeftCount + zeroDigitCount + digitRightCount; + /* The effectiveDecimalPos is the position the decimal is at or + * would be at if there is no decimal. Note that if decimalPos<0, + * then digitTotalCount == digitLeftCount + zeroDigitCount. + */ + int effectiveDecimalPos = decimalPos >= 0 ? + decimalPos : digitTotalCount; + setMinimumIntegerDigits(effectiveDecimalPos - digitLeftCount); + setMaximumIntegerDigits(useExponentialNotation ? + digitLeftCount + getMinimumIntegerDigits() : + MAXIMUM_INTEGER_DIGITS); + setMaximumFractionDigits(decimalPos >= 0 ? + (digitTotalCount - decimalPos) : 0); + setMinimumFractionDigits(decimalPos >= 0 ? + (digitLeftCount + zeroDigitCount - decimalPos) : 0); + setGroupingUsed(groupingCount > 0); + this.groupingSize = (groupingCount > 0) ? groupingCount : 0; + this.multiplier = multiplier; + setDecimalSeparatorAlwaysShown(decimalPos == 0 || + decimalPos == digitTotalCount); + } else { + negPrefixPattern = prefix.toString(); + negSuffixPattern = suffix.toString(); + gotNegative = true; + } + } + + if (pattern.length() == 0) { + posPrefixPattern = posSuffixPattern = ""; + setMinimumIntegerDigits(0); + setMaximumIntegerDigits(MAXIMUM_INTEGER_DIGITS); + setMinimumFractionDigits(0); + setMaximumFractionDigits(MAXIMUM_FRACTION_DIGITS); + } + + // If there was no negative pattern, or if the negative pattern is + // identical to the positive pattern, then prepend the minus sign to + // the positive pattern to form the negative pattern. + if (!gotNegative || + (negPrefixPattern.equals(posPrefixPattern) + && negSuffixPattern.equals(posSuffixPattern))) { + negSuffixPattern = posSuffixPattern; + negPrefixPattern = "'-" + posPrefixPattern; + } + + expandAffixes(); + } + + /** + * Sets the maximum number of digits allowed in the integer portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of newValue and + * 309 is used. Negative input values are replaced with 0. + * @see NumberFormat#setMaximumIntegerDigits + */ + public void setMaximumIntegerDigits(int newValue) { + maximumIntegerDigits = Math.min(Math.max(0, newValue), MAXIMUM_INTEGER_DIGITS); + super.setMaximumIntegerDigits((maximumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? + DOUBLE_INTEGER_DIGITS : maximumIntegerDigits); + if (minimumIntegerDigits > maximumIntegerDigits) { + minimumIntegerDigits = maximumIntegerDigits; + super.setMinimumIntegerDigits((minimumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? + DOUBLE_INTEGER_DIGITS : minimumIntegerDigits); + } + } + + /** + * Sets the minimum number of digits allowed in the integer portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of newValue and + * 309 is used. Negative input values are replaced with 0. + * @see NumberFormat#setMinimumIntegerDigits + */ + public void setMinimumIntegerDigits(int newValue) { + minimumIntegerDigits = Math.min(Math.max(0, newValue), MAXIMUM_INTEGER_DIGITS); + super.setMinimumIntegerDigits((minimumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? + DOUBLE_INTEGER_DIGITS : minimumIntegerDigits); + if (minimumIntegerDigits > maximumIntegerDigits) { + maximumIntegerDigits = minimumIntegerDigits; + super.setMaximumIntegerDigits((maximumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? + DOUBLE_INTEGER_DIGITS : maximumIntegerDigits); + } + } + + /** + * Sets the maximum number of digits allowed in the fraction portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of newValue and + * 340 is used. Negative input values are replaced with 0. + * @see NumberFormat#setMaximumFractionDigits + */ + public void setMaximumFractionDigits(int newValue) { + maximumFractionDigits = Math.min(Math.max(0, newValue), MAXIMUM_FRACTION_DIGITS); + super.setMaximumFractionDigits((maximumFractionDigits > DOUBLE_FRACTION_DIGITS) ? + DOUBLE_FRACTION_DIGITS : maximumFractionDigits); + if (minimumFractionDigits > maximumFractionDigits) { + minimumFractionDigits = maximumFractionDigits; + super.setMinimumFractionDigits((minimumFractionDigits > DOUBLE_FRACTION_DIGITS) ? + DOUBLE_FRACTION_DIGITS : minimumFractionDigits); + } + } + + /** + * Sets the minimum number of digits allowed in the fraction portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of newValue and + * 340 is used. Negative input values are replaced with 0. + * @see NumberFormat#setMinimumFractionDigits + */ + public void setMinimumFractionDigits(int newValue) { + minimumFractionDigits = Math.min(Math.max(0, newValue), MAXIMUM_FRACTION_DIGITS); + super.setMinimumFractionDigits((minimumFractionDigits > DOUBLE_FRACTION_DIGITS) ? + DOUBLE_FRACTION_DIGITS : minimumFractionDigits); + if (minimumFractionDigits > maximumFractionDigits) { + maximumFractionDigits = minimumFractionDigits; + super.setMaximumFractionDigits((maximumFractionDigits > DOUBLE_FRACTION_DIGITS) ? + DOUBLE_FRACTION_DIGITS : maximumFractionDigits); + } + } + + /** + * Gets the maximum number of digits allowed in the integer portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of the return value and + * 309 is used. + * @see #setMaximumIntegerDigits + */ + public int getMaximumIntegerDigits() { + return maximumIntegerDigits; + } + + /** + * Gets the minimum number of digits allowed in the integer portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of the return value and + * 309 is used. + * @see #setMinimumIntegerDigits + */ + public int getMinimumIntegerDigits() { + return minimumIntegerDigits; + } + + /** + * Gets the maximum number of digits allowed in the fraction portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of the return value and + * 340 is used. + * @see #setMaximumFractionDigits + */ + public int getMaximumFractionDigits() { + return maximumFractionDigits; + } + + /** + * Gets the minimum number of digits allowed in the fraction portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of the return value and + * 340 is used. + * @see #setMinimumFractionDigits + */ + public int getMinimumFractionDigits() { + return minimumFractionDigits; + } + + /** + * Gets the currency used by this decimal format when formatting + * currency values. + * The currency is obtained by calling + * {@link DecimalFormatSymbols#getCurrency DecimalFormatSymbols.getCurrency} + * on this number format's symbols. + * + * @return the currency used by this decimal format, or null + * @since 1.4 + */ + public Currency getCurrency() { + return symbols.getCurrency(); + } + + /** + * Sets the currency used by this number format when formatting + * currency values. This does not update the minimum or maximum + * number of fraction digits used by the number format. + * The currency is set by calling + * {@link DecimalFormatSymbols#setCurrency DecimalFormatSymbols.setCurrency} + * on this number format's symbols. + * + * @param currency the new currency to be used by this decimal format + * @exception NullPointerException if currency is null + * @since 1.4 + */ + public void setCurrency(Currency currency) { + if (currency != symbols.getCurrency()) { + symbols.setCurrency(currency); + if (isCurrencyFormat) { + expandAffixes(); + } + } + } + + /** + * Gets the {@link java.math.RoundingMode} used in this DecimalFormat. + * + * @return The RoundingMode used for this DecimalFormat. + * @see #setRoundingMode(RoundingMode) + * @since 1.6 + */ + public RoundingMode getRoundingMode() { + return roundingMode; + } + + /** + * Sets the {@link java.math.RoundingMode} used in this DecimalFormat. + * + * @param roundingMode The RoundingMode to be used + * @see #getRoundingMode() + * @exception NullPointerException if roundingMode is null. + * @since 1.6 + */ + public void setRoundingMode(RoundingMode roundingMode) { + if (roundingMode == null) { + throw new NullPointerException(); + } + + this.roundingMode = roundingMode; + digitList.setRoundingMode(roundingMode); + } + + /** + * Adjusts the minimum and maximum fraction digits to values that + * are reasonable for the currency's default fraction digits. + */ + void adjustForCurrencyDefaultFractionDigits() { + Currency currency = symbols.getCurrency(); + if (currency == null) { + try { + currency = Currency.getInstance(symbols.getInternationalCurrencySymbol()); + } catch (IllegalArgumentException e) { + } + } + if (currency != null) { + int digits = currency.getDefaultFractionDigits(); + if (digits != -1) { + int oldMinDigits = getMinimumFractionDigits(); + // Common patterns are "#.##", "#.00", "#". + // Try to adjust all of them in a reasonable way. + if (oldMinDigits == getMaximumFractionDigits()) { + setMinimumFractionDigits(digits); + setMaximumFractionDigits(digits); + } else { + setMinimumFractionDigits(Math.min(digits, oldMinDigits)); + setMaximumFractionDigits(digits); + } + } + } + } + + /** + * Reads the default serializable fields from the stream and performs + * validations and adjustments for older serialized versions. The + * validations and adjustments are: + *

    + *
  1. + * Verify that the superclass's digit count fields correctly reflect + * the limits imposed on formatting numbers other than + * BigInteger and BigDecimal objects. These + * limits are stored in the superclass for serialization compatibility + * with older versions, while the limits for BigInteger and + * BigDecimal objects are kept in this class. + * If, in the superclass, the minimum or maximum integer digit count is + * larger than DOUBLE_INTEGER_DIGITS or if the minimum or + * maximum fraction digit count is larger than + * DOUBLE_FRACTION_DIGITS, then the stream data is invalid + * and this method throws an InvalidObjectException. + *
  2. + * If serialVersionOnStream is less than 4, initialize + * roundingMode to {@link java.math.RoundingMode#HALF_EVEN + * RoundingMode.HALF_EVEN}. This field is new with version 4. + *
  3. + * If serialVersionOnStream is less than 3, then call + * the setters for the minimum and maximum integer and fraction digits with + * the values of the corresponding superclass getters to initialize the + * fields in this class. The fields in this class are new with version 3. + *
  4. + * If serialVersionOnStream is less than 1, indicating that + * the stream was written by JDK 1.1, initialize + * useExponentialNotation + * to false, since it was not present in JDK 1.1. + *
  5. + * Set serialVersionOnStream to the maximum allowed value so + * that default serialization will work properly if this object is streamed + * out again. + *
+ * + *

Stream versions older than 2 will not have the affix pattern variables + * posPrefixPattern etc. As a result, they will be initialized + * to null, which means the affix strings will be taken as + * literal values. This is exactly what we want, since that corresponds to + * the pre-version-2 behavior. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + digitList = new DigitList(); + + if (serialVersionOnStream < 4) { + setRoundingMode(RoundingMode.HALF_EVEN); + } + // We only need to check the maximum counts because NumberFormat + // .readObject has already ensured that the maximum is greater than the + // minimum count. + if (super.getMaximumIntegerDigits() > DOUBLE_INTEGER_DIGITS || + super.getMaximumFractionDigits() > DOUBLE_FRACTION_DIGITS) { + throw new InvalidObjectException("Digit count out of range"); + } + if (serialVersionOnStream < 3) { + setMaximumIntegerDigits(super.getMaximumIntegerDigits()); + setMinimumIntegerDigits(super.getMinimumIntegerDigits()); + setMaximumFractionDigits(super.getMaximumFractionDigits()); + setMinimumFractionDigits(super.getMinimumFractionDigits()); + } + if (serialVersionOnStream < 1) { + // Didn't have exponential fields + useExponentialNotation = false; + } + serialVersionOnStream = currentSerialVersion; + } + + //---------------------------------------------------------------------- + // INSTANCE VARIABLES + //---------------------------------------------------------------------- + + private transient DigitList digitList = new DigitList(); + + /** + * The symbol used as a prefix when formatting positive numbers, e.g. "+". + * + * @serial + * @see #getPositivePrefix + */ + private String positivePrefix = ""; + + /** + * The symbol used as a suffix when formatting positive numbers. + * This is often an empty string. + * + * @serial + * @see #getPositiveSuffix + */ + private String positiveSuffix = ""; + + /** + * The symbol used as a prefix when formatting negative numbers, e.g. "-". + * + * @serial + * @see #getNegativePrefix + */ + private String negativePrefix = "-"; + + /** + * The symbol used as a suffix when formatting negative numbers. + * This is often an empty string. + * + * @serial + * @see #getNegativeSuffix + */ + private String negativeSuffix = ""; + + /** + * The prefix pattern for non-negative numbers. This variable corresponds + * to positivePrefix. + * + *

This pattern is expanded by the method expandAffix() to + * positivePrefix to update the latter to reflect changes in + * symbols. If this variable is null then + * positivePrefix is taken as a literal value that does not + * change when symbols changes. This variable is always + * null for DecimalFormat objects older than + * stream version 2 restored from stream. + * + * @serial + * @since 1.3 + */ + private String posPrefixPattern; + + /** + * The suffix pattern for non-negative numbers. This variable corresponds + * to positiveSuffix. This variable is analogous to + * posPrefixPattern; see that variable for further + * documentation. + * + * @serial + * @since 1.3 + */ + private String posSuffixPattern; + + /** + * The prefix pattern for negative numbers. This variable corresponds + * to negativePrefix. This variable is analogous to + * posPrefixPattern; see that variable for further + * documentation. + * + * @serial + * @since 1.3 + */ + private String negPrefixPattern; + + /** + * The suffix pattern for negative numbers. This variable corresponds + * to negativeSuffix. This variable is analogous to + * posPrefixPattern; see that variable for further + * documentation. + * + * @serial + * @since 1.3 + */ + private String negSuffixPattern; + + /** + * The multiplier for use in percent, per mille, etc. + * + * @serial + * @see #getMultiplier + */ + private int multiplier = 1; + + /** + * The number of digits between grouping separators in the integer + * portion of a number. Must be greater than 0 if + * NumberFormat.groupingUsed is true. + * + * @serial + * @see #getGroupingSize + * @see java.text.NumberFormat#isGroupingUsed + */ + private byte groupingSize = 3; // invariant, > 0 if useThousands + + /** + * If true, forces the decimal separator to always appear in a formatted + * number, even if the fractional part of the number is zero. + * + * @serial + * @see #isDecimalSeparatorAlwaysShown + */ + private boolean decimalSeparatorAlwaysShown = false; + + /** + * If true, parse returns BigDecimal wherever possible. + * + * @serial + * @see #isParseBigDecimal + * @since 1.5 + */ + private boolean parseBigDecimal = false; + + + /** + * True if this object represents a currency format. This determines + * whether the monetary decimal separator is used instead of the normal one. + */ + private transient boolean isCurrencyFormat = false; + + /** + * The DecimalFormatSymbols object used by this format. + * It contains the symbols used to format numbers, e.g. the grouping separator, + * decimal separator, and so on. + * + * @serial + * @see #setDecimalFormatSymbols + * @see java.text.DecimalFormatSymbols + */ + private DecimalFormatSymbols symbols = null; // LIU new DecimalFormatSymbols(); + + /** + * True to force the use of exponential (i.e. scientific) notation when formatting + * numbers. + * + * @serial + * @since 1.2 + */ + private boolean useExponentialNotation; // Newly persistent in the Java 2 platform v.1.2 + + /** + * FieldPositions describing the positive prefix String. This is + * lazily created. Use getPositivePrefixFieldPositions + * when needed. + */ + private transient FieldPosition[] positivePrefixFieldPositions; + + /** + * FieldPositions describing the positive suffix String. This is + * lazily created. Use getPositiveSuffixFieldPositions + * when needed. + */ + private transient FieldPosition[] positiveSuffixFieldPositions; + + /** + * FieldPositions describing the negative prefix String. This is + * lazily created. Use getNegativePrefixFieldPositions + * when needed. + */ + private transient FieldPosition[] negativePrefixFieldPositions; + + /** + * FieldPositions describing the negative suffix String. This is + * lazily created. Use getNegativeSuffixFieldPositions + * when needed. + */ + private transient FieldPosition[] negativeSuffixFieldPositions; + + /** + * The minimum number of digits used to display the exponent when a number is + * formatted in exponential notation. This field is ignored if + * useExponentialNotation is not true. + * + * @serial + * @since 1.2 + */ + private byte minExponentDigits; // Newly persistent in the Java 2 platform v.1.2 + + /** + * The maximum number of digits allowed in the integer portion of a + * BigInteger or BigDecimal number. + * maximumIntegerDigits must be greater than or equal to + * minimumIntegerDigits. + * + * @serial + * @see #getMaximumIntegerDigits + * @since 1.5 + */ + private int maximumIntegerDigits = super.getMaximumIntegerDigits(); + + /** + * The minimum number of digits allowed in the integer portion of a + * BigInteger or BigDecimal number. + * minimumIntegerDigits must be less than or equal to + * maximumIntegerDigits. + * + * @serial + * @see #getMinimumIntegerDigits + * @since 1.5 + */ + private int minimumIntegerDigits = super.getMinimumIntegerDigits(); + + /** + * The maximum number of digits allowed in the fractional portion of a + * BigInteger or BigDecimal number. + * maximumFractionDigits must be greater than or equal to + * minimumFractionDigits. + * + * @serial + * @see #getMaximumFractionDigits + * @since 1.5 + */ + private int maximumFractionDigits = super.getMaximumFractionDigits(); + + /** + * The minimum number of digits allowed in the fractional portion of a + * BigInteger or BigDecimal number. + * minimumFractionDigits must be less than or equal to + * maximumFractionDigits. + * + * @serial + * @see #getMinimumFractionDigits + * @since 1.5 + */ + private int minimumFractionDigits = super.getMinimumFractionDigits(); + + /** + * The {@link java.math.RoundingMode} used in this DecimalFormat. + * + * @serial + * @since 1.6 + */ + private RoundingMode roundingMode = RoundingMode.HALF_EVEN; + + //---------------------------------------------------------------------- + + static final int currentSerialVersion = 4; + + /** + * The internal serial version which says which version was written. + * Possible values are: + *

    + *
  • 0 (default): versions before the Java 2 platform v1.2 + *
  • 1: version for 1.2, which includes the two new fields + * useExponentialNotation and + * minExponentDigits. + *
  • 2: version for 1.3 and later, which adds four new fields: + * posPrefixPattern, posSuffixPattern, + * negPrefixPattern, and negSuffixPattern. + *
  • 3: version for 1.5 and later, which adds five new fields: + * maximumIntegerDigits, + * minimumIntegerDigits, + * maximumFractionDigits, + * minimumFractionDigits, and + * parseBigDecimal. + *
  • 4: version for 1.6 and later, which adds one new field: + * roundingMode. + *
+ * @since 1.2 + * @serial + */ + private int serialVersionOnStream = currentSerialVersion; + + //---------------------------------------------------------------------- + // CONSTANTS + //---------------------------------------------------------------------- + + // Constants for characters used in programmatic (unlocalized) patterns. + private static final char PATTERN_ZERO_DIGIT = '0'; + private static final char PATTERN_GROUPING_SEPARATOR = ','; + private static final char PATTERN_DECIMAL_SEPARATOR = '.'; + private static final char PATTERN_PER_MILLE = '\u2030'; + private static final char PATTERN_PERCENT = '%'; + private static final char PATTERN_DIGIT = '#'; + private static final char PATTERN_SEPARATOR = ';'; + private static final String PATTERN_EXPONENT = "E"; + private static final char PATTERN_MINUS = '-'; + + /** + * The CURRENCY_SIGN is the standard Unicode symbol for currency. It + * is used in patterns and substituted with either the currency symbol, + * or if it is doubled, with the international currency symbol. If the + * CURRENCY_SIGN is seen in a pattern, then the decimal separator is + * replaced with the monetary decimal separator. + * + * The CURRENCY_SIGN is not localized. + */ + private static final char CURRENCY_SIGN = '\u00A4'; + + private static final char QUOTE = '\''; + + private static FieldPosition[] EmptyFieldPositionArray = new FieldPosition[0]; + + // Upper limit on integer and fraction digits for a Java double + static final int DOUBLE_INTEGER_DIGITS = 309; + static final int DOUBLE_FRACTION_DIGITS = 340; + + // Upper limit on integer and fraction digits for BigDecimal and BigInteger + static final int MAXIMUM_INTEGER_DIGITS = Integer.MAX_VALUE; + static final int MAXIMUM_FRACTION_DIGITS = Integer.MAX_VALUE; + + // Proclaim JDK 1.1 serial compatibility. + static final long serialVersionUID = 864413376551465018L; + + /** + * Cache to hold the NumberPattern of a Locale. + */ + private static final ConcurrentMap cachedLocaleData + = new ConcurrentHashMap(3); +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/DecimalFormatSymbols.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/DecimalFormatSymbols.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,834 @@ +/* + * Copyright (c) 1996, 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.Currency; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This class represents the set of symbols (such as the decimal separator, + * the grouping separator, and so on) needed by DecimalFormat + * to format numbers. DecimalFormat creates for itself an instance of + * DecimalFormatSymbols from its locale data. If you need to change any + * of these symbols, you can get the DecimalFormatSymbols object from + * your DecimalFormat and modify it. + * + * @see java.util.Locale + * @see DecimalFormat + * @author Mark Davis + * @author Alan Liu + */ + +public class DecimalFormatSymbols implements Cloneable, Serializable { + + /** + * Create a DecimalFormatSymbols object for the default locale. + * This constructor can only construct instances for the locales + * supported by the Java runtime environment, not for those + * supported by installed + * {@link java.text.spi.DecimalFormatSymbolsProvider DecimalFormatSymbolsProvider} + * implementations. For full locale coverage, use the + * {@link #getInstance(Locale) getInstance} method. + */ + public DecimalFormatSymbols() { + initialize( Locale.getDefault(Locale.Category.FORMAT) ); + } + + /** + * Create a DecimalFormatSymbols object for the given locale. + * This constructor can only construct instances for the locales + * supported by the Java runtime environment, not for those + * supported by installed + * {@link java.text.spi.DecimalFormatSymbolsProvider DecimalFormatSymbolsProvider} + * implementations. For full locale coverage, use the + * {@link #getInstance(Locale) getInstance} method. + * + * @exception NullPointerException if locale is null + */ + public DecimalFormatSymbols( Locale locale ) { + initialize( locale ); + } + + /** + * Returns an array of all locales for which the + * getInstance methods of this class can return + * localized instances. + * The returned array represents the union of locales supported by the Java + * runtime and by installed + * {@link java.text.spi.DecimalFormatSymbolsProvider DecimalFormatSymbolsProvider} + * implementations. It must contain at least a Locale + * instance equal to {@link java.util.Locale#US Locale.US}. + * + * @return An array of locales for which localized + * DecimalFormatSymbols instances are available. + * @since 1.6 + */ + public static Locale[] getAvailableLocales() { + return new Locale[] { Locale.US }; +// LocaleServiceProviderPool pool = +// LocaleServiceProviderPool.getPool(DecimalFormatSymbolsProvider.class); +// return pool.getAvailableLocales(); + } + + /** + * Gets the DecimalFormatSymbols instance for the default + * locale. This method provides access to DecimalFormatSymbols + * instances for locales supported by the Java runtime itself as well + * as for those supported by installed + * {@link java.text.spi.DecimalFormatSymbolsProvider + * DecimalFormatSymbolsProvider} implementations. + * @return a DecimalFormatSymbols instance. + * @since 1.6 + */ + public static final DecimalFormatSymbols getInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the DecimalFormatSymbols instance for the specified + * locale. This method provides access to DecimalFormatSymbols + * instances for locales supported by the Java runtime itself as well + * as for those supported by installed + * {@link java.text.spi.DecimalFormatSymbolsProvider + * DecimalFormatSymbolsProvider} implementations. + * @param locale the desired locale. + * @return a DecimalFormatSymbols instance. + * @exception NullPointerException if locale is null + * @since 1.6 + */ + public static final DecimalFormatSymbols getInstance(Locale locale) { +/* + // Check whether a provider can provide an implementation that's closer + // to the requested locale than what the Java runtime itself can provide. + LocaleServiceProviderPool pool = + LocaleServiceProviderPool.getPool(DecimalFormatSymbolsProvider.class); + if (pool.hasProviders()) { + DecimalFormatSymbols providersInstance = pool.getLocalizedObject( + DecimalFormatSymbolsGetter.INSTANCE, locale); + if (providersInstance != null) { + return providersInstance; + } + } +*/ + return new DecimalFormatSymbols(locale); + } + + /** + * Gets the character used for zero. Different for Arabic, etc. + */ + public char getZeroDigit() { + return zeroDigit; + } + + /** + * Sets the character used for zero. Different for Arabic, etc. + */ + public void setZeroDigit(char zeroDigit) { + this.zeroDigit = zeroDigit; + } + + /** + * Gets the character used for thousands separator. Different for French, etc. + */ + public char getGroupingSeparator() { + return groupingSeparator; + } + + /** + * Sets the character used for thousands separator. Different for French, etc. + */ + public void setGroupingSeparator(char groupingSeparator) { + this.groupingSeparator = groupingSeparator; + } + + /** + * Gets the character used for decimal sign. Different for French, etc. + */ + public char getDecimalSeparator() { + return decimalSeparator; + } + + /** + * Sets the character used for decimal sign. Different for French, etc. + */ + public void setDecimalSeparator(char decimalSeparator) { + this.decimalSeparator = decimalSeparator; + } + + /** + * Gets the character used for per mille sign. Different for Arabic, etc. + */ + public char getPerMill() { + return perMill; + } + + /** + * Sets the character used for per mille sign. Different for Arabic, etc. + */ + public void setPerMill(char perMill) { + this.perMill = perMill; + } + + /** + * Gets the character used for percent sign. Different for Arabic, etc. + */ + public char getPercent() { + return percent; + } + + /** + * Sets the character used for percent sign. Different for Arabic, etc. + */ + public void setPercent(char percent) { + this.percent = percent; + } + + /** + * Gets the character used for a digit in a pattern. + */ + public char getDigit() { + return digit; + } + + /** + * Sets the character used for a digit in a pattern. + */ + public void setDigit(char digit) { + this.digit = digit; + } + + /** + * Gets the character used to separate positive and negative subpatterns + * in a pattern. + */ + public char getPatternSeparator() { + return patternSeparator; + } + + /** + * Sets the character used to separate positive and negative subpatterns + * in a pattern. + */ + public void setPatternSeparator(char patternSeparator) { + this.patternSeparator = patternSeparator; + } + + /** + * Gets the string used to represent infinity. Almost always left + * unchanged. + */ + public String getInfinity() { + return infinity; + } + + /** + * Sets the string used to represent infinity. Almost always left + * unchanged. + */ + public void setInfinity(String infinity) { + this.infinity = infinity; + } + + /** + * Gets the string used to represent "not a number". Almost always left + * unchanged. + */ + public String getNaN() { + return NaN; + } + + /** + * Sets the string used to represent "not a number". Almost always left + * unchanged. + */ + public void setNaN(String NaN) { + this.NaN = NaN; + } + + /** + * Gets the character used to represent minus sign. If no explicit + * negative format is specified, one is formed by prefixing + * minusSign to the positive format. + */ + public char getMinusSign() { + return minusSign; + } + + /** + * Sets the character used to represent minus sign. If no explicit + * negative format is specified, one is formed by prefixing + * minusSign to the positive format. + */ + public void setMinusSign(char minusSign) { + this.minusSign = minusSign; + } + + /** + * Returns the currency symbol for the currency of these + * DecimalFormatSymbols in their locale. + * @since 1.2 + */ + public String getCurrencySymbol() + { + return currencySymbol; + } + + /** + * Sets the currency symbol for the currency of these + * DecimalFormatSymbols in their locale. + * @since 1.2 + */ + public void setCurrencySymbol(String currency) + { + currencySymbol = currency; + } + + /** + * Returns the ISO 4217 currency code of the currency of these + * DecimalFormatSymbols. + * @since 1.2 + */ + public String getInternationalCurrencySymbol() + { + return intlCurrencySymbol; + } + + /** + * Sets the ISO 4217 currency code of the currency of these + * DecimalFormatSymbols. + * If the currency code is valid (as defined by + * {@link java.util.Currency#getInstance(java.lang.String) Currency.getInstance}), + * this also sets the currency attribute to the corresponding Currency + * instance and the currency symbol attribute to the currency's symbol + * in the DecimalFormatSymbols' locale. If the currency code is not valid, + * then the currency attribute is set to null and the currency symbol + * attribute is not modified. + * + * @see #setCurrency + * @see #setCurrencySymbol + * @since 1.2 + */ + public void setInternationalCurrencySymbol(String currencyCode) + { + intlCurrencySymbol = currencyCode; + currency = null; + if (currencyCode != null) { + try { + currency = Currency.getInstance(currencyCode); + currencySymbol = currency.getSymbol(); + } catch (IllegalArgumentException e) { + } + } + } + + /** + * Gets the currency of these DecimalFormatSymbols. May be null if the + * currency symbol attribute was previously set to a value that's not + * a valid ISO 4217 currency code. + * + * @return the currency used, or null + * @since 1.4 + */ + public Currency getCurrency() { + return currency; + } + + /** + * Sets the currency of these DecimalFormatSymbols. + * This also sets the currency symbol attribute to the currency's symbol + * in the DecimalFormatSymbols' locale, and the international currency + * symbol attribute to the currency's ISO 4217 currency code. + * + * @param currency the new currency to be used + * @exception NullPointerException if currency is null + * @since 1.4 + * @see #setCurrencySymbol + * @see #setInternationalCurrencySymbol + */ + public void setCurrency(Currency currency) { + if (currency == null) { + throw new NullPointerException(); + } + this.currency = currency; + intlCurrencySymbol = currency.getCurrencyCode(); + currencySymbol = currency.getSymbol(locale); + } + + + /** + * Returns the monetary decimal separator. + * @since 1.2 + */ + public char getMonetaryDecimalSeparator() + { + return monetarySeparator; + } + + /** + * Sets the monetary decimal separator. + * @since 1.2 + */ + public void setMonetaryDecimalSeparator(char sep) + { + monetarySeparator = sep; + } + + //------------------------------------------------------------ + // BEGIN Package Private methods ... to be made public later + //------------------------------------------------------------ + + /** + * Returns the character used to separate the mantissa from the exponent. + */ + char getExponentialSymbol() + { + return exponential; + } + /** + * Returns the string used to separate the mantissa from the exponent. + * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4. + * + * @return the exponent separator string + * @see #setExponentSeparator(java.lang.String) + * @since 1.6 + */ + public String getExponentSeparator() + { + return exponentialSeparator; + } + + /** + * Sets the character used to separate the mantissa from the exponent. + */ + void setExponentialSymbol(char exp) + { + exponential = exp; + } + + /** + * Sets the string used to separate the mantissa from the exponent. + * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4. + * + * @param exp the exponent separator string + * @exception NullPointerException if exp is null + * @see #getExponentSeparator() + * @since 1.6 + */ + public void setExponentSeparator(String exp) + { + if (exp == null) { + throw new NullPointerException(); + } + exponentialSeparator = exp; + } + + + //------------------------------------------------------------ + // END Package Private methods ... to be made public later + //------------------------------------------------------------ + + /** + * Standard override. + */ + public Object clone() { + try { + return (DecimalFormatSymbols)super.clone(); + // other fields are bit-copied + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * Override equals. + */ + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (getClass() != obj.getClass()) return false; + DecimalFormatSymbols other = (DecimalFormatSymbols) obj; + return (zeroDigit == other.zeroDigit && + groupingSeparator == other.groupingSeparator && + decimalSeparator == other.decimalSeparator && + percent == other.percent && + perMill == other.perMill && + digit == other.digit && + minusSign == other.minusSign && + patternSeparator == other.patternSeparator && + infinity.equals(other.infinity) && + NaN.equals(other.NaN) && + currencySymbol.equals(other.currencySymbol) && + intlCurrencySymbol.equals(other.intlCurrencySymbol) && + currency == other.currency && + monetarySeparator == other.monetarySeparator && + exponentialSeparator.equals(other.exponentialSeparator) && + locale.equals(other.locale)); + } + + /** + * Override hashCode. + */ + public int hashCode() { + int result = zeroDigit; + result = result * 37 + groupingSeparator; + result = result * 37 + decimalSeparator; + return result; + } + + /** + * Initializes the symbols from the FormatData resource bundle. + */ + private void initialize( Locale locale ) { + this.locale = locale; + + // get resource bundle data - try the cache first + boolean needCacheUpdate = false; + Object[] data = cachedLocaleData.get(locale); + if (data == null) { /* cache miss */ + // When numbering system is thai (Locale's extension contains u-nu-thai), + // we read the data from th_TH_TH. + Locale lookupLocale = locale; + String numberType = locale.getUnicodeLocaleType("nu"); + if (numberType != null && numberType.equals("thai")) { + lookupLocale = new Locale("th", "TH", "TH"); + } + data = new Object[3]; +// ResourceBundle rb = LocaleData.getNumberFormatData(lookupLocale); +// data[0] = rb.getStringArray("NumberElements"); + needCacheUpdate = true; + } + + String[] numberElements = (String[]) data[0]; + + decimalSeparator = numberElements[0].charAt(0); + groupingSeparator = numberElements[1].charAt(0); + patternSeparator = numberElements[2].charAt(0); + percent = numberElements[3].charAt(0); + zeroDigit = numberElements[4].charAt(0); //different for Arabic,etc. + digit = numberElements[5].charAt(0); + minusSign = numberElements[6].charAt(0); + exponential = numberElements[7].charAt(0); + exponentialSeparator = numberElements[7]; //string representation new since 1.6 + perMill = numberElements[8].charAt(0); + infinity = numberElements[9]; + NaN = numberElements[10]; + + // Try to obtain the currency used in the locale's country. + // Check for empty country string separately because it's a valid + // country ID for Locale (and used for the C locale), but not a valid + // ISO 3166 country code, and exceptions are expensive. + if (!"".equals(locale.getCountry())) { + try { + currency = Currency.getInstance(locale); + } catch (IllegalArgumentException e) { + // use default values below for compatibility + } + } + if (currency != null) { + intlCurrencySymbol = currency.getCurrencyCode(); + if (data[1] != null && data[1] == intlCurrencySymbol) { + currencySymbol = (String) data[2]; + } else { + currencySymbol = currency.getSymbol(locale); + data[1] = intlCurrencySymbol; + data[2] = currencySymbol; + needCacheUpdate = true; + } + } else { + // default values + intlCurrencySymbol = "XXX"; + try { + currency = Currency.getInstance(intlCurrencySymbol); + } catch (IllegalArgumentException e) { + } + currencySymbol = "\u00A4"; + } + // Currently the monetary decimal separator is the same as the + // standard decimal separator for all locales that we support. + // If that changes, add a new entry to NumberElements. + monetarySeparator = decimalSeparator; + + if (needCacheUpdate) { + cachedLocaleData.putIfAbsent(locale, data); + } + } + + /** + * Reads the default serializable fields, provides default values for objects + * in older serial versions, and initializes non-serializable fields. + * If serialVersionOnStream + * is less than 1, initializes monetarySeparator to be + * the same as decimalSeparator and exponential + * to be 'E'. + * If serialVersionOnStream is less than 2, + * initializes localeto the root locale, and initializes + * If serialVersionOnStream is less than 3, it initializes + * exponentialSeparator using exponential. + * Sets serialVersionOnStream back to the maximum allowed value so that + * default serialization will work properly if this object is streamed out again. + * Initializes the currency from the intlCurrencySymbol field. + * + * @since JDK 1.1.6 + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + if (serialVersionOnStream < 1) { + // Didn't have monetarySeparator or exponential field; + // use defaults. + monetarySeparator = decimalSeparator; + exponential = 'E'; + } + if (serialVersionOnStream < 2) { + // didn't have locale; use root locale + locale = Locale.ROOT; + } + if (serialVersionOnStream < 3) { + // didn't have exponentialSeparator. Create one using exponential + exponentialSeparator = Character.toString(exponential); + } + serialVersionOnStream = currentSerialVersion; + + if (intlCurrencySymbol != null) { + try { + currency = Currency.getInstance(intlCurrencySymbol); + } catch (IllegalArgumentException e) { + } + } + } + + /** + * Character used for zero. + * + * @serial + * @see #getZeroDigit + */ + private char zeroDigit; + + /** + * Character used for thousands separator. + * + * @serial + * @see #getGroupingSeparator + */ + private char groupingSeparator; + + /** + * Character used for decimal sign. + * + * @serial + * @see #getDecimalSeparator + */ + private char decimalSeparator; + + /** + * Character used for per mille sign. + * + * @serial + * @see #getPerMill + */ + private char perMill; + + /** + * Character used for percent sign. + * @serial + * @see #getPercent + */ + private char percent; + + /** + * Character used for a digit in a pattern. + * + * @serial + * @see #getDigit + */ + private char digit; + + /** + * Character used to separate positive and negative subpatterns + * in a pattern. + * + * @serial + * @see #getPatternSeparator + */ + private char patternSeparator; + + /** + * String used to represent infinity. + * @serial + * @see #getInfinity + */ + private String infinity; + + /** + * String used to represent "not a number". + * @serial + * @see #getNaN + */ + private String NaN; + + /** + * Character used to represent minus sign. + * @serial + * @see #getMinusSign + */ + private char minusSign; + + /** + * String denoting the local currency, e.g. "$". + * @serial + * @see #getCurrencySymbol + */ + private String currencySymbol; + + /** + * ISO 4217 currency code denoting the local currency, e.g. "USD". + * @serial + * @see #getInternationalCurrencySymbol + */ + private String intlCurrencySymbol; + + /** + * The decimal separator used when formatting currency values. + * @serial + * @since JDK 1.1.6 + * @see #getMonetaryDecimalSeparator + */ + private char monetarySeparator; // Field new in JDK 1.1.6 + + /** + * The character used to distinguish the exponent in a number formatted + * in exponential notation, e.g. 'E' for a number such as "1.23E45". + *

+ * Note that the public API provides no way to set this field, + * even though it is supported by the implementation and the stream format. + * The intent is that this will be added to the API in the future. + * + * @serial + * @since JDK 1.1.6 + */ + private char exponential; // Field new in JDK 1.1.6 + + /** + * The string used to separate the mantissa from the exponent. + * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4. + *

+ * If both exponential and exponentialSeparator + * exist, this exponentialSeparator has the precedence. + * + * @serial + * @since 1.6 + */ + private String exponentialSeparator; // Field new in JDK 1.6 + + /** + * The locale of these currency format symbols. + * + * @serial + * @since 1.4 + */ + private Locale locale; + + // currency; only the ISO code is serialized. + private transient Currency currency; + + // Proclaim JDK 1.1 FCS compatibility + static final long serialVersionUID = 5772796243397350300L; + + // The internal serial version which says which version was written + // - 0 (default) for version up to JDK 1.1.5 + // - 1 for version from JDK 1.1.6, which includes two new fields: + // monetarySeparator and exponential. + // - 2 for version from J2SE 1.4, which includes locale field. + // - 3 for version from J2SE 1.6, which includes exponentialSeparator field. + private static final int currentSerialVersion = 3; + + /** + * Describes the version of DecimalFormatSymbols present on the stream. + * Possible values are: + *

    + *
  • 0 (or uninitialized): versions prior to JDK 1.1.6. + * + *
  • 1: Versions written by JDK 1.1.6 or later, which include + * two new fields: monetarySeparator and exponential. + *
  • 2: Versions written by J2SE 1.4 or later, which include a + * new locale field. + *
  • 3: Versions written by J2SE 1.6 or later, which include a + * new exponentialSeparator field. + *
+ * When streaming out a DecimalFormatSymbols, the most recent format + * (corresponding to the highest allowable serialVersionOnStream) + * is always written. + * + * @serial + * @since JDK 1.1.6 + */ + private int serialVersionOnStream = currentSerialVersion; + + /** + * cache to hold the NumberElements and the Currency + * of a Locale. + */ + private static final ConcurrentHashMap cachedLocaleData = new ConcurrentHashMap(3); + + /** + * Obtains a DecimalFormatSymbols instance from a DecimalFormatSymbolsProvider + * implementation. + private static class DecimalFormatSymbolsGetter + implements LocaleServiceProviderPool.LocalizedObjectGetter { + private static final DecimalFormatSymbolsGetter INSTANCE = + new DecimalFormatSymbolsGetter(); + + public DecimalFormatSymbols getObject( + DecimalFormatSymbolsProvider decimalFormatSymbolsProvider, + Locale locale, + String key, + Object... params) { + assert params.length == 0; + return decimalFormatSymbolsProvider.getInstance(locale); + } + } + */ +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/DigitList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/DigitList.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,715 @@ +/* + * 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; + +/** + * Digit List. Private to DecimalFormat. + * Handles the transcoding + * between numeric values and strings of characters. Only handles + * non-negative numbers. The division of labor between DigitList and + * DecimalFormat is that DigitList handles the radix 10 representation + * issues; DecimalFormat handles the locale-specific issues such as + * positive/negative, grouping, decimal point, currency, and so on. + * + * A DigitList is really a representation of a floating point value. + * It may be an integer value; we assume that a double has sufficient + * precision to represent all digits of a long. + * + * The DigitList representation consists of a string of characters, + * which are the digits radix 10, from '0' to '9'. It also has a radix + * 10 exponent associated with it. The value represented by a DigitList + * object can be computed by mulitplying the fraction f, where 0 <= f < 1, + * derived by placing all the digits of the list to the right of the + * decimal point, by 10^exponent. + * + * @see Locale + * @see Format + * @see NumberFormat + * @see DecimalFormat + * @see ChoiceFormat + * @see MessageFormat + * @author Mark Davis, Alan Liu + */ +final class DigitList implements Cloneable { + /** + * The maximum number of significant digits in an IEEE 754 double, that + * is, in a Java double. This must not be increased, or garbage digits + * will be generated, and should not be decreased, or accuracy will be lost. + */ + public static final int MAX_COUNT = 19; // == Long.toString(Long.MAX_VALUE).length() + + /** + * These data members are intentionally public and can be set directly. + * + * The value represented is given by placing the decimal point before + * digits[decimalAt]. If decimalAt is < 0, then leading zeros between + * the decimal point and the first nonzero digit are implied. If decimalAt + * is > count, then trailing zeros between the digits[count-1] and the + * decimal point are implied. + * + * Equivalently, the represented value is given by f * 10^decimalAt. Here + * f is a value 0.1 <= f < 1 arrived at by placing the digits in Digits to + * the right of the decimal. + * + * DigitList is normalized, so if it is non-zero, figits[0] is non-zero. We + * don't allow denormalized numbers because our exponent is effectively of + * unlimited magnitude. The count value contains the number of significant + * digits present in digits[]. + * + * Zero is represented by any DigitList with count == 0 or with each digits[i] + * for all i <= count == '0'. + */ + public int decimalAt = 0; + public int count = 0; + public char[] digits = new char[MAX_COUNT]; + + private char[] data; + private RoundingMode roundingMode = RoundingMode.HALF_EVEN; + private boolean isNegative = false; + + /** + * Return true if the represented number is zero. + */ + boolean isZero() { + for (int i=0; i < count; ++i) { + if (digits[i] != '0') { + return false; + } + } + return true; + } + + /** + * Set the rounding mode + */ + void setRoundingMode(RoundingMode r) { + roundingMode = r; + } + + /** + * Clears out the digits. + * Use before appending them. + * Typically, you set a series of digits with append, then at the point + * you hit the decimal point, you set myDigitList.decimalAt = myDigitList.count; + * then go on appending digits. + */ + public void clear () { + decimalAt = 0; + count = 0; + } + + /** + * Appends a digit to the list, extending the list when necessary. + */ + public void append(char digit) { + if (count == digits.length) { + char[] data = new char[count + 100]; + System.arraycopy(digits, 0, data, 0, count); + digits = data; + } + digits[count++] = digit; + } + + /** + * Utility routine to get the value of the digit list + * If (count == 0) this throws a NumberFormatException, which + * mimics Long.parseLong(). + */ + public final double getDouble() { + if (count == 0) { + return 0.0; + } + + StringBuffer temp = getStringBuffer(); + temp.append('.'); + temp.append(digits, 0, count); + temp.append('E'); + temp.append(decimalAt); + return Double.parseDouble(temp.toString()); + } + + /** + * Utility routine to get the value of the digit list. + * If (count == 0) this returns 0, unlike Long.parseLong(). + */ + public final long getLong() { + // for now, simple implementation; later, do proper IEEE native stuff + + if (count == 0) { + return 0; + } + + // We have to check for this, because this is the one NEGATIVE value + // we represent. If we tried to just pass the digits off to parseLong, + // we'd get a parse failure. + if (isLongMIN_VALUE()) { + return Long.MIN_VALUE; + } + + StringBuffer temp = getStringBuffer(); + temp.append(digits, 0, count); + for (int i = count; i < decimalAt; ++i) { + temp.append('0'); + } + return Long.parseLong(temp.toString()); + } + + public final BigDecimal getBigDecimal() { + if (count == 0) { + if (decimalAt == 0) { + return BigDecimal.ZERO; + } else { + return new BigDecimal("0E" + decimalAt); + } + } + + if (decimalAt == count) { + return new BigDecimal(digits, 0, count); + } else { + return new BigDecimal(digits, 0, count).scaleByPowerOfTen(decimalAt - count); + } + } + + /** + * Return true if the number represented by this object can fit into + * a long. + * @param isPositive true if this number should be regarded as positive + * @param ignoreNegativeZero true if -0 should be regarded as identical to + * +0; otherwise they are considered distinct + * @return true if this number fits into a Java long + */ + boolean fitsIntoLong(boolean isPositive, boolean ignoreNegativeZero) { + // Figure out if the result will fit in a long. We have to + // first look for nonzero digits after the decimal point; + // then check the size. If the digit count is 18 or less, then + // the value can definitely be represented as a long. If it is 19 + // then it may be too large. + + // Trim trailing zeros. This does not change the represented value. + while (count > 0 && digits[count - 1] == '0') { + --count; + } + + if (count == 0) { + // Positive zero fits into a long, but negative zero can only + // be represented as a double. - bug 4162852 + return isPositive || ignoreNegativeZero; + } + + if (decimalAt < count || decimalAt > MAX_COUNT) { + return false; + } + + if (decimalAt < MAX_COUNT) return true; + + // At this point we have decimalAt == count, and count == MAX_COUNT. + // The number will overflow if it is larger than 9223372036854775807 + // or smaller than -9223372036854775808. + for (int i=0; i max) return false; + if (dig < max) return true; + } + + // At this point the first count digits match. If decimalAt is less + // than count, then the remaining digits are zero, and we return true. + if (count < decimalAt) return true; + + // Now we have a representation of Long.MIN_VALUE, without the leading + // negative sign. If this represents a positive value, then it does + // not fit; otherwise it fits. + return !isPositive; + } + + /** + * Set the digit list to a representation of the given double value. + * This method supports fixed-point notation. + * @param isNegative Boolean value indicating whether the number is negative. + * @param source Value to be converted; must not be Inf, -Inf, Nan, + * or a value <= 0. + * @param maximumFractionDigits The most fractional digits which should + * be converted. + */ + public final void set(boolean isNegative, double source, int maximumFractionDigits) { + set(isNegative, source, maximumFractionDigits, true); + } + + /** + * Set the digit list to a representation of the given double value. + * This method supports both fixed-point and exponential notation. + * @param isNegative Boolean value indicating whether the number is negative. + * @param source Value to be converted; must not be Inf, -Inf, Nan, + * or a value <= 0. + * @param maximumDigits The most fractional or total digits which should + * be converted. + * @param fixedPoint If true, then maximumDigits is the maximum + * fractional digits to be converted. If false, total digits. + */ + final void set(boolean isNegative, double source, int maximumDigits, boolean fixedPoint) { + set(isNegative, Double.toString(source), maximumDigits, fixedPoint); + } + + /** + * Generate a representation of the form DDDDD, DDDDD.DDDDD, or + * DDDDDE+/-DDDDD. + */ + final void set(boolean isNegative, String s, int maximumDigits, boolean fixedPoint) { + this.isNegative = isNegative; + int len = s.length(); + char[] source = getDataChars(len); + s.getChars(0, len, source, 0); + + decimalAt = -1; + count = 0; + int exponent = 0; + // Number of zeros between decimal point and first non-zero digit after + // decimal point, for numbers < 1. + int leadingZerosAfterDecimal = 0; + boolean nonZeroDigitSeen = false; + + for (int i = 0; i < len; ) { + char c = source[i++]; + if (c == '.') { + decimalAt = count; + } else if (c == 'e' || c == 'E') { + exponent = parseInt(source, i, len); + break; + } else { + if (!nonZeroDigitSeen) { + nonZeroDigitSeen = (c != '0'); + if (!nonZeroDigitSeen && decimalAt != -1) + ++leadingZerosAfterDecimal; + } + if (nonZeroDigitSeen) { + digits[count++] = c; + } + } + } + if (decimalAt == -1) { + decimalAt = count; + } + if (nonZeroDigitSeen) { + decimalAt += exponent - leadingZerosAfterDecimal; + } + + if (fixedPoint) { + // The negative of the exponent represents the number of leading + // zeros between the decimal and the first non-zero digit, for + // a value < 0.1 (e.g., for 0.00123, -decimalAt == 2). If this + // is more than the maximum fraction digits, then we have an underflow + // for the printed representation. + if (-decimalAt > maximumDigits) { + // Handle an underflow to zero when we round something like + // 0.0009 to 2 fractional digits. + count = 0; + return; + } else if (-decimalAt == maximumDigits) { + // If we round 0.0009 to 3 fractional digits, then we have to + // create a new one digit in the least significant location. + if (shouldRoundUp(0)) { + count = 1; + ++decimalAt; + digits[0] = '1'; + } else { + count = 0; + } + return; + } + // else fall through + } + + // Eliminate trailing zeros. + while (count > 1 && digits[count - 1] == '0') { + --count; + } + + // Eliminate digits beyond maximum digits to be displayed. + // Round up if appropriate. + round(fixedPoint ? (maximumDigits + decimalAt) : maximumDigits); + } + + /** + * Round the representation to the given number of digits. + * @param maximumDigits The maximum number of digits to be shown. + * Upon return, count will be less than or equal to maximumDigits. + */ + private final void round(int maximumDigits) { + // Eliminate digits beyond maximum digits to be displayed. + // Round up if appropriate. + if (maximumDigits >= 0 && maximumDigits < count) { + if (shouldRoundUp(maximumDigits)) { + // Rounding up involved incrementing digits from LSD to MSD. + // In most cases this is simple, but in a worst case situation + // (9999..99) we have to adjust the decimalAt value. + for (;;) { + --maximumDigits; + if (maximumDigits < 0) { + // We have all 9's, so we increment to a single digit + // of one and adjust the exponent. + digits[0] = '1'; + ++decimalAt; + maximumDigits = 0; // Adjust the count + break; + } + + ++digits[maximumDigits]; + if (digits[maximumDigits] <= '9') break; + // digits[maximumDigits] = '0'; // Unnecessary since we'll truncate this + } + ++maximumDigits; // Increment for use as count + } + count = maximumDigits; + + // Eliminate trailing zeros. + while (count > 1 && digits[count-1] == '0') { + --count; + } + } + } + + + /** + * Return true if truncating the representation to the given number + * of digits will result in an increment to the last digit. This + * method implements the rounding modes defined in the + * java.math.RoundingMode class. + * [bnf] + * @param maximumDigits the number of digits to keep, from 0 to + * count-1. If 0, then all digits are rounded away, and + * this method returns true if a one should be generated (e.g., formatting + * 0.09 with "#.#"). + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return true if digit maximumDigits-1 should be + * incremented + */ + private boolean shouldRoundUp(int maximumDigits) { + if (maximumDigits < count) { + switch(roundingMode) { + case UP: + for (int i=maximumDigits; i= '5') { + return true; + } + break; + case HALF_DOWN: + if (digits[maximumDigits] > '5') { + return true; + } else if (digits[maximumDigits] == '5' ) { + for (int i=maximumDigits+1; i '5') { + return true; + } else if (digits[maximumDigits] == '5' ) { + for (int i=maximumDigits+1; i 0 && (digits[maximumDigits-1] % 2 != 0); + } + break; + case UNNECESSARY: + for (int i=maximumDigits; i= 0 or == + * Long.MIN_VALUE. + * @param maximumDigits The most digits which should be converted. + * If maximumDigits is lower than the number of significant digits + * in source, the representation will be rounded. Ignored if <= 0. + */ + public final void set(boolean isNegative, long source, int maximumDigits) { + this.isNegative = isNegative; + + // This method does not expect a negative number. However, + // "source" can be a Long.MIN_VALUE (-9223372036854775808), + // if the number being formatted is a Long.MIN_VALUE. In that + // case, it will be formatted as -Long.MIN_VALUE, a number + // which is outside the legal range of a long, but which can + // be represented by DigitList. + if (source <= 0) { + if (source == Long.MIN_VALUE) { + decimalAt = count = MAX_COUNT; + System.arraycopy(LONG_MIN_REP, 0, digits, 0, count); + } else { + decimalAt = count = 0; // Values <= 0 format as zero + } + } else { + // Rewritten to improve performance. I used to call + // Long.toString(), which was about 4x slower than this code. + int left = MAX_COUNT; + int right; + while (source > 0) { + digits[--left] = (char)('0' + (source % 10)); + source /= 10; + } + decimalAt = MAX_COUNT - left; + // Don't copy trailing zeros. We are guaranteed that there is at + // least one non-zero digit, so we don't have to check lower bounds. + for (right = MAX_COUNT - 1; digits[right] == '0'; --right) + ; + count = right - left + 1; + System.arraycopy(digits, left, digits, 0, count); + } + if (maximumDigits > 0) round(maximumDigits); + } + + /** + * Set the digit list to a representation of the given BigDecimal value. + * This method supports both fixed-point and exponential notation. + * @param isNegative Boolean value indicating whether the number is negative. + * @param source Value to be converted; must not be a value <= 0. + * @param maximumDigits The most fractional or total digits which should + * be converted. + * @param fixedPoint If true, then maximumDigits is the maximum + * fractional digits to be converted. If false, total digits. + */ + final void set(boolean isNegative, BigDecimal source, int maximumDigits, boolean fixedPoint) { + String s = source.toString(); + extendDigits(s.length()); + + set(isNegative, s, maximumDigits, fixedPoint); + } + + /** + * Set the digit list to a representation of the given BigInteger value. + * @param isNegative Boolean value indicating whether the number is negative. + * @param source Value to be converted; must be >= 0. + * @param maximumDigits The most digits which should be converted. + * If maximumDigits is lower than the number of significant digits + * in source, the representation will be rounded. Ignored if <= 0. + */ + final void set(boolean isNegative, BigInteger source, int maximumDigits) { + this.isNegative = isNegative; + String s = source.toString(); + int len = s.length(); + extendDigits(len); + s.getChars(0, len, digits, 0); + + decimalAt = len; + int right; + for (right = len - 1; right >= 0 && digits[right] == '0'; --right) + ; + count = right + 1; + + if (maximumDigits > 0) { + round(maximumDigits); + } + } + + /** + * equality test between two digit lists. + */ + public boolean equals(Object obj) { + if (this == obj) // quick check + return true; + if (!(obj instanceof DigitList)) // (1) same object? + return false; + DigitList other = (DigitList) obj; + if (count != other.count || + decimalAt != other.decimalAt) + return false; + for (int i = 0; i < count; i++) + if (digits[i] != other.digits[i]) + return false; + return true; + } + + /** + * Generates the hash code for the digit list. + */ + public int hashCode() { + int hashcode = decimalAt; + + for (int i = 0; i < count; i++) { + hashcode = hashcode * 37 + digits[i]; + } + + return hashcode; + } + + /** + * Creates a copy of this object. + * @return a clone of this instance. + */ + public Object clone() { + try { + DigitList other = (DigitList) super.clone(); + char[] newDigits = new char[digits.length]; + System.arraycopy(digits, 0, newDigits, 0, digits.length); + other.digits = newDigits; + other.tempBuffer = null; + return other; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * Returns true if this DigitList represents Long.MIN_VALUE; + * false, otherwise. This is required so that getLong() works. + */ + private boolean isLongMIN_VALUE() { + if (decimalAt != count || count != MAX_COUNT) { + return false; + } + + for (int i = 0; i < count; ++i) { + if (digits[i] != LONG_MIN_REP[i]) return false; + } + + return true; + } + + private static final int parseInt(char[] str, int offset, int strLen) { + char c; + boolean positive = true; + if ((c = str[offset]) == '-') { + positive = false; + offset++; + } else if (c == '+') { + offset++; + } + + int value = 0; + while (offset < strLen) { + c = str[offset++]; + if (c >= '0' && c <= '9') { + value = value * 10 + (c - '0'); + } else { + break; + } + } + return positive ? value : -value; + } + + // The digit part of -9223372036854775808L + private static final char[] LONG_MIN_REP = "9223372036854775808".toCharArray(); + + public String toString() { + if (isZero()) { + return "0"; + } + StringBuffer buf = getStringBuffer(); + buf.append("0."); + buf.append(digits, 0, count); + buf.append("x10^"); + buf.append(decimalAt); + return buf.toString(); + } + + private StringBuffer tempBuffer; + + private StringBuffer getStringBuffer() { + if (tempBuffer == null) { + tempBuffer = new StringBuffer(MAX_COUNT); + } else { + tempBuffer.setLength(0); + } + return tempBuffer; + } + + private void extendDigits(int len) { + if (len > digits.length) { + digits = new char[len]; + } + } + + private final char[] getDataChars(int length) { + if (data == null || data.length < length) { + data = new char[length]; + } + return data; + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/DontCareFieldPosition.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/DontCareFieldPosition.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,53 @@ +/* + * 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 java.text; + +/** + * DontCareFieldPosition defines no-op FieldDelegate. Its + * singleton is used for the format methods that don't take a + * FieldPosition. + */ +class DontCareFieldPosition extends FieldPosition { + // The singleton of DontCareFieldPosition. + static final FieldPosition INSTANCE = new DontCareFieldPosition(); + + private final Format.FieldDelegate noDelegate = new Format.FieldDelegate() { + public void formatted(Format.Field attr, Object value, int start, + int end, StringBuffer buffer) { + } + public void formatted(int fieldID, Format.Field attr, Object value, + int start, int end, StringBuffer buffer) { + } + }; + + private DontCareFieldPosition() { + super(0); + } + + Format.FieldDelegate getFieldDelegate() { + return noDelegate; + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/FieldPosition.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/FieldPosition.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,303 @@ +/* + * Copyright (c) 1996, 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +/** + * FieldPosition is a simple class used by Format + * and its subclasses to identify fields in formatted output. Fields can + * be identified in two ways: + *
    + *
  • By an integer constant, whose names typically end with + * _FIELD. The constants are defined in the various + * subclasses of Format. + *
  • By a Format.Field constant, see ERA_FIELD + * and its friends in DateFormat for an example. + *
+ *

+ * FieldPosition keeps track of the position of the + * field within the formatted output with two indices: the index + * of the first character of the field and the index of the last + * character of the field. + * + *

+ * One version of the format method in the various + * Format classes requires a FieldPosition + * object as an argument. You use this format method + * to perform partial formatting or to get information about the + * formatted output (such as the position of a field). + * + *

+ * If you are interested in the positions of all attributes in the + * formatted string use the Format method + * formatToCharacterIterator. + * + * @author Mark Davis + * @see java.text.Format + */ +public class FieldPosition { + + /** + * Input: Desired field to determine start and end offsets for. + * The meaning depends on the subclass of Format. + */ + int field = 0; + + /** + * Output: End offset of field in text. + * If the field does not occur in the text, 0 is returned. + */ + int endIndex = 0; + + /** + * Output: Start offset of field in text. + * If the field does not occur in the text, 0 is returned. + */ + int beginIndex = 0; + + /** + * Desired field this FieldPosition is for. + */ + private Format.Field attribute; + + /** + * Creates a FieldPosition object for the given field. Fields are + * identified by constants, whose names typically end with _FIELD, + * in the various subclasses of Format. + * + * @see java.text.NumberFormat#INTEGER_FIELD + * @see java.text.NumberFormat#FRACTION_FIELD + * @see java.text.DateFormat#YEAR_FIELD + * @see java.text.DateFormat#MONTH_FIELD + */ + public FieldPosition(int field) { + this.field = field; + } + + /** + * Creates a FieldPosition object for the given field constant. Fields are + * identified by constants defined in the various Format + * subclasses. This is equivalent to calling + * new FieldPosition(attribute, -1). + * + * @param attribute Format.Field constant identifying a field + * @since 1.4 + */ + public FieldPosition(Format.Field attribute) { + this(attribute, -1); + } + + /** + * Creates a FieldPosition object for the given field. + * The field is identified by an attribute constant from one of the + * Field subclasses as well as an integer field ID + * defined by the Format subclasses. Format + * subclasses that are aware of Field should give precedence + * to attribute and ignore fieldID if + * attribute is not null. However, older Format + * subclasses may not be aware of Field and rely on + * fieldID. If the field has no corresponding integer + * constant, fieldID should be -1. + * + * @param attribute Format.Field constant identifying a field + * @param fieldID integer constantce identifying a field + * @since 1.4 + */ + public FieldPosition(Format.Field attribute, int fieldID) { + this.attribute = attribute; + this.field = fieldID; + } + + /** + * Returns the field identifier as an attribute constant + * from one of the Field subclasses. May return null if + * the field is specified only by an integer field ID. + * + * @return Identifier for the field + * @since 1.4 + */ + public Format.Field getFieldAttribute() { + return attribute; + } + + /** + * Retrieves the field identifier. + */ + public int getField() { + return field; + } + + /** + * Retrieves the index of the first character in the requested field. + */ + public int getBeginIndex() { + return beginIndex; + } + + /** + * Retrieves the index of the character following the last character in the + * requested field. + */ + public int getEndIndex() { + return endIndex; + } + + /** + * Sets the begin index. For use by subclasses of Format. + * @since 1.2 + */ + public void setBeginIndex(int bi) { + beginIndex = bi; + } + + /** + * Sets the end index. For use by subclasses of Format. + * @since 1.2 + */ + public void setEndIndex(int ei) { + endIndex = ei; + } + + /** + * Returns a Format.FieldDelegate instance that is associated + * with the FieldPosition. When the delegate is notified of the same + * field the FieldPosition is associated with, the begin/end will be + * adjusted. + */ + Format.FieldDelegate getFieldDelegate() { + return new Delegate(); + } + + /** + * Overrides equals + */ + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!(obj instanceof FieldPosition)) + return false; + FieldPosition other = (FieldPosition) obj; + if (attribute == null) { + if (other.attribute != null) { + return false; + } + } + else if (!attribute.equals(other.attribute)) { + return false; + } + return (beginIndex == other.beginIndex + && endIndex == other.endIndex + && field == other.field); + } + + /** + * Returns a hash code for this FieldPosition. + * @return a hash code value for this object + */ + public int hashCode() { + return (field << 24) | (beginIndex << 16) | endIndex; + } + + /** + * Return a string representation of this FieldPosition. + * @return a string representation of this object + */ + public String toString() { + return getClass().getName() + + "[field=" + field + ",attribute=" + attribute + + ",beginIndex=" + beginIndex + + ",endIndex=" + endIndex + ']'; + } + + + /** + * Return true if the receiver wants a Format.Field value and + * attribute is equal to it. + */ + private boolean matchesField(Format.Field attribute) { + if (this.attribute != null) { + return this.attribute.equals(attribute); + } + return false; + } + + /** + * Return true if the receiver wants a Format.Field value and + * attribute is equal to it, or true if the receiver + * represents an inteter constant and field equals it. + */ + private boolean matchesField(Format.Field attribute, int field) { + if (this.attribute != null) { + return this.attribute.equals(attribute); + } + return (field == this.field); + } + + + /** + * An implementation of FieldDelegate that will adjust the begin/end + * of the FieldPosition if the arguments match the field of + * the FieldPosition. + */ + private class Delegate implements Format.FieldDelegate { + /** + * Indicates whether the field has been encountered before. If this + * is true, and formatted is invoked, the begin/end + * are not updated. + */ + private boolean encounteredField; + + public void formatted(Format.Field attr, Object value, int start, + int end, StringBuffer buffer) { + if (!encounteredField && matchesField(attr)) { + setBeginIndex(start); + setEndIndex(end); + encounteredField = (start != end); + } + } + + public void formatted(int fieldID, Format.Field attr, Object value, + int start, int end, StringBuffer buffer) { + if (!encounteredField && matchesField(attr, fieldID)) { + setBeginIndex(start); + setEndIndex(end); + encounteredField = (start != end); + } + } + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/Format.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/Format.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,406 @@ +/* + * 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.Serializable; + +/** + * Format is an abstract base class for formatting locale-sensitive + * information such as dates, messages, and numbers. + * + *

+ * Format defines the programming interface for formatting + * locale-sensitive objects into Strings (the + * format method) and for parsing Strings back + * into objects (the parseObject method). + * + *

+ * Generally, a format's parseObject method must be able to parse + * any string formatted by its format method. However, there may + * be exceptional cases where this is not possible. For example, a + * format method might create two adjacent integer numbers with + * no separator in between, and in this case the parseObject could + * not tell which digits belong to which number. + * + *

Subclassing

+ * + *

+ * The Java Platform provides three specialized subclasses of Format-- + * DateFormat, MessageFormat, and + * NumberFormat--for formatting dates, messages, and numbers, + * respectively. + *

+ * Concrete subclasses must implement three methods: + *

    + *
  1. format(Object obj, StringBuffer toAppendTo, FieldPosition pos) + *
  2. formatToCharacterIterator(Object obj) + *
  3. parseObject(String source, ParsePosition pos) + *
+ * These general methods allow polymorphic parsing and formatting of objects + * and are used, for example, by MessageFormat. + * Subclasses often also provide additional format methods for + * specific input types as well as parse methods for specific + * result types. Any parse method that does not take a + * ParsePosition argument should throw ParseException + * when no text in the required format is at the beginning of the input text. + * + *

+ * Most subclasses will also implement the following factory methods: + *

    + *
  1. + * getInstance for getting a useful format object appropriate + * for the current locale + *
  2. + * getInstance(Locale) for getting a useful format + * object appropriate for the specified locale + *
+ * In addition, some subclasses may also implement other + * getXxxxInstance methods for more specialized control. For + * example, the NumberFormat class provides + * getPercentInstance and getCurrencyInstance + * methods for getting specialized number formatters. + * + *

+ * Subclasses of Format that allow programmers to create objects + * for locales (with getInstance(Locale) for example) + * must also implement the following class method: + *

+ *
+ * public static Locale[] getAvailableLocales()
+ * 
+ *
+ * + *

+ * And finally subclasses may define a set of constants to identify the various + * fields in the formatted output. These constants are used to create a FieldPosition + * object which identifies what information is contained in the field and its + * position in the formatted result. These constants should be named + * item_FIELD where item identifies + * the field. For examples of these constants, see ERA_FIELD and its + * friends in {@link DateFormat}. + * + *

Synchronization

+ * + *

+ * Formats are generally not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + * @see java.text.ParsePosition + * @see java.text.FieldPosition + * @see java.text.NumberFormat + * @see java.text.DateFormat + * @see java.text.MessageFormat + * @author Mark Davis + */ +public abstract class Format implements Serializable, Cloneable { + + private static final long serialVersionUID = -299282585814624189L; + + /** + * Sole constructor. (For invocation by subclass constructors, typically + * implicit.) + */ + protected Format() { + } + + /** + * Formats an object to produce a string. This is equivalent to + *

+ * {@link #format(Object, StringBuffer, FieldPosition) format}(obj, + * new StringBuffer(), new FieldPosition(0)).toString(); + *
+ * + * @param obj The object to format + * @return Formatted string. + * @exception IllegalArgumentException if the Format cannot format the given + * object + */ + public final String format (Object obj) { + return format(obj, new StringBuffer(), new FieldPosition(0)).toString(); + } + + /** + * Formats an object and appends the resulting text to a given string + * buffer. + * If the pos argument identifies a field used by the format, + * then its indices are set to the beginning and end of the first such + * field encountered. + * + * @param obj The object to format + * @param toAppendTo where the text is to be appended + * @param pos A FieldPosition identifying a field + * in the formatted text + * @return the string buffer passed in as toAppendTo, + * with formatted text appended + * @exception NullPointerException if toAppendTo or + * pos is null + * @exception IllegalArgumentException if the Format cannot format the given + * object + */ + public abstract StringBuffer format(Object obj, + StringBuffer toAppendTo, + FieldPosition pos); + + /** + * Formats an Object producing an AttributedCharacterIterator. + * You can use the returned AttributedCharacterIterator + * to build the resulting String, as well as to determine information + * about the resulting String. + *

+ * Each attribute key of the AttributedCharacterIterator will be of type + * Field. It is up to each Format implementation + * to define what the legal values are for each attribute in the + * AttributedCharacterIterator, but typically the attribute + * key is also used as the attribute value. + *

The default implementation creates an + * AttributedCharacterIterator with no attributes. Subclasses + * that support fields should override this and create an + * AttributedCharacterIterator with meaningful attributes. + * + * @exception NullPointerException if obj is null. + * @exception IllegalArgumentException when the Format cannot format the + * given object. + * @param obj The object to format + * @return AttributedCharacterIterator describing the formatted value. + * @since 1.4 + */ + public AttributedCharacterIterator formatToCharacterIterator(Object obj) { + return createAttributedCharacterIterator(format(obj)); + } + + /** + * Parses text from a string to produce an object. + *

+ * The method attempts to parse text starting at the index given by + * pos. + * If parsing succeeds, then the index of pos is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * object is returned. The updated pos can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of pos is not + * changed, the error index of pos is set to the index of + * the character where the error occurred, and null is returned. + * + * @param source A String, part of which should be parsed. + * @param pos A ParsePosition object with index and error + * index information as described above. + * @return An Object parsed from the string. In case of + * error, returns null. + * @exception NullPointerException if pos is null. + */ + public abstract Object parseObject (String source, ParsePosition pos); + + /** + * Parses text from the beginning of the given string to produce an object. + * The method may not use the entire text of the given string. + * + * @param source A String whose beginning should be parsed. + * @return An Object parsed from the string. + * @exception ParseException if the beginning of the specified string + * cannot be parsed. + */ + public Object parseObject(String source) throws ParseException { + ParsePosition pos = new ParsePosition(0); + Object result = parseObject(source, pos); + if (pos.index == 0) { + throw new ParseException("Format.parseObject(String) failed", + pos.errorIndex); + } + return result; + } + + /** + * Creates and returns a copy of this object. + * + * @return a clone of this instance. + */ + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + // will never happen + return null; + } + } + + // + // Convenience methods for creating AttributedCharacterIterators from + // different parameters. + // + + /** + * Creates an AttributedCharacterIterator for the String + * s. + * + * @param s String to create AttributedCharacterIterator from + * @return AttributedCharacterIterator wrapping s + */ + AttributedCharacterIterator createAttributedCharacterIterator(String s) { + AttributedString as = new AttributedString(s); + + return as.getIterator(); + } + + /** + * Creates an AttributedCharacterIterator containg the + * concatenated contents of the passed in + * AttributedCharacterIterators. + * + * @param iterators AttributedCharacterIterators used to create resulting + * AttributedCharacterIterators + * @return AttributedCharacterIterator wrapping passed in + * AttributedCharacterIterators + */ + AttributedCharacterIterator createAttributedCharacterIterator( + AttributedCharacterIterator[] iterators) { + AttributedString as = new AttributedString(iterators); + + return as.getIterator(); + } + + /** + * Returns an AttributedCharacterIterator with the String + * string and additional key/value pair key, + * value. + * + * @param string String to create AttributedCharacterIterator from + * @param key Key for AttributedCharacterIterator + * @param value Value associated with key in AttributedCharacterIterator + * @return AttributedCharacterIterator wrapping args + */ + AttributedCharacterIterator createAttributedCharacterIterator( + String string, AttributedCharacterIterator.Attribute key, + Object value) { + AttributedString as = new AttributedString(string); + + as.addAttribute(key, value); + return as.getIterator(); + } + + /** + * Creates an AttributedCharacterIterator with the contents of + * iterator and the additional attribute key + * value. + * + * @param iterator Initial AttributedCharacterIterator to add arg to + * @param key Key for AttributedCharacterIterator + * @param value Value associated with key in AttributedCharacterIterator + * @return AttributedCharacterIterator wrapping args + */ + AttributedCharacterIterator createAttributedCharacterIterator( + AttributedCharacterIterator iterator, + AttributedCharacterIterator.Attribute key, Object value) { + AttributedString as = new AttributedString(iterator); + + as.addAttribute(key, value); + return as.getIterator(); + } + + + /** + * Defines constants that are used as attribute keys in the + * AttributedCharacterIterator returned + * from Format.formatToCharacterIterator and as + * field identifiers in FieldPosition. + * + * @since 1.4 + */ + public static class Field extends AttributedCharacterIterator.Attribute { + + // Proclaim serial compatibility with 1.4 FCS + private static final long serialVersionUID = 276966692217360283L; + + /** + * Creates a Field with the specified name. + * + * @param name Name of the attribute + */ + protected Field(String name) { + super(name); + } + } + + + /** + * FieldDelegate is notified by the various Format + * implementations as they are formatting the Objects. This allows for + * storage of the individual sections of the formatted String for + * later use, such as in a FieldPosition or for an + * AttributedCharacterIterator. + *

+ * Delegates should NOT assume that the Format will notify + * the delegate of fields in any particular order. + * + * @see FieldPosition.Delegate + * @see CharacterIteratorFieldDelegate + */ + interface FieldDelegate { + /** + * Notified when a particular region of the String is formatted. This + * method will be invoked if there is no corresponding integer field id + * matching attr. + * + * @param attr Identifies the field matched + * @param value Value associated with the field + * @param start Beginning location of the field, will be >= 0 + * @param end End of the field, will be >= start and <= buffer.length() + * @param buffer Contains current formatted value, receiver should + * NOT modify it. + */ + public void formatted(Format.Field attr, Object value, int start, + int end, StringBuffer buffer); + + /** + * Notified when a particular region of the String is formatted. + * + * @param fieldID Identifies the field by integer + * @param attr Identifies the field matched + * @param value Value associated with the field + * @param start Beginning location of the field, will be >= 0 + * @param end End of the field, will be >= start and <= buffer.length() + * @param buffer Contains current formatted value, receiver should + * NOT modify it. + */ + public void formatted(int fieldID, Format.Field attr, Object value, + int start, int end, StringBuffer buffer); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/MessageFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/MessageFormat.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1594 @@ +/* + * Copyright (c) 1996, 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.InvalidObjectException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; + + +/** + * MessageFormat provides a means to produce concatenated + * messages in a language-neutral way. Use this to construct messages + * displayed for end users. + * + *

+ * MessageFormat takes a set of objects, formats them, then + * inserts the formatted strings into the pattern at the appropriate places. + * + *

+ * Note: + * MessageFormat differs from the other Format + * classes in that you create a MessageFormat object with one + * of its constructors (not with a getInstance style factory + * method). The factory methods aren't necessary because MessageFormat + * itself doesn't implement locale specific behavior. Any locale specific + * behavior is defined by the pattern that you provide as well as the + * subformats used for inserted arguments. + * + *

Patterns and Their Interpretation

+ * + * MessageFormat uses patterns of the following form: + *
+ * MessageFormatPattern:
+ *         String
+ *         MessageFormatPattern FormatElement String
+ *
+ * FormatElement:
+ *         { ArgumentIndex }
+ *         { ArgumentIndex , FormatType }
+ *         { ArgumentIndex , FormatType , FormatStyle }
+ *
+ * FormatType: one of 
+ *         number date time choice
+ *
+ * FormatStyle:
+ *         short
+ *         medium
+ *         long
+ *         full
+ *         integer
+ *         currency
+ *         percent
+ *         SubformatPattern
+ * 
+ * + *

Within a String, a pair of single quotes can be used to + * quote any arbitrary characters except single quotes. For example, + * pattern string "'{0}'" represents string + * "{0}", not a FormatElement. A single quote itself + * must be represented by doubled single quotes {@code ''} throughout a + * String. For example, pattern string "'{''}'" is + * interpreted as a sequence of '{ (start of quoting and a + * left curly brace), '' (a single quote), and + * }' (a right curly brace and end of quoting), + * not '{' and '}' (quoted left and + * right curly braces): representing string "{'}", + * not "{}". + * + *

A SubformatPattern is interpreted by its corresponding + * subformat, and subformat-dependent pattern rules apply. For example, + * pattern string "{1,number,$'#',##}" + * (SubformatPattern with underline) will produce a number format + * with the pound-sign quoted, with a result such as: {@code + * "$#31,45"}. Refer to each {@code Format} subclass documentation for + * details. + * + *

Any unmatched quote is treated as closed at the end of the given + * pattern. For example, pattern string {@code "'{0}"} is treated as + * pattern {@code "'{0}'"}. + * + *

Any curly braces within an unquoted pattern must be balanced. For + * example, "ab {0} de" and "ab '}' de" are + * valid patterns, but "ab {0'}' de", "ab } de" + * and "''{''" are not. + * + *

+ *

Warning:
The rules for using quotes within message + * format patterns unfortunately have shown to be somewhat confusing. + * In particular, it isn't always obvious to localizers whether single + * quotes need to be doubled or not. Make sure to inform localizers about + * the rules, and tell them (for example, by using comments in resource + * bundle source files) which strings will be processed by {@code MessageFormat}. + * Note that localizers may need to use single quotes in translated + * strings where the original version doesn't have them. + *
+ *

+ * The ArgumentIndex value is a non-negative integer written + * using the digits {@code '0'} through {@code '9'}, and represents an index into the + * {@code arguments} array passed to the {@code format} methods + * or the result array returned by the {@code parse} methods. + *

+ * The FormatType and FormatStyle values are used to create + * a {@code Format} instance for the format element. The following + * table shows how the values map to {@code Format} instances. Combinations not + * shown in the table are illegal. A SubformatPattern must + * be a valid pattern string for the {@code Format} subclass used. + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
FormatType + * FormatStyle + * Subformat Created + *
(none) + * (none) + * null + *
number + * (none) + * {@link NumberFormat#getInstance(Locale) NumberFormat.getInstance}{@code (getLocale())} + *
integer + * {@link NumberFormat#getIntegerInstance(Locale) NumberFormat.getIntegerInstance}{@code (getLocale())} + *
currency + * {@link NumberFormat#getCurrencyInstance(Locale) NumberFormat.getCurrencyInstance}{@code (getLocale())} + *
percent + * {@link NumberFormat#getPercentInstance(Locale) NumberFormat.getPercentInstance}{@code (getLocale())} + *
SubformatPattern + * {@code new} {@link DecimalFormat#DecimalFormat(String,DecimalFormatSymbols) DecimalFormat}{@code (subformatPattern,} {@link DecimalFormatSymbols#getInstance(Locale) DecimalFormatSymbols.getInstance}{@code (getLocale()))} + *
date + * (none) + * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} + *
short + * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())} + *
medium + * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} + *
long + * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())} + *
full + * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())} + *
SubformatPattern + * {@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())} + *
time + * (none) + * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} + *
short + * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())} + *
medium + * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} + *
long + * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())} + *
full + * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())} + *
SubformatPattern + * {@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())} + *
choice + * SubformatPattern + * {@code new} {@link ChoiceFormat#ChoiceFormat(String) ChoiceFormat}{@code (subformatPattern)} + *
+ *

+ * + *

Usage Information

+ * + *

+ * Here are some examples of usage. + * In real internationalized programs, the message format pattern and other + * static strings will, of course, be obtained from resource bundles. + * Other parameters will be dynamically determined at runtime. + *

+ * The first example uses the static method MessageFormat.format, + * which internally creates a MessageFormat for one-time use: + *

+ * int planet = 7;
+ * String event = "a disturbance in the Force";
+ *
+ * String result = MessageFormat.format(
+ *     "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
+ *     planet, new Date(), event);
+ * 
+ * The output is: + *
+ * At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7.
+ * 
+ * + *

+ * The following example creates a MessageFormat instance that + * can be used repeatedly: + *

+ * int fileCount = 1273;
+ * String diskName = "MyDisk";
+ * Object[] testArgs = {new Long(fileCount), diskName};
+ *
+ * MessageFormat form = new MessageFormat(
+ *     "The disk \"{1}\" contains {0} file(s).");
+ *
+ * System.out.println(form.format(testArgs));
+ * 
+ * The output with different values for fileCount: + *
+ * The disk "MyDisk" contains 0 file(s).
+ * The disk "MyDisk" contains 1 file(s).
+ * The disk "MyDisk" contains 1,273 file(s).
+ * 
+ * + *

+ * For more sophisticated patterns, you can use a ChoiceFormat + * to produce correct forms for singular and plural: + *

+ * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
+ * double[] filelimits = {0,1,2};
+ * String[] filepart = {"no files","one file","{0,number} files"};
+ * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
+ * form.setFormatByArgumentIndex(0, fileform);
+ *
+ * int fileCount = 1273;
+ * String diskName = "MyDisk";
+ * Object[] testArgs = {new Long(fileCount), diskName};
+ *
+ * System.out.println(form.format(testArgs));
+ * 
+ * The output with different values for fileCount: + *
+ * The disk "MyDisk" contains no files.
+ * The disk "MyDisk" contains one file.
+ * The disk "MyDisk" contains 1,273 files.
+ * 
+ * + *

+ * You can create the ChoiceFormat programmatically, as in the + * above example, or by using a pattern. See {@link ChoiceFormat} + * for more information. + *

+ * form.applyPattern(
+ *    "There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}.");
+ * 
+ * + *

+ * Note: As we see above, the string produced + * by a ChoiceFormat in MessageFormat is treated as special; + * occurrences of '{' are used to indicate subformats, and cause recursion. + * If you create both a MessageFormat and ChoiceFormat + * programmatically (instead of using the string patterns), then be careful not to + * produce a format that recurses on itself, which will cause an infinite loop. + *

+ * When a single argument is parsed more than once in the string, the last match + * will be the final result of the parsing. For example, + *

+ * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");
+ * Object[] objs = {new Double(3.1415)};
+ * String result = mf.format( objs );
+ * // result now equals "3.14, 3.1"
+ * objs = null;
+ * objs = mf.parse(result, new ParsePosition(0));
+ * // objs now equals {new Double(3.1)}
+ * 
+ * + *

+ * Likewise, parsing with a {@code MessageFormat} object using patterns containing + * multiple occurrences of the same argument would return the last match. For + * example, + *

+ * MessageFormat mf = new MessageFormat("{0}, {0}, {0}");
+ * String forParsing = "x, y, z";
+ * Object[] objs = mf.parse(forParsing, new ParsePosition(0));
+ * // result now equals {new String("z")}
+ * 
+ * + *

Synchronization

+ * + *

+ * Message formats are not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + * @see java.util.Locale + * @see Format + * @see NumberFormat + * @see DecimalFormat + * @see DecimalFormatSymbols + * @see ChoiceFormat + * @see DateFormat + * @see SimpleDateFormat + * + * @author Mark Davis + */ + +public class MessageFormat extends Format { + + private static final long serialVersionUID = 6479157306784022952L; + + /** + * Constructs a MessageFormat for the default locale and the + * specified pattern. + * The constructor first sets the locale, then parses the pattern and + * creates a list of subformats for the format elements contained in it. + * Patterns and their interpretation are specified in the + * class description. + * + * @param pattern the pattern for this message format + * @exception IllegalArgumentException if the pattern is invalid + */ + public MessageFormat(String pattern) { + this.locale = Locale.getDefault(Locale.Category.FORMAT); + applyPattern(pattern); + } + + /** + * Constructs a MessageFormat for the specified locale and + * pattern. + * The constructor first sets the locale, then parses the pattern and + * creates a list of subformats for the format elements contained in it. + * Patterns and their interpretation are specified in the + * class description. + * + * @param pattern the pattern for this message format + * @param locale the locale for this message format + * @exception IllegalArgumentException if the pattern is invalid + * @since 1.4 + */ + public MessageFormat(String pattern, Locale locale) { + this.locale = locale; + applyPattern(pattern); + } + + /** + * Sets the locale to be used when creating or comparing subformats. + * This affects subsequent calls + *

    + *
  • to the {@link #applyPattern applyPattern} + * and {@link #toPattern toPattern} methods if format elements specify + * a format type and therefore have the subformats created in the + * applyPattern method, as well as + *
  • to the format and + * {@link #formatToCharacterIterator formatToCharacterIterator} methods + * if format elements do not specify a format type and therefore have + * the subformats created in the formatting methods. + *
+ * Subformats that have already been created are not affected. + * + * @param locale the locale to be used when creating or comparing subformats + */ + public void setLocale(Locale locale) { + this.locale = locale; + } + + /** + * Gets the locale that's used when creating or comparing subformats. + * + * @return the locale used when creating or comparing subformats + */ + public Locale getLocale() { + return locale; + } + + + /** + * Sets the pattern used by this message format. + * The method parses the pattern and creates a list of subformats + * for the format elements contained in it. + * Patterns and their interpretation are specified in the + * class description. + * + * @param pattern the pattern for this message format + * @exception IllegalArgumentException if the pattern is invalid + */ + public void applyPattern(String pattern) { + StringBuilder[] segments = new StringBuilder[4]; + // Allocate only segments[SEG_RAW] here. The rest are + // allocated on demand. + segments[SEG_RAW] = new StringBuilder(); + + int part = SEG_RAW; + int formatNumber = 0; + boolean inQuote = false; + int braceStack = 0; + maxOffset = -1; + for (int i = 0; i < pattern.length(); ++i) { + char ch = pattern.charAt(i); + if (part == SEG_RAW) { + if (ch == '\'') { + if (i + 1 < pattern.length() + && pattern.charAt(i+1) == '\'') { + segments[part].append(ch); // handle doubles + ++i; + } else { + inQuote = !inQuote; + } + } else if (ch == '{' && !inQuote) { + part = SEG_INDEX; + if (segments[SEG_INDEX] == null) { + segments[SEG_INDEX] = new StringBuilder(); + } + } else { + segments[part].append(ch); + } + } else { + if (inQuote) { // just copy quotes in parts + segments[part].append(ch); + if (ch == '\'') { + inQuote = false; + } + } else { + switch (ch) { + case ',': + if (part < SEG_MODIFIER) { + if (segments[++part] == null) { + segments[part] = new StringBuilder(); + } + } else { + segments[part].append(ch); + } + break; + case '{': + ++braceStack; + segments[part].append(ch); + break; + case '}': + if (braceStack == 0) { + part = SEG_RAW; + makeFormat(i, formatNumber, segments); + formatNumber++; + // throw away other segments + segments[SEG_INDEX] = null; + segments[SEG_TYPE] = null; + segments[SEG_MODIFIER] = null; + } else { + --braceStack; + segments[part].append(ch); + } + break; + case ' ': + // Skip any leading space chars for SEG_TYPE. + if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) { + segments[part].append(ch); + } + break; + case '\'': + inQuote = true; + // fall through, so we keep quotes in other parts + default: + segments[part].append(ch); + break; + } + } + } + } + if (braceStack == 0 && part != 0) { + maxOffset = -1; + throw new IllegalArgumentException("Unmatched braces in the pattern."); + } + this.pattern = segments[0].toString(); + } + + + /** + * Returns a pattern representing the current state of the message format. + * The string is constructed from internal information and therefore + * does not necessarily equal the previously applied pattern. + * + * @return a pattern representing the current state of the message format + */ + public String toPattern() { + // later, make this more extensible + int lastOffset = 0; + StringBuilder result = new StringBuilder(); + for (int i = 0; i <= maxOffset; ++i) { + copyAndFixQuotes(pattern, lastOffset, offsets[i], result); + lastOffset = offsets[i]; + result.append('{').append(argumentNumbers[i]); + Format fmt = formats[i]; + if (fmt == null) { + // do nothing, string format + } else if (fmt instanceof NumberFormat) { + if (fmt.equals(NumberFormat.getInstance(locale))) { + result.append(",number"); + } else if (fmt.equals(NumberFormat.getCurrencyInstance(locale))) { + result.append(",number,currency"); + } else if (fmt.equals(NumberFormat.getPercentInstance(locale))) { + result.append(",number,percent"); + } else if (fmt.equals(NumberFormat.getIntegerInstance(locale))) { + result.append(",number,integer"); + } else { + if (fmt instanceof DecimalFormat) { + result.append(",number,").append(((DecimalFormat)fmt).toPattern()); + } else if (fmt instanceof ChoiceFormat) { + result.append(",choice,").append(((ChoiceFormat)fmt).toPattern()); + } else { + // UNKNOWN + } + } + } else if (fmt instanceof DateFormat) { + int index; + for (index = MODIFIER_DEFAULT; index < DATE_TIME_MODIFIERS.length; index++) { + DateFormat df = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[index], + locale); + if (fmt.equals(df)) { + result.append(",date"); + break; + } + df = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[index], + locale); + if (fmt.equals(df)) { + result.append(",time"); + break; + } + } + if (index >= DATE_TIME_MODIFIERS.length) { + if (fmt instanceof SimpleDateFormat) { + result.append(",date,").append(((SimpleDateFormat)fmt).toPattern()); + } else { + // UNKNOWN + } + } else if (index != MODIFIER_DEFAULT) { + result.append(',').append(DATE_TIME_MODIFIER_KEYWORDS[index]); + } + } else { + //result.append(", unknown"); + } + result.append('}'); + } + copyAndFixQuotes(pattern, lastOffset, pattern.length(), result); + return result.toString(); + } + + /** + * Sets the formats to use for the values passed into + * format methods or returned from parse + * methods. The indices of elements in newFormats + * correspond to the argument indices used in the previously set + * pattern string. + * The order of formats in newFormats thus corresponds to + * the order of elements in the arguments array passed + * to the format methods or the result array returned + * by the parse methods. + *

+ * If an argument index is used for more than one format element + * in the pattern string, then the corresponding new format is used + * for all such format elements. If an argument index is not used + * for any format element in the pattern string, then the + * corresponding new format is ignored. If fewer formats are provided + * than needed, then only the formats for argument indices less + * than newFormats.length are replaced. + * + * @param newFormats the new formats to use + * @exception NullPointerException if newFormats is null + * @since 1.4 + */ + public void setFormatsByArgumentIndex(Format[] newFormats) { + for (int i = 0; i <= maxOffset; i++) { + int j = argumentNumbers[i]; + if (j < newFormats.length) { + formats[i] = newFormats[j]; + } + } + } + + /** + * Sets the formats to use for the format elements in the + * previously set pattern string. + * The order of formats in newFormats corresponds to + * the order of format elements in the pattern string. + *

+ * If more formats are provided than needed by the pattern string, + * the remaining ones are ignored. If fewer formats are provided + * than needed, then only the first newFormats.length + * formats are replaced. + *

+ * Since the order of format elements in a pattern string often + * changes during localization, it is generally better to use the + * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex} + * method, which assumes an order of formats corresponding to the + * order of elements in the arguments array passed to + * the format methods or the result array returned by + * the parse methods. + * + * @param newFormats the new formats to use + * @exception NullPointerException if newFormats is null + */ + public void setFormats(Format[] newFormats) { + int runsToCopy = newFormats.length; + if (runsToCopy > maxOffset + 1) { + runsToCopy = maxOffset + 1; + } + for (int i = 0; i < runsToCopy; i++) { + formats[i] = newFormats[i]; + } + } + + /** + * Sets the format to use for the format elements within the + * previously set pattern string that use the given argument + * index. + * The argument index is part of the format element definition and + * represents an index into the arguments array passed + * to the format methods or the result array returned + * by the parse methods. + *

+ * If the argument index is used for more than one format element + * in the pattern string, then the new format is used for all such + * format elements. If the argument index is not used for any format + * element in the pattern string, then the new format is ignored. + * + * @param argumentIndex the argument index for which to use the new format + * @param newFormat the new format to use + * @since 1.4 + */ + public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) { + for (int j = 0; j <= maxOffset; j++) { + if (argumentNumbers[j] == argumentIndex) { + formats[j] = newFormat; + } + } + } + + /** + * Sets the format to use for the format element with the given + * format element index within the previously set pattern string. + * The format element index is the zero-based number of the format + * element counting from the start of the pattern string. + *

+ * Since the order of format elements in a pattern string often + * changes during localization, it is generally better to use the + * {@link #setFormatByArgumentIndex setFormatByArgumentIndex} + * method, which accesses format elements based on the argument + * index they specify. + * + * @param formatElementIndex the index of a format element within the pattern + * @param newFormat the format to use for the specified format element + * @exception ArrayIndexOutOfBoundsException if {@code formatElementIndex} is equal to or + * larger than the number of format elements in the pattern string + */ + public void setFormat(int formatElementIndex, Format newFormat) { + formats[formatElementIndex] = newFormat; + } + + /** + * Gets the formats used for the values passed into + * format methods or returned from parse + * methods. The indices of elements in the returned array + * correspond to the argument indices used in the previously set + * pattern string. + * The order of formats in the returned array thus corresponds to + * the order of elements in the arguments array passed + * to the format methods or the result array returned + * by the parse methods. + *

+ * If an argument index is used for more than one format element + * in the pattern string, then the format used for the last such + * format element is returned in the array. If an argument index + * is not used for any format element in the pattern string, then + * null is returned in the array. + * + * @return the formats used for the arguments within the pattern + * @since 1.4 + */ + public Format[] getFormatsByArgumentIndex() { + int maximumArgumentNumber = -1; + for (int i = 0; i <= maxOffset; i++) { + if (argumentNumbers[i] > maximumArgumentNumber) { + maximumArgumentNumber = argumentNumbers[i]; + } + } + Format[] resultArray = new Format[maximumArgumentNumber + 1]; + for (int i = 0; i <= maxOffset; i++) { + resultArray[argumentNumbers[i]] = formats[i]; + } + return resultArray; + } + + /** + * Gets the formats used for the format elements in the + * previously set pattern string. + * The order of formats in the returned array corresponds to + * the order of format elements in the pattern string. + *

+ * Since the order of format elements in a pattern string often + * changes during localization, it's generally better to use the + * {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex} + * method, which assumes an order of formats corresponding to the + * order of elements in the arguments array passed to + * the format methods or the result array returned by + * the parse methods. + * + * @return the formats used for the format elements in the pattern + */ + public Format[] getFormats() { + Format[] resultArray = new Format[maxOffset + 1]; + System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1); + return resultArray; + } + + /** + * Formats an array of objects and appends the MessageFormat's + * pattern, with format elements replaced by the formatted objects, to the + * provided StringBuffer. + *

+ * The text substituted for the individual format elements is derived from + * the current subformat of the format element and the + * arguments element at the format element's argument index + * as indicated by the first matching line of the following table. An + * argument is unavailable if arguments is + * null or has fewer than argumentIndex+1 elements. + *

+ * + * + * + * + * + * + * + * + * + * + *
Subformat + * Argument + * Formatted Text + *
any + * unavailable + * "{" + argumentIndex + "}" + *
any + * null + * "null" + *
instanceof ChoiceFormat + * any + * subformat.format(argument).indexOf('{') >= 0 ?
+ * (new MessageFormat(subformat.format(argument), getLocale())).format(argument) : + * subformat.format(argument)
+ *
!= null + * any + * subformat.format(argument) + *
null + * instanceof Number + * NumberFormat.getInstance(getLocale()).format(argument) + *
null + * instanceof Date + * DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument) + *
null + * instanceof String + * argument + *
null + * any + * argument.toString() + *
+ *

+ * If pos is non-null, and refers to + * Field.ARGUMENT, the location of the first formatted + * string will be returned. + * + * @param arguments an array of objects to be formatted and substituted. + * @param result where text is appended. + * @param pos On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @exception IllegalArgumentException if an argument in the + * arguments array is not of the type + * expected by the format element(s) that use it. + */ + public final StringBuffer format(Object[] arguments, StringBuffer result, + FieldPosition pos) + { + return subformat(arguments, result, pos, null); + } + + /** + * Creates a MessageFormat with the given pattern and uses it + * to format the given arguments. This is equivalent to + *

+ * (new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString() + *
+ * + * @exception IllegalArgumentException if the pattern is invalid, + * or if an argument in the arguments array + * is not of the type expected by the format element(s) + * that use it. + */ + public static String format(String pattern, Object ... arguments) { + MessageFormat temp = new MessageFormat(pattern); + return temp.format(arguments); + } + + // Overrides + /** + * Formats an array of objects and appends the MessageFormat's + * pattern, with format elements replaced by the formatted objects, to the + * provided StringBuffer. + * This is equivalent to + *
+ * {@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos) + *
+ * + * @param arguments an array of objects to be formatted and substituted. + * @param result where text is appended. + * @param pos On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @exception IllegalArgumentException if an argument in the + * arguments array is not of the type + * expected by the format element(s) that use it. + */ + public final StringBuffer format(Object arguments, StringBuffer result, + FieldPosition pos) + { + return subformat((Object[]) arguments, result, pos, null); + } + + /** + * Formats an array of objects and inserts them into the + * MessageFormat's pattern, producing an + * AttributedCharacterIterator. + * You can use the returned AttributedCharacterIterator + * to build the resulting String, as well as to determine information + * about the resulting String. + *

+ * The text of the returned AttributedCharacterIterator is + * the same that would be returned by + *

+ * {@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString() + *
+ *

+ * In addition, the AttributedCharacterIterator contains at + * least attributes indicating where text was generated from an + * argument in the arguments array. The keys of these attributes are of + * type MessageFormat.Field, their values are + * Integer objects indicating the index in the arguments + * array of the argument from which the text was generated. + *

+ * The attributes/value from the underlying Format + * instances that MessageFormat uses will also be + * placed in the resulting AttributedCharacterIterator. + * This allows you to not only find where an argument is placed in the + * resulting String, but also which fields it contains in turn. + * + * @param arguments an array of objects to be formatted and substituted. + * @return AttributedCharacterIterator describing the formatted value. + * @exception NullPointerException if arguments is null. + * @exception IllegalArgumentException if an argument in the + * arguments array is not of the type + * expected by the format element(s) that use it. + * @since 1.4 + */ + public AttributedCharacterIterator formatToCharacterIterator(Object arguments) { + StringBuffer result = new StringBuffer(); + ArrayList iterators = new ArrayList(); + + if (arguments == null) { + throw new NullPointerException( + "formatToCharacterIterator must be passed non-null object"); + } + subformat((Object[]) arguments, result, null, iterators); + if (iterators.size() == 0) { + return createAttributedCharacterIterator(""); + } + return createAttributedCharacterIterator( + (AttributedCharacterIterator[])iterators.toArray( + new AttributedCharacterIterator[iterators.size()])); + } + + /** + * Parses the string. + * + *

Caveats: The parse may fail in a number of circumstances. + * For example: + *

    + *
  • If one of the arguments does not occur in the pattern. + *
  • If the format of an argument loses information, such as + * with a choice format where a large number formats to "many". + *
  • Does not yet handle recursion (where + * the substituted strings contain {n} references.) + *
  • Will not always find a match (or the correct match) + * if some part of the parse is ambiguous. + * For example, if the pattern "{1},{2}" is used with the + * string arguments {"a,b", "c"}, it will format as "a,b,c". + * When the result is parsed, it will return {"a", "b,c"}. + *
  • If a single argument is parsed more than once in the string, + * then the later parse wins. + *
+ * When the parse fails, use ParsePosition.getErrorIndex() to find out + * where in the string the parsing failed. The returned error + * index is the starting offset of the sub-patterns that the string + * is comparing with. For example, if the parsing string "AAA {0} BBB" + * is comparing against the pattern "AAD {0} BBB", the error index is + * 0. When an error occurs, the call to this method will return null. + * If the source is null, return an empty array. + */ + public Object[] parse(String source, ParsePosition pos) { + if (source == null) { + Object[] empty = {}; + return empty; + } + + int maximumArgumentNumber = -1; + for (int i = 0; i <= maxOffset; i++) { + if (argumentNumbers[i] > maximumArgumentNumber) { + maximumArgumentNumber = argumentNumbers[i]; + } + } + Object[] resultArray = new Object[maximumArgumentNumber + 1]; + + int patternOffset = 0; + int sourceOffset = pos.index; + ParsePosition tempStatus = new ParsePosition(0); + for (int i = 0; i <= maxOffset; ++i) { + // match up to format + int len = offsets[i] - patternOffset; + if (len == 0 || pattern.regionMatches(patternOffset, + source, sourceOffset, len)) { + sourceOffset += len; + patternOffset += len; + } else { + pos.errorIndex = sourceOffset; + return null; // leave index as is to signal error + } + + // now use format + if (formats[i] == null) { // string format + // if at end, use longest possible match + // otherwise uses first match to intervening string + // does NOT recursively try all possibilities + int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length(); + + int next; + if (patternOffset >= tempLength) { + next = source.length(); + }else{ + next = source.indexOf(pattern.substring(patternOffset, tempLength), + sourceOffset); + } + + if (next < 0) { + pos.errorIndex = sourceOffset; + return null; // leave index as is to signal error + } else { + String strValue= source.substring(sourceOffset,next); + if (!strValue.equals("{"+argumentNumbers[i]+"}")) + resultArray[argumentNumbers[i]] + = source.substring(sourceOffset,next); + sourceOffset = next; + } + } else { + tempStatus.index = sourceOffset; + resultArray[argumentNumbers[i]] + = formats[i].parseObject(source,tempStatus); + if (tempStatus.index == sourceOffset) { + pos.errorIndex = sourceOffset; + return null; // leave index as is to signal error + } + sourceOffset = tempStatus.index; // update + } + } + int len = pattern.length() - patternOffset; + if (len == 0 || pattern.regionMatches(patternOffset, + source, sourceOffset, len)) { + pos.index = sourceOffset + len; + } else { + pos.errorIndex = sourceOffset; + return null; // leave index as is to signal error + } + return resultArray; + } + + /** + * Parses text from the beginning of the given string to produce an object + * array. + * The method may not use the entire text of the given string. + *

+ * See the {@link #parse(String, ParsePosition)} method for more information + * on message parsing. + * + * @param source A String whose beginning should be parsed. + * @return An Object array parsed from the string. + * @exception ParseException if the beginning of the specified string + * cannot be parsed. + */ + public Object[] parse(String source) throws ParseException { + ParsePosition pos = new ParsePosition(0); + Object[] result = parse(source, pos); + if (pos.index == 0) // unchanged, returned object is null + throw new ParseException("MessageFormat parse error!", pos.errorIndex); + + return result; + } + + /** + * Parses text from a string to produce an object array. + *

+ * The method attempts to parse text starting at the index given by + * pos. + * If parsing succeeds, then the index of pos is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * object array is returned. The updated pos can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of pos is not + * changed, the error index of pos is set to the index of + * the character where the error occurred, and null is returned. + *

+ * See the {@link #parse(String, ParsePosition)} method for more information + * on message parsing. + * + * @param source A String, part of which should be parsed. + * @param pos A ParsePosition object with index and error + * index information as described above. + * @return An Object array parsed from the string. In case of + * error, returns null. + * @exception NullPointerException if pos is null. + */ + public Object parseObject(String source, ParsePosition pos) { + return parse(source, pos); + } + + /** + * Creates and returns a copy of this object. + * + * @return a clone of this instance. + */ + public Object clone() { + MessageFormat other = (MessageFormat) super.clone(); + + // clone arrays. Can't do with utility because of bug in Cloneable + other.formats = (Format[]) formats.clone(); // shallow clone + for (int i = 0; i < formats.length; ++i) { + if (formats[i] != null) + other.formats[i] = (Format)formats[i].clone(); + } + // for primitives or immutables, shallow clone is enough + other.offsets = (int[]) offsets.clone(); + other.argumentNumbers = (int[]) argumentNumbers.clone(); + + return other; + } + + /** + * Equality comparison between two message format objects + */ + public boolean equals(Object obj) { + if (this == obj) // quick check + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + MessageFormat other = (MessageFormat) obj; + return (maxOffset == other.maxOffset + && pattern.equals(other.pattern) + && ((locale != null && locale.equals(other.locale)) + || (locale == null && other.locale == null)) + && Arrays.equals(offsets,other.offsets) + && Arrays.equals(argumentNumbers,other.argumentNumbers) + && Arrays.equals(formats,other.formats)); + } + + /** + * Generates a hash code for the message format object. + */ + public int hashCode() { + return pattern.hashCode(); // enough for reasonable distribution + } + + + /** + * Defines constants that are used as attribute keys in the + * AttributedCharacterIterator returned + * from MessageFormat.formatToCharacterIterator. + * + * @since 1.4 + */ + public static class Field extends Format.Field { + + // Proclaim serial compatibility with 1.4 FCS + private static final long serialVersionUID = 7899943957617360810L; + + /** + * Creates a Field with the specified name. + * + * @param name Name of the attribute + */ + protected Field(String name) { + super(name); + } + + /** + * Resolves instances being deserialized to the predefined constants. + * + * @throws InvalidObjectException if the constant could not be + * resolved. + * @return resolved MessageFormat.Field constant + */ + protected Object readResolve() throws InvalidObjectException { + if (this.getClass() != MessageFormat.Field.class) { + throw new InvalidObjectException("subclass didn't correctly implement readResolve"); + } + + return ARGUMENT; + } + + // + // The constants + // + + /** + * Constant identifying a portion of a message that was generated + * from an argument passed into formatToCharacterIterator. + * The value associated with the key will be an Integer + * indicating the index in the arguments array of the + * argument from which the text was generated. + */ + public final static Field ARGUMENT = + new Field("message argument field"); + } + + // ===========================privates============================ + + /** + * The locale to use for formatting numbers and dates. + * @serial + */ + private Locale locale; + + /** + * The string that the formatted values are to be plugged into. In other words, this + * is the pattern supplied on construction with all of the {} expressions taken out. + * @serial + */ + private String pattern = ""; + + /** The initially expected number of subformats in the format */ + private static final int INITIAL_FORMATS = 10; + + /** + * An array of formatters, which are used to format the arguments. + * @serial + */ + private Format[] formats = new Format[INITIAL_FORMATS]; + + /** + * The positions where the results of formatting each argument are to be inserted + * into the pattern. + * @serial + */ + private int[] offsets = new int[INITIAL_FORMATS]; + + /** + * The argument numbers corresponding to each formatter. (The formatters are stored + * in the order they occur in the pattern, not in the order in which the arguments + * are specified.) + * @serial + */ + private int[] argumentNumbers = new int[INITIAL_FORMATS]; + + /** + * One less than the number of entries in offsets. Can also be thought of + * as the index of the highest-numbered element in offsets that is being used. + * All of these arrays should have the same number of elements being used as offsets + * does, and so this variable suffices to tell us how many entries are in all of them. + * @serial + */ + private int maxOffset = -1; + + /** + * Internal routine used by format. If characterIterators is + * non-null, AttributedCharacterIterator will be created from the + * subformats as necessary. If characterIterators is null + * and fp is non-null and identifies + * Field.MESSAGE_ARGUMENT, the location of + * the first replaced argument will be set in it. + * + * @exception IllegalArgumentException if an argument in the + * arguments array is not of the type + * expected by the format element(s) that use it. + */ + private StringBuffer subformat(Object[] arguments, StringBuffer result, + FieldPosition fp, List characterIterators) { + // note: this implementation assumes a fast substring & index. + // if this is not true, would be better to append chars one by one. + int lastOffset = 0; + int last = result.length(); + for (int i = 0; i <= maxOffset; ++i) { + result.append(pattern.substring(lastOffset, offsets[i])); + lastOffset = offsets[i]; + int argumentNumber = argumentNumbers[i]; + if (arguments == null || argumentNumber >= arguments.length) { + result.append('{').append(argumentNumber).append('}'); + continue; + } + // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3); + if (false) { // if (argRecursion == 3){ + // prevent loop!!! + result.append('\uFFFD'); + } else { + Object obj = arguments[argumentNumber]; + String arg = null; + Format subFormatter = null; + if (obj == null) { + arg = "null"; + } else if (formats[i] != null) { + subFormatter = formats[i]; + if (subFormatter instanceof ChoiceFormat) { + arg = formats[i].format(obj); + if (arg.indexOf('{') >= 0) { + subFormatter = new MessageFormat(arg, locale); + obj = arguments; + arg = null; + } + } + } else if (obj instanceof Number) { + // format number if can + subFormatter = NumberFormat.getInstance(locale); + } else if (obj instanceof Date) { + // format a Date if can + subFormatter = DateFormat.getDateTimeInstance( + DateFormat.SHORT, DateFormat.SHORT, locale);//fix + } else if (obj instanceof String) { + arg = (String) obj; + + } else { + arg = obj.toString(); + if (arg == null) arg = "null"; + } + + // At this point we are in two states, either subFormatter + // is non-null indicating we should format obj using it, + // or arg is non-null and we should use it as the value. + + if (characterIterators != null) { + // If characterIterators is non-null, it indicates we need + // to get the CharacterIterator from the child formatter. + if (last != result.length()) { + characterIterators.add( + createAttributedCharacterIterator(result.substring + (last))); + last = result.length(); + } + if (subFormatter != null) { + AttributedCharacterIterator subIterator = + subFormatter.formatToCharacterIterator(obj); + + append(result, subIterator); + if (last != result.length()) { + characterIterators.add( + createAttributedCharacterIterator( + subIterator, Field.ARGUMENT, + Integer.valueOf(argumentNumber))); + last = result.length(); + } + arg = null; + } + if (arg != null && arg.length() > 0) { + result.append(arg); + characterIterators.add( + createAttributedCharacterIterator( + arg, Field.ARGUMENT, + Integer.valueOf(argumentNumber))); + last = result.length(); + } + } + else { + if (subFormatter != null) { + arg = subFormatter.format(obj); + } + last = result.length(); + result.append(arg); + if (i == 0 && fp != null && Field.ARGUMENT.equals( + fp.getFieldAttribute())) { + fp.setBeginIndex(last); + fp.setEndIndex(result.length()); + } + last = result.length(); + } + } + } + result.append(pattern.substring(lastOffset, pattern.length())); + if (characterIterators != null && last != result.length()) { + characterIterators.add(createAttributedCharacterIterator( + result.substring(last))); + } + return result; + } + + /** + * Convenience method to append all the characters in + * iterator to the StringBuffer result. + */ + private void append(StringBuffer result, CharacterIterator iterator) { + if (iterator.first() != CharacterIterator.DONE) { + char aChar; + + result.append(iterator.first()); + while ((aChar = iterator.next()) != CharacterIterator.DONE) { + result.append(aChar); + } + } + } + + // Indices for segments + private static final int SEG_RAW = 0; + private static final int SEG_INDEX = 1; + private static final int SEG_TYPE = 2; + private static final int SEG_MODIFIER = 3; // modifier or subformat + + // Indices for type keywords + private static final int TYPE_NULL = 0; + private static final int TYPE_NUMBER = 1; + private static final int TYPE_DATE = 2; + private static final int TYPE_TIME = 3; + private static final int TYPE_CHOICE = 4; + + private static final String[] TYPE_KEYWORDS = { + "", + "number", + "date", + "time", + "choice" + }; + + // Indices for number modifiers + private static final int MODIFIER_DEFAULT = 0; // common in number and date-time + private static final int MODIFIER_CURRENCY = 1; + private static final int MODIFIER_PERCENT = 2; + private static final int MODIFIER_INTEGER = 3; + + private static final String[] NUMBER_MODIFIER_KEYWORDS = { + "", + "currency", + "percent", + "integer" + }; + + // Indices for date-time modifiers + private static final int MODIFIER_SHORT = 1; + private static final int MODIFIER_MEDIUM = 2; + private static final int MODIFIER_LONG = 3; + private static final int MODIFIER_FULL = 4; + + private static final String[] DATE_TIME_MODIFIER_KEYWORDS = { + "", + "short", + "medium", + "long", + "full" + }; + + // Date-time style values corresponding to the date-time modifiers. + private static final int[] DATE_TIME_MODIFIERS = { + DateFormat.DEFAULT, + DateFormat.SHORT, + DateFormat.MEDIUM, + DateFormat.LONG, + DateFormat.FULL, + }; + + private void makeFormat(int position, int offsetNumber, + StringBuilder[] textSegments) + { + String[] segments = new String[textSegments.length]; + for (int i = 0; i < textSegments.length; i++) { + StringBuilder oneseg = textSegments[i]; + segments[i] = (oneseg != null) ? oneseg.toString() : ""; + } + + // get the argument number + int argumentNumber; + try { + argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always unlocalized! + } catch (NumberFormatException e) { + throw new IllegalArgumentException("can't parse argument number: " + + segments[SEG_INDEX], e); + } + if (argumentNumber < 0) { + throw new IllegalArgumentException("negative argument number: " + + argumentNumber); + } + + // resize format information arrays if necessary + if (offsetNumber >= formats.length) { + int newLength = formats.length * 2; + Format[] newFormats = new Format[newLength]; + int[] newOffsets = new int[newLength]; + int[] newArgumentNumbers = new int[newLength]; + System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1); + System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1); + System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0, maxOffset + 1); + formats = newFormats; + offsets = newOffsets; + argumentNumbers = newArgumentNumbers; + } + int oldMaxOffset = maxOffset; + maxOffset = offsetNumber; + offsets[offsetNumber] = segments[SEG_RAW].length(); + argumentNumbers[offsetNumber] = argumentNumber; + + // now get the format + Format newFormat = null; + if (segments[SEG_TYPE].length() != 0) { + int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS); + switch (type) { + case TYPE_NULL: + // Type "" is allowed. e.g., "{0,}", "{0,,}", and "{0,,#}" + // are treated as "{0}". + break; + + case TYPE_NUMBER: + switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) { + case MODIFIER_DEFAULT: + newFormat = NumberFormat.getInstance(locale); + break; + case MODIFIER_CURRENCY: + newFormat = NumberFormat.getCurrencyInstance(locale); + break; + case MODIFIER_PERCENT: + newFormat = NumberFormat.getPercentInstance(locale); + break; + case MODIFIER_INTEGER: + newFormat = NumberFormat.getIntegerInstance(locale); + break; + default: // DecimalFormat pattern + try { + newFormat = new DecimalFormat(segments[SEG_MODIFIER], + DecimalFormatSymbols.getInstance(locale)); + } catch (IllegalArgumentException e) { + maxOffset = oldMaxOffset; + throw e; + } + break; + } + break; + + case TYPE_DATE: + case TYPE_TIME: + int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS); + if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) { + if (type == TYPE_DATE) { + newFormat = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[mod], + locale); + } else { + newFormat = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[mod], + locale); + } + } else { + // SimpleDateFormat pattern + try { + newFormat = new SimpleDateFormat(segments[SEG_MODIFIER], locale); + } catch (IllegalArgumentException e) { + maxOffset = oldMaxOffset; + throw e; + } + } + break; + + case TYPE_CHOICE: + try { + // ChoiceFormat pattern + newFormat = new ChoiceFormat(segments[SEG_MODIFIER]); + } catch (Exception e) { + maxOffset = oldMaxOffset; + throw new IllegalArgumentException("Choice Pattern incorrect: " + + segments[SEG_MODIFIER], e); + } + break; + + default: + maxOffset = oldMaxOffset; + throw new IllegalArgumentException("unknown format type: " + + segments[SEG_TYPE]); + } + } + formats[offsetNumber] = newFormat; + } + + private static final int findKeyword(String s, String[] list) { + for (int i = 0; i < list.length; ++i) { + if (s.equals(list[i])) + return i; + } + + // Try trimmed lowercase. + String ls = s.trim().toLowerCase(Locale.ROOT); + if (ls != s) { + for (int i = 0; i < list.length; ++i) { + if (ls.equals(list[i])) + return i; + } + } + return -1; + } + + private static final void copyAndFixQuotes(String source, int start, int end, + StringBuilder target) { + boolean quoted = false; + + for (int i = start; i < end; ++i) { + char ch = source.charAt(i); + if (ch == '{') { + if (!quoted) { + target.append('\''); + quoted = true; + } + target.append(ch); + } else if (ch == '\'') { + target.append("''"); + } else { + if (quoted) { + target.append('\''); + quoted = false; + } + target.append(ch); + } + } + if (quoted) { + target.append('\''); + } + } + + /** + * After reading an object from the input stream, do a simple verification + * to maintain class invariants. + * @throws InvalidObjectException if the objects read from the stream is invalid. + */ + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + boolean isValid = maxOffset >= -1 + && formats.length > maxOffset + && offsets.length > maxOffset + && argumentNumbers.length > maxOffset; + if (isValid) { + int lastOffset = pattern.length() + 1; + for (int i = maxOffset; i >= 0; --i) { + if ((offsets[i] < 0) || (offsets[i] > lastOffset)) { + isValid = false; + break; + } else { + lastOffset = offsets[i]; + } + } + } + if (!isValid) { + throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream."); + } + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/NumberFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/NumberFormat.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1159 @@ +/* + * Copyright (c) 1996, 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.InvalidObjectException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.Currency; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * NumberFormat is the abstract base class for all number + * formats. This class provides the interface for formatting and parsing + * numbers. NumberFormat also provides methods for determining + * which locales have number formats, and what their names are. + * + *

+ * NumberFormat helps you to format and parse numbers for any locale. + * Your code can be completely independent of the locale conventions for + * decimal points, thousands-separators, or even the particular decimal + * digits used, or whether the number format is even decimal. + * + *

+ * To format a number for the current Locale, use one of the factory + * class methods: + *

+ *
+ *  myString = NumberFormat.getInstance().format(myNumber);
+ * 
+ *
+ * If you are formatting multiple numbers, it is + * more efficient to get the format and use it multiple times so that + * the system doesn't have to fetch the information about the local + * language and country conventions multiple times. + *
+ *
+ * NumberFormat nf = NumberFormat.getInstance();
+ * for (int i = 0; i < myNumber.length; ++i) {
+ *     output.println(nf.format(myNumber[i]) + "; ");
+ * }
+ * 
+ *
+ * To format a number for a different Locale, specify it in the + * call to getInstance. + *
+ *
+ * NumberFormat nf = NumberFormat.getInstance(Locale.FRENCH);
+ * 
+ *
+ * You can also use a NumberFormat to parse numbers: + *
+ *
+ * myNumber = nf.parse(myString);
+ * 
+ *
+ * Use getInstance or getNumberInstance to get the + * normal number format. Use getIntegerInstance to get an + * integer number format. Use getCurrencyInstance to get the + * currency number format. And use getPercentInstance to get a + * format for displaying percentages. With this format, a fraction like + * 0.53 is displayed as 53%. + * + *

+ * You can also control the display of numbers with such methods as + * setMinimumFractionDigits. + * If you want even more control over the format or parsing, + * or want to give your users more control, + * you can try casting the NumberFormat you get from the factory methods + * to a DecimalFormat. This will work for the vast majority + * of locales; just remember to put it in a try block in case you + * encounter an unusual one. + * + *

+ * NumberFormat and DecimalFormat are designed such that some controls + * work for formatting and others work for parsing. The following is + * the detailed description for each these control methods, + *

+ * setParseIntegerOnly : only affects parsing, e.g. + * if true, "3456.78" -> 3456 (and leaves the parse position just after index 6) + * if false, "3456.78" -> 3456.78 (and leaves the parse position just after index 8) + * This is independent of formatting. If you want to not show a decimal point + * where there might be no digits after the decimal point, use + * setDecimalSeparatorAlwaysShown. + *

+ * setDecimalSeparatorAlwaysShown : only affects formatting, and only where + * there might be no digits after the decimal point, such as with a pattern + * like "#,##0.##", e.g., + * if true, 3456.00 -> "3,456." + * if false, 3456.00 -> "3456" + * This is independent of parsing. If you want parsing to stop at the decimal + * point, use setParseIntegerOnly. + * + *

+ * You can also use forms of the parse and format + * methods with ParsePosition and FieldPosition to + * allow you to: + *

    + *
  • progressively parse through pieces of a string + *
  • align the decimal point and other areas + *
+ * For example, you can align numbers in two ways: + *
    + *
  1. If you are using a monospaced font with spacing for alignment, + * you can pass the FieldPosition in your format call, with + * field = INTEGER_FIELD. On output, + * getEndIndex will be set to the offset between the + * last character of the integer and the decimal. Add + * (desiredSpaceCount - getEndIndex) spaces at the front of the string. + * + *
  2. If you are using proportional fonts, + * instead of padding with spaces, measure the width + * of the string in pixels from the start to getEndIndex. + * Then move the pen by + * (desiredPixelWidth - widthToAlignmentPoint) before drawing the text. + * It also works where there is no decimal, but possibly additional + * characters at the end, e.g., with parentheses in negative + * numbers: "(12)" for -12. + *
+ * + *

Synchronization

+ * + *

+ * Number formats are generally not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + * @see DecimalFormat + * @see ChoiceFormat + * @author Mark Davis + * @author Helena Shih + */ +public abstract class NumberFormat extends Format { + + /** + * Field constant used to construct a FieldPosition object. Signifies that + * the position of the integer part of a formatted number should be returned. + * @see java.text.FieldPosition + */ + public static final int INTEGER_FIELD = 0; + + /** + * Field constant used to construct a FieldPosition object. Signifies that + * the position of the fraction part of a formatted number should be returned. + * @see java.text.FieldPosition + */ + public static final int FRACTION_FIELD = 1; + + /** + * Sole constructor. (For invocation by subclass constructors, typically + * implicit.) + */ + protected NumberFormat() { + } + + /** + * Formats a number and appends the resulting text to the given string + * buffer. + * The number can be of any subclass of {@link java.lang.Number}. + *

+ * This implementation extracts the number's value using + * {@link java.lang.Number#longValue()} for all integral type values that + * can be converted to long without loss of information, + * including BigInteger values with a + * {@link java.math.BigInteger#bitLength() bit length} of less than 64, + * and {@link java.lang.Number#doubleValue()} for all other types. It + * then calls + * {@link #format(long,java.lang.StringBuffer,java.text.FieldPosition)} + * or {@link #format(double,java.lang.StringBuffer,java.text.FieldPosition)}. + * This may result in loss of magnitude information and precision for + * BigInteger and BigDecimal values. + * @param number the number to format + * @param toAppendTo the StringBuffer to which the formatted + * text is to be appended + * @param pos On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @return the value passed in as toAppendTo + * @exception IllegalArgumentException if number is + * null or not an instance of Number. + * @exception NullPointerException if toAppendTo or + * pos is null + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + public StringBuffer format(Object number, + StringBuffer toAppendTo, + FieldPosition pos) { + if (number instanceof Long || number instanceof Integer || + number instanceof Short || number instanceof Byte || + number instanceof AtomicInteger || number instanceof AtomicLong || + (number instanceof BigInteger && + ((BigInteger)number).bitLength() < 64)) { + return format(((Number)number).longValue(), toAppendTo, pos); + } else if (number instanceof Number) { + return format(((Number)number).doubleValue(), toAppendTo, pos); + } else { + throw new IllegalArgumentException("Cannot format given Object as a Number"); + } + } + + /** + * Parses text from a string to produce a Number. + *

+ * The method attempts to parse text starting at the index given by + * pos. + * If parsing succeeds, then the index of pos is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * number is returned. The updated pos can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of pos is not + * changed, the error index of pos is set to the index of + * the character where the error occurred, and null is returned. + *

+ * See the {@link #parse(String, ParsePosition)} method for more information + * on number parsing. + * + * @param source A String, part of which should be parsed. + * @param pos A ParsePosition object with index and error + * index information as described above. + * @return A Number parsed from the string. In case of + * error, returns null. + * @exception NullPointerException if pos is null. + */ + public final Object parseObject(String source, ParsePosition pos) { + return parse(source, pos); + } + + /** + * Specialization of format. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.Format#format + */ + public final String format(double number) { + return format(number, new StringBuffer(), + DontCareFieldPosition.INSTANCE).toString(); + } + + /** + * Specialization of format. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.Format#format + */ + public final String format(long number) { + return format(number, new StringBuffer(), + DontCareFieldPosition.INSTANCE).toString(); + } + + /** + * Specialization of format. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.Format#format + */ + public abstract StringBuffer format(double number, + StringBuffer toAppendTo, + FieldPosition pos); + + /** + * Specialization of format. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.Format#format + */ + public abstract StringBuffer format(long number, + StringBuffer toAppendTo, + FieldPosition pos); + + /** + * Returns a Long if possible (e.g., within the range [Long.MIN_VALUE, + * Long.MAX_VALUE] and with no decimals), otherwise a Double. + * If IntegerOnly is set, will stop at a decimal + * point (or equivalent; e.g., for rational numbers "1 2/3", will stop + * after the 1). + * Does not throw an exception; if no object can be parsed, index is + * unchanged! + * @see java.text.NumberFormat#isParseIntegerOnly + * @see java.text.Format#parseObject + */ + public abstract Number parse(String source, ParsePosition parsePosition); + + /** + * Parses text from the beginning of the given string to produce a number. + * The method may not use the entire text of the given string. + *

+ * See the {@link #parse(String, ParsePosition)} method for more information + * on number parsing. + * + * @param source A String whose beginning should be parsed. + * @return A Number parsed from the string. + * @exception ParseException if the beginning of the specified string + * cannot be parsed. + */ + public Number parse(String source) throws ParseException { + ParsePosition parsePosition = new ParsePosition(0); + Number result = parse(source, parsePosition); + if (parsePosition.index == 0) { + throw new ParseException("Unparseable number: \"" + source + "\"", + parsePosition.errorIndex); + } + return result; + } + + /** + * Returns true if this format will parse numbers as integers only. + * For example in the English locale, with ParseIntegerOnly true, the + * string "1234." would be parsed as the integer value 1234 and parsing + * would stop at the "." character. Of course, the exact format accepted + * by the parse operation is locale dependant and determined by sub-classes + * of NumberFormat. + */ + public boolean isParseIntegerOnly() { + return parseIntegerOnly; + } + + /** + * Sets whether or not numbers should be parsed as integers only. + * @see #isParseIntegerOnly + */ + public void setParseIntegerOnly(boolean value) { + parseIntegerOnly = value; + } + + //============== Locale Stuff ===================== + + /** + * Returns a general-purpose number format for the current default locale. + * This is the same as calling + * {@link #getNumberInstance() getNumberInstance()}. + */ + public final static NumberFormat getInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE); + } + + /** + * Returns a general-purpose number format for the specified locale. + * This is the same as calling + * {@link #getNumberInstance(java.util.Locale) getNumberInstance(inLocale)}. + */ + public static NumberFormat getInstance(Locale inLocale) { + return getInstance(inLocale, NUMBERSTYLE); + } + + /** * Returns a general-purpose number format for the current default locale. + */ + public final static NumberFormat getNumberInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE); + } + + /** + * Returns a general-purpose number format for the specified locale. + */ + public static NumberFormat getNumberInstance(Locale inLocale) { + return getInstance(inLocale, NUMBERSTYLE); + } + + /** + * Returns an integer number format for the current default locale. The + * returned number format is configured to round floating point numbers + * to the nearest integer using half-even rounding (see {@link + * java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}) for formatting, + * and to parse only the integer part of an input string (see {@link + * #isParseIntegerOnly isParseIntegerOnly}). + * + * @see #getRoundingMode() + * @return a number format for integer values + * @since 1.4 + */ + public final static NumberFormat getIntegerInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT), INTEGERSTYLE); + } + + /** + * Returns an integer number format for the specified locale. The + * returned number format is configured to round floating point numbers + * to the nearest integer using half-even rounding (see {@link + * java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}) for formatting, + * and to parse only the integer part of an input string (see {@link + * #isParseIntegerOnly isParseIntegerOnly}). + * + * @see #getRoundingMode() + * @return a number format for integer values + * @since 1.4 + */ + public static NumberFormat getIntegerInstance(Locale inLocale) { + return getInstance(inLocale, INTEGERSTYLE); + } + + /** + * Returns a currency format for the current default locale. + */ + public final static NumberFormat getCurrencyInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT), CURRENCYSTYLE); + } + + /** + * Returns a currency format for the specified locale. + */ + public static NumberFormat getCurrencyInstance(Locale inLocale) { + return getInstance(inLocale, CURRENCYSTYLE); + } + + /** + * Returns a percentage format for the current default locale. + */ + public final static NumberFormat getPercentInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT), PERCENTSTYLE); + } + + /** + * Returns a percentage format for the specified locale. + */ + public static NumberFormat getPercentInstance(Locale inLocale) { + return getInstance(inLocale, PERCENTSTYLE); + } + + /** + * Returns a scientific format for the current default locale. + */ + /*public*/ final static NumberFormat getScientificInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT), SCIENTIFICSTYLE); + } + + /** + * Returns a scientific format for the specified locale. + */ + /*public*/ static NumberFormat getScientificInstance(Locale inLocale) { + return getInstance(inLocale, SCIENTIFICSTYLE); + } + + /** + * Returns an array of all locales for which the + * get*Instance methods of this class can return + * localized instances. + * The returned array represents the union of locales supported by the Java + * runtime and by installed + * {@link java.text.spi.NumberFormatProvider NumberFormatProvider} implementations. + * It must contain at least a Locale instance equal to + * {@link java.util.Locale#US Locale.US}. + * + * @return An array of locales for which localized + * NumberFormat instances are available. + */ + public static Locale[] getAvailableLocales() { + return new Locale[] { Locale.US }; +// LocaleServiceProviderPool pool = +// LocaleServiceProviderPool.getPool(NumberFormatProvider.class); +// return pool.getAvailableLocales(); + } + + /** + * Overrides hashCode + */ + public int hashCode() { + return maximumIntegerDigits * 37 + maxFractionDigits; + // just enough fields for a reasonable distribution + } + + /** + * Overrides equals + */ + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (getClass() != obj.getClass()) { + return false; + } + NumberFormat other = (NumberFormat) obj; + return (maximumIntegerDigits == other.maximumIntegerDigits + && minimumIntegerDigits == other.minimumIntegerDigits + && maximumFractionDigits == other.maximumFractionDigits + && minimumFractionDigits == other.minimumFractionDigits + && groupingUsed == other.groupingUsed + && parseIntegerOnly == other.parseIntegerOnly); + } + + /** + * Overrides Cloneable + */ + public Object clone() { + NumberFormat other = (NumberFormat) super.clone(); + return other; + } + + /** + * Returns true if grouping is used in this format. For example, in the + * English locale, with grouping on, the number 1234567 might be formatted + * as "1,234,567". The grouping separator as well as the size of each group + * is locale dependant and is determined by sub-classes of NumberFormat. + * @see #setGroupingUsed + */ + public boolean isGroupingUsed() { + return groupingUsed; + } + + /** + * Set whether or not grouping will be used in this format. + * @see #isGroupingUsed + */ + public void setGroupingUsed(boolean newValue) { + groupingUsed = newValue; + } + + /** + * Returns the maximum number of digits allowed in the integer portion of a + * number. + * @see #setMaximumIntegerDigits + */ + public int getMaximumIntegerDigits() { + return maximumIntegerDigits; + } + + /** + * Sets the maximum number of digits allowed in the integer portion of a + * number. maximumIntegerDigits must be >= minimumIntegerDigits. If the + * new value for maximumIntegerDigits is less than the current value + * of minimumIntegerDigits, then minimumIntegerDigits will also be set to + * the new value. + * @param newValue the maximum number of integer digits to be shown; if + * less than zero, then zero is used. The concrete subclass may enforce an + * upper limit to this value appropriate to the numeric type being formatted. + * @see #getMaximumIntegerDigits + */ + public void setMaximumIntegerDigits(int newValue) { + maximumIntegerDigits = Math.max(0,newValue); + if (minimumIntegerDigits > maximumIntegerDigits) { + minimumIntegerDigits = maximumIntegerDigits; + } + } + + /** + * Returns the minimum number of digits allowed in the integer portion of a + * number. + * @see #setMinimumIntegerDigits + */ + public int getMinimumIntegerDigits() { + return minimumIntegerDigits; + } + + /** + * Sets the minimum number of digits allowed in the integer portion of a + * number. minimumIntegerDigits must be <= maximumIntegerDigits. If the + * new value for minimumIntegerDigits exceeds the current value + * of maximumIntegerDigits, then maximumIntegerDigits will also be set to + * the new value + * @param newValue the minimum number of integer digits to be shown; if + * less than zero, then zero is used. The concrete subclass may enforce an + * upper limit to this value appropriate to the numeric type being formatted. + * @see #getMinimumIntegerDigits + */ + public void setMinimumIntegerDigits(int newValue) { + minimumIntegerDigits = Math.max(0,newValue); + if (minimumIntegerDigits > maximumIntegerDigits) { + maximumIntegerDigits = minimumIntegerDigits; + } + } + + /** + * Returns the maximum number of digits allowed in the fraction portion of a + * number. + * @see #setMaximumFractionDigits + */ + public int getMaximumFractionDigits() { + return maximumFractionDigits; + } + + /** + * Sets the maximum number of digits allowed in the fraction portion of a + * number. maximumFractionDigits must be >= minimumFractionDigits. If the + * new value for maximumFractionDigits is less than the current value + * of minimumFractionDigits, then minimumFractionDigits will also be set to + * the new value. + * @param newValue the maximum number of fraction digits to be shown; if + * less than zero, then zero is used. The concrete subclass may enforce an + * upper limit to this value appropriate to the numeric type being formatted. + * @see #getMaximumFractionDigits + */ + public void setMaximumFractionDigits(int newValue) { + maximumFractionDigits = Math.max(0,newValue); + if (maximumFractionDigits < minimumFractionDigits) { + minimumFractionDigits = maximumFractionDigits; + } + } + + /** + * Returns the minimum number of digits allowed in the fraction portion of a + * number. + * @see #setMinimumFractionDigits + */ + public int getMinimumFractionDigits() { + return minimumFractionDigits; + } + + /** + * Sets the minimum number of digits allowed in the fraction portion of a + * number. minimumFractionDigits must be <= maximumFractionDigits. If the + * new value for minimumFractionDigits exceeds the current value + * of maximumFractionDigits, then maximumIntegerDigits will also be set to + * the new value + * @param newValue the minimum number of fraction digits to be shown; if + * less than zero, then zero is used. The concrete subclass may enforce an + * upper limit to this value appropriate to the numeric type being formatted. + * @see #getMinimumFractionDigits + */ + public void setMinimumFractionDigits(int newValue) { + minimumFractionDigits = Math.max(0,newValue); + if (maximumFractionDigits < minimumFractionDigits) { + maximumFractionDigits = minimumFractionDigits; + } + } + + /** + * Gets the currency used by this number format when formatting + * currency values. The initial value is derived in a locale dependent + * way. The returned value may be null if no valid + * currency could be determined and no currency has been set using + * {@link #setCurrency(java.util.Currency) setCurrency}. + *

+ * The default implementation throws + * UnsupportedOperationException. + * + * @return the currency used by this number format, or null + * @exception UnsupportedOperationException if the number format class + * doesn't implement currency formatting + * @since 1.4 + */ + public Currency getCurrency() { + throw new UnsupportedOperationException(); + } + + /** + * Sets the currency used by this number format when formatting + * currency values. This does not update the minimum or maximum + * number of fraction digits used by the number format. + *

+ * The default implementation throws + * UnsupportedOperationException. + * + * @param currency the new currency to be used by this number format + * @exception UnsupportedOperationException if the number format class + * doesn't implement currency formatting + * @exception NullPointerException if currency is null + * @since 1.4 + */ + public void setCurrency(Currency currency) { + throw new UnsupportedOperationException(); + } + + /** + * Gets the {@link java.math.RoundingMode} used in this NumberFormat. + * The default implementation of this method in NumberFormat + * always throws {@link java.lang.UnsupportedOperationException}. + * Subclasses which handle different rounding modes should override + * this method. + * + * @exception UnsupportedOperationException The default implementation + * always throws this exception + * @return The RoundingMode used for this NumberFormat. + * @see #setRoundingMode(RoundingMode) + * @since 1.6 + */ + public RoundingMode getRoundingMode() { + throw new UnsupportedOperationException(); + } + + /** + * Sets the {@link java.math.RoundingMode} used in this NumberFormat. + * The default implementation of this method in NumberFormat always + * throws {@link java.lang.UnsupportedOperationException}. + * Subclasses which handle different rounding modes should override + * this method. + * + * @exception UnsupportedOperationException The default implementation + * always throws this exception + * @exception NullPointerException if roundingMode is null + * @param roundingMode The RoundingMode to be used + * @see #getRoundingMode() + * @since 1.6 + */ + public void setRoundingMode(RoundingMode roundingMode) { + throw new UnsupportedOperationException(); + } + + // =======================privates=============================== + + private static NumberFormat getInstance(Locale desiredLocale, + int choice) { + // Check whether a provider can provide an implementation that's closer + // to the requested locale than what the Java runtime itself can provide. +// LocaleServiceProviderPool pool = +// LocaleServiceProviderPool.getPool(NumberFormatProvider.class); +// if (pool.hasProviders()) { +// NumberFormat providersInstance = pool.getLocalizedObject( +// NumberFormatGetter.INSTANCE, +// desiredLocale, +// choice); +// if (providersInstance != null) { +// return providersInstance; +// } +// } + + /* try the cache first */ + String[] numberPatterns = (String[])cachedLocaleData.get(desiredLocale); +// if (numberPatterns == null) { /* cache miss */ +// ResourceBundle resource = LocaleData.getNumberFormatData(desiredLocale); +// numberPatterns = resource.getStringArray("NumberPatterns"); +// /* update cache */ +// cachedLocaleData.put(desiredLocale, numberPatterns); +// } + + DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(desiredLocale); + int entry = (choice == INTEGERSTYLE) ? NUMBERSTYLE : choice; + DecimalFormat format = new DecimalFormat(numberPatterns[entry], symbols); + + if (choice == INTEGERSTYLE) { + format.setMaximumFractionDigits(0); + format.setDecimalSeparatorAlwaysShown(false); + format.setParseIntegerOnly(true); + } else if (choice == CURRENCYSTYLE) { + format.adjustForCurrencyDefaultFractionDigits(); + } + + return format; + } + + /** + * First, read in the default serializable data. + * + * Then, if serialVersionOnStream is less than 1, indicating that + * the stream was written by JDK 1.1, + * set the int fields such as maximumIntegerDigits + * to be equal to the byte fields such as maxIntegerDigits, + * since the int fields were not present in JDK 1.1. + * Finally, set serialVersionOnStream back to the maximum allowed value so that + * default serialization will work properly if this object is streamed out again. + * + *

If minimumIntegerDigits is greater than + * maximumIntegerDigits or minimumFractionDigits + * is greater than maximumFractionDigits, then the stream data + * is invalid and this method throws an InvalidObjectException. + * In addition, if any of these values is negative, then this method throws + * an InvalidObjectException. + * + * @since 1.2 + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + if (serialVersionOnStream < 1) { + // Didn't have additional int fields, reassign to use them. + maximumIntegerDigits = maxIntegerDigits; + minimumIntegerDigits = minIntegerDigits; + maximumFractionDigits = maxFractionDigits; + minimumFractionDigits = minFractionDigits; + } + if (minimumIntegerDigits > maximumIntegerDigits || + minimumFractionDigits > maximumFractionDigits || + minimumIntegerDigits < 0 || minimumFractionDigits < 0) { + throw new InvalidObjectException("Digit count range invalid"); + } + serialVersionOnStream = currentSerialVersion; + } + + /** + * Write out the default serializable data, after first setting + * the byte fields such as maxIntegerDigits to be + * equal to the int fields such as maximumIntegerDigits + * (or to Byte.MAX_VALUE, whichever is smaller), for compatibility + * with the JDK 1.1 version of the stream format. + * + * @since 1.2 + */ + private void writeObject(ObjectOutputStream stream) + throws IOException + { + maxIntegerDigits = (maximumIntegerDigits > Byte.MAX_VALUE) ? + Byte.MAX_VALUE : (byte)maximumIntegerDigits; + minIntegerDigits = (minimumIntegerDigits > Byte.MAX_VALUE) ? + Byte.MAX_VALUE : (byte)minimumIntegerDigits; + maxFractionDigits = (maximumFractionDigits > Byte.MAX_VALUE) ? + Byte.MAX_VALUE : (byte)maximumFractionDigits; + minFractionDigits = (minimumFractionDigits > Byte.MAX_VALUE) ? + Byte.MAX_VALUE : (byte)minimumFractionDigits; + stream.defaultWriteObject(); + } + + /** + * Cache to hold the NumberPatterns of a Locale. + */ + private static final Hashtable cachedLocaleData = new Hashtable(3); + + // Constants used by factory methods to specify a style of format. + private static final int NUMBERSTYLE = 0; + private static final int CURRENCYSTYLE = 1; + private static final int PERCENTSTYLE = 2; + private static final int SCIENTIFICSTYLE = 3; + private static final int INTEGERSTYLE = 4; + + /** + * True if the grouping (i.e. thousands) separator is used when + * formatting and parsing numbers. + * + * @serial + * @see #isGroupingUsed + */ + private boolean groupingUsed = true; + + /** + * The maximum number of digits allowed in the integer portion of a + * number. maxIntegerDigits must be greater than or equal to + * minIntegerDigits. + *

+ * Note: This field exists only for serialization + * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new + * int field maximumIntegerDigits is used instead. + * When writing to a stream, maxIntegerDigits is set to + * maximumIntegerDigits or Byte.MAX_VALUE, + * whichever is smaller. When reading from a stream, this field is used + * only if serialVersionOnStream is less than 1. + * + * @serial + * @see #getMaximumIntegerDigits + */ + private byte maxIntegerDigits = 40; + + /** + * The minimum number of digits allowed in the integer portion of a + * number. minimumIntegerDigits must be less than or equal to + * maximumIntegerDigits. + *

+ * Note: This field exists only for serialization + * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new + * int field minimumIntegerDigits is used instead. + * When writing to a stream, minIntegerDigits is set to + * minimumIntegerDigits or Byte.MAX_VALUE, + * whichever is smaller. When reading from a stream, this field is used + * only if serialVersionOnStream is less than 1. + * + * @serial + * @see #getMinimumIntegerDigits + */ + private byte minIntegerDigits = 1; + + /** + * The maximum number of digits allowed in the fractional portion of a + * number. maximumFractionDigits must be greater than or equal to + * minimumFractionDigits. + *

+ * Note: This field exists only for serialization + * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new + * int field maximumFractionDigits is used instead. + * When writing to a stream, maxFractionDigits is set to + * maximumFractionDigits or Byte.MAX_VALUE, + * whichever is smaller. When reading from a stream, this field is used + * only if serialVersionOnStream is less than 1. + * + * @serial + * @see #getMaximumFractionDigits + */ + private byte maxFractionDigits = 3; // invariant, >= minFractionDigits + + /** + * The minimum number of digits allowed in the fractional portion of a + * number. minimumFractionDigits must be less than or equal to + * maximumFractionDigits. + *

+ * Note: This field exists only for serialization + * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new + * int field minimumFractionDigits is used instead. + * When writing to a stream, minFractionDigits is set to + * minimumFractionDigits or Byte.MAX_VALUE, + * whichever is smaller. When reading from a stream, this field is used + * only if serialVersionOnStream is less than 1. + * + * @serial + * @see #getMinimumFractionDigits + */ + private byte minFractionDigits = 0; + + /** + * True if this format will parse numbers as integers only. + * + * @serial + * @see #isParseIntegerOnly + */ + private boolean parseIntegerOnly = false; + + // new fields for 1.2. byte is too small for integer digits. + + /** + * The maximum number of digits allowed in the integer portion of a + * number. maximumIntegerDigits must be greater than or equal to + * minimumIntegerDigits. + * + * @serial + * @since 1.2 + * @see #getMaximumIntegerDigits + */ + private int maximumIntegerDigits = 40; + + /** + * The minimum number of digits allowed in the integer portion of a + * number. minimumIntegerDigits must be less than or equal to + * maximumIntegerDigits. + * + * @serial + * @since 1.2 + * @see #getMinimumIntegerDigits + */ + private int minimumIntegerDigits = 1; + + /** + * The maximum number of digits allowed in the fractional portion of a + * number. maximumFractionDigits must be greater than or equal to + * minimumFractionDigits. + * + * @serial + * @since 1.2 + * @see #getMaximumFractionDigits + */ + private int maximumFractionDigits = 3; // invariant, >= minFractionDigits + + /** + * The minimum number of digits allowed in the fractional portion of a + * number. minimumFractionDigits must be less than or equal to + * maximumFractionDigits. + * + * @serial + * @since 1.2 + * @see #getMinimumFractionDigits + */ + private int minimumFractionDigits = 0; + + static final int currentSerialVersion = 1; + + /** + * Describes the version of NumberFormat present on the stream. + * Possible values are: + *

    + *
  • 0 (or uninitialized): the JDK 1.1 version of the stream format. + * In this version, the int fields such as + * maximumIntegerDigits were not present, and the byte + * fields such as maxIntegerDigits are used instead. + * + *
  • 1: the 1.2 version of the stream format. The values of the + * byte fields such as maxIntegerDigits are ignored, + * and the int fields such as maximumIntegerDigits + * are used instead. + *
+ * When streaming out a NumberFormat, the most recent format + * (corresponding to the highest allowable serialVersionOnStream) + * is always written. + * + * @serial + * @since 1.2 + */ + private int serialVersionOnStream = currentSerialVersion; + + // Removed "implements Cloneable" clause. Needs to update serialization + // ID for backward compatibility. + static final long serialVersionUID = -2308460125733713944L; + + + // + // class for AttributedCharacterIterator attributes + // + /** + * Defines constants that are used as attribute keys in the + * AttributedCharacterIterator returned + * from NumberFormat.formatToCharacterIterator and as + * field identifiers in FieldPosition. + * + * @since 1.4 + */ + public static class Field extends Format.Field { + + // Proclaim serial compatibility with 1.4 FCS + private static final long serialVersionUID = 7494728892700160890L; + + // table of all instances in this class, used by readResolve + private static final Map instanceMap = new HashMap(11); + + /** + * Creates a Field instance with the specified + * name. + * + * @param name Name of the attribute + */ + protected Field(String name) { + super(name); + if (this.getClass() == NumberFormat.Field.class) { + instanceMap.put(name, this); + } + } + + /** + * Resolves instances being deserialized to the predefined constants. + * + * @throws InvalidObjectException if the constant could not be resolved. + * @return resolved NumberFormat.Field constant + */ + protected Object readResolve() throws InvalidObjectException { + if (this.getClass() != NumberFormat.Field.class) { + throw new InvalidObjectException("subclass didn't correctly implement readResolve"); + } + + Object instance = instanceMap.get(getName()); + if (instance != null) { + return instance; + } else { + throw new InvalidObjectException("unknown attribute name"); + } + } + + /** + * Constant identifying the integer field. + */ + public static final Field INTEGER = new Field("integer"); + + /** + * Constant identifying the fraction field. + */ + public static final Field FRACTION = new Field("fraction"); + + /** + * Constant identifying the exponent field. + */ + public static final Field EXPONENT = new Field("exponent"); + + /** + * Constant identifying the decimal separator field. + */ + public static final Field DECIMAL_SEPARATOR = + new Field("decimal separator"); + + /** + * Constant identifying the sign field. + */ + public static final Field SIGN = new Field("sign"); + + /** + * Constant identifying the grouping separator field. + */ + public static final Field GROUPING_SEPARATOR = + new Field("grouping separator"); + + /** + * Constant identifying the exponent symbol field. + */ + public static final Field EXPONENT_SYMBOL = new + Field("exponent symbol"); + + /** + * Constant identifying the percent field. + */ + public static final Field PERCENT = new Field("percent"); + + /** + * Constant identifying the permille field. + */ + public static final Field PERMILLE = new Field("per mille"); + + /** + * Constant identifying the currency field. + */ + public static final Field CURRENCY = new Field("currency"); + + /** + * Constant identifying the exponent sign field. + */ + public static final Field EXPONENT_SIGN = new Field("exponent sign"); + } + + /** + * Obtains a NumberFormat instance from a NumberFormatProvider implementation. + * + private static class NumberFormatGetter + implements LocaleServiceProviderPool.LocalizedObjectGetter { + private static final NumberFormatGetter INSTANCE = new NumberFormatGetter(); + + public NumberFormat getObject(NumberFormatProvider numberFormatProvider, + Locale locale, + String key, + Object... params) { + assert params.length == 1; + int choice = (Integer)params[0]; + + switch (choice) { + case NUMBERSTYLE: + return numberFormatProvider.getNumberInstance(locale); + case PERCENTSTYLE: + return numberFormatProvider.getPercentInstance(locale); + case CURRENCYSTYLE: + return numberFormatProvider.getCurrencyInstance(locale); + case INTEGERSTYLE: + return numberFormatProvider.getIntegerInstance(locale); + default: + assert false : choice; + } + + return null; + } + } + */ +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/ParseException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/ParseException.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,78 @@ +/* + * Copyright (c) 1996, 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +/** + * Signals that an error has been reached unexpectedly + * while parsing. + * @see java.lang.Exception + * @see java.text.Format + * @see java.text.FieldPosition + * @author Mark Davis + */ +public +class ParseException extends Exception { + + /** + * Constructs a ParseException with the specified detail message and + * offset. + * A detail message is a String that describes this particular exception. + * @param s the detail message + * @param errorOffset the position where the error is found while parsing. + */ + public ParseException(String s, int errorOffset) { + super(s); + this.errorOffset = errorOffset; + } + + /** + * Returns the position where the error was found. + */ + public int getErrorOffset () { + return errorOffset; + } + + //============ privates ============ + /** + * The zero-based character offset into the string being parsed at which + * the error was found during parsing. + * @serial + */ + private int errorOffset; +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/ParsePosition.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/ParsePosition.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,139 @@ +/* + * Copyright (c) 1996, 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + + +/** + * ParsePosition is a simple class used by Format + * and its subclasses to keep track of the current position during parsing. + * The parseObject method in the various Format + * classes requires a ParsePosition object as an argument. + * + *

+ * By design, as you parse through a string with different formats, + * you can use the same ParsePosition, since the index parameter + * records the current position. + * + * @author Mark Davis + * @see java.text.Format + */ + +public class ParsePosition { + + /** + * Input: the place you start parsing. + *
Output: position where the parse stopped. + * This is designed to be used serially, + * with each call setting index up for the next one. + */ + int index = 0; + int errorIndex = -1; + + /** + * Retrieve the current parse position. On input to a parse method, this + * is the index of the character at which parsing will begin; on output, it + * is the index of the character following the last character parsed. + */ + public int getIndex() { + return index; + } + + /** + * Set the current parse position. + */ + public void setIndex(int index) { + this.index = index; + } + + /** + * Create a new ParsePosition with the given initial index. + */ + public ParsePosition(int index) { + this.index = index; + } + /** + * Set the index at which a parse error occurred. Formatters + * should set this before returning an error code from their + * parseObject method. The default value is -1 if this is not set. + * @since 1.2 + */ + public void setErrorIndex(int ei) + { + errorIndex = ei; + } + + /** + * Retrieve the index at which an error occurred, or -1 if the + * error index has not been set. + * @since 1.2 + */ + public int getErrorIndex() + { + return errorIndex; + } + /** + * Overrides equals + */ + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!(obj instanceof ParsePosition)) + return false; + ParsePosition other = (ParsePosition) obj; + return (index == other.index && errorIndex == other.errorIndex); + } + + /** + * Returns a hash code for this ParsePosition. + * @return a hash code value for this object + */ + public int hashCode() { + return (errorIndex << 16) | index; + } + + /** + * Return a string representation of this ParsePosition. + * @return a string representation of this object + */ + public String toString() { + return getClass().getName() + + "[index=" + index + + ",errorIndex=" + errorIndex + ']'; + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/text/SimpleDateFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/SimpleDateFormat.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,2383 @@ +/* + * 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved + * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.SimpleTimeZone; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static java.text.DateFormatSymbols.*; + +/** + * SimpleDateFormat is a concrete class for formatting and + * parsing dates in a locale-sensitive manner. It allows for formatting + * (date -> text), parsing (text -> date), and normalization. + * + *

+ * SimpleDateFormat allows you to start by choosing + * any user-defined patterns for date-time formatting. However, you + * are encouraged to create a date-time formatter with either + * getTimeInstance, getDateInstance, or + * getDateTimeInstance in DateFormat. Each + * of these class methods can return a date/time formatter initialized + * with a default format pattern. You may modify the format pattern + * using the applyPattern methods as desired. + * For more information on using these methods, see + * {@link DateFormat}. + * + *

Date and Time Patterns

+ *

+ * Date and time formats are specified by date and time pattern + * strings. + * Within date and time pattern strings, unquoted letters from + * 'A' to 'Z' and from 'a' to + * 'z' are interpreted as pattern letters representing the + * components of a date or time string. + * Text can be quoted using single quotes (') to avoid + * interpretation. + * "''" represents a single quote. + * All other characters are not interpreted; they're simply copied into the + * output string during formatting or matched against the input string + * during parsing. + *

+ * The following pattern letters are defined (all other characters from + * 'A' to 'Z' and from 'a' to + * 'z' are reserved): + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Letter + * Date or Time Component + * Presentation + * Examples + *
G + * Era designator + * Text + * AD + *
y + * Year + * Year + * 1996; 96 + *
Y + * Week year + * Year + * 2009; 09 + *
M + * Month in year + * Month + * July; Jul; 07 + *
w + * Week in year + * Number + * 27 + *
W + * Week in month + * Number + * 2 + *
D + * Day in year + * Number + * 189 + *
d + * Day in month + * Number + * 10 + *
F + * Day of week in month + * Number + * 2 + *
E + * Day name in week + * Text + * Tuesday; Tue + *
u + * Day number of week (1 = Monday, ..., 7 = Sunday) + * Number + * 1 + *
a + * Am/pm marker + * Text + * PM + *
H + * Hour in day (0-23) + * Number + * 0 + *
k + * Hour in day (1-24) + * Number + * 24 + *
K + * Hour in am/pm (0-11) + * Number + * 0 + *
h + * Hour in am/pm (1-12) + * Number + * 12 + *
m + * Minute in hour + * Number + * 30 + *
s + * Second in minute + * Number + * 55 + *
S + * Millisecond + * Number + * 978 + *
z + * Time zone + * General time zone + * Pacific Standard Time; PST; GMT-08:00 + *
Z + * Time zone + * RFC 822 time zone + * -0800 + *
X + * Time zone + * ISO 8601 time zone + * -08; -0800; -08:00 + *
+ *
+ * Pattern letters are usually repeated, as their number determines the + * exact presentation: + *
    + *
  • Text: + * For formatting, if the number of pattern letters is 4 or more, + * the full form is used; otherwise a short or abbreviated form + * is used if available. + * For parsing, both forms are accepted, independent of the number + * of pattern letters.

  • + *
  • Number: + * For formatting, the number of pattern letters is the minimum + * number of digits, and shorter numbers are zero-padded to this amount. + * For parsing, the number of pattern letters is ignored unless + * it's needed to separate two adjacent fields.

  • + *
  • Year: + * If the formatter's {@link #getCalendar() Calendar} is the Gregorian + * calendar, the following rules are applied.
    + *
      + *
    • For formatting, if the number of pattern letters is 2, the year + * is truncated to 2 digits; otherwise it is interpreted as a + * number. + *
    • For parsing, if the number of pattern letters is more than 2, + * the year is interpreted literally, regardless of the number of + * digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to + * Jan 11, 12 A.D. + *
    • For parsing with the abbreviated year pattern ("y" or "yy"), + * SimpleDateFormat must interpret the abbreviated year + * relative to some century. It does this by adjusting dates to be + * within 80 years before and 20 years after the time the SimpleDateFormat + * instance is created. For example, using a pattern of "MM/dd/yy" and a + * SimpleDateFormat instance created on Jan 1, 1997, the string + * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64" + * would be interpreted as May 4, 1964. + * During parsing, only strings consisting of exactly two digits, as defined by + * {@link Character#isDigit(char)}, will be parsed into the default century. + * Any other numeric string, such as a one digit string, a three or more digit + * string, or a two digit string that isn't all digits (for example, "-1"), is + * interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the + * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC. + *
    + * Otherwise, calendar system specific forms are applied. + * For both formatting and parsing, if the number of pattern + * letters is 4 or more, a calendar specific {@linkplain + * Calendar#LONG long form} is used. Otherwise, a calendar + * specific {@linkplain Calendar#SHORT short or abbreviated form} + * is used.
    + *
    + * If week year {@code 'Y'} is specified and the {@linkplain + * #getCalendar() calendar} doesn't support any week + * years, the calendar year ({@code 'y'}) is used instead. The + * support of week years can be tested with a call to {@link + * DateFormat#getCalendar() getCalendar()}.{@link + * java.util.Calendar#isWeekDateSupported() + * isWeekDateSupported()}.

  • + *
  • Month: + * If the number of pattern letters is 3 or more, the month is + * interpreted as text; otherwise, + * it is interpreted as a number.

  • + *
  • General time zone: + * Time zones are interpreted as text if they have + * names. For time zones representing a GMT offset value, the + * following syntax is used: + *
    + *     GMTOffsetTimeZone:
    + *             GMT Sign Hours : Minutes
    + *     Sign: one of
    + *             + -
    + *     Hours:
    + *             Digit
    + *             Digit Digit
    + *     Minutes:
    + *             Digit Digit
    + *     Digit: one of
    + *             0 1 2 3 4 5 6 7 8 9
    + * Hours must be between 0 and 23, and Minutes must be between + * 00 and 59. The format is locale independent and digits must be taken + * from the Basic Latin block of the Unicode standard. + *

    For parsing, RFC 822 time zones are also + * accepted.

  • + *
  • RFC 822 time zone: + * For formatting, the RFC 822 4-digit time zone format is used: + * + *
    + *     RFC822TimeZone:
    + *             Sign TwoDigitHours Minutes
    + *     TwoDigitHours:
    + *             Digit Digit
    + * TwoDigitHours must be between 00 and 23. Other definitions + * are as for general time zones. + * + *

    For parsing, general time zones are also + * accepted. + *

  • ISO 8601 Time zone: + * The number of pattern letters designates the format for both formatting + * and parsing as follows: + *
    + *     ISO8601TimeZone:
    + *             OneLetterISO8601TimeZone
    + *             TwoLetterISO8601TimeZone
    + *             ThreeLetterISO8601TimeZone
    + *     OneLetterISO8601TimeZone:
    + *             Sign TwoDigitHours
    + *             {@code Z}
    + *     TwoLetterISO8601TimeZone:
    + *             Sign TwoDigitHours Minutes
    + *             {@code Z}
    + *     ThreeLetterISO8601TimeZone:
    + *             Sign TwoDigitHours {@code :} Minutes
    + *             {@code Z}
    + * Other definitions are as for general time zones or + * RFC 822 time zones. + * + *

    For formatting, if the offset value from GMT is 0, {@code "Z"} is + * produced. If the number of pattern letters is 1, any fraction of an hour + * is ignored. For example, if the pattern is {@code "X"} and the time zone is + * {@code "GMT+05:30"}, {@code "+05"} is produced. + * + *

    For parsing, {@code "Z"} is parsed as the UTC time zone designator. + * General time zones are not accepted. + * + *

    If the number of pattern letters is 4 or more, {@link + * IllegalArgumentException} is thrown when constructing a {@code + * SimpleDateFormat} or {@linkplain #applyPattern(String) applying a + * pattern}. + *

+ * SimpleDateFormat also supports localized date and time + * pattern strings. In these strings, the pattern letters described above + * may be replaced with other, locale dependent, pattern letters. + * SimpleDateFormat does not deal with the localization of text + * other than the pattern letters; that's up to the client of the class. + *

+ * + *

Examples

+ * + * The following examples show how date and time patterns are interpreted in + * the U.S. locale. The given date and time are 2001-07-04 12:08:56 local time + * in the U.S. Pacific Time time zone. + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + *
Date and Time Pattern + * Result + *
"yyyy.MM.dd G 'at' HH:mm:ss z" + * 2001.07.04 AD at 12:08:56 PDT + *
"EEE, MMM d, ''yy" + * Wed, Jul 4, '01 + *
"h:mm a" + * 12:08 PM + *
"hh 'o''clock' a, zzzz" + * 12 o'clock PM, Pacific Daylight Time + *
"K:mm a, z" + * 0:08 PM, PDT + *
"yyyyy.MMMMM.dd GGG hh:mm aaa" + * 02001.July.04 AD 12:08 PM + *
"EEE, d MMM yyyy HH:mm:ss Z" + * Wed, 4 Jul 2001 12:08:56 -0700 + *
"yyMMddHHmmssZ" + * 010704120856-0700 + *
"yyyy-MM-dd'T'HH:mm:ss.SSSZ" + * 2001-07-04T12:08:56.235-0700 + *
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + * 2001-07-04T12:08:56.235-07:00 + *
"YYYY-'W'ww-u" + * 2001-W27-3 + *
+ *
+ * + *

Synchronization

+ * + *

+ * Date formats are not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + * @see Java Tutorial + * @see java.util.Calendar + * @see java.util.TimeZone + * @see DateFormat + * @see DateFormatSymbols + * @author Mark Davis, Chen-Lieh Huang, Alan Liu + */ +public class SimpleDateFormat extends DateFormat { + + // the official serial version ID which says cryptically + // which version we're compatible with + static final long serialVersionUID = 4774881970558875024L; + + // the internal serial version which says which version was written + // - 0 (default) for version up to JDK 1.1.3 + // - 1 for version from JDK 1.1.4, which includes a new field + static final int currentSerialVersion = 1; + + /** + * The version of the serialized data on the stream. Possible values: + *

    + *
  • 0 or not present on stream: JDK 1.1.3. This version + * has no defaultCenturyStart on stream. + *
  • 1 JDK 1.1.4 or later. This version adds + * defaultCenturyStart. + *
+ * When streaming out this class, the most recent format + * and the highest allowable serialVersionOnStream + * is written. + * @serial + * @since JDK1.1.4 + */ + private int serialVersionOnStream = currentSerialVersion; + + /** + * The pattern string of this formatter. This is always a non-localized + * pattern. May not be null. See class documentation for details. + * @serial + */ + private String pattern; + + /** + * Saved numberFormat and pattern. + * @see SimpleDateFormat#checkNegativeNumberExpression + */ + transient private NumberFormat originalNumberFormat; + transient private String originalNumberPattern; + + /** + * The minus sign to be used with format and parse. + */ + transient private char minusSign = '-'; + + /** + * True when a negative sign follows a number. + * (True as default in Arabic.) + */ + transient private boolean hasFollowingMinusSign = false; + + /** + * The compiled pattern. + */ + transient private char[] compiledPattern; + + /** + * Tags for the compiled pattern. + */ + private final static int TAG_QUOTE_ASCII_CHAR = 100; + private final static int TAG_QUOTE_CHARS = 101; + + /** + * Locale dependent digit zero. + * @see #zeroPaddingNumber + * @see java.text.DecimalFormatSymbols#getZeroDigit + */ + transient private char zeroDigit; + + /** + * The symbols used by this formatter for week names, month names, + * etc. May not be null. + * @serial + * @see java.text.DateFormatSymbols + */ + private DateFormatSymbols formatData; + + /** + * We map dates with two-digit years into the century starting at + * defaultCenturyStart, which may be any date. May + * not be null. + * @serial + * @since JDK1.1.4 + */ + private Date defaultCenturyStart; + + transient private int defaultCenturyStartYear; + + private static final int MILLIS_PER_MINUTE = 60 * 1000; + + // For time zones that have no names, use strings GMT+minutes and + // GMT-minutes. For instance, in France the time zone is GMT+60. + private static final String GMT = "GMT"; + + /** + * Cache to hold the DateTimePatterns of a Locale. + */ + private static final ConcurrentMap cachedLocaleData + = new ConcurrentHashMap(3); + + /** + * Cache NumberFormat instances with Locale key. + */ + private static final ConcurrentMap cachedNumberFormatData + = new ConcurrentHashMap(3); + + /** + * The Locale used to instantiate this + * SimpleDateFormat. The value may be null if this object + * has been created by an older SimpleDateFormat and + * deserialized. + * + * @serial + * @since 1.6 + */ + private Locale locale; + + /** + * Indicates whether this SimpleDateFormat should use + * the DateFormatSymbols. If true, the format and parse methods + * use the DateFormatSymbols values. If false, the format and + * parse methods call Calendar.getDisplayName or + * Calendar.getDisplayNames. + */ + transient boolean useDateFormatSymbols; + + /** + * Constructs a SimpleDateFormat using the default pattern and + * date format symbols for the default locale. + * Note: This constructor may not support all locales. + * For full coverage, use the factory methods in the {@link DateFormat} + * class. + */ + public SimpleDateFormat() { + this(SHORT, SHORT, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Constructs a SimpleDateFormat using the given pattern and + * the default date format symbols for the default locale. + * Note: This constructor may not support all locales. + * For full coverage, use the factory methods in the {@link DateFormat} + * class. + * + * @param pattern the pattern describing the date and time format + * @exception NullPointerException if the given pattern is null + * @exception IllegalArgumentException if the given pattern is invalid + */ + public SimpleDateFormat(String pattern) + { + this(pattern, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Constructs a SimpleDateFormat using the given pattern and + * the default date format symbols for the given locale. + * Note: This constructor may not support all locales. + * For full coverage, use the factory methods in the {@link DateFormat} + * class. + * + * @param pattern the pattern describing the date and time format + * @param locale the locale whose date format symbols should be used + * @exception NullPointerException if the given pattern or locale is null + * @exception IllegalArgumentException if the given pattern is invalid + */ + public SimpleDateFormat(String pattern, Locale locale) + { + if (pattern == null || locale == null) { + throw new NullPointerException(); + } + + initializeCalendar(locale); + this.pattern = pattern; + this.formatData = DateFormatSymbols.getInstanceRef(locale); + this.locale = locale; + initialize(locale); + } + + /** + * Constructs a SimpleDateFormat using the given pattern and + * date format symbols. + * + * @param pattern the pattern describing the date and time format + * @param formatSymbols the date format symbols to be used for formatting + * @exception NullPointerException if the given pattern or formatSymbols is null + * @exception IllegalArgumentException if the given pattern is invalid + */ + public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols) + { + if (pattern == null || formatSymbols == null) { + throw new NullPointerException(); + } + + this.pattern = pattern; + this.formatData = (DateFormatSymbols) formatSymbols.clone(); + this.locale = Locale.getDefault(Locale.Category.FORMAT); + initializeCalendar(this.locale); + initialize(this.locale); + useDateFormatSymbols = true; + } + + /* Package-private, called by DateFormat factory methods */ + SimpleDateFormat(int timeStyle, int dateStyle, Locale loc) { + if (loc == null) { + throw new NullPointerException(); + } + + this.locale = loc; + // initialize calendar and related fields + initializeCalendar(loc); + + /* try the cache first */ + String[] dateTimePatterns = cachedLocaleData.get(loc); + if (dateTimePatterns == null) { /* cache miss */ + ResourceBundle r = null; // LocaleData.getDateFormatData(loc); + if (!isGregorianCalendar()) { + try { + dateTimePatterns = r.getStringArray(getCalendarName() + ".DateTimePatterns"); + } catch (MissingResourceException e) { + } + } + if (dateTimePatterns == null) { + dateTimePatterns = r.getStringArray("DateTimePatterns"); + } + /* update cache */ + cachedLocaleData.putIfAbsent(loc, dateTimePatterns); + } + formatData = DateFormatSymbols.getInstanceRef(loc); + if ((timeStyle >= 0) && (dateStyle >= 0)) { + Object[] dateTimeArgs = {dateTimePatterns[timeStyle], + dateTimePatterns[dateStyle + 4]}; + pattern = MessageFormat.format(dateTimePatterns[8], dateTimeArgs); + } + else if (timeStyle >= 0) { + pattern = dateTimePatterns[timeStyle]; + } + else if (dateStyle >= 0) { + pattern = dateTimePatterns[dateStyle + 4]; + } + else { + throw new IllegalArgumentException("No date or time style specified"); + } + + initialize(loc); + } + + /* Initialize compiledPattern and numberFormat fields */ + private void initialize(Locale loc) { + // Verify and compile the given pattern. + compiledPattern = compile(pattern); + + /* try the cache first */ + numberFormat = cachedNumberFormatData.get(loc); + if (numberFormat == null) { /* cache miss */ + numberFormat = NumberFormat.getIntegerInstance(loc); + numberFormat.setGroupingUsed(false); + + /* update cache */ + cachedNumberFormatData.putIfAbsent(loc, numberFormat); + } + numberFormat = (NumberFormat) numberFormat.clone(); + + initializeDefaultCentury(); + } + + private void initializeCalendar(Locale loc) { + if (calendar == null) { + assert loc != null; + // The format object must be constructed using the symbols for this zone. + // However, the calendar should use the current default TimeZone. + // If this is not contained in the locale zone strings, then the zone + // will be formatted using generic GMT+/-H:MM nomenclature. + calendar = Calendar.getInstance(TimeZone.getDefault(), loc); + } + } + + /** + * Returns the compiled form of the given pattern. The syntax of + * the compiled pattern is: + *
+ * CompiledPattern: + * EntryList + * EntryList: + * Entry + * EntryList Entry + * Entry: + * TagField + * TagField data + * TagField: + * Tag Length + * TaggedData + * Tag: + * pattern_char_index + * TAG_QUOTE_CHARS + * Length: + * short_length + * long_length + * TaggedData: + * TAG_QUOTE_ASCII_CHAR ascii_char + * + *
+ * + * where `short_length' is an 8-bit unsigned integer between 0 and + * 254. `long_length' is a sequence of an 8-bit integer 255 and a + * 32-bit signed integer value which is split into upper and lower + * 16-bit fields in two char's. `pattern_char_index' is an 8-bit + * integer between 0 and 18. `ascii_char' is an 7-bit ASCII + * character value. `data' depends on its Tag value. + *

+ * If Length is short_length, Tag and short_length are packed in a + * single char, as illustrated below. + *

+ * char[0] = (Tag << 8) | short_length; + *
+ * + * If Length is long_length, Tag and 255 are packed in the first + * char and a 32-bit integer, as illustrated below. + *
+ * char[0] = (Tag << 8) | 255; + * char[1] = (char) (long_length >>> 16); + * char[2] = (char) (long_length & 0xffff); + *
+ *

+ * If Tag is a pattern_char_index, its Length is the number of + * pattern characters. For example, if the given pattern is + * "yyyy", Tag is 1 and Length is 4, followed by no data. + *

+ * If Tag is TAG_QUOTE_CHARS, its Length is the number of char's + * following the TagField. For example, if the given pattern is + * "'o''clock'", Length is 7 followed by a char sequence of + * o&nbs;'&nbs;c&nbs;l&nbs;o&nbs;c&nbs;k. + *

+ * TAG_QUOTE_ASCII_CHAR is a special tag and has an ASCII + * character in place of Length. For example, if the given pattern + * is "'o'", the TaggedData entry is + * ((TAG_QUOTE_ASCII_CHAR&nbs;<<&nbs;8)&nbs;|&nbs;'o'). + * + * @exception NullPointerException if the given pattern is null + * @exception IllegalArgumentException if the given pattern is invalid + */ + private char[] compile(String pattern) { + int length = pattern.length(); + boolean inQuote = false; + StringBuilder compiledPattern = new StringBuilder(length * 2); + StringBuilder tmpBuffer = null; + int count = 0; + int lastTag = -1; + + for (int i = 0; i < length; i++) { + char c = pattern.charAt(i); + + if (c == '\'') { + // '' is treated as a single quote regardless of being + // in a quoted section. + if ((i + 1) < length) { + c = pattern.charAt(i + 1); + if (c == '\'') { + i++; + if (count != 0) { + encode(lastTag, count, compiledPattern); + lastTag = -1; + count = 0; + } + if (inQuote) { + tmpBuffer.append(c); + } else { + compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); + } + continue; + } + } + if (!inQuote) { + if (count != 0) { + encode(lastTag, count, compiledPattern); + lastTag = -1; + count = 0; + } + if (tmpBuffer == null) { + tmpBuffer = new StringBuilder(length); + } else { + tmpBuffer.setLength(0); + } + inQuote = true; + } else { + int len = tmpBuffer.length(); + if (len == 1) { + char ch = tmpBuffer.charAt(0); + if (ch < 128) { + compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch)); + } else { + compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | 1)); + compiledPattern.append(ch); + } + } else { + encode(TAG_QUOTE_CHARS, len, compiledPattern); + compiledPattern.append(tmpBuffer); + } + inQuote = false; + } + continue; + } + if (inQuote) { + tmpBuffer.append(c); + continue; + } + if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) { + if (count != 0) { + encode(lastTag, count, compiledPattern); + lastTag = -1; + count = 0; + } + if (c < 128) { + // In most cases, c would be a delimiter, such as ':'. + compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); + } else { + // Take any contiguous non-ASCII alphabet characters and + // put them in a single TAG_QUOTE_CHARS. + int j; + for (j = i + 1; j < length; j++) { + char d = pattern.charAt(j); + if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) { + break; + } + } + compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | (j - i))); + for (; i < j; i++) { + compiledPattern.append(pattern.charAt(i)); + } + i--; + } + continue; + } + + int tag; + if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -1) { + throw new IllegalArgumentException("Illegal pattern character " + + "'" + c + "'"); + } + if (lastTag == -1 || lastTag == tag) { + lastTag = tag; + count++; + continue; + } + encode(lastTag, count, compiledPattern); + lastTag = tag; + count = 1; + } + + if (inQuote) { + throw new IllegalArgumentException("Unterminated quote"); + } + + if (count != 0) { + encode(lastTag, count, compiledPattern); + } + + // Copy the compiled pattern to a char array + int len = compiledPattern.length(); + char[] r = new char[len]; + compiledPattern.getChars(0, len, r, 0); + return r; + } + + /** + * Encodes the given tag and length and puts encoded char(s) into buffer. + */ + private static final void encode(int tag, int length, StringBuilder buffer) { + if (tag == PATTERN_ISO_ZONE && length >= 4) { + throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length); + } + if (length < 255) { + buffer.append((char)(tag << 8 | length)); + } else { + buffer.append((char)((tag << 8) | 0xff)); + buffer.append((char)(length >>> 16)); + buffer.append((char)(length & 0xffff)); + } + } + + /* Initialize the fields we use to disambiguate ambiguous years. Separate + * so we can call it from readObject(). + */ + private void initializeDefaultCentury() { + calendar.setTimeInMillis(System.currentTimeMillis()); + calendar.add( Calendar.YEAR, -80 ); + parseAmbiguousDatesAsAfter(calendar.getTime()); + } + + /* Define one-century window into which to disambiguate dates using + * two-digit years. + */ + private void parseAmbiguousDatesAsAfter(Date startDate) { + defaultCenturyStart = startDate; + calendar.setTime(startDate); + defaultCenturyStartYear = calendar.get(Calendar.YEAR); + } + + /** + * Sets the 100-year period 2-digit years will be interpreted as being in + * to begin on the date the user specifies. + * + * @param startDate During parsing, two digit years will be placed in the range + * startDate to startDate + 100 years. + * @see #get2DigitYearStart + * @since 1.2 + */ + public void set2DigitYearStart(Date startDate) { + parseAmbiguousDatesAsAfter(new Date(startDate.getTime())); + } + + /** + * Returns the beginning date of the 100-year period 2-digit years are interpreted + * as being within. + * + * @return the start of the 100-year period into which two digit years are + * parsed + * @see #set2DigitYearStart + * @since 1.2 + */ + public Date get2DigitYearStart() { + return (Date) defaultCenturyStart.clone(); + } + + /** + * Formats the given Date into a date/time string and appends + * the result to the given StringBuffer. + * + * @param date the date-time value to be formatted into a date-time string. + * @param toAppendTo where the new date-time text is to be appended. + * @param pos the formatting position. On input: an alignment field, + * if desired. On output: the offsets of the alignment field. + * @return the formatted date-time string. + * @exception NullPointerException if the given {@code date} is {@code null}. + */ + public StringBuffer format(Date date, StringBuffer toAppendTo, + FieldPosition pos) + { + pos.beginIndex = pos.endIndex = 0; + return format(date, toAppendTo, pos.getFieldDelegate()); + } + + // Called from Format after creating a FieldDelegate + private StringBuffer format(Date date, StringBuffer toAppendTo, + FieldDelegate delegate) { + // Convert input date to time field list + calendar.setTime(date); + + boolean useDateFormatSymbols = useDateFormatSymbols(); + + for (int i = 0; i < compiledPattern.length; ) { + int tag = compiledPattern[i] >>> 8; + int count = compiledPattern[i++] & 0xff; + if (count == 255) { + count = compiledPattern[i++] << 16; + count |= compiledPattern[i++]; + } + + switch (tag) { + case TAG_QUOTE_ASCII_CHAR: + toAppendTo.append((char)count); + break; + + case TAG_QUOTE_CHARS: + toAppendTo.append(compiledPattern, i, count); + i += count; + break; + + default: + subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols); + break; + } + } + return toAppendTo; + } + + /** + * Formats an Object producing an AttributedCharacterIterator. + * You can use the returned AttributedCharacterIterator + * to build the resulting String, as well as to determine information + * about the resulting String. + *

+ * Each attribute key of the AttributedCharacterIterator will be of type + * DateFormat.Field, with the corresponding attribute value + * being the same as the attribute key. + * + * @exception NullPointerException if obj is null. + * @exception IllegalArgumentException if the Format cannot format the + * given object, or if the Format's pattern string is invalid. + * @param obj The object to format + * @return AttributedCharacterIterator describing the formatted value. + * @since 1.4 + */ + public AttributedCharacterIterator formatToCharacterIterator(Object obj) { + StringBuffer sb = new StringBuffer(); + CharacterIteratorFieldDelegate delegate = new + CharacterIteratorFieldDelegate(); + + if (obj instanceof Date) { + format((Date)obj, sb, delegate); + } + else if (obj instanceof Number) { + format(new Date(((Number)obj).longValue()), sb, delegate); + } + else if (obj == null) { + throw new NullPointerException( + "formatToCharacterIterator must be passed non-null object"); + } + else { + throw new IllegalArgumentException( + "Cannot format given Object as a Date"); + } + return delegate.getIterator(sb.toString()); + } + + // Map index into pattern character string to Calendar field number + private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = + { + Calendar.ERA, Calendar.YEAR, Calendar.MONTH, Calendar.DATE, + Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, Calendar.MINUTE, + Calendar.SECOND, Calendar.MILLISECOND, Calendar.DAY_OF_WEEK, + Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH, + Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, + Calendar.AM_PM, Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET, + Calendar.ZONE_OFFSET, + // Pseudo Calendar fields + CalendarBuilder.WEEK_YEAR, + CalendarBuilder.ISO_DAY_OF_WEEK, + Calendar.ZONE_OFFSET + }; + + // Map index into pattern character string to DateFormat field number + private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = { + DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD, + DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, + DateFormat.HOUR_OF_DAY0_FIELD, DateFormat.MINUTE_FIELD, + DateFormat.SECOND_FIELD, DateFormat.MILLISECOND_FIELD, + DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, + DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, DateFormat.WEEK_OF_YEAR_FIELD, + DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD, + DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, + DateFormat.TIMEZONE_FIELD, DateFormat.TIMEZONE_FIELD, + DateFormat.YEAR_FIELD, DateFormat.DAY_OF_WEEK_FIELD, + DateFormat.TIMEZONE_FIELD + }; + + // Maps from DecimalFormatSymbols index to Field constant + private static final Field[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID = { + Field.ERA, Field.YEAR, Field.MONTH, Field.DAY_OF_MONTH, + Field.HOUR_OF_DAY1, Field.HOUR_OF_DAY0, Field.MINUTE, + Field.SECOND, Field.MILLISECOND, Field.DAY_OF_WEEK, + Field.DAY_OF_YEAR, Field.DAY_OF_WEEK_IN_MONTH, + Field.WEEK_OF_YEAR, Field.WEEK_OF_MONTH, + Field.AM_PM, Field.HOUR1, Field.HOUR0, Field.TIME_ZONE, + Field.TIME_ZONE, + Field.YEAR, Field.DAY_OF_WEEK, + Field.TIME_ZONE + }; + + /** + * Private member function that does the real date/time formatting. + */ + private void subFormat(int patternCharIndex, int count, + FieldDelegate delegate, StringBuffer buffer, + boolean useDateFormatSymbols) + { + int maxIntCount = Integer.MAX_VALUE; + String current = null; + int beginOffset = buffer.length(); + + int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; + int value; + if (field == CalendarBuilder.WEEK_YEAR) { + if (calendar.isWeekDateSupported()) { + value = calendar.getWeekYear(); + } else { + // use calendar year 'y' instead + patternCharIndex = PATTERN_YEAR; + field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; + value = calendar.get(field); + } + } else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) { + value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK)); + } else { + value = calendar.get(field); + } + + int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT; + if (!useDateFormatSymbols && field != CalendarBuilder.ISO_DAY_OF_WEEK) { + current = calendar.getDisplayName(field, style, locale); + } + + // Note: zeroPaddingNumber() assumes that maxDigits is either + // 2 or maxIntCount. If we make any changes to this, + // zeroPaddingNumber() must be fixed. + + switch (patternCharIndex) { + case PATTERN_ERA: // 'G' + if (useDateFormatSymbols) { + String[] eras = formatData.getEras(); + if (value < eras.length) + current = eras[value]; + } + if (current == null) + current = ""; + break; + + case PATTERN_WEEK_YEAR: // 'Y' + case PATTERN_YEAR: // 'y' + if (calendar instanceof GregorianCalendar) { + if (count != 2) + zeroPaddingNumber(value, count, maxIntCount, buffer); + else // count == 2 + zeroPaddingNumber(value, 2, 2, buffer); // clip 1996 to 96 + } else { + if (current == null) { + zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count, + maxIntCount, buffer); + } + } + break; + + case PATTERN_MONTH: // 'M' + if (useDateFormatSymbols) { + String[] months; + if (count >= 4) { + months = formatData.getMonths(); + current = months[value]; + } else if (count == 3) { + months = formatData.getShortMonths(); + current = months[value]; + } + } else { + if (count < 3) { + current = null; + } + } + if (current == null) { + zeroPaddingNumber(value+1, count, maxIntCount, buffer); + } + break; + + case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59 + if (current == null) { + if (value == 0) + zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY)+1, + count, maxIntCount, buffer); + else + zeroPaddingNumber(value, count, maxIntCount, buffer); + } + break; + + case PATTERN_DAY_OF_WEEK: // 'E' + if (useDateFormatSymbols) { + String[] weekdays; + if (count >= 4) { + weekdays = formatData.getWeekdays(); + current = weekdays[value]; + } else { // count < 4, use abbreviated form if exists + weekdays = formatData.getShortWeekdays(); + current = weekdays[value]; + } + } + break; + + case PATTERN_AM_PM: // 'a' + if (useDateFormatSymbols) { + String[] ampm = formatData.getAmPmStrings(); + current = ampm[value]; + } + break; + + case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM + if (current == null) { + if (value == 0) + zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR)+1, + count, maxIntCount, buffer); + else + zeroPaddingNumber(value, count, maxIntCount, buffer); + } + break; + + case PATTERN_ZONE_NAME: // 'z' + if (current == null) { + if (formatData.locale == null || formatData.isZoneStringsSet) { + int zoneIndex = + formatData.getZoneIndex(calendar.getTimeZone().getID()); + if (zoneIndex == -1) { + value = calendar.get(Calendar.ZONE_OFFSET) + + calendar.get(Calendar.DST_OFFSET); +// buffer.append(ZoneInfoFile.toCustomID(value)); + } else { + int index = (calendar.get(Calendar.DST_OFFSET) == 0) ? 1: 3; + if (count < 4) { + // Use the short name + index++; + } + String[][] zoneStrings = formatData.getZoneStringsWrapper(); + buffer.append(zoneStrings[zoneIndex][index]); + } + } else { + TimeZone tz = calendar.getTimeZone(); + boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0); + int tzstyle = (count < 4 ? TimeZone.SHORT : TimeZone.LONG); + buffer.append(tz.getDisplayName(daylight, tzstyle, formatData.locale)); + } + } + break; + + case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form) + value = (calendar.get(Calendar.ZONE_OFFSET) + + calendar.get(Calendar.DST_OFFSET)) / 60000; + + int width = 4; + if (value >= 0) { + buffer.append('+'); + } else { + width++; + } + + int num = (value / 60) * 100 + (value % 60); +// CalendarUtils.sprintf0d(buffer, num, width); + break; + + case PATTERN_ISO_ZONE: // 'X' + value = calendar.get(Calendar.ZONE_OFFSET) + + calendar.get(Calendar.DST_OFFSET); + + if (value == 0) { + buffer.append('Z'); + break; + } + + value /= 60000; + if (value >= 0) { + buffer.append('+'); + } else { + buffer.append('-'); + value = -value; + } + +// CalendarUtils.sprintf0d(buffer, value / 60, 2); + if (count == 1) { + break; + } + + if (count == 3) { + buffer.append(':'); + } +// CalendarUtils.sprintf0d(buffer, value % 60, 2); + break; + + default: + // case PATTERN_DAY_OF_MONTH: // 'd' + // case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59 + // case PATTERN_MINUTE: // 'm' + // case PATTERN_SECOND: // 's' + // case PATTERN_MILLISECOND: // 'S' + // case PATTERN_DAY_OF_YEAR: // 'D' + // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F' + // case PATTERN_WEEK_OF_YEAR: // 'w' + // case PATTERN_WEEK_OF_MONTH: // 'W' + // case PATTERN_HOUR0: // 'K' eg, 11PM + 1 hour =>> 0 AM + // case PATTERN_ISO_DAY_OF_WEEK: // 'u' pseudo field, Monday = 1, ..., Sunday = 7 + if (current == null) { + zeroPaddingNumber(value, count, maxIntCount, buffer); + } + break; + } // switch (patternCharIndex) + + if (current != null) { + buffer.append(current); + } + + int fieldID = PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]; + Field f = PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID[patternCharIndex]; + + delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer); + } + + /** + * Formats a number with the specified minimum and maximum number of digits. + */ + private final void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer) + { + // Optimization for 1, 2 and 4 digit numbers. This should + // cover most cases of formatting date/time related items. + // Note: This optimization code assumes that maxDigits is + // either 2 or Integer.MAX_VALUE (maxIntCount in format()). + try { + if (zeroDigit == 0) { + zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit(); + } + if (value >= 0) { + if (value < 100 && minDigits >= 1 && minDigits <= 2) { + if (value < 10) { + if (minDigits == 2) { + buffer.append(zeroDigit); + } + buffer.append((char)(zeroDigit + value)); + } else { + buffer.append((char)(zeroDigit + value / 10)); + buffer.append((char)(zeroDigit + value % 10)); + } + return; + } else if (value >= 1000 && value < 10000) { + if (minDigits == 4) { + buffer.append((char)(zeroDigit + value / 1000)); + value %= 1000; + buffer.append((char)(zeroDigit + value / 100)); + value %= 100; + buffer.append((char)(zeroDigit + value / 10)); + buffer.append((char)(zeroDigit + value % 10)); + return; + } + if (minDigits == 2 && maxDigits == 2) { + zeroPaddingNumber(value % 100, 2, 2, buffer); + return; + } + } + } + } catch (Exception e) { + } + + numberFormat.setMinimumIntegerDigits(minDigits); + numberFormat.setMaximumIntegerDigits(maxDigits); + numberFormat.format((long)value, buffer, DontCareFieldPosition.INSTANCE); + } + + + /** + * Parses text from a string to produce a Date. + *

+ * The method attempts to parse text starting at the index given by + * pos. + * If parsing succeeds, then the index of pos is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * date is returned. The updated pos can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of pos is not + * changed, the error index of pos is set to the index of + * the character where the error occurred, and null is returned. + * + *

This parsing operation uses the {@link DateFormat#calendar + * calendar} to produce a {@code Date}. All of the {@code + * calendar}'s date-time fields are {@linkplain Calendar#clear() + * cleared} before parsing, and the {@code calendar}'s default + * values of the date-time fields are used for any missing + * date-time information. For example, the year value of the + * parsed {@code Date} is 1970 with {@link GregorianCalendar} if + * no year value is given from the parsing operation. The {@code + * TimeZone} value may be overwritten, depending on the given + * pattern and the time zone value in {@code text}. Any {@code + * TimeZone} value that has previously been set by a call to + * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need + * to be restored for further operations. + * + * @param text A String, part of which should be parsed. + * @param pos A ParsePosition object with index and error + * index information as described above. + * @return A Date parsed from the string. In case of + * error, returns null. + * @exception NullPointerException if text or pos is null. + */ + public Date parse(String text, ParsePosition pos) + { + checkNegativeNumberExpression(); + + int start = pos.index; + int oldStart = start; + int textLength = text.length(); + + boolean[] ambiguousYear = {false}; + + CalendarBuilder calb = new CalendarBuilder(); + + for (int i = 0; i < compiledPattern.length; ) { + int tag = compiledPattern[i] >>> 8; + int count = compiledPattern[i++] & 0xff; + if (count == 255) { + count = compiledPattern[i++] << 16; + count |= compiledPattern[i++]; + } + + switch (tag) { + case TAG_QUOTE_ASCII_CHAR: + if (start >= textLength || text.charAt(start) != (char)count) { + pos.index = oldStart; + pos.errorIndex = start; + return null; + } + start++; + break; + + case TAG_QUOTE_CHARS: + while (count-- > 0) { + if (start >= textLength || text.charAt(start) != compiledPattern[i++]) { + pos.index = oldStart; + pos.errorIndex = start; + return null; + } + start++; + } + break; + + default: + // Peek the next pattern to determine if we need to + // obey the number of pattern letters for + // parsing. It's required when parsing contiguous + // digit text (e.g., "20010704") with a pattern which + // has no delimiters between fields, like "yyyyMMdd". + boolean obeyCount = false; + + // In Arabic, a minus sign for a negative number is put after + // the number. Even in another locale, a minus sign can be + // put after a number using DateFormat.setNumberFormat(). + // If both the minus sign and the field-delimiter are '-', + // subParse() needs to determine whether a '-' after a number + // in the given text is a delimiter or is a minus sign for the + // preceding number. We give subParse() a clue based on the + // information in compiledPattern. + boolean useFollowingMinusSignAsDelimiter = false; + + if (i < compiledPattern.length) { + int nextTag = compiledPattern[i] >>> 8; + if (!(nextTag == TAG_QUOTE_ASCII_CHAR || + nextTag == TAG_QUOTE_CHARS)) { + obeyCount = true; + } + + if (hasFollowingMinusSign && + (nextTag == TAG_QUOTE_ASCII_CHAR || + nextTag == TAG_QUOTE_CHARS)) { + int c; + if (nextTag == TAG_QUOTE_ASCII_CHAR) { + c = compiledPattern[i] & 0xff; + } else { + c = compiledPattern[i+1]; + } + + if (c == minusSign) { + useFollowingMinusSignAsDelimiter = true; + } + } + } + start = subParse(text, start, tag, count, obeyCount, + ambiguousYear, pos, + useFollowingMinusSignAsDelimiter, calb); + if (start < 0) { + pos.index = oldStart; + return null; + } + } + } + + // At this point the fields of Calendar have been set. Calendar + // will fill in default values for missing fields when the time + // is computed. + + pos.index = start; + + Date parsedDate; + try { + parsedDate = calb.establish(calendar).getTime(); + // If the year value is ambiguous, + // then the two-digit year == the default start year + if (ambiguousYear[0]) { + if (parsedDate.before(defaultCenturyStart)) { + parsedDate = calb.addYear(100).establish(calendar).getTime(); + } + } + } + // An IllegalArgumentException will be thrown by Calendar.getTime() + // if any fields are out of range, e.g., MONTH == 17. + catch (IllegalArgumentException e) { + pos.errorIndex = start; + pos.index = oldStart; + return null; + } + + return parsedDate; + } + + /** + * Private code-size reduction function used by subParse. + * @param text the time text being parsed. + * @param start where to start parsing. + * @param field the date field being parsed. + * @param data the string array to parsed. + * @return the new start position if matching succeeded; a negative number + * indicating matching failure, otherwise. + */ + private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb) + { + int i = 0; + int count = data.length; + + if (field == Calendar.DAY_OF_WEEK) i = 1; + + // There may be multiple strings in the data[] array which begin with + // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech). + // We keep track of the longest match, and return that. Note that this + // unfortunately requires us to test all array elements. + int bestMatchLength = 0, bestMatch = -1; + for (; i bestMatchLength && + text.regionMatches(true, start, data[i], 0, length)) + { + bestMatch = i; + bestMatchLength = length; + } + } + if (bestMatch >= 0) + { + calb.set(field, bestMatch); + return start + bestMatchLength; + } + return -start; + } + + /** + * Performs the same thing as matchString(String, int, int, + * String[]). This method takes a Map instead of + * String[]. + */ + private int matchString(String text, int start, int field, + Map data, CalendarBuilder calb) { + if (data != null) { + String bestMatch = null; + + for (String name : data.keySet()) { + int length = name.length(); + if (bestMatch == null || length > bestMatch.length()) { + if (text.regionMatches(true, start, name, 0, length)) { + bestMatch = name; + } + } + } + + if (bestMatch != null) { + calb.set(field, data.get(bestMatch)); + return start + bestMatch.length(); + } + } + return -start; + } + + private int matchZoneString(String text, int start, String[] zoneNames) { + for (int i = 1; i <= 4; ++i) { + // Checking long and short zones [1 & 2], + // and long and short daylight [3 & 4]. + String zoneName = zoneNames[i]; + if (text.regionMatches(true, start, + zoneName, 0, zoneName.length())) { + return i; + } + } + return -1; + } + + private boolean matchDSTString(String text, int start, int zoneIndex, int standardIndex, + String[][] zoneStrings) { + int index = standardIndex + 2; + String zoneName = zoneStrings[zoneIndex][index]; + if (text.regionMatches(true, start, + zoneName, 0, zoneName.length())) { + return true; + } + return false; + } + + /** + * find time zone 'text' matched zoneStrings and set to internal + * calendar. + */ + private int subParseZoneString(String text, int start, CalendarBuilder calb) { + boolean useSameName = false; // true if standard and daylight time use the same abbreviation. + TimeZone currentTimeZone = getTimeZone(); + + // At this point, check for named time zones by looking through + // the locale data from the TimeZoneNames strings. + // Want to be able to parse both short and long forms. + int zoneIndex = formatData.getZoneIndex(currentTimeZone.getID()); + TimeZone tz = null; + String[][] zoneStrings = formatData.getZoneStringsWrapper(); + String[] zoneNames = null; + int nameIndex = 0; + if (zoneIndex != -1) { + zoneNames = zoneStrings[zoneIndex]; + if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) { + if (nameIndex <= 2) { + // Check if the standard name (abbr) and the daylight name are the same. + useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]); + } + tz = TimeZone.getTimeZone(zoneNames[0]); + } + } + if (tz == null) { + zoneIndex = formatData.getZoneIndex(TimeZone.getDefault().getID()); + if (zoneIndex != -1) { + zoneNames = zoneStrings[zoneIndex]; + if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) { + if (nameIndex <= 2) { + useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]); + } + tz = TimeZone.getTimeZone(zoneNames[0]); + } + } + } + + if (tz == null) { + int len = zoneStrings.length; + for (int i = 0; i < len; i++) { + zoneNames = zoneStrings[i]; + if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) { + if (nameIndex <= 2) { + useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]); + } + tz = TimeZone.getTimeZone(zoneNames[0]); + break; + } + } + } + if (tz != null) { // Matched any ? + if (!tz.equals(currentTimeZone)) { + setTimeZone(tz); + } + // If the time zone matched uses the same name + // (abbreviation) for both standard and daylight time, + // let the time zone in the Calendar decide which one. + // + // Also if tz.getDSTSaving() returns 0 for DST, use tz to + // determine the local time. (6645292) + int dstAmount = (nameIndex >= 3) ? tz.getDSTSavings() : 0; + if (!(useSameName || (nameIndex >= 3 && dstAmount == 0))) { + calb.set(Calendar.ZONE_OFFSET, tz.getRawOffset()) + .set(Calendar.DST_OFFSET, dstAmount); + } + return (start + zoneNames[nameIndex].length()); + } + return 0; + } + + /** + * Parses numeric forms of time zone offset, such as "hh:mm", and + * sets calb to the parsed value. + * + * @param text the text to be parsed + * @param start the character position to start parsing + * @param sign 1: positive; -1: negative + * @param count 0: 'Z' or "GMT+hh:mm" parsing; 1 - 3: the number of 'X's + * @param colon true - colon required between hh and mm; false - no colon required + * @param calb a CalendarBuilder in which the parsed value is stored + * @return updated parsed position, or its negative value to indicate a parsing error + */ + private int subParseNumericZone(String text, int start, int sign, int count, + boolean colon, CalendarBuilder calb) { + int index = start; + + parse: + try { + char c = text.charAt(index++); + // Parse hh + int hours; + if (!isDigit(c)) { + break parse; + } + hours = c - '0'; + c = text.charAt(index++); + if (isDigit(c)) { + hours = hours * 10 + (c - '0'); + } else { + // If no colon in RFC 822 or 'X' (ISO), two digits are + // required. + if (count > 0 || !colon) { + break parse; + } + --index; + } + if (hours > 23) { + break parse; + } + int minutes = 0; + if (count != 1) { + // Proceed with parsing mm + c = text.charAt(index++); + if (colon) { + if (c != ':') { + break parse; + } + c = text.charAt(index++); + } + if (!isDigit(c)) { + break parse; + } + minutes = c - '0'; + c = text.charAt(index++); + if (!isDigit(c)) { + break parse; + } + minutes = minutes * 10 + (c - '0'); + if (minutes > 59) { + break parse; + } + } + minutes += hours * 60; + calb.set(Calendar.ZONE_OFFSET, minutes * MILLIS_PER_MINUTE * sign) + .set(Calendar.DST_OFFSET, 0); + return index; + } catch (IndexOutOfBoundsException e) { + } + return 1 - index; // -(index - 1) + } + + private boolean isDigit(char c) { + return c >= '0' && c <= '9'; + } + + /** + * Private member function that converts the parsed date strings into + * timeFields. Returns -start (for ParsePosition) if failed. + * @param text the time text to be parsed. + * @param start where to start parsing. + * @param ch the pattern character for the date field text to be parsed. + * @param count the count of a pattern character. + * @param obeyCount if true, then the next field directly abuts this one, + * and we should use the count to know when to stop parsing. + * @param ambiguousYear return parameter; upon return, if ambiguousYear[0] + * is true, then a two-digit year was parsed and may need to be readjusted. + * @param origPos origPos.errorIndex is used to return an error index + * at which a parse error occurred, if matching failure occurs. + * @return the new start position if matching succeeded; -1 indicating + * matching failure, otherwise. In case matching failure occurred, + * an error index is set to origPos.errorIndex. + */ + private int subParse(String text, int start, int patternCharIndex, int count, + boolean obeyCount, boolean[] ambiguousYear, + ParsePosition origPos, + boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) { + Number number = null; + int value = 0; + ParsePosition pos = new ParsePosition(0); + pos.index = start; + if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) { + // use calendar year 'y' instead + patternCharIndex = PATTERN_YEAR; + } + int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; + + // If there are any spaces here, skip over them. If we hit the end + // of the string, then fail. + for (;;) { + if (pos.index >= text.length()) { + origPos.errorIndex = start; + return -1; + } + char c = text.charAt(pos.index); + if (c != ' ' && c != '\t') break; + ++pos.index; + } + + parsing: + { + // We handle a few special cases here where we need to parse + // a number value. We handle further, more generic cases below. We need + // to handle some of them here because some fields require extra processing on + // the parsed value. + if (patternCharIndex == PATTERN_HOUR_OF_DAY1 || + patternCharIndex == PATTERN_HOUR1 || + (patternCharIndex == PATTERN_MONTH && count <= 2) || + patternCharIndex == PATTERN_YEAR || + patternCharIndex == PATTERN_WEEK_YEAR) { + // It would be good to unify this with the obeyCount logic below, + // but that's going to be difficult. + if (obeyCount) { + if ((start+count) > text.length()) { + break parsing; + } + number = numberFormat.parse(text.substring(0, start+count), pos); + } else { + number = numberFormat.parse(text, pos); + } + if (number == null) { + if (patternCharIndex != PATTERN_YEAR || calendar instanceof GregorianCalendar) { + break parsing; + } + } else { + value = number.intValue(); + + if (useFollowingMinusSignAsDelimiter && (value < 0) && + (((pos.index < text.length()) && + (text.charAt(pos.index) != minusSign)) || + ((pos.index == text.length()) && + (text.charAt(pos.index-1) == minusSign)))) { + value = -value; + pos.index--; + } + } + } + + boolean useDateFormatSymbols = useDateFormatSymbols(); + + int index; + switch (patternCharIndex) { + case PATTERN_ERA: // 'G' + if (useDateFormatSymbols) { + if ((index = matchString(text, start, Calendar.ERA, formatData.getEras(), calb)) > 0) { + return index; + } + } else { + Map map = calendar.getDisplayNames(field, + Calendar.ALL_STYLES, + locale); + if ((index = matchString(text, start, field, map, calb)) > 0) { + return index; + } + } + break parsing; + + case PATTERN_WEEK_YEAR: // 'Y' + case PATTERN_YEAR: // 'y' + if (!(calendar instanceof GregorianCalendar)) { + // calendar might have text representations for year values, + // such as "\u5143" in JapaneseImperialCalendar. + int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT; + Map map = calendar.getDisplayNames(field, style, locale); + if (map != null) { + if ((index = matchString(text, start, field, map, calb)) > 0) { + return index; + } + } + calb.set(field, value); + return pos.index; + } + + // If there are 3 or more YEAR pattern characters, this indicates + // that the year value is to be treated literally, without any + // two-digit year adjustments (e.g., from "01" to 2001). Otherwise + // we made adjustments to place the 2-digit year in the proper + // century, for parsed strings from "00" to "99". Any other string + // is treated literally: "2250", "-1", "1", "002". + if (count <= 2 && (pos.index - start) == 2 + && Character.isDigit(text.charAt(start)) + && Character.isDigit(text.charAt(start+1))) { + // Assume for example that the defaultCenturyStart is 6/18/1903. + // This means that two-digit years will be forced into the range + // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02 + // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond + // to 1904, 1905, etc. If the year is 03, then it is 2003 if the + // other fields specify a date before 6/18, or 1903 if they specify a + // date afterwards. As a result, 03 is an ambiguous year. All other + // two-digit years are unambiguous. + int ambiguousTwoDigitYear = defaultCenturyStartYear % 100; + ambiguousYear[0] = value == ambiguousTwoDigitYear; + value += (defaultCenturyStartYear/100)*100 + + (value < ambiguousTwoDigitYear ? 100 : 0); + } + calb.set(field, value); + return pos.index; + + case PATTERN_MONTH: // 'M' + if (count <= 2) // i.e., M or MM. + { + // Don't want to parse the month if it is a string + // while pattern uses numeric style: M or MM. + // [We computed 'value' above.] + calb.set(Calendar.MONTH, value - 1); + return pos.index; + } + + if (useDateFormatSymbols) { + // count >= 3 // i.e., MMM or MMMM + // Want to be able to parse both short and long forms. + // Try count == 4 first: + int newStart = 0; + if ((newStart = matchString(text, start, Calendar.MONTH, + formatData.getMonths(), calb)) > 0) { + return newStart; + } + // count == 4 failed, now try count == 3 + if ((index = matchString(text, start, Calendar.MONTH, + formatData.getShortMonths(), calb)) > 0) { + return index; + } + } else { + Map map = calendar.getDisplayNames(field, + Calendar.ALL_STYLES, + locale); + if ((index = matchString(text, start, field, map, calb)) > 0) { + return index; + } + } + break parsing; + + case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59 + if (!isLenient()) { + // Validate the hour value in non-lenient + if (value < 1 || value > 24) { + break parsing; + } + } + // [We computed 'value' above.] + if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY)+1) + value = 0; + calb.set(Calendar.HOUR_OF_DAY, value); + return pos.index; + + case PATTERN_DAY_OF_WEEK: // 'E' + { + if (useDateFormatSymbols) { + // Want to be able to parse both short and long forms. + // Try count == 4 (DDDD) first: + int newStart = 0; + if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK, + formatData.getWeekdays(), calb)) > 0) { + return newStart; + } + // DDDD failed, now try DDD + if ((index = matchString(text, start, Calendar.DAY_OF_WEEK, + formatData.getShortWeekdays(), calb)) > 0) { + return index; + } + } else { + int[] styles = { Calendar.LONG, Calendar.SHORT }; + for (int style : styles) { + Map map = calendar.getDisplayNames(field, style, locale); + if ((index = matchString(text, start, field, map, calb)) > 0) { + return index; + } + } + } + } + break parsing; + + case PATTERN_AM_PM: // 'a' + if (useDateFormatSymbols) { + if ((index = matchString(text, start, Calendar.AM_PM, + formatData.getAmPmStrings(), calb)) > 0) { + return index; + } + } else { + Map map = calendar.getDisplayNames(field, Calendar.ALL_STYLES, locale); + if ((index = matchString(text, start, field, map, calb)) > 0) { + return index; + } + } + break parsing; + + case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM + if (!isLenient()) { + // Validate the hour value in non-lenient + if (value < 1 || value > 12) { + break parsing; + } + } + // [We computed 'value' above.] + if (value == calendar.getLeastMaximum(Calendar.HOUR)+1) + value = 0; + calb.set(Calendar.HOUR, value); + return pos.index; + + case PATTERN_ZONE_NAME: // 'z' + case PATTERN_ZONE_VALUE: // 'Z' + { + int sign = 0; + try { + char c = text.charAt(pos.index); + if (c == '+') { + sign = 1; + } else if (c == '-') { + sign = -1; + } + if (sign == 0) { + // Try parsing a custom time zone "GMT+hh:mm" or "GMT". + if ((c == 'G' || c == 'g') + && (text.length() - start) >= GMT.length() + && text.regionMatches(true, start, GMT, 0, GMT.length())) { + pos.index = start + GMT.length(); + + if ((text.length() - pos.index) > 0) { + c = text.charAt(pos.index); + if (c == '+') { + sign = 1; + } else if (c == '-') { + sign = -1; + } + } + + if (sign == 0) { /* "GMT" without offset */ + calb.set(Calendar.ZONE_OFFSET, 0) + .set(Calendar.DST_OFFSET, 0); + return pos.index; + } + + // Parse the rest as "hh:mm" + int i = subParseNumericZone(text, ++pos.index, + sign, 0, true, calb); + if (i > 0) { + return i; + } + pos.index = -i; + } else { + // Try parsing the text as a time zone + // name or abbreviation. + int i = subParseZoneString(text, pos.index, calb); + if (i > 0) { + return i; + } + pos.index = -i; + } + } else { + // Parse the rest as "hhmm" (RFC 822) + int i = subParseNumericZone(text, ++pos.index, + sign, 0, false, calb); + if (i > 0) { + return i; + } + pos.index = -i; + } + } catch (IndexOutOfBoundsException e) { + } + } + break parsing; + + case PATTERN_ISO_ZONE: // 'X' + { + if ((text.length() - pos.index) <= 0) { + break parsing; + } + + int sign = 0; + char c = text.charAt(pos.index); + if (c == 'Z') { + calb.set(Calendar.ZONE_OFFSET, 0).set(Calendar.DST_OFFSET, 0); + return ++pos.index; + } + + // parse text as "+/-hh[[:]mm]" based on count + if (c == '+') { + sign = 1; + } else if (c == '-') { + sign = -1; + } else { + ++pos.index; + break parsing; + } + int i = subParseNumericZone(text, ++pos.index, sign, count, + count == 3, calb); + if (i > 0) { + return i; + } + pos.index = -i; + } + break parsing; + + default: + // case PATTERN_DAY_OF_MONTH: // 'd' + // case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59 + // case PATTERN_MINUTE: // 'm' + // case PATTERN_SECOND: // 's' + // case PATTERN_MILLISECOND: // 'S' + // case PATTERN_DAY_OF_YEAR: // 'D' + // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F' + // case PATTERN_WEEK_OF_YEAR: // 'w' + // case PATTERN_WEEK_OF_MONTH: // 'W' + // case PATTERN_HOUR0: // 'K' 0-based. eg, 11PM + 1 hour =>> 0 AM + // case PATTERN_ISO_DAY_OF_WEEK: // 'u' (pseudo field); + + // Handle "generic" fields + if (obeyCount) { + if ((start+count) > text.length()) { + break parsing; + } + number = numberFormat.parse(text.substring(0, start+count), pos); + } else { + number = numberFormat.parse(text, pos); + } + if (number != null) { + value = number.intValue(); + + if (useFollowingMinusSignAsDelimiter && (value < 0) && + (((pos.index < text.length()) && + (text.charAt(pos.index) != minusSign)) || + ((pos.index == text.length()) && + (text.charAt(pos.index-1) == minusSign)))) { + value = -value; + pos.index--; + } + + calb.set(field, value); + return pos.index; + } + break parsing; + } + } + + // Parsing failed. + origPos.errorIndex = pos.index; + return -1; + } + + private final String getCalendarName() { + return calendar.getClass().getName(); + } + + private boolean useDateFormatSymbols() { + if (useDateFormatSymbols) { + return true; + } + return isGregorianCalendar() || locale == null; + } + + private boolean isGregorianCalendar() { + return "java.util.GregorianCalendar".equals(getCalendarName()); + } + + /** + * Translates a pattern, mapping each character in the from string to the + * corresponding character in the to string. + * + * @exception IllegalArgumentException if the given pattern is invalid + */ + private String translatePattern(String pattern, String from, String to) { + StringBuilder result = new StringBuilder(); + boolean inQuote = false; + for (int i = 0; i < pattern.length(); ++i) { + char c = pattern.charAt(i); + if (inQuote) { + if (c == '\'') + inQuote = false; + } + else { + if (c == '\'') + inQuote = true; + else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + int ci = from.indexOf(c); + if (ci >= 0) { + // patternChars is longer than localPatternChars due + // to serialization compatibility. The pattern letters + // unsupported by localPatternChars pass through. + if (ci < to.length()) { + c = to.charAt(ci); + } + } else { + throw new IllegalArgumentException("Illegal pattern " + + " character '" + + c + "'"); + } + } + } + result.append(c); + } + if (inQuote) + throw new IllegalArgumentException("Unfinished quote in pattern"); + return result.toString(); + } + + /** + * Returns a pattern string describing this date format. + * + * @return a pattern string describing this date format. + */ + public String toPattern() { + return pattern; + } + + /** + * Returns a localized pattern string describing this date format. + * + * @return a localized pattern string describing this date format. + */ + public String toLocalizedPattern() { + return translatePattern(pattern, + DateFormatSymbols.patternChars, + formatData.getLocalPatternChars()); + } + + /** + * Applies the given pattern string to this date format. + * + * @param pattern the new date and time pattern for this date format + * @exception NullPointerException if the given pattern is null + * @exception IllegalArgumentException if the given pattern is invalid + */ + public void applyPattern(String pattern) + { + compiledPattern = compile(pattern); + this.pattern = pattern; + } + + /** + * Applies the given localized pattern string to this date format. + * + * @param pattern a String to be mapped to the new date and time format + * pattern for this format + * @exception NullPointerException if the given pattern is null + * @exception IllegalArgumentException if the given pattern is invalid + */ + public void applyLocalizedPattern(String pattern) { + String p = translatePattern(pattern, + formatData.getLocalPatternChars(), + DateFormatSymbols.patternChars); + compiledPattern = compile(p); + this.pattern = p; + } + + /** + * Gets a copy of the date and time format symbols of this date format. + * + * @return the date and time format symbols of this date format + * @see #setDateFormatSymbols + */ + public DateFormatSymbols getDateFormatSymbols() + { + return (DateFormatSymbols)formatData.clone(); + } + + /** + * Sets the date and time format symbols of this date format. + * + * @param newFormatSymbols the new date and time format symbols + * @exception NullPointerException if the given newFormatSymbols is null + * @see #getDateFormatSymbols + */ + public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) + { + this.formatData = (DateFormatSymbols)newFormatSymbols.clone(); + useDateFormatSymbols = true; + } + + /** + * Creates a copy of this SimpleDateFormat. This also + * clones the format's date format symbols. + * + * @return a clone of this SimpleDateFormat + */ + public Object clone() { + SimpleDateFormat other = (SimpleDateFormat) super.clone(); + other.formatData = (DateFormatSymbols) formatData.clone(); + return other; + } + + /** + * Returns the hash code value for this SimpleDateFormat object. + * + * @return the hash code value for this SimpleDateFormat object. + */ + public int hashCode() + { + return pattern.hashCode(); + // just enough fields for a reasonable distribution + } + + /** + * Compares the given object with this SimpleDateFormat for + * equality. + * + * @return true if the given object is equal to this + * SimpleDateFormat + */ + public boolean equals(Object obj) + { + if (!super.equals(obj)) return false; // super does class check + SimpleDateFormat that = (SimpleDateFormat) obj; + return (pattern.equals(that.pattern) + && formatData.equals(that.formatData)); + } + + /** + * After reading an object from the input stream, the format + * pattern in the object is verified. + *

+ * @exception InvalidObjectException if the pattern is invalid + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + + try { + compiledPattern = compile(pattern); + } catch (Exception e) { + throw new InvalidObjectException("invalid pattern"); + } + + if (serialVersionOnStream < 1) { + // didn't have defaultCenturyStart field + initializeDefaultCentury(); + } + else { + // fill in dependent transient field + parseAmbiguousDatesAsAfter(defaultCenturyStart); + } + serialVersionOnStream = currentSerialVersion; + + // If the deserialized object has a SimpleTimeZone, try + // to replace it with a ZoneInfo equivalent in order to + // be compatible with the SimpleTimeZone-based + // implementation as much as possible. + TimeZone tz = getTimeZone(); + if (tz instanceof SimpleTimeZone) { + String id = tz.getID(); + TimeZone zi = TimeZone.getTimeZone(id); + if (zi != null && zi.hasSameRules(tz) && zi.getID().equals(id)) { + setTimeZone(zi); + } + } + } + + /** + * Analyze the negative subpattern of DecimalFormat and set/update values + * as necessary. + */ + private void checkNegativeNumberExpression() { + if ((numberFormat instanceof DecimalFormat) && + !numberFormat.equals(originalNumberFormat)) { + String numberPattern = ((DecimalFormat)numberFormat).toPattern(); + if (!numberPattern.equals(originalNumberPattern)) { + hasFollowingMinusSign = false; + + int separatorIndex = numberPattern.indexOf(';'); + // If the negative subpattern is not absent, we have to analayze + // it in order to check if it has a following minus sign. + if (separatorIndex > -1) { + int minusIndex = numberPattern.indexOf('-', separatorIndex); + if ((minusIndex > numberPattern.lastIndexOf('0')) && + (minusIndex > numberPattern.lastIndexOf('#'))) { + hasFollowingMinusSign = true; + minusSign = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getMinusSign(); + } + } + originalNumberPattern = numberPattern; + } + originalNumberFormat = numberFormat; + } + } + + private static final class GregorianCalendar extends Calendar { + @Override + protected void computeTime() { + } + + @Override + protected void computeFields() { + } + + @Override + public void add(int field, int amount) { + } + + @Override + public void roll(int field, boolean up) { + } + + @Override + public int getMinimum(int field) { + return 0; + } + + @Override + public int getMaximum(int field) { + return 0; + } + + @Override + public int getGreatestMinimum(int field) { + return 0; + } + + @Override + public int getLeastMaximum(int field) { + return 0; + } + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/BitSet.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/BitSet.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1188 @@ +/* + * Copyright (c) 1995, 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.util; + +import java.io.*; + +/** + * This class implements a vector of bits that grows as needed. Each + * component of the bit set has a {@code boolean} value. The + * bits of a {@code BitSet} are indexed by nonnegative integers. + * Individual indexed bits can be examined, set, or cleared. One + * {@code BitSet} may be used to modify the contents of another + * {@code BitSet} through logical AND, logical inclusive OR, and + * logical exclusive OR operations. + * + *

By default, all bits in the set initially have the value + * {@code false}. + * + *

Every bit set has a current size, which is the number of bits + * of space currently in use by the bit set. Note that the size is + * related to the implementation of a bit set, so it may change with + * implementation. The length of a bit set relates to logical length + * of a bit set and is defined independently of implementation. + * + *

Unless otherwise noted, passing a null parameter to any of the + * methods in a {@code BitSet} will result in a + * {@code NullPointerException}. + * + *

A {@code BitSet} is not safe for multithreaded use without + * external synchronization. + * + * @author Arthur van Hoff + * @author Michael McCloskey + * @author Martin Buchholz + * @since JDK1.0 + */ +public class BitSet implements Cloneable, java.io.Serializable { + /* + * BitSets are packed into arrays of "words." Currently a word is + * a long, which consists of 64 bits, requiring 6 address bits. + * The choice of word size is determined purely by performance concerns. + */ + private final static int ADDRESS_BITS_PER_WORD = 6; + private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD; + private final static int BIT_INDEX_MASK = BITS_PER_WORD - 1; + + /* Used to shift left or right for a partial word mask */ + private static final long WORD_MASK = 0xffffffffffffffffL; + + /** + * @serialField bits long[] + * + * The bits in this BitSet. The ith bit is stored in bits[i/64] at + * bit position i % 64 (where bit position 0 refers to the least + * significant bit and 63 refers to the most significant bit). + */ + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("bits", long[].class), + }; + + /** + * The internal field corresponding to the serialField "bits". + */ + private long[] words; + + /** + * The number of words in the logical size of this BitSet. + */ + private transient int wordsInUse = 0; + + /** + * Whether the size of "words" is user-specified. If so, we assume + * the user knows what he's doing and try harder to preserve it. + */ + private transient boolean sizeIsSticky = false; + + /* use serialVersionUID from JDK 1.0.2 for interoperability */ + private static final long serialVersionUID = 7997698588986878753L; + + /** + * Given a bit index, return word index containing it. + */ + private static int wordIndex(int bitIndex) { + return bitIndex >> ADDRESS_BITS_PER_WORD; + } + + /** + * Every public method must preserve these invariants. + */ + private void checkInvariants() { + assert(wordsInUse == 0 || words[wordsInUse - 1] != 0); + assert(wordsInUse >= 0 && wordsInUse <= words.length); + assert(wordsInUse == words.length || words[wordsInUse] == 0); + } + + /** + * Sets the field wordsInUse to the logical size in words of the bit set. + * WARNING:This method assumes that the number of words actually in use is + * less than or equal to the current value of wordsInUse! + */ + private void recalculateWordsInUse() { + // Traverse the bitset until a used word is found + int i; + for (i = wordsInUse-1; i >= 0; i--) + if (words[i] != 0) + break; + + wordsInUse = i+1; // The new logical size + } + + /** + * Creates a new bit set. All bits are initially {@code false}. + */ + public BitSet() { + initWords(BITS_PER_WORD); + sizeIsSticky = false; + } + + /** + * Creates a bit set whose initial size is large enough to explicitly + * represent bits with indices in the range {@code 0} through + * {@code nbits-1}. All bits are initially {@code false}. + * + * @param nbits the initial size of the bit set + * @throws NegativeArraySizeException if the specified initial size + * is negative + */ + public BitSet(int nbits) { + // nbits can't be negative; size 0 is OK + if (nbits < 0) + throw new NegativeArraySizeException("nbits < 0: " + nbits); + + initWords(nbits); + sizeIsSticky = true; + } + + private void initWords(int nbits) { + words = new long[wordIndex(nbits-1) + 1]; + } + + /** + * Creates a bit set using words as the internal representation. + * The last word (if there is one) must be non-zero. + */ + private BitSet(long[] words) { + this.words = words; + this.wordsInUse = words.length; + checkInvariants(); + } + + /** + * Returns a new bit set containing all the bits in the given long array. + * + *

More precisely, + *
{@code BitSet.valueOf(longs).get(n) == ((longs[n/64] & (1L<<(n%64))) != 0)} + *
for all {@code n < 64 * longs.length}. + * + *

This method is equivalent to + * {@code BitSet.valueOf(LongBuffer.wrap(longs))}. + * + * @param longs a long array containing a little-endian representation + * of a sequence of bits to be used as the initial bits of the + * new bit set + * @since 1.7 + */ + public static BitSet valueOf(long[] longs) { + int n; + for (n = longs.length; n > 0 && longs[n - 1] == 0; n--) + ; + return new BitSet(Arrays.copyOf(longs, n)); + } + + /** + * Returns a new bit set containing all the bits in the given long + * buffer between its position and limit. + * + *

More precisely, + *
{@code BitSet.valueOf(lb).get(n) == ((lb.get(lb.position()+n/64) & (1L<<(n%64))) != 0)} + *
for all {@code n < 64 * lb.remaining()}. + * + *

The long buffer is not modified by this method, and no + * reference to the buffer is retained by the bit set. + * + * @param lb a long buffer containing a little-endian representation + * of a sequence of bits between its position and limit, to be + * used as the initial bits of the new bit set + * @since 1.7 + */ +// public static BitSet valueOf(LongBuffer lb) { +// lb = lb.slice(); +// int n; +// for (n = lb.remaining(); n > 0 && lb.get(n - 1) == 0; n--) +// ; +// long[] words = new long[n]; +// lb.get(words); +// return new BitSet(words); +// } + + /** + * Returns a new bit set containing all the bits in the given byte array. + * + *

More precisely, + *
{@code BitSet.valueOf(bytes).get(n) == ((bytes[n/8] & (1<<(n%8))) != 0)} + *
for all {@code n < 8 * bytes.length}. + * + *

This method is equivalent to + * {@code BitSet.valueOf(ByteBuffer.wrap(bytes))}. + * + * @param bytes a byte array containing a little-endian + * representation of a sequence of bits to be used as the + * initial bits of the new bit set + * @since 1.7 + */ +// public static BitSet valueOf(byte[] bytes) { +// return BitSet.valueOf(ByteBuffer.wrap(bytes)); +// } + + /** + * Returns a new bit set containing all the bits in the given byte + * buffer between its position and limit. + * + *

More precisely, + *
{@code BitSet.valueOf(bb).get(n) == ((bb.get(bb.position()+n/8) & (1<<(n%8))) != 0)} + *
for all {@code n < 8 * bb.remaining()}. + * + *

The byte buffer is not modified by this method, and no + * reference to the buffer is retained by the bit set. + * + * @param bb a byte buffer containing a little-endian representation + * of a sequence of bits between its position and limit, to be + * used as the initial bits of the new bit set + * @since 1.7 + */ +// public static BitSet valueOf(ByteBuffer bb) { +// bb = bb.slice().order(ByteOrder.LITTLE_ENDIAN); +// int n; +// for (n = bb.remaining(); n > 0 && bb.get(n - 1) == 0; n--) +// ; +// long[] words = new long[(n + 7) / 8]; +// bb.limit(n); +// int i = 0; +// while (bb.remaining() >= 8) +// words[i++] = bb.getLong(); +// for (int remaining = bb.remaining(), j = 0; j < remaining; j++) +// words[i] |= (bb.get() & 0xffL) << (8 * j); +// return new BitSet(words); +// } + + /** + * Returns a new byte array containing all the bits in this bit set. + * + *

More precisely, if + *
{@code byte[] bytes = s.toByteArray();} + *
then {@code bytes.length == (s.length()+7)/8} and + *
{@code s.get(n) == ((bytes[n/8] & (1<<(n%8))) != 0)} + *
for all {@code n < 8 * bytes.length}. + * + * @return a byte array containing a little-endian representation + * of all the bits in this bit set + * @since 1.7 + */ +// public byte[] toByteArray() { +// int n = wordsInUse; +// if (n == 0) +// return new byte[0]; +// int len = 8 * (n-1); +// for (long x = words[n - 1]; x != 0; x >>>= 8) +// len++; +// byte[] bytes = new byte[len]; +// ByteBuffer bb = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); +// for (int i = 0; i < n - 1; i++) +// bb.putLong(words[i]); +// for (long x = words[n - 1]; x != 0; x >>>= 8) +// bb.put((byte) (x & 0xff)); +// return bytes; +// } + + /** + * Returns a new long array containing all the bits in this bit set. + * + *

More precisely, if + *
{@code long[] longs = s.toLongArray();} + *
then {@code longs.length == (s.length()+63)/64} and + *
{@code s.get(n) == ((longs[n/64] & (1L<<(n%64))) != 0)} + *
for all {@code n < 64 * longs.length}. + * + * @return a long array containing a little-endian representation + * of all the bits in this bit set + * @since 1.7 + */ + public long[] toLongArray() { + return Arrays.copyOf(words, wordsInUse); + } + + /** + * Ensures that the BitSet can hold enough words. + * @param wordsRequired the minimum acceptable number of words. + */ + private void ensureCapacity(int wordsRequired) { + if (words.length < wordsRequired) { + // Allocate larger of doubled size or required size + int request = Math.max(2 * words.length, wordsRequired); + words = Arrays.copyOf(words, request); + sizeIsSticky = false; + } + } + + /** + * Ensures that the BitSet can accommodate a given wordIndex, + * temporarily violating the invariants. The caller must + * restore the invariants before returning to the user, + * possibly using recalculateWordsInUse(). + * @param wordIndex the index to be accommodated. + */ + private void expandTo(int wordIndex) { + int wordsRequired = wordIndex+1; + if (wordsInUse < wordsRequired) { + ensureCapacity(wordsRequired); + wordsInUse = wordsRequired; + } + } + + /** + * Checks that fromIndex ... toIndex is a valid range of bit indices. + */ + private static void checkRange(int fromIndex, int toIndex) { + if (fromIndex < 0) + throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex); + if (toIndex < 0) + throw new IndexOutOfBoundsException("toIndex < 0: " + toIndex); + if (fromIndex > toIndex) + throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + + " > toIndex: " + toIndex); + } + + /** + * Sets the bit at the specified index to the complement of its + * current value. + * + * @param bitIndex the index of the bit to flip + * @throws IndexOutOfBoundsException if the specified index is negative + * @since 1.4 + */ + public void flip(int bitIndex) { + if (bitIndex < 0) + throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex); + + int wordIndex = wordIndex(bitIndex); + expandTo(wordIndex); + + words[wordIndex] ^= (1L << bitIndex); + + recalculateWordsInUse(); + checkInvariants(); + } + + /** + * Sets each bit from the specified {@code fromIndex} (inclusive) to the + * specified {@code toIndex} (exclusive) to the complement of its current + * value. + * + * @param fromIndex index of the first bit to flip + * @param toIndex index after the last bit to flip + * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, + * or {@code toIndex} is negative, or {@code fromIndex} is + * larger than {@code toIndex} + * @since 1.4 + */ + public void flip(int fromIndex, int toIndex) { + checkRange(fromIndex, toIndex); + + if (fromIndex == toIndex) + return; + + int startWordIndex = wordIndex(fromIndex); + int endWordIndex = wordIndex(toIndex - 1); + expandTo(endWordIndex); + + long firstWordMask = WORD_MASK << fromIndex; + long lastWordMask = WORD_MASK >>> -toIndex; + if (startWordIndex == endWordIndex) { + // Case 1: One word + words[startWordIndex] ^= (firstWordMask & lastWordMask); + } else { + // Case 2: Multiple words + // Handle first word + words[startWordIndex] ^= firstWordMask; + + // Handle intermediate words, if any + for (int i = startWordIndex+1; i < endWordIndex; i++) + words[i] ^= WORD_MASK; + + // Handle last word + words[endWordIndex] ^= lastWordMask; + } + + recalculateWordsInUse(); + checkInvariants(); + } + + /** + * Sets the bit at the specified index to {@code true}. + * + * @param bitIndex a bit index + * @throws IndexOutOfBoundsException if the specified index is negative + * @since JDK1.0 + */ + public void set(int bitIndex) { + if (bitIndex < 0) + throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex); + + int wordIndex = wordIndex(bitIndex); + expandTo(wordIndex); + + words[wordIndex] |= (1L << bitIndex); // Restores invariants + + checkInvariants(); + } + + /** + * Sets the bit at the specified index to the specified value. + * + * @param bitIndex a bit index + * @param value a boolean value to set + * @throws IndexOutOfBoundsException if the specified index is negative + * @since 1.4 + */ + public void set(int bitIndex, boolean value) { + if (value) + set(bitIndex); + else + clear(bitIndex); + } + + /** + * Sets the bits from the specified {@code fromIndex} (inclusive) to the + * specified {@code toIndex} (exclusive) to {@code true}. + * + * @param fromIndex index of the first bit to be set + * @param toIndex index after the last bit to be set + * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, + * or {@code toIndex} is negative, or {@code fromIndex} is + * larger than {@code toIndex} + * @since 1.4 + */ + public void set(int fromIndex, int toIndex) { + checkRange(fromIndex, toIndex); + + if (fromIndex == toIndex) + return; + + // Increase capacity if necessary + int startWordIndex = wordIndex(fromIndex); + int endWordIndex = wordIndex(toIndex - 1); + expandTo(endWordIndex); + + long firstWordMask = WORD_MASK << fromIndex; + long lastWordMask = WORD_MASK >>> -toIndex; + if (startWordIndex == endWordIndex) { + // Case 1: One word + words[startWordIndex] |= (firstWordMask & lastWordMask); + } else { + // Case 2: Multiple words + // Handle first word + words[startWordIndex] |= firstWordMask; + + // Handle intermediate words, if any + for (int i = startWordIndex+1; i < endWordIndex; i++) + words[i] = WORD_MASK; + + // Handle last word (restores invariants) + words[endWordIndex] |= lastWordMask; + } + + checkInvariants(); + } + + /** + * Sets the bits from the specified {@code fromIndex} (inclusive) to the + * specified {@code toIndex} (exclusive) to the specified value. + * + * @param fromIndex index of the first bit to be set + * @param toIndex index after the last bit to be set + * @param value value to set the selected bits to + * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, + * or {@code toIndex} is negative, or {@code fromIndex} is + * larger than {@code toIndex} + * @since 1.4 + */ + public void set(int fromIndex, int toIndex, boolean value) { + if (value) + set(fromIndex, toIndex); + else + clear(fromIndex, toIndex); + } + + /** + * Sets the bit specified by the index to {@code false}. + * + * @param bitIndex the index of the bit to be cleared + * @throws IndexOutOfBoundsException if the specified index is negative + * @since JDK1.0 + */ + public void clear(int bitIndex) { + if (bitIndex < 0) + throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex); + + int wordIndex = wordIndex(bitIndex); + if (wordIndex >= wordsInUse) + return; + + words[wordIndex] &= ~(1L << bitIndex); + + recalculateWordsInUse(); + checkInvariants(); + } + + /** + * Sets the bits from the specified {@code fromIndex} (inclusive) to the + * specified {@code toIndex} (exclusive) to {@code false}. + * + * @param fromIndex index of the first bit to be cleared + * @param toIndex index after the last bit to be cleared + * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, + * or {@code toIndex} is negative, or {@code fromIndex} is + * larger than {@code toIndex} + * @since 1.4 + */ + public void clear(int fromIndex, int toIndex) { + checkRange(fromIndex, toIndex); + + if (fromIndex == toIndex) + return; + + int startWordIndex = wordIndex(fromIndex); + if (startWordIndex >= wordsInUse) + return; + + int endWordIndex = wordIndex(toIndex - 1); + if (endWordIndex >= wordsInUse) { + toIndex = length(); + endWordIndex = wordsInUse - 1; + } + + long firstWordMask = WORD_MASK << fromIndex; + long lastWordMask = WORD_MASK >>> -toIndex; + if (startWordIndex == endWordIndex) { + // Case 1: One word + words[startWordIndex] &= ~(firstWordMask & lastWordMask); + } else { + // Case 2: Multiple words + // Handle first word + words[startWordIndex] &= ~firstWordMask; + + // Handle intermediate words, if any + for (int i = startWordIndex+1; i < endWordIndex; i++) + words[i] = 0; + + // Handle last word + words[endWordIndex] &= ~lastWordMask; + } + + recalculateWordsInUse(); + checkInvariants(); + } + + /** + * Sets all of the bits in this BitSet to {@code false}. + * + * @since 1.4 + */ + public void clear() { + while (wordsInUse > 0) + words[--wordsInUse] = 0; + } + + /** + * Returns the value of the bit with the specified index. The value + * is {@code true} if the bit with the index {@code bitIndex} + * is currently set in this {@code BitSet}; otherwise, the result + * is {@code false}. + * + * @param bitIndex the bit index + * @return the value of the bit with the specified index + * @throws IndexOutOfBoundsException if the specified index is negative + */ + public boolean get(int bitIndex) { + if (bitIndex < 0) + throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex); + + checkInvariants(); + + int wordIndex = wordIndex(bitIndex); + return (wordIndex < wordsInUse) + && ((words[wordIndex] & (1L << bitIndex)) != 0); + } + + /** + * Returns a new {@code BitSet} composed of bits from this {@code BitSet} + * from {@code fromIndex} (inclusive) to {@code toIndex} (exclusive). + * + * @param fromIndex index of the first bit to include + * @param toIndex index after the last bit to include + * @return a new {@code BitSet} from a range of this {@code BitSet} + * @throws IndexOutOfBoundsException if {@code fromIndex} is negative, + * or {@code toIndex} is negative, or {@code fromIndex} is + * larger than {@code toIndex} + * @since 1.4 + */ + public BitSet get(int fromIndex, int toIndex) { + checkRange(fromIndex, toIndex); + + checkInvariants(); + + int len = length(); + + // If no set bits in range return empty bitset + if (len <= fromIndex || fromIndex == toIndex) + return new BitSet(0); + + // An optimization + if (toIndex > len) + toIndex = len; + + BitSet result = new BitSet(toIndex - fromIndex); + int targetWords = wordIndex(toIndex - fromIndex - 1) + 1; + int sourceIndex = wordIndex(fromIndex); + boolean wordAligned = ((fromIndex & BIT_INDEX_MASK) == 0); + + // Process all words but the last word + for (int i = 0; i < targetWords - 1; i++, sourceIndex++) + result.words[i] = wordAligned ? words[sourceIndex] : + (words[sourceIndex] >>> fromIndex) | + (words[sourceIndex+1] << -fromIndex); + + // Process the last word + long lastWordMask = WORD_MASK >>> -toIndex; + result.words[targetWords - 1] = + ((toIndex-1) & BIT_INDEX_MASK) < (fromIndex & BIT_INDEX_MASK) + ? /* straddles source words */ + ((words[sourceIndex] >>> fromIndex) | + (words[sourceIndex+1] & lastWordMask) << -fromIndex) + : + ((words[sourceIndex] & lastWordMask) >>> fromIndex); + + // Set wordsInUse correctly + result.wordsInUse = targetWords; + result.recalculateWordsInUse(); + result.checkInvariants(); + + return result; + } + + /** + * Returns the index of the first bit that is set to {@code true} + * that occurs on or after the specified starting index. If no such + * bit exists then {@code -1} is returned. + * + *

To iterate over the {@code true} bits in a {@code BitSet}, + * use the following loop: + * + *

 {@code
+     * for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) {
+     *     // operate on index i here
+     * }}
+ * + * @param fromIndex the index to start checking from (inclusive) + * @return the index of the next set bit, or {@code -1} if there + * is no such bit + * @throws IndexOutOfBoundsException if the specified index is negative + * @since 1.4 + */ + public int nextSetBit(int fromIndex) { + if (fromIndex < 0) + throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex); + + checkInvariants(); + + int u = wordIndex(fromIndex); + if (u >= wordsInUse) + return -1; + + long word = words[u] & (WORD_MASK << fromIndex); + + while (true) { + if (word != 0) + return (u * BITS_PER_WORD) + Long.numberOfTrailingZeros(word); + if (++u == wordsInUse) + return -1; + word = words[u]; + } + } + + /** + * Returns the index of the first bit that is set to {@code false} + * that occurs on or after the specified starting index. + * + * @param fromIndex the index to start checking from (inclusive) + * @return the index of the next clear bit + * @throws IndexOutOfBoundsException if the specified index is negative + * @since 1.4 + */ + public int nextClearBit(int fromIndex) { + // Neither spec nor implementation handle bitsets of maximal length. + // See 4816253. + if (fromIndex < 0) + throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex); + + checkInvariants(); + + int u = wordIndex(fromIndex); + if (u >= wordsInUse) + return fromIndex; + + long word = ~words[u] & (WORD_MASK << fromIndex); + + while (true) { + if (word != 0) + return (u * BITS_PER_WORD) + Long.numberOfTrailingZeros(word); + if (++u == wordsInUse) + return wordsInUse * BITS_PER_WORD; + word = ~words[u]; + } + } + + /** + * Returns the index of the nearest bit that is set to {@code true} + * that occurs on or before the specified starting index. + * If no such bit exists, or if {@code -1} is given as the + * starting index, then {@code -1} is returned. + * + *

To iterate over the {@code true} bits in a {@code BitSet}, + * use the following loop: + * + *

 {@code
+     * for (int i = bs.length(); (i = bs.previousSetBit(i-1)) >= 0; ) {
+     *     // operate on index i here
+     * }}
+ * + * @param fromIndex the index to start checking from (inclusive) + * @return the index of the previous set bit, or {@code -1} if there + * is no such bit + * @throws IndexOutOfBoundsException if the specified index is less + * than {@code -1} + * @since 1.7 + */ + public int previousSetBit(int fromIndex) { + if (fromIndex < 0) { + if (fromIndex == -1) + return -1; + throw new IndexOutOfBoundsException( + "fromIndex < -1: " + fromIndex); + } + + checkInvariants(); + + int u = wordIndex(fromIndex); + if (u >= wordsInUse) + return length() - 1; + + long word = words[u] & (WORD_MASK >>> -(fromIndex+1)); + + while (true) { + if (word != 0) + return (u+1) * BITS_PER_WORD - 1 - Long.numberOfLeadingZeros(word); + if (u-- == 0) + return -1; + word = words[u]; + } + } + + /** + * Returns the index of the nearest bit that is set to {@code false} + * that occurs on or before the specified starting index. + * If no such bit exists, or if {@code -1} is given as the + * starting index, then {@code -1} is returned. + * + * @param fromIndex the index to start checking from (inclusive) + * @return the index of the previous clear bit, or {@code -1} if there + * is no such bit + * @throws IndexOutOfBoundsException if the specified index is less + * than {@code -1} + * @since 1.7 + */ + public int previousClearBit(int fromIndex) { + if (fromIndex < 0) { + if (fromIndex == -1) + return -1; + throw new IndexOutOfBoundsException( + "fromIndex < -1: " + fromIndex); + } + + checkInvariants(); + + int u = wordIndex(fromIndex); + if (u >= wordsInUse) + return fromIndex; + + long word = ~words[u] & (WORD_MASK >>> -(fromIndex+1)); + + while (true) { + if (word != 0) + return (u+1) * BITS_PER_WORD -1 - Long.numberOfLeadingZeros(word); + if (u-- == 0) + return -1; + word = ~words[u]; + } + } + + /** + * Returns the "logical size" of this {@code BitSet}: the index of + * the highest set bit in the {@code BitSet} plus one. Returns zero + * if the {@code BitSet} contains no set bits. + * + * @return the logical size of this {@code BitSet} + * @since 1.2 + */ + public int length() { + if (wordsInUse == 0) + return 0; + + return BITS_PER_WORD * (wordsInUse - 1) + + (BITS_PER_WORD - Long.numberOfLeadingZeros(words[wordsInUse - 1])); + } + + /** + * Returns true if this {@code BitSet} contains no bits that are set + * to {@code true}. + * + * @return boolean indicating whether this {@code BitSet} is empty + * @since 1.4 + */ + public boolean isEmpty() { + return wordsInUse == 0; + } + + /** + * Returns true if the specified {@code BitSet} has any bits set to + * {@code true} that are also set to {@code true} in this {@code BitSet}. + * + * @param set {@code BitSet} to intersect with + * @return boolean indicating whether this {@code BitSet} intersects + * the specified {@code BitSet} + * @since 1.4 + */ + public boolean intersects(BitSet set) { + for (int i = Math.min(wordsInUse, set.wordsInUse) - 1; i >= 0; i--) + if ((words[i] & set.words[i]) != 0) + return true; + return false; + } + + /** + * Returns the number of bits set to {@code true} in this {@code BitSet}. + * + * @return the number of bits set to {@code true} in this {@code BitSet} + * @since 1.4 + */ + public int cardinality() { + int sum = 0; + for (int i = 0; i < wordsInUse; i++) + sum += Long.bitCount(words[i]); + return sum; + } + + /** + * Performs a logical AND of this target bit set with the + * argument bit set. This bit set is modified so that each bit in it + * has the value {@code true} if and only if it both initially + * had the value {@code true} and the corresponding bit in the + * bit set argument also had the value {@code true}. + * + * @param set a bit set + */ + public void and(BitSet set) { + if (this == set) + return; + + while (wordsInUse > set.wordsInUse) + words[--wordsInUse] = 0; + + // Perform logical AND on words in common + for (int i = 0; i < wordsInUse; i++) + words[i] &= set.words[i]; + + recalculateWordsInUse(); + checkInvariants(); + } + + /** + * Performs a logical OR of this bit set with the bit set + * argument. This bit set is modified so that a bit in it has the + * value {@code true} if and only if it either already had the + * value {@code true} or the corresponding bit in the bit set + * argument has the value {@code true}. + * + * @param set a bit set + */ + public void or(BitSet set) { + if (this == set) + return; + + int wordsInCommon = Math.min(wordsInUse, set.wordsInUse); + + if (wordsInUse < set.wordsInUse) { + ensureCapacity(set.wordsInUse); + wordsInUse = set.wordsInUse; + } + + // Perform logical OR on words in common + for (int i = 0; i < wordsInCommon; i++) + words[i] |= set.words[i]; + + // Copy any remaining words + if (wordsInCommon < set.wordsInUse) + System.arraycopy(set.words, wordsInCommon, + words, wordsInCommon, + wordsInUse - wordsInCommon); + + // recalculateWordsInUse() is unnecessary + checkInvariants(); + } + + /** + * Performs a logical XOR of this bit set with the bit set + * argument. This bit set is modified so that a bit in it has the + * value {@code true} if and only if one of the following + * statements holds: + *
    + *
  • The bit initially has the value {@code true}, and the + * corresponding bit in the argument has the value {@code false}. + *
  • The bit initially has the value {@code false}, and the + * corresponding bit in the argument has the value {@code true}. + *
+ * + * @param set a bit set + */ + public void xor(BitSet set) { + int wordsInCommon = Math.min(wordsInUse, set.wordsInUse); + + if (wordsInUse < set.wordsInUse) { + ensureCapacity(set.wordsInUse); + wordsInUse = set.wordsInUse; + } + + // Perform logical XOR on words in common + for (int i = 0; i < wordsInCommon; i++) + words[i] ^= set.words[i]; + + // Copy any remaining words + if (wordsInCommon < set.wordsInUse) + System.arraycopy(set.words, wordsInCommon, + words, wordsInCommon, + set.wordsInUse - wordsInCommon); + + recalculateWordsInUse(); + checkInvariants(); + } + + /** + * Clears all of the bits in this {@code BitSet} whose corresponding + * bit is set in the specified {@code BitSet}. + * + * @param set the {@code BitSet} with which to mask this + * {@code BitSet} + * @since 1.2 + */ + public void andNot(BitSet set) { + // Perform logical (a & !b) on words in common + for (int i = Math.min(wordsInUse, set.wordsInUse) - 1; i >= 0; i--) + words[i] &= ~set.words[i]; + + recalculateWordsInUse(); + checkInvariants(); + } + + /** + * Returns the hash code value for this bit set. The hash code depends + * only on which bits are set within this {@code BitSet}. + * + *

The hash code is defined to be the result of the following + * calculation: + *

 {@code
+     * public int hashCode() {
+     *     long h = 1234;
+     *     long[] words = toLongArray();
+     *     for (int i = words.length; --i >= 0; )
+     *         h ^= words[i] * (i + 1);
+     *     return (int)((h >> 32) ^ h);
+     * }}
+ * Note that the hash code changes if the set of bits is altered. + * + * @return the hash code value for this bit set + */ + public int hashCode() { + long h = 1234; + for (int i = wordsInUse; --i >= 0; ) + h ^= words[i] * (i + 1); + + return (int)((h >> 32) ^ h); + } + + /** + * Returns the number of bits of space actually in use by this + * {@code BitSet} to represent bit values. + * The maximum element in the set is the size - 1st element. + * + * @return the number of bits currently in this bit set + */ + public int size() { + return words.length * BITS_PER_WORD; + } + + /** + * Compares this object against the specified object. + * The result is {@code true} if and only if the argument is + * not {@code null} and is a {@code Bitset} object that has + * exactly the same set of bits set to {@code true} as this bit + * set. That is, for every nonnegative {@code int} index {@code k}, + *
((BitSet)obj).get(k) == this.get(k)
+ * must be true. The current sizes of the two bit sets are not compared. + * + * @param obj the object to compare with + * @return {@code true} if the objects are the same; + * {@code false} otherwise + * @see #size() + */ + public boolean equals(Object obj) { + if (!(obj instanceof BitSet)) + return false; + if (this == obj) + return true; + + BitSet set = (BitSet) obj; + + checkInvariants(); + set.checkInvariants(); + + if (wordsInUse != set.wordsInUse) + return false; + + // Check words in use by both BitSets + for (int i = 0; i < wordsInUse; i++) + if (words[i] != set.words[i]) + return false; + + return true; + } + + /** + * Cloning this {@code BitSet} produces a new {@code BitSet} + * that is equal to it. + * The clone of the bit set is another bit set that has exactly the + * same bits set to {@code true} as this bit set. + * + * @return a clone of this bit set + * @see #size() + */ + public Object clone() { + if (! sizeIsSticky) + trimToSize(); + + try { + BitSet result = (BitSet) super.clone(); + result.words = words.clone(); + result.checkInvariants(); + return result; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * Attempts to reduce internal storage used for the bits in this bit set. + * Calling this method may, but is not required to, affect the value + * returned by a subsequent call to the {@link #size()} method. + */ + private void trimToSize() { + if (wordsInUse != words.length) { + words = Arrays.copyOf(words, wordsInUse); + checkInvariants(); + } + } + + /** + * Save the state of the {@code BitSet} instance to a stream (i.e., + * serialize it). + */ + private void writeObject(ObjectOutputStream s) + throws IOException { + + checkInvariants(); + + if (! sizeIsSticky) + trimToSize(); + + ObjectOutputStream.PutField fields = s.putFields(); + fields.put("bits", words); + s.writeFields(); + } + + /** + * Reconstitute the {@code BitSet} instance from a stream (i.e., + * deserialize it). + */ + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + + ObjectInputStream.GetField fields = s.readFields(); + words = (long[]) fields.get("bits", null); + + // Assume maximum length then find real length + // because recalculateWordsInUse assumes maintenance + // or reduction in logical size + wordsInUse = words.length; + recalculateWordsInUse(); + sizeIsSticky = (words.length > 0 && words[words.length-1] == 0L); // heuristic + checkInvariants(); + } + + /** + * Returns a string representation of this bit set. For every index + * for which this {@code BitSet} contains a bit in the set + * state, the decimal representation of that index is included in + * the result. Such indices are listed in order from lowest to + * highest, separated by ", " (a comma and a space) and + * surrounded by braces, resulting in the usual mathematical + * notation for a set of integers. + * + *

Example: + *

+     * BitSet drPepper = new BitSet();
+ * Now {@code drPepper.toString()} returns "{@code {}}".

+ *

+     * drPepper.set(2);
+ * Now {@code drPepper.toString()} returns "{@code {2}}".

+ *

+     * drPepper.set(4);
+     * drPepper.set(10);
+ * Now {@code drPepper.toString()} returns "{@code {2, 4, 10}}". + * + * @return a string representation of this bit set + */ + public String toString() { + checkInvariants(); + + int numBits = (wordsInUse > 128) ? + cardinality() : wordsInUse * BITS_PER_WORD; + StringBuilder b = new StringBuilder(6*numBits + 2); + b.append('{'); + + int i = nextSetBit(0); + if (i != -1) { + b.append(i); + for (i = nextSetBit(i+1); i >= 0; i = nextSetBit(i+1)) { + int endOfRun = nextClearBit(i); + do { b.append(", ").append(i); } + while (++i < endOfRun); + } + } + + b.append('}'); + return b.toString(); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/Calendar.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/Calendar.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,2806 @@ +/* + * 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996-1998 - All Rights Reserved + * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.util; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OptionalDataException; +import java.io.Serializable; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.text.DateFormat; +import java.text.DateFormatSymbols; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * The Calendar class is an abstract class that provides methods + * for converting between a specific instant in time and a set of {@link + * #fields calendar fields} such as YEAR, MONTH, + * DAY_OF_MONTH, HOUR, and so on, and for + * manipulating the calendar fields, such as getting the date of the next + * week. An instant in time can be represented by a millisecond value that is + * an offset from the Epoch, January 1, 1970 + * 00:00:00.000 GMT (Gregorian). + * + *

The class also provides additional fields and methods for + * implementing a concrete calendar system outside the package. Those + * fields and methods are defined as protected. + * + *

+ * Like other locale-sensitive classes, Calendar provides a + * class method, getInstance, for getting a generally useful + * object of this type. Calendar's getInstance method + * returns a Calendar object whose + * calendar fields have been initialized with the current date and time: + *

+ *
+ *     Calendar rightNow = Calendar.getInstance();
+ * 
+ *
+ * + *

A Calendar object can produce all the calendar field values + * needed to implement the date-time formatting for a particular language and + * calendar style (for example, Japanese-Gregorian, Japanese-Traditional). + * Calendar defines the range of values returned by + * certain calendar fields, as well as their meaning. For example, + * the first month of the calendar system has value MONTH == + * JANUARY for all calendars. Other values are defined by the + * concrete subclass, such as ERA. See individual field + * documentation and subclass documentation for details. + * + *

Getting and Setting Calendar Field Values

+ * + *

The calendar field values can be set by calling the set + * methods. Any field values set in a Calendar will not be + * interpreted until it needs to calculate its time value (milliseconds from + * the Epoch) or values of the calendar fields. Calling the + * get, getTimeInMillis, getTime, + * add and roll involves such calculation. + * + *

Leniency

+ * + *

Calendar has two modes for interpreting the calendar + * fields, lenient and non-lenient. When a + * Calendar is in lenient mode, it accepts a wider range of + * calendar field values than it produces. When a Calendar + * recomputes calendar field values for return by get(), all of + * the calendar fields are normalized. For example, a lenient + * GregorianCalendar interprets MONTH == JANUARY, + * DAY_OF_MONTH == 32 as February 1. + + *

When a Calendar is in non-lenient mode, it throws an + * exception if there is any inconsistency in its calendar fields. For + * example, a GregorianCalendar always produces + * DAY_OF_MONTH values between 1 and the length of the month. A + * non-lenient GregorianCalendar throws an exception upon + * calculating its time or calendar field values if any out-of-range field + * value has been set. + * + *

First Week

+ * + * Calendar defines a locale-specific seven day week using two + * parameters: the first day of the week and the minimal days in first week + * (from 1 to 7). These numbers are taken from the locale resource data when a + * Calendar is constructed. They may also be specified explicitly + * through the methods for setting their values. + * + *

When setting or getting the WEEK_OF_MONTH or + * WEEK_OF_YEAR fields, Calendar must determine the + * first week of the month or year as a reference point. The first week of a + * month or year is defined as the earliest seven day period beginning on + * getFirstDayOfWeek() and containing at least + * getMinimalDaysInFirstWeek() days of that month or year. Weeks + * numbered ..., -1, 0 precede the first week; weeks numbered 2, 3,... follow + * it. Note that the normalized numbering returned by get() may be + * different. For example, a specific Calendar subclass may + * designate the week before week 1 of a year as week n of + * the previous year. + * + *

Calendar Fields Resolution

+ * + * When computing a date and time from the calendar fields, there + * may be insufficient information for the computation (such as only + * year and month with no day of month), or there may be inconsistent + * information (such as Tuesday, July 15, 1996 (Gregorian) -- July 15, + * 1996 is actually a Monday). Calendar will resolve + * calendar field values to determine the date and time in the + * following way. + * + *

If there is any conflict in calendar field values, + * Calendar gives priorities to calendar fields that have been set + * more recently. The following are the default combinations of the + * calendar fields. The most recent combination, as determined by the + * most recently set single field, will be used. + * + *

For the date fields: + *

+ *
+ * YEAR + MONTH + DAY_OF_MONTH
+ * YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
+ * YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
+ * YEAR + DAY_OF_YEAR
+ * YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
+ * 
+ * + * For the time of day fields: + *
+ *
+ * HOUR_OF_DAY
+ * AM_PM + HOUR
+ * 
+ * + *

If there are any calendar fields whose values haven't been set in the selected + * field combination, Calendar uses their default values. The default + * value of each field may vary by concrete calendar systems. For example, in + * GregorianCalendar, the default of a field is the same as that + * of the start of the Epoch: i.e., YEAR = 1970, MONTH = + * JANUARY, DAY_OF_MONTH = 1, etc. + * + *

+ * Note: There are certain possible ambiguities in + * interpretation of certain singular times, which are resolved in the + * following ways: + *

    + *
  1. 23:59 is the last minute of the day and 00:00 is the first + * minute of the next day. Thus, 23:59 on Dec 31, 1999 < 00:00 on + * Jan 1, 2000 < 00:01 on Jan 1, 2000. + * + *
  2. Although historically not precise, midnight also belongs to "am", + * and noon belongs to "pm", so on the same day, + * 12:00 am (midnight) < 12:01 am, and 12:00 pm (noon) < 12:01 pm + *
+ * + *

+ * The date or time format strings are not part of the definition of a + * calendar, as those must be modifiable or overridable by the user at + * runtime. Use {@link DateFormat} + * to format dates. + * + *

Field Manipulation

+ * + * The calendar fields can be changed using three methods: + * set(), add(), and roll().

+ * + *

set(f, value) changes calendar field + * f to value. In addition, it sets an + * internal member variable to indicate that calendar field f has + * been changed. Although calendar field f is changed immediately, + * the calendar's time value in milliseconds is not recomputed until the next call to + * get(), getTime(), getTimeInMillis(), + * add(), or roll() is made. Thus, multiple calls to + * set() do not trigger multiple, unnecessary + * computations. As a result of changing a calendar field using + * set(), other calendar fields may also change, depending on the + * calendar field, the calendar field value, and the calendar system. In addition, + * get(f) will not necessarily return value set by + * the call to the set method + * after the calendar fields have been recomputed. The specifics are determined by + * the concrete calendar class.

+ * + *

Example: Consider a GregorianCalendar + * originally set to August 31, 1999. Calling set(Calendar.MONTH, + * Calendar.SEPTEMBER) sets the date to September 31, + * 1999. This is a temporary internal representation that resolves to + * October 1, 1999 if getTime()is then called. However, a + * call to set(Calendar.DAY_OF_MONTH, 30) before the call to + * getTime() sets the date to September 30, 1999, since + * no recomputation occurs after set() itself.

+ * + *

add(f, delta) adds delta + * to field f. This is equivalent to calling set(f, + * get(f) + delta) with two adjustments:

+ * + *
+ *

Add rule 1. The value of field f + * after the call minus the value of field f before the + * call is delta, modulo any overflow that has occurred in + * field f. Overflow occurs when a field value exceeds its + * range and, as a result, the next larger field is incremented or + * decremented and the field value is adjusted back into its range.

+ * + *

Add rule 2. If a smaller field is expected to be + * invariant, but it is impossible for it to be equal to its + * prior value because of changes in its minimum or maximum after field + * f is changed or other constraints, such as time zone + * offset changes, then its value is adjusted to be as close + * as possible to its expected value. A smaller field represents a + * smaller unit of time. HOUR is a smaller field than + * DAY_OF_MONTH. No adjustment is made to smaller fields + * that are not expected to be invariant. The calendar system + * determines what fields are expected to be invariant.

+ *
+ * + *

In addition, unlike set(), add() forces + * an immediate recomputation of the calendar's milliseconds and all + * fields.

+ * + *

Example: Consider a GregorianCalendar + * originally set to August 31, 1999. Calling add(Calendar.MONTH, + * 13) sets the calendar to September 30, 2000. Add rule + * 1 sets the MONTH field to September, since + * adding 13 months to August gives September of the next year. Since + * DAY_OF_MONTH cannot be 31 in September in a + * GregorianCalendar, add rule 2 sets the + * DAY_OF_MONTH to 30, the closest possible value. Although + * it is a smaller field, DAY_OF_WEEK is not adjusted by + * rule 2, since it is expected to change when the month changes in a + * GregorianCalendar.

+ * + *

roll(f, delta) adds + * delta to field f without changing larger + * fields. This is equivalent to calling add(f, delta) with + * the following adjustment:

+ * + *
+ *

Roll rule. Larger fields are unchanged after the + * call. A larger field represents a larger unit of + * time. DAY_OF_MONTH is a larger field than + * HOUR.

+ *
+ * + *

Example: See {@link java.util.GregorianCalendar#roll(int, int)}. + * + *

Usage model. To motivate the behavior of + * add() and roll(), consider a user interface + * component with increment and decrement buttons for the month, day, and + * year, and an underlying GregorianCalendar. If the + * interface reads January 31, 1999 and the user presses the month + * increment button, what should it read? If the underlying + * implementation uses set(), it might read March 3, 1999. A + * better result would be February 28, 1999. Furthermore, if the user + * presses the month increment button again, it should read March 31, + * 1999, not March 28, 1999. By saving the original date and using either + * add() or roll(), depending on whether larger + * fields should be affected, the user interface can behave as most users + * will intuitively expect.

+ * + * @see java.lang.System#currentTimeMillis() + * @see Date + * @see GregorianCalendar + * @see TimeZone + * @see java.text.DateFormat + * @author Mark Davis, David Goldsmith, Chen-Lieh Huang, Alan Liu + * @since JDK1.1 + */ +public abstract class Calendar implements Serializable, Cloneable, Comparable { + + // Data flow in Calendar + // --------------------- + + // The current time is represented in two ways by Calendar: as UTC + // milliseconds from the epoch (1 January 1970 0:00 UTC), and as local + // fields such as MONTH, HOUR, AM_PM, etc. It is possible to compute the + // millis from the fields, and vice versa. The data needed to do this + // conversion is encapsulated by a TimeZone object owned by the Calendar. + // The data provided by the TimeZone object may also be overridden if the + // user sets the ZONE_OFFSET and/or DST_OFFSET fields directly. The class + // keeps track of what information was most recently set by the caller, and + // uses that to compute any other information as needed. + + // If the user sets the fields using set(), the data flow is as follows. + // This is implemented by the Calendar subclass's computeTime() method. + // During this process, certain fields may be ignored. The disambiguation + // algorithm for resolving which fields to pay attention to is described + // in the class documentation. + + // local fields (YEAR, MONTH, DATE, HOUR, MINUTE, etc.) + // | + // | Using Calendar-specific algorithm + // V + // local standard millis + // | + // | Using TimeZone or user-set ZONE_OFFSET / DST_OFFSET + // V + // UTC millis (in time data member) + + // If the user sets the UTC millis using setTime() or setTimeInMillis(), + // the data flow is as follows. This is implemented by the Calendar + // subclass's computeFields() method. + + // UTC millis (in time data member) + // | + // | Using TimeZone getOffset() + // V + // local standard millis + // | + // | Using Calendar-specific algorithm + // V + // local fields (YEAR, MONTH, DATE, HOUR, MINUTE, etc.) + + // In general, a round trip from fields, through local and UTC millis, and + // back out to fields is made when necessary. This is implemented by the + // complete() method. Resolving a partial set of fields into a UTC millis + // value allows all remaining fields to be generated from that value. If + // the Calendar is lenient, the fields are also renormalized to standard + // ranges when they are regenerated. + + /** + * Field number for get and set indicating the + * era, e.g., AD or BC in the Julian calendar. This is a calendar-specific + * value; see subclass documentation. + * + * @see GregorianCalendar#AD + * @see GregorianCalendar#BC + */ + public final static int ERA = 0; + + /** + * Field number for get and set indicating the + * year. This is a calendar-specific value; see subclass documentation. + */ + public final static int YEAR = 1; + + /** + * Field number for get and set indicating the + * month. This is a calendar-specific value. The first month of + * the year in the Gregorian and Julian calendars is + * JANUARY which is 0; the last depends on the number + * of months in a year. + * + * @see #JANUARY + * @see #FEBRUARY + * @see #MARCH + * @see #APRIL + * @see #MAY + * @see #JUNE + * @see #JULY + * @see #AUGUST + * @see #SEPTEMBER + * @see #OCTOBER + * @see #NOVEMBER + * @see #DECEMBER + * @see #UNDECIMBER + */ + public final static int MONTH = 2; + + /** + * Field number for get and set indicating the + * week number within the current year. The first week of the year, as + * defined by getFirstDayOfWeek() and + * getMinimalDaysInFirstWeek(), has value 1. Subclasses define + * the value of WEEK_OF_YEAR for days before the first week of + * the year. + * + * @see #getFirstDayOfWeek + * @see #getMinimalDaysInFirstWeek + */ + public final static int WEEK_OF_YEAR = 3; + + /** + * Field number for get and set indicating the + * week number within the current month. The first week of the month, as + * defined by getFirstDayOfWeek() and + * getMinimalDaysInFirstWeek(), has value 1. Subclasses define + * the value of WEEK_OF_MONTH for days before the first week of + * the month. + * + * @see #getFirstDayOfWeek + * @see #getMinimalDaysInFirstWeek + */ + public final static int WEEK_OF_MONTH = 4; + + /** + * Field number for get and set indicating the + * day of the month. This is a synonym for DAY_OF_MONTH. + * The first day of the month has value 1. + * + * @see #DAY_OF_MONTH + */ + public final static int DATE = 5; + + /** + * Field number for get and set indicating the + * day of the month. This is a synonym for DATE. + * The first day of the month has value 1. + * + * @see #DATE + */ + public final static int DAY_OF_MONTH = 5; + + /** + * Field number for get and set indicating the day + * number within the current year. The first day of the year has value 1. + */ + public final static int DAY_OF_YEAR = 6; + + /** + * Field number for get and set indicating the day + * of the week. This field takes values SUNDAY, + * MONDAY, TUESDAY, WEDNESDAY, + * THURSDAY, FRIDAY, and SATURDAY. + * + * @see #SUNDAY + * @see #MONDAY + * @see #TUESDAY + * @see #WEDNESDAY + * @see #THURSDAY + * @see #FRIDAY + * @see #SATURDAY + */ + public final static int DAY_OF_WEEK = 7; + + /** + * Field number for get and set indicating the + * ordinal number of the day of the week within the current month. Together + * with the DAY_OF_WEEK field, this uniquely specifies a day + * within a month. Unlike WEEK_OF_MONTH and + * WEEK_OF_YEAR, this field's value does not depend on + * getFirstDayOfWeek() or + * getMinimalDaysInFirstWeek(). DAY_OF_MONTH 1 + * through 7 always correspond to DAY_OF_WEEK_IN_MONTH + * 1; 8 through 14 correspond to + * DAY_OF_WEEK_IN_MONTH 2, and so on. + * DAY_OF_WEEK_IN_MONTH 0 indicates the week before + * DAY_OF_WEEK_IN_MONTH 1. Negative values count back from the + * end of the month, so the last Sunday of a month is specified as + * DAY_OF_WEEK = SUNDAY, DAY_OF_WEEK_IN_MONTH = -1. Because + * negative values count backward they will usually be aligned differently + * within the month than positive values. For example, if a month has 31 + * days, DAY_OF_WEEK_IN_MONTH -1 will overlap + * DAY_OF_WEEK_IN_MONTH 5 and the end of 4. + * + * @see #DAY_OF_WEEK + * @see #WEEK_OF_MONTH + */ + public final static int DAY_OF_WEEK_IN_MONTH = 8; + + /** + * Field number for get and set indicating + * whether the HOUR is before or after noon. + * E.g., at 10:04:15.250 PM the AM_PM is PM. + * + * @see #AM + * @see #PM + * @see #HOUR + */ + public final static int AM_PM = 9; + + /** + * Field number for get and set indicating the + * hour of the morning or afternoon. HOUR is used for the + * 12-hour clock (0 - 11). Noon and midnight are represented by 0, not by 12. + * E.g., at 10:04:15.250 PM the HOUR is 10. + * + * @see #AM_PM + * @see #HOUR_OF_DAY + */ + public final static int HOUR = 10; + + /** + * Field number for get and set indicating the + * hour of the day. HOUR_OF_DAY is used for the 24-hour clock. + * E.g., at 10:04:15.250 PM the HOUR_OF_DAY is 22. + * + * @see #HOUR + */ + public final static int HOUR_OF_DAY = 11; + + /** + * Field number for get and set indicating the + * minute within the hour. + * E.g., at 10:04:15.250 PM the MINUTE is 4. + */ + public final static int MINUTE = 12; + + /** + * Field number for get and set indicating the + * second within the minute. + * E.g., at 10:04:15.250 PM the SECOND is 15. + */ + public final static int SECOND = 13; + + /** + * Field number for get and set indicating the + * millisecond within the second. + * E.g., at 10:04:15.250 PM the MILLISECOND is 250. + */ + public final static int MILLISECOND = 14; + + /** + * Field number for get and set + * indicating the raw offset from GMT in milliseconds. + *

+ * This field reflects the correct GMT offset value of the time + * zone of this Calendar if the + * TimeZone implementation subclass supports + * historical GMT offset changes. + */ + public final static int ZONE_OFFSET = 15; + + /** + * Field number for get and set indicating the + * daylight saving offset in milliseconds. + *

+ * This field reflects the correct daylight saving offset value of + * the time zone of this Calendar if the + * TimeZone implementation subclass supports + * historical Daylight Saving Time schedule changes. + */ + public final static int DST_OFFSET = 16; + + /** + * The number of distinct fields recognized by get and set. + * Field numbers range from 0..FIELD_COUNT-1. + */ + public final static int FIELD_COUNT = 17; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Sunday. + */ + public final static int SUNDAY = 1; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Monday. + */ + public final static int MONDAY = 2; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Tuesday. + */ + public final static int TUESDAY = 3; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Wednesday. + */ + public final static int WEDNESDAY = 4; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Thursday. + */ + public final static int THURSDAY = 5; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Friday. + */ + public final static int FRIDAY = 6; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Saturday. + */ + public final static int SATURDAY = 7; + + /** + * Value of the {@link #MONTH} field indicating the + * first month of the year in the Gregorian and Julian calendars. + */ + public final static int JANUARY = 0; + + /** + * Value of the {@link #MONTH} field indicating the + * second month of the year in the Gregorian and Julian calendars. + */ + public final static int FEBRUARY = 1; + + /** + * Value of the {@link #MONTH} field indicating the + * third month of the year in the Gregorian and Julian calendars. + */ + public final static int MARCH = 2; + + /** + * Value of the {@link #MONTH} field indicating the + * fourth month of the year in the Gregorian and Julian calendars. + */ + public final static int APRIL = 3; + + /** + * Value of the {@link #MONTH} field indicating the + * fifth month of the year in the Gregorian and Julian calendars. + */ + public final static int MAY = 4; + + /** + * Value of the {@link #MONTH} field indicating the + * sixth month of the year in the Gregorian and Julian calendars. + */ + public final static int JUNE = 5; + + /** + * Value of the {@link #MONTH} field indicating the + * seventh month of the year in the Gregorian and Julian calendars. + */ + public final static int JULY = 6; + + /** + * Value of the {@link #MONTH} field indicating the + * eighth month of the year in the Gregorian and Julian calendars. + */ + public final static int AUGUST = 7; + + /** + * Value of the {@link #MONTH} field indicating the + * ninth month of the year in the Gregorian and Julian calendars. + */ + public final static int SEPTEMBER = 8; + + /** + * Value of the {@link #MONTH} field indicating the + * tenth month of the year in the Gregorian and Julian calendars. + */ + public final static int OCTOBER = 9; + + /** + * Value of the {@link #MONTH} field indicating the + * eleventh month of the year in the Gregorian and Julian calendars. + */ + public final static int NOVEMBER = 10; + + /** + * Value of the {@link #MONTH} field indicating the + * twelfth month of the year in the Gregorian and Julian calendars. + */ + public final static int DECEMBER = 11; + + /** + * Value of the {@link #MONTH} field indicating the + * thirteenth month of the year. Although GregorianCalendar + * does not use this value, lunar calendars do. + */ + public final static int UNDECIMBER = 12; + + /** + * Value of the {@link #AM_PM} field indicating the + * period of the day from midnight to just before noon. + */ + public final static int AM = 0; + + /** + * Value of the {@link #AM_PM} field indicating the + * period of the day from noon to just before midnight. + */ + public final static int PM = 1; + + /** + * A style specifier for {@link #getDisplayNames(int, int, Locale) + * getDisplayNames} indicating names in all styles, such as + * "January" and "Jan". + * + * @see #SHORT + * @see #LONG + * @since 1.6 + */ + public static final int ALL_STYLES = 0; + + /** + * A style specifier for {@link #getDisplayName(int, int, Locale) + * getDisplayName} and {@link #getDisplayNames(int, int, Locale) + * getDisplayNames} indicating a short name, such as "Jan". + * + * @see #LONG + * @since 1.6 + */ + public static final int SHORT = 1; + + /** + * A style specifier for {@link #getDisplayName(int, int, Locale) + * getDisplayName} and {@link #getDisplayNames(int, int, Locale) + * getDisplayNames} indicating a long name, such as "January". + * + * @see #SHORT + * @since 1.6 + */ + public static final int LONG = 2; + + // Internal notes: + // Calendar contains two kinds of time representations: current "time" in + // milliseconds, and a set of calendar "fields" representing the current time. + // The two representations are usually in sync, but can get out of sync + // as follows. + // 1. Initially, no fields are set, and the time is invalid. + // 2. If the time is set, all fields are computed and in sync. + // 3. If a single field is set, the time is invalid. + // Recomputation of the time and fields happens when the object needs + // to return a result to the user, or use a result for a computation. + + /** + * The calendar field values for the currently set time for this calendar. + * This is an array of FIELD_COUNT integers, with index values + * ERA through DST_OFFSET. + * @serial + */ + protected int fields[]; + + /** + * The flags which tell if a specified calendar field for the calendar is set. + * A new object has no fields set. After the first call to a method + * which generates the fields, they all remain set after that. + * This is an array of FIELD_COUNT booleans, with index values + * ERA through DST_OFFSET. + * @serial + */ + protected boolean isSet[]; + + /** + * Pseudo-time-stamps which specify when each field was set. There + * are two special values, UNSET and COMPUTED. Values from + * MINIMUM_USER_SET to Integer.MAX_VALUE are legal user set values. + */ + transient private int stamp[]; + + /** + * The currently set time for this calendar, expressed in milliseconds after + * January 1, 1970, 0:00:00 GMT. + * @see #isTimeSet + * @serial + */ + protected long time; + + /** + * True if then the value of time is valid. + * The time is made invalid by a change to an item of field[]. + * @see #time + * @serial + */ + protected boolean isTimeSet; + + /** + * True if fields[] are in sync with the currently set time. + * If false, then the next attempt to get the value of a field will + * force a recomputation of all fields from the current value of + * time. + * @serial + */ + protected boolean areFieldsSet; + + /** + * True if all fields have been set. + * @serial + */ + transient boolean areAllFieldsSet; + + /** + * True if this calendar allows out-of-range field values during computation + * of time from fields[]. + * @see #setLenient + * @see #isLenient + * @serial + */ + private boolean lenient = true; + + /** + * The TimeZone used by this calendar. Calendar + * uses the time zone data to translate between locale and GMT time. + * @serial + */ + private TimeZone zone; + + /** + * True if zone references to a shared TimeZone object. + */ + transient private boolean sharedZone = false; + + /** + * The first day of the week, with possible values SUNDAY, + * MONDAY, etc. This is a locale-dependent value. + * @serial + */ + private int firstDayOfWeek; + + /** + * The number of days required for the first week in a month or year, + * with possible values from 1 to 7. This is a locale-dependent value. + * @serial + */ + private int minimalDaysInFirstWeek; + + /** + * Cache to hold the firstDayOfWeek and minimalDaysInFirstWeek + * of a Locale. + */ + private static final ConcurrentMap cachedLocaleData + = new ConcurrentHashMap(3); + + // Special values of stamp[] + /** + * The corresponding fields[] has no value. + */ + private static final int UNSET = 0; + + /** + * The value of the corresponding fields[] has been calculated internally. + */ + private static final int COMPUTED = 1; + + /** + * The value of the corresponding fields[] has been set externally. Stamp + * values which are greater than 1 represents the (pseudo) time when the + * corresponding fields[] value was set. + */ + private static final int MINIMUM_USER_STAMP = 2; + + /** + * The mask value that represents all of the fields. + */ + static final int ALL_FIELDS = (1 << FIELD_COUNT) - 1; + + /** + * The next available value for stamp[], an internal array. + * This actually should not be written out to the stream, and will probably + * be removed from the stream in the near future. In the meantime, + * a value of MINIMUM_USER_STAMP should be used. + * @serial + */ + private int nextStamp = MINIMUM_USER_STAMP; + + // the internal serial version which says which version was written + // - 0 (default) for version up to JDK 1.1.5 + // - 1 for version from JDK 1.1.6, which writes a correct 'time' value + // as well as compatible values for other fields. This is a + // transitional format. + // - 2 (not implemented yet) a future version, in which fields[], + // areFieldsSet, and isTimeSet become transient, and isSet[] is + // removed. In JDK 1.1.6 we write a format compatible with version 2. + static final int currentSerialVersion = 1; + + /** + * The version of the serialized data on the stream. Possible values: + *

+ *
0 or not present on stream
+ *
+ * JDK 1.1.5 or earlier. + *
+ *
1
+ *
+ * JDK 1.1.6 or later. Writes a correct 'time' value + * as well as compatible values for other fields. This is a + * transitional format. + *
+ *
+ * When streaming out this class, the most recent format + * and the highest allowable serialVersionOnStream + * is written. + * @serial + * @since JDK1.1.6 + */ + private int serialVersionOnStream = currentSerialVersion; + + // Proclaim serialization compatibility with JDK 1.1 + static final long serialVersionUID = -1807547505821590642L; + + // Mask values for calendar fields + final static int ERA_MASK = (1 << ERA); + final static int YEAR_MASK = (1 << YEAR); + final static int MONTH_MASK = (1 << MONTH); + final static int WEEK_OF_YEAR_MASK = (1 << WEEK_OF_YEAR); + final static int WEEK_OF_MONTH_MASK = (1 << WEEK_OF_MONTH); + final static int DAY_OF_MONTH_MASK = (1 << DAY_OF_MONTH); + final static int DATE_MASK = DAY_OF_MONTH_MASK; + final static int DAY_OF_YEAR_MASK = (1 << DAY_OF_YEAR); + final static int DAY_OF_WEEK_MASK = (1 << DAY_OF_WEEK); + final static int DAY_OF_WEEK_IN_MONTH_MASK = (1 << DAY_OF_WEEK_IN_MONTH); + final static int AM_PM_MASK = (1 << AM_PM); + final static int HOUR_MASK = (1 << HOUR); + final static int HOUR_OF_DAY_MASK = (1 << HOUR_OF_DAY); + final static int MINUTE_MASK = (1 << MINUTE); + final static int SECOND_MASK = (1 << SECOND); + final static int MILLISECOND_MASK = (1 << MILLISECOND); + final static int ZONE_OFFSET_MASK = (1 << ZONE_OFFSET); + final static int DST_OFFSET_MASK = (1 << DST_OFFSET); + + /** + * Constructs a Calendar with the default time zone + * and locale. + * @see TimeZone#getDefault + */ + protected Calendar() + { + this(TimeZone.getDefaultRef(), Locale.getDefault(Locale.Category.FORMAT)); + sharedZone = true; + } + + /** + * Constructs a calendar with the specified time zone and locale. + * + * @param zone the time zone to use + * @param aLocale the locale for the week data + */ + protected Calendar(TimeZone zone, Locale aLocale) + { + fields = new int[FIELD_COUNT]; + isSet = new boolean[FIELD_COUNT]; + stamp = new int[FIELD_COUNT]; + + this.zone = zone; + setWeekCountData(aLocale); + } + + /** + * Gets a calendar using the default time zone and locale. The + * Calendar returned is based on the current time + * in the default time zone with the default locale. + * + * @return a Calendar. + */ + public static Calendar getInstance() + { + Calendar cal = createCalendar(TimeZone.getDefaultRef(), Locale.getDefault(Locale.Category.FORMAT)); + cal.sharedZone = true; + return cal; + } + + /** + * Gets a calendar using the specified time zone and default locale. + * The Calendar returned is based on the current time + * in the given time zone with the default locale. + * + * @param zone the time zone to use + * @return a Calendar. + */ + public static Calendar getInstance(TimeZone zone) + { + return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets a calendar using the default time zone and specified locale. + * The Calendar returned is based on the current time + * in the default time zone with the given locale. + * + * @param aLocale the locale for the week data + * @return a Calendar. + */ + public static Calendar getInstance(Locale aLocale) + { + Calendar cal = createCalendar(TimeZone.getDefaultRef(), aLocale); + cal.sharedZone = true; + return cal; + } + + /** + * Gets a calendar with the specified time zone and locale. + * The Calendar returned is based on the current time + * in the given time zone with the given locale. + * + * @param zone the time zone to use + * @param aLocale the locale for the week data + * @return a Calendar. + */ + public static Calendar getInstance(TimeZone zone, + Locale aLocale) + { + return createCalendar(zone, aLocale); + } + + private static Calendar createCalendar(TimeZone zone, + Locale aLocale) + { + Calendar cal = null; + + String caltype = aLocale.getUnicodeLocaleType("ca"); + if (caltype == null) { + // Calendar type is not specified. + // If the specified locale is a Thai locale, + // returns a BuddhistCalendar instance. + if ("th".equals(aLocale.getLanguage()) + && ("TH".equals(aLocale.getCountry()))) { +// cal = new BuddhistCalendar(zone, aLocale); + } else { +// cal = new GregorianCalendar(zone, aLocale); + } + } else if (caltype.equals("japanese")) { +// cal = new JapaneseImperialCalendar(zone, aLocale); + } else if (caltype.equals("buddhist")) { +// cal = new BuddhistCalendar(zone, aLocale); + } else { + // Unsupported calendar type. + // Use Gregorian calendar as a fallback. +// cal = new GregorianCalendar(zone, aLocale); + } + + return cal; + } + + /** + * Returns an array of all locales for which the getInstance + * methods of this class can return localized instances. + * The array returned must contain at least a Locale + * instance equal to {@link java.util.Locale#US Locale.US}. + * + * @return An array of locales for which localized + * Calendar instances are available. + */ + public static synchronized Locale[] getAvailableLocales() + { + return DateFormat.getAvailableLocales(); + } + + /** + * Converts the current calendar field values in {@link #fields fields[]} + * to the millisecond time value + * {@link #time}. + * + * @see #complete() + * @see #computeFields() + */ + protected abstract void computeTime(); + + /** + * Converts the current millisecond time value {@link #time} + * to calendar field values in {@link #fields fields[]}. + * This allows you to sync up the calendar field values with + * a new time that is set for the calendar. The time is not + * recomputed first; to recompute the time, then the fields, call the + * {@link #complete()} method. + * + * @see #computeTime() + */ + protected abstract void computeFields(); + + /** + * Returns a Date object representing this + * Calendar's time value (millisecond offset from the Epoch"). + * + * @return a Date representing the time value. + * @see #setTime(Date) + * @see #getTimeInMillis() + */ + public final Date getTime() { + return new Date(getTimeInMillis()); + } + + /** + * Sets this Calendar's time with the given Date. + *

+ * Note: Calling setTime() with + * Date(Long.MAX_VALUE) or Date(Long.MIN_VALUE) + * may yield incorrect field values from get(). + * + * @param date the given Date. + * @see #getTime() + * @see #setTimeInMillis(long) + */ + public final void setTime(Date date) { + setTimeInMillis(date.getTime()); + } + + /** + * Returns this Calendar's time value in milliseconds. + * + * @return the current time as UTC milliseconds from the epoch. + * @see #getTime() + * @see #setTimeInMillis(long) + */ + public long getTimeInMillis() { + if (!isTimeSet) { + updateTime(); + } + return time; + } + + /** + * Sets this Calendar's current time from the given long value. + * + * @param millis the new time in UTC milliseconds from the epoch. + * @see #setTime(Date) + * @see #getTimeInMillis() + */ + public void setTimeInMillis(long millis) { + // If we don't need to recalculate the calendar field values, + // do nothing. +// if (time == millis && isTimeSet && areFieldsSet && areAllFieldsSet +// && (zone instanceof ZoneInfo) && !((ZoneInfo)zone).isDirty()) { +// return; +// } + time = millis; + isTimeSet = true; + areFieldsSet = false; + computeFields(); + areAllFieldsSet = areFieldsSet = true; + } + + /** + * Returns the value of the given calendar field. In lenient mode, + * all calendar fields are normalized. In non-lenient mode, all + * calendar fields are validated and this method throws an + * exception if any calendar fields have out-of-range values. The + * normalization and validation are handled by the + * {@link #complete()} method, which process is calendar + * system dependent. + * + * @param field the given calendar field. + * @return the value for the given calendar field. + * @throws ArrayIndexOutOfBoundsException if the specified field is out of range + * (field < 0 || field >= FIELD_COUNT). + * @see #set(int,int) + * @see #complete() + */ + public int get(int field) + { + complete(); + return internalGet(field); + } + + /** + * Returns the value of the given calendar field. This method does + * not involve normalization or validation of the field value. + * + * @param field the given calendar field. + * @return the value for the given calendar field. + * @see #get(int) + */ + protected final int internalGet(int field) + { + return fields[field]; + } + + /** + * Sets the value of the given calendar field. This method does + * not affect any setting state of the field in this + * Calendar instance. + * + * @throws IndexOutOfBoundsException if the specified field is out of range + * (field < 0 || field >= FIELD_COUNT). + * @see #areFieldsSet + * @see #isTimeSet + * @see #areAllFieldsSet + * @see #set(int,int) + */ + final void internalSet(int field, int value) + { + fields[field] = value; + } + + /** + * Sets the given calendar field to the given value. The value is not + * interpreted by this method regardless of the leniency mode. + * + * @param field the given calendar field. + * @param value the value to be set for the given calendar field. + * @throws ArrayIndexOutOfBoundsException if the specified field is out of range + * (field < 0 || field >= FIELD_COUNT). + * in non-lenient mode. + * @see #set(int,int,int) + * @see #set(int,int,int,int,int) + * @see #set(int,int,int,int,int,int) + * @see #get(int) + */ + public void set(int field, int value) + { + // If the fields are partially normalized, calculate all the + // fields before changing any fields. + if (areFieldsSet && !areAllFieldsSet) { + computeFields(); + } + internalSet(field, value); + isTimeSet = false; + areFieldsSet = false; + isSet[field] = true; + stamp[field] = nextStamp++; + if (nextStamp == Integer.MAX_VALUE) { + adjustStamp(); + } + } + + /** + * Sets the values for the calendar fields YEAR, + * MONTH, and DAY_OF_MONTH. + * Previous values of other calendar fields are retained. If this is not desired, + * call {@link #clear()} first. + * + * @param year the value used to set the YEAR calendar field. + * @param month the value used to set the MONTH calendar field. + * Month value is 0-based. e.g., 0 for January. + * @param date the value used to set the DAY_OF_MONTH calendar field. + * @see #set(int,int) + * @see #set(int,int,int,int,int) + * @see #set(int,int,int,int,int,int) + */ + public final void set(int year, int month, int date) + { + set(YEAR, year); + set(MONTH, month); + set(DATE, date); + } + + /** + * Sets the values for the calendar fields YEAR, + * MONTH, DAY_OF_MONTH, + * HOUR_OF_DAY, and MINUTE. + * Previous values of other fields are retained. If this is not desired, + * call {@link #clear()} first. + * + * @param year the value used to set the YEAR calendar field. + * @param month the value used to set the MONTH calendar field. + * Month value is 0-based. e.g., 0 for January. + * @param date the value used to set the DAY_OF_MONTH calendar field. + * @param hourOfDay the value used to set the HOUR_OF_DAY calendar field. + * @param minute the value used to set the MINUTE calendar field. + * @see #set(int,int) + * @see #set(int,int,int) + * @see #set(int,int,int,int,int,int) + */ + public final void set(int year, int month, int date, int hourOfDay, int minute) + { + set(YEAR, year); + set(MONTH, month); + set(DATE, date); + set(HOUR_OF_DAY, hourOfDay); + set(MINUTE, minute); + } + + /** + * Sets the values for the fields YEAR, MONTH, + * DAY_OF_MONTH, HOUR, MINUTE, and + * SECOND. + * Previous values of other fields are retained. If this is not desired, + * call {@link #clear()} first. + * + * @param year the value used to set the YEAR calendar field. + * @param month the value used to set the MONTH calendar field. + * Month value is 0-based. e.g., 0 for January. + * @param date the value used to set the DAY_OF_MONTH calendar field. + * @param hourOfDay the value used to set the HOUR_OF_DAY calendar field. + * @param minute the value used to set the MINUTE calendar field. + * @param second the value used to set the SECOND calendar field. + * @see #set(int,int) + * @see #set(int,int,int) + * @see #set(int,int,int,int,int) + */ + public final void set(int year, int month, int date, int hourOfDay, int minute, + int second) + { + set(YEAR, year); + set(MONTH, month); + set(DATE, date); + set(HOUR_OF_DAY, hourOfDay); + set(MINUTE, minute); + set(SECOND, second); + } + + /** + * Sets all the calendar field values and the time value + * (millisecond offset from the Epoch) of + * this Calendar undefined. This means that {@link + * #isSet(int) isSet()} will return false for all the + * calendar fields, and the date and time calculations will treat + * the fields as if they had never been set. A + * Calendar implementation class may use its specific + * default field values for date/time calculations. For example, + * GregorianCalendar uses 1970 if the + * YEAR field value is undefined. + * + * @see #clear(int) + */ + public final void clear() + { + for (int i = 0; i < fields.length; ) { + stamp[i] = fields[i] = 0; // UNSET == 0 + isSet[i++] = false; + } + areAllFieldsSet = areFieldsSet = false; + isTimeSet = false; + } + + /** + * Sets the given calendar field value and the time value + * (millisecond offset from the Epoch) of + * this Calendar undefined. This means that {@link + * #isSet(int) isSet(field)} will return false, and + * the date and time calculations will treat the field as if it + * had never been set. A Calendar implementation + * class may use the field's specific default value for date and + * time calculations. + * + *

The {@link #HOUR_OF_DAY}, {@link #HOUR} and {@link #AM_PM} + * fields are handled independently and the the resolution rule for the time of + * day is applied. Clearing one of the fields doesn't reset + * the hour of day value of this Calendar. Use {@link + * #set(int,int) set(Calendar.HOUR_OF_DAY, 0)} to reset the hour + * value. + * + * @param field the calendar field to be cleared. + * @see #clear() + */ + public final void clear(int field) + { + fields[field] = 0; + stamp[field] = UNSET; + isSet[field] = false; + + areAllFieldsSet = areFieldsSet = false; + isTimeSet = false; + } + + /** + * Determines if the given calendar field has a value set, + * including cases that the value has been set by internal fields + * calculations triggered by a get method call. + * + * @return true if the given calendar field has a value set; + * false otherwise. + */ + public final boolean isSet(int field) + { + return stamp[field] != UNSET; + } + + /** + * Returns the string representation of the calendar + * field value in the given style and + * locale. If no string representation is + * applicable, null is returned. This method calls + * {@link Calendar#get(int) get(field)} to get the calendar + * field value if the string representation is + * applicable to the given calendar field. + * + *

For example, if this Calendar is a + * GregorianCalendar and its date is 2005-01-01, then + * the string representation of the {@link #MONTH} field would be + * "January" in the long style in an English locale or "Jan" in + * the short style. However, no string representation would be + * available for the {@link #DAY_OF_MONTH} field, and this method + * would return null. + * + *

The default implementation supports the calendar fields for + * which a {@link DateFormatSymbols} has names in the given + * locale. + * + * @param field + * the calendar field for which the string representation + * is returned + * @param style + * the style applied to the string representation; one of + * {@link #SHORT} or {@link #LONG}. + * @param locale + * the locale for the string representation + * @return the string representation of the given + * field in the given style, or + * null if no string representation is + * applicable. + * @exception IllegalArgumentException + * if field or style is invalid, + * or if this Calendar is non-lenient and any + * of the calendar fields have invalid values + * @exception NullPointerException + * if locale is null + * @since 1.6 + */ + public String getDisplayName(int field, int style, Locale locale) { + if (!checkDisplayNameParams(field, style, ALL_STYLES, LONG, locale, + ERA_MASK|MONTH_MASK|DAY_OF_WEEK_MASK|AM_PM_MASK)) { + return null; + } + + DateFormatSymbols symbols = DateFormatSymbols.getInstance(locale); + String[] strings = getFieldStrings(field, style, symbols); + if (strings != null) { + int fieldValue = get(field); + if (fieldValue < strings.length) { + return strings[fieldValue]; + } + } + return null; + } + + /** + * Returns a Map containing all names of the calendar + * field in the given style and + * locale and their corresponding field values. For + * example, if this Calendar is a {@link + * GregorianCalendar}, the returned map would contain "Jan" to + * {@link #JANUARY}, "Feb" to {@link #FEBRUARY}, and so on, in the + * {@linkplain #SHORT short} style in an English locale. + * + *

The values of other calendar fields may be taken into + * account to determine a set of display names. For example, if + * this Calendar is a lunisolar calendar system and + * the year value given by the {@link #YEAR} field has a leap + * month, this method would return month names containing the leap + * month name, and month names are mapped to their values specific + * for the year. + * + *

The default implementation supports display names contained in + * a {@link DateFormatSymbols}. For example, if field + * is {@link #MONTH} and style is {@link + * #ALL_STYLES}, this method returns a Map containing + * all strings returned by {@link DateFormatSymbols#getShortMonths()} + * and {@link DateFormatSymbols#getMonths()}. + * + * @param field + * the calendar field for which the display names are returned + * @param style + * the style applied to the display names; one of {@link + * #SHORT}, {@link #LONG}, or {@link #ALL_STYLES}. + * @param locale + * the locale for the display names + * @return a Map containing all display names in + * style and locale and their + * field values, or null if no display names + * are defined for field + * @exception IllegalArgumentException + * if field or style is invalid, + * or if this Calendar is non-lenient and any + * of the calendar fields have invalid values + * @exception NullPointerException + * if locale is null + * @since 1.6 + */ + public Map getDisplayNames(int field, int style, Locale locale) { + if (!checkDisplayNameParams(field, style, ALL_STYLES, LONG, locale, + ERA_MASK|MONTH_MASK|DAY_OF_WEEK_MASK|AM_PM_MASK)) { + return null; + } + + // ALL_STYLES + if (style == ALL_STYLES) { + Map shortNames = getDisplayNamesImpl(field, SHORT, locale); + if (field == ERA || field == AM_PM) { + return shortNames; + } + Map longNames = getDisplayNamesImpl(field, LONG, locale); + if (shortNames == null) { + return longNames; + } + if (longNames != null) { + shortNames.putAll(longNames); + } + return shortNames; + } + + // SHORT or LONG + return getDisplayNamesImpl(field, style, locale); + } + + private Map getDisplayNamesImpl(int field, int style, Locale locale) { + DateFormatSymbols symbols = DateFormatSymbols.getInstance(locale); + String[] strings = getFieldStrings(field, style, symbols); + if (strings != null) { + Map names = new HashMap(); + for (int i = 0; i < strings.length; i++) { + if (strings[i].length() == 0) { + continue; + } + names.put(strings[i], i); + } + return names; + } + return null; + } + + boolean checkDisplayNameParams(int field, int style, int minStyle, int maxStyle, + Locale locale, int fieldMask) { + if (field < 0 || field >= fields.length || + style < minStyle || style > maxStyle) { + throw new IllegalArgumentException(); + } + if (locale == null) { + throw new NullPointerException(); + } + return isFieldSet(fieldMask, field); + } + + private String[] getFieldStrings(int field, int style, DateFormatSymbols symbols) { + String[] strings = null; + switch (field) { + case ERA: + strings = symbols.getEras(); + break; + + case MONTH: + strings = (style == LONG) ? symbols.getMonths() : symbols.getShortMonths(); + break; + + case DAY_OF_WEEK: + strings = (style == LONG) ? symbols.getWeekdays() : symbols.getShortWeekdays(); + break; + + case AM_PM: + strings = symbols.getAmPmStrings(); + break; + } + return strings; + } + + /** + * Fills in any unset fields in the calendar fields. First, the {@link + * #computeTime()} method is called if the time value (millisecond offset + * from the Epoch) has not been calculated from + * calendar field values. Then, the {@link #computeFields()} method is + * called to calculate all calendar field values. + */ + protected void complete() + { + if (!isTimeSet) + updateTime(); + if (!areFieldsSet || !areAllFieldsSet) { + computeFields(); // fills in unset fields + areAllFieldsSet = areFieldsSet = true; + } + } + + /** + * Returns whether the value of the specified calendar field has been set + * externally by calling one of the setter methods rather than by the + * internal time calculation. + * + * @return true if the field has been set externally, + * false otherwise. + * @exception IndexOutOfBoundsException if the specified + * field is out of range + * (field < 0 || field >= FIELD_COUNT). + * @see #selectFields() + * @see #setFieldsComputed(int) + */ + final boolean isExternallySet(int field) { + return stamp[field] >= MINIMUM_USER_STAMP; + } + + /** + * Returns a field mask (bit mask) indicating all calendar fields that + * have the state of externally or internally set. + * + * @return a bit mask indicating set state fields + */ + final int getSetStateFields() { + int mask = 0; + for (int i = 0; i < fields.length; i++) { + if (stamp[i] != UNSET) { + mask |= 1 << i; + } + } + return mask; + } + + /** + * Sets the state of the specified calendar fields to + * computed. This state means that the specified calendar fields + * have valid values that have been set by internal time calculation + * rather than by calling one of the setter methods. + * + * @param fieldMask the field to be marked as computed. + * @exception IndexOutOfBoundsException if the specified + * field is out of range + * (field < 0 || field >= FIELD_COUNT). + * @see #isExternallySet(int) + * @see #selectFields() + */ + final void setFieldsComputed(int fieldMask) { + if (fieldMask == ALL_FIELDS) { + for (int i = 0; i < fields.length; i++) { + stamp[i] = COMPUTED; + isSet[i] = true; + } + areFieldsSet = areAllFieldsSet = true; + } else { + for (int i = 0; i < fields.length; i++) { + if ((fieldMask & 1) == 1) { + stamp[i] = COMPUTED; + isSet[i] = true; + } else { + if (areAllFieldsSet && !isSet[i]) { + areAllFieldsSet = false; + } + } + fieldMask >>>= 1; + } + } + } + + /** + * Sets the state of the calendar fields that are not specified + * by fieldMask to unset. If fieldMask + * specifies all the calendar fields, then the state of this + * Calendar becomes that all the calendar fields are in sync + * with the time value (millisecond offset from the Epoch). + * + * @param fieldMask the field mask indicating which calendar fields are in + * sync with the time value. + * @exception IndexOutOfBoundsException if the specified + * field is out of range + * (field < 0 || field >= FIELD_COUNT). + * @see #isExternallySet(int) + * @see #selectFields() + */ + final void setFieldsNormalized(int fieldMask) { + if (fieldMask != ALL_FIELDS) { + for (int i = 0; i < fields.length; i++) { + if ((fieldMask & 1) == 0) { + stamp[i] = fields[i] = 0; // UNSET == 0 + isSet[i] = false; + } + fieldMask >>= 1; + } + } + + // Some or all of the fields are in sync with the + // milliseconds, but the stamp values are not normalized yet. + areFieldsSet = true; + areAllFieldsSet = false; + } + + /** + * Returns whether the calendar fields are partially in sync with the time + * value or fully in sync but not stamp values are not normalized yet. + */ + final boolean isPartiallyNormalized() { + return areFieldsSet && !areAllFieldsSet; + } + + /** + * Returns whether the calendar fields are fully in sync with the time + * value. + */ + final boolean isFullyNormalized() { + return areFieldsSet && areAllFieldsSet; + } + + /** + * Marks this Calendar as not sync'd. + */ + final void setUnnormalized() { + areFieldsSet = areAllFieldsSet = false; + } + + /** + * Returns whether the specified field is on in the + * fieldMask. + */ + static final boolean isFieldSet(int fieldMask, int field) { + return (fieldMask & (1 << field)) != 0; + } + + /** + * Returns a field mask indicating which calendar field values + * to be used to calculate the time value. The calendar fields are + * returned as a bit mask, each bit of which corresponds to a field, i.e., + * the mask value of field is (1 << + * field). For example, 0x26 represents the YEAR, + * MONTH, and DAY_OF_MONTH fields (i.e., 0x26 is + * equal to + * (1<<YEAR)|(1<<MONTH)|(1<<DAY_OF_MONTH)). + * + *

This method supports the calendar fields resolution as described in + * the class description. If the bit mask for a given field is on and its + * field has not been set (i.e., isSet(field) is + * false), then the default value of the field has to be + * used, which case means that the field has been selected because the + * selected combination involves the field. + * + * @return a bit mask of selected fields + * @see #isExternallySet(int) + * @see #setInternallySetState(int) + */ + final int selectFields() { + // This implementation has been taken from the GregorianCalendar class. + + // The YEAR field must always be used regardless of its SET + // state because YEAR is a mandatory field to determine the date + // and the default value (EPOCH_YEAR) may change through the + // normalization process. + int fieldMask = YEAR_MASK; + + if (stamp[ERA] != UNSET) { + fieldMask |= ERA_MASK; + } + // Find the most recent group of fields specifying the day within + // the year. These may be any of the following combinations: + // MONTH + DAY_OF_MONTH + // MONTH + WEEK_OF_MONTH + DAY_OF_WEEK + // MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK + // DAY_OF_YEAR + // WEEK_OF_YEAR + DAY_OF_WEEK + // We look for the most recent of the fields in each group to determine + // the age of the group. For groups involving a week-related field such + // as WEEK_OF_MONTH, DAY_OF_WEEK_IN_MONTH, or WEEK_OF_YEAR, both the + // week-related field and the DAY_OF_WEEK must be set for the group as a + // whole to be considered. (See bug 4153860 - liu 7/24/98.) + int dowStamp = stamp[DAY_OF_WEEK]; + int monthStamp = stamp[MONTH]; + int domStamp = stamp[DAY_OF_MONTH]; + int womStamp = aggregateStamp(stamp[WEEK_OF_MONTH], dowStamp); + int dowimStamp = aggregateStamp(stamp[DAY_OF_WEEK_IN_MONTH], dowStamp); + int doyStamp = stamp[DAY_OF_YEAR]; + int woyStamp = aggregateStamp(stamp[WEEK_OF_YEAR], dowStamp); + + int bestStamp = domStamp; + if (womStamp > bestStamp) { + bestStamp = womStamp; + } + if (dowimStamp > bestStamp) { + bestStamp = dowimStamp; + } + if (doyStamp > bestStamp) { + bestStamp = doyStamp; + } + if (woyStamp > bestStamp) { + bestStamp = woyStamp; + } + + /* No complete combination exists. Look for WEEK_OF_MONTH, + * DAY_OF_WEEK_IN_MONTH, or WEEK_OF_YEAR alone. Treat DAY_OF_WEEK alone + * as DAY_OF_WEEK_IN_MONTH. + */ + if (bestStamp == UNSET) { + womStamp = stamp[WEEK_OF_MONTH]; + dowimStamp = Math.max(stamp[DAY_OF_WEEK_IN_MONTH], dowStamp); + woyStamp = stamp[WEEK_OF_YEAR]; + bestStamp = Math.max(Math.max(womStamp, dowimStamp), woyStamp); + + /* Treat MONTH alone or no fields at all as DAY_OF_MONTH. This may + * result in bestStamp = domStamp = UNSET if no fields are set, + * which indicates DAY_OF_MONTH. + */ + if (bestStamp == UNSET) { + bestStamp = domStamp = monthStamp; + } + } + + if (bestStamp == domStamp || + (bestStamp == womStamp && stamp[WEEK_OF_MONTH] >= stamp[WEEK_OF_YEAR]) || + (bestStamp == dowimStamp && stamp[DAY_OF_WEEK_IN_MONTH] >= stamp[WEEK_OF_YEAR])) { + fieldMask |= MONTH_MASK; + if (bestStamp == domStamp) { + fieldMask |= DAY_OF_MONTH_MASK; + } else { + assert (bestStamp == womStamp || bestStamp == dowimStamp); + if (dowStamp != UNSET) { + fieldMask |= DAY_OF_WEEK_MASK; + } + if (womStamp == dowimStamp) { + // When they are equal, give the priority to + // WEEK_OF_MONTH for compatibility. + if (stamp[WEEK_OF_MONTH] >= stamp[DAY_OF_WEEK_IN_MONTH]) { + fieldMask |= WEEK_OF_MONTH_MASK; + } else { + fieldMask |= DAY_OF_WEEK_IN_MONTH_MASK; + } + } else { + if (bestStamp == womStamp) { + fieldMask |= WEEK_OF_MONTH_MASK; + } else { + assert (bestStamp == dowimStamp); + if (stamp[DAY_OF_WEEK_IN_MONTH] != UNSET) { + fieldMask |= DAY_OF_WEEK_IN_MONTH_MASK; + } + } + } + } + } else { + assert (bestStamp == doyStamp || bestStamp == woyStamp || + bestStamp == UNSET); + if (bestStamp == doyStamp) { + fieldMask |= DAY_OF_YEAR_MASK; + } else { + assert (bestStamp == woyStamp); + if (dowStamp != UNSET) { + fieldMask |= DAY_OF_WEEK_MASK; + } + fieldMask |= WEEK_OF_YEAR_MASK; + } + } + + // Find the best set of fields specifying the time of day. There + // are only two possibilities here; the HOUR_OF_DAY or the + // AM_PM and the HOUR. + int hourOfDayStamp = stamp[HOUR_OF_DAY]; + int hourStamp = aggregateStamp(stamp[HOUR], stamp[AM_PM]); + bestStamp = (hourStamp > hourOfDayStamp) ? hourStamp : hourOfDayStamp; + + // if bestStamp is still UNSET, then take HOUR or AM_PM. (See 4846659) + if (bestStamp == UNSET) { + bestStamp = Math.max(stamp[HOUR], stamp[AM_PM]); + } + + // Hours + if (bestStamp != UNSET) { + if (bestStamp == hourOfDayStamp) { + fieldMask |= HOUR_OF_DAY_MASK; + } else { + fieldMask |= HOUR_MASK; + if (stamp[AM_PM] != UNSET) { + fieldMask |= AM_PM_MASK; + } + } + } + if (stamp[MINUTE] != UNSET) { + fieldMask |= MINUTE_MASK; + } + if (stamp[SECOND] != UNSET) { + fieldMask |= SECOND_MASK; + } + if (stamp[MILLISECOND] != UNSET) { + fieldMask |= MILLISECOND_MASK; + } + if (stamp[ZONE_OFFSET] >= MINIMUM_USER_STAMP) { + fieldMask |= ZONE_OFFSET_MASK; + } + if (stamp[DST_OFFSET] >= MINIMUM_USER_STAMP) { + fieldMask |= DST_OFFSET_MASK; + } + + return fieldMask; + } + + /** + * Returns the pseudo-time-stamp for two fields, given their + * individual pseudo-time-stamps. If either of the fields + * is unset, then the aggregate is unset. Otherwise, the + * aggregate is the later of the two stamps. + */ + private static final int aggregateStamp(int stamp_a, int stamp_b) { + if (stamp_a == UNSET || stamp_b == UNSET) { + return UNSET; + } + return (stamp_a > stamp_b) ? stamp_a : stamp_b; + } + + /** + * Compares this Calendar to the specified + * Object. The result is true if and only if + * the argument is a Calendar object of the same calendar + * system that represents the same time value (millisecond offset from the + * Epoch) under the same + * Calendar parameters as this object. + * + *

The Calendar parameters are the values represented + * by the isLenient, getFirstDayOfWeek, + * getMinimalDaysInFirstWeek and getTimeZone + * methods. If there is any difference in those parameters + * between the two Calendars, this method returns + * false. + * + *

Use the {@link #compareTo(Calendar) compareTo} method to + * compare only the time values. + * + * @param obj the object to compare with. + * @return true if this object is equal to obj; + * false otherwise. + */ + public boolean equals(Object obj) { + if (this == obj) + return true; + try { + Calendar that = (Calendar)obj; + return compareTo(getMillisOf(that)) == 0 && + lenient == that.lenient && + firstDayOfWeek == that.firstDayOfWeek && + minimalDaysInFirstWeek == that.minimalDaysInFirstWeek && + zone.equals(that.zone); + } catch (Exception e) { + // Note: GregorianCalendar.computeTime throws + // IllegalArgumentException if the ERA value is invalid + // even it's in lenient mode. + } + return false; + } + + /** + * Returns a hash code for this calendar. + * + * @return a hash code value for this object. + * @since 1.2 + */ + public int hashCode() { + // 'otheritems' represents the hash code for the previous versions. + int otheritems = (lenient ? 1 : 0) + | (firstDayOfWeek << 1) + | (minimalDaysInFirstWeek << 4) + | (zone.hashCode() << 7); + long t = getMillisOf(this); + return (int) t ^ (int)(t >> 32) ^ otheritems; + } + + /** + * Returns whether this Calendar represents a time + * before the time represented by the specified + * Object. This method is equivalent to: + *

+ * compareTo(when) < 0 + *
+ * if and only if when is a Calendar + * instance. Otherwise, the method returns false. + * + * @param when the Object to be compared + * @return true if the time of this + * Calendar is before the time represented by + * when; false otherwise. + * @see #compareTo(Calendar) + */ + public boolean before(Object when) { + return when instanceof Calendar + && compareTo((Calendar)when) < 0; + } + + /** + * Returns whether this Calendar represents a time + * after the time represented by the specified + * Object. This method is equivalent to: + *
+ * compareTo(when) > 0 + *
+ * if and only if when is a Calendar + * instance. Otherwise, the method returns false. + * + * @param when the Object to be compared + * @return true if the time of this Calendar is + * after the time represented by when; false + * otherwise. + * @see #compareTo(Calendar) + */ + public boolean after(Object when) { + return when instanceof Calendar + && compareTo((Calendar)when) > 0; + } + + /** + * Compares the time values (millisecond offsets from the Epoch) represented by two + * Calendar objects. + * + * @param anotherCalendar the Calendar to be compared. + * @return the value 0 if the time represented by the argument + * is equal to the time represented by this Calendar; a value + * less than 0 if the time of this Calendar is + * before the time represented by the argument; and a value greater than + * 0 if the time of this Calendar is after the + * time represented by the argument. + * @exception NullPointerException if the specified Calendar is + * null. + * @exception IllegalArgumentException if the time value of the + * specified Calendar object can't be obtained due to + * any invalid calendar values. + * @since 1.5 + */ + public int compareTo(Calendar anotherCalendar) { + return compareTo(getMillisOf(anotherCalendar)); + } + + /** + * Adds or subtracts the specified amount of time to the given calendar field, + * based on the calendar's rules. For example, to subtract 5 days from + * the current time of the calendar, you can achieve it by calling: + *

add(Calendar.DAY_OF_MONTH, -5). + * + * @param field the calendar field. + * @param amount the amount of date or time to be added to the field. + * @see #roll(int,int) + * @see #set(int,int) + */ + abstract public void add(int field, int amount); + + /** + * Adds or subtracts (up/down) a single unit of time on the given time + * field without changing larger fields. For example, to roll the current + * date up by one day, you can achieve it by calling: + *

roll(Calendar.DATE, true). + * When rolling on the year or Calendar.YEAR field, it will roll the year + * value in the range between 1 and the value returned by calling + * getMaximum(Calendar.YEAR). + * When rolling on the month or Calendar.MONTH field, other fields like + * date might conflict and, need to be changed. For instance, + * rolling the month on the date 01/31/96 will result in 02/29/96. + * When rolling on the hour-in-day or Calendar.HOUR_OF_DAY field, it will + * roll the hour value in the range between 0 and 23, which is zero-based. + * + * @param field the time field. + * @param up indicates if the value of the specified time field is to be + * rolled up or rolled down. Use true if rolling up, false otherwise. + * @see Calendar#add(int,int) + * @see Calendar#set(int,int) + */ + abstract public void roll(int field, boolean up); + + /** + * Adds the specified (signed) amount to the specified calendar field + * without changing larger fields. A negative amount means to roll + * down. + * + *

NOTE: This default implementation on Calendar just repeatedly calls the + * version of {@link #roll(int,boolean) roll()} that rolls by one unit. This may not + * always do the right thing. For example, if the DAY_OF_MONTH field is 31, + * rolling through February will leave it set to 28. The GregorianCalendar + * version of this function takes care of this problem. Other subclasses + * should also provide overrides of this function that do the right thing. + * + * @param field the calendar field. + * @param amount the signed amount to add to the calendar field. + * @since 1.2 + * @see #roll(int,boolean) + * @see #add(int,int) + * @see #set(int,int) + */ + public void roll(int field, int amount) + { + while (amount > 0) { + roll(field, true); + amount--; + } + while (amount < 0) { + roll(field, false); + amount++; + } + } + + /** + * Sets the time zone with the given time zone value. + * + * @param value the given time zone. + */ + public void setTimeZone(TimeZone value) + { + zone = value; + sharedZone = false; + /* Recompute the fields from the time using the new zone. This also + * works if isTimeSet is false (after a call to set()). In that case + * the time will be computed from the fields using the new zone, then + * the fields will get recomputed from that. Consider the sequence of + * calls: cal.setTimeZone(EST); cal.set(HOUR, 1); cal.setTimeZone(PST). + * Is cal set to 1 o'clock EST or 1 o'clock PST? Answer: PST. More + * generally, a call to setTimeZone() affects calls to set() BEFORE AND + * AFTER it up to the next call to complete(). + */ + areAllFieldsSet = areFieldsSet = false; + } + + /** + * Gets the time zone. + * + * @return the time zone object associated with this calendar. + */ + public TimeZone getTimeZone() + { + // If the TimeZone object is shared by other Calendar instances, then + // create a clone. + if (sharedZone) { + zone = (TimeZone) zone.clone(); + sharedZone = false; + } + return zone; + } + + /** + * Returns the time zone (without cloning). + */ + TimeZone getZone() { + return zone; + } + + /** + * Sets the sharedZone flag to shared. + */ + void setZoneShared(boolean shared) { + sharedZone = shared; + } + + /** + * Specifies whether or not date/time interpretation is to be lenient. With + * lenient interpretation, a date such as "February 942, 1996" will be + * treated as being equivalent to the 941st day after February 1, 1996. + * With strict (non-lenient) interpretation, such dates will cause an exception to be + * thrown. The default is lenient. + * + * @param lenient true if the lenient mode is to be turned + * on; false if it is to be turned off. + * @see #isLenient() + * @see java.text.DateFormat#setLenient + */ + public void setLenient(boolean lenient) + { + this.lenient = lenient; + } + + /** + * Tells whether date/time interpretation is to be lenient. + * + * @return true if the interpretation mode of this calendar is lenient; + * false otherwise. + * @see #setLenient(boolean) + */ + public boolean isLenient() + { + return lenient; + } + + /** + * Sets what the first day of the week is; e.g., SUNDAY in the U.S., + * MONDAY in France. + * + * @param value the given first day of the week. + * @see #getFirstDayOfWeek() + * @see #getMinimalDaysInFirstWeek() + */ + public void setFirstDayOfWeek(int value) + { + if (firstDayOfWeek == value) { + return; + } + firstDayOfWeek = value; + invalidateWeekFields(); + } + + /** + * Gets what the first day of the week is; e.g., SUNDAY in the U.S., + * MONDAY in France. + * + * @return the first day of the week. + * @see #setFirstDayOfWeek(int) + * @see #getMinimalDaysInFirstWeek() + */ + public int getFirstDayOfWeek() + { + return firstDayOfWeek; + } + + /** + * Sets what the minimal days required in the first week of the year are; + * For example, if the first week is defined as one that contains the first + * day of the first month of a year, call this method with value 1. If it + * must be a full week, use value 7. + * + * @param value the given minimal days required in the first week + * of the year. + * @see #getMinimalDaysInFirstWeek() + */ + public void setMinimalDaysInFirstWeek(int value) + { + if (minimalDaysInFirstWeek == value) { + return; + } + minimalDaysInFirstWeek = value; + invalidateWeekFields(); + } + + /** + * Gets what the minimal days required in the first week of the year are; + * e.g., if the first week is defined as one that contains the first day + * of the first month of a year, this method returns 1. If + * the minimal days required must be a full week, this method + * returns 7. + * + * @return the minimal days required in the first week of the year. + * @see #setMinimalDaysInFirstWeek(int) + */ + public int getMinimalDaysInFirstWeek() + { + return minimalDaysInFirstWeek; + } + + /** + * Returns whether this {@code Calendar} supports week dates. + * + *

The default implementation of this method returns {@code false}. + * + * @return {@code true} if this {@code Calendar} supports week dates; + * {@code false} otherwise. + * @see #getWeekYear() + * @see #setWeekDate(int,int,int) + * @see #getWeeksInWeekYear() + * @since 1.7 + */ + public boolean isWeekDateSupported() { + return false; + } + + /** + * Returns the week year represented by this {@code Calendar}. The + * week year is in sync with the week cycle. The {@linkplain + * #getFirstDayOfWeek() first day of the first week} is the first + * day of the week year. + * + *

The default implementation of this method throws an + * {@link UnsupportedOperationException}. + * + * @return the week year of this {@code Calendar} + * @exception UnsupportedOperationException + * if any week year numbering isn't supported + * in this {@code Calendar}. + * @see #isWeekDateSupported() + * @see #getFirstDayOfWeek() + * @see #getMinimalDaysInFirstWeek() + * @since 1.7 + */ + public int getWeekYear() { + throw new UnsupportedOperationException(); + } + + /** + * Sets the date of this {@code Calendar} with the the given date + * specifiers - week year, week of year, and day of week. + * + *

Unlike the {@code set} method, all of the calendar fields + * and {@code time} values are calculated upon return. + * + *

If {@code weekOfYear} is out of the valid week-of-year range + * in {@code weekYear}, the {@code weekYear} and {@code + * weekOfYear} values are adjusted in lenient mode, or an {@code + * IllegalArgumentException} is thrown in non-lenient mode. + * + *

The default implementation of this method throws an + * {@code UnsupportedOperationException}. + * + * @param weekYear the week year + * @param weekOfYear the week number based on {@code weekYear} + * @param dayOfWeek the day of week value: one of the constants + * for the {@link #DAY_OF_WEEK} field: {@link + * #SUNDAY}, ..., {@link #SATURDAY}. + * @exception IllegalArgumentException + * if any of the given date specifiers is invalid + * or any of the calendar fields are inconsistent + * with the given date specifiers in non-lenient mode + * @exception UnsupportedOperationException + * if any week year numbering isn't supported in this + * {@code Calendar}. + * @see #isWeekDateSupported() + * @see #getFirstDayOfWeek() + * @see #getMinimalDaysInFirstWeek() + * @since 1.7 + */ + public void setWeekDate(int weekYear, int weekOfYear, int dayOfWeek) { + throw new UnsupportedOperationException(); + } + + /** + * Returns the number of weeks in the week year represented by this + * {@code Calendar}. + * + *

The default implementation of this method throws an + * {@code UnsupportedOperationException}. + * + * @return the number of weeks in the week year. + * @exception UnsupportedOperationException + * if any week year numbering isn't supported in this + * {@code Calendar}. + * @see #WEEK_OF_YEAR + * @see #isWeekDateSupported() + * @see #getWeekYear() + * @see #getActualMaximum(int) + * @since 1.7 + */ + public int getWeeksInWeekYear() { + throw new UnsupportedOperationException(); + } + + /** + * Returns the minimum value for the given calendar field of this + * Calendar instance. The minimum value is defined as + * the smallest value returned by the {@link #get(int) get} method + * for any possible time value. The minimum value depends on + * calendar system specific parameters of the instance. + * + * @param field the calendar field. + * @return the minimum value for the given calendar field. + * @see #getMaximum(int) + * @see #getGreatestMinimum(int) + * @see #getLeastMaximum(int) + * @see #getActualMinimum(int) + * @see #getActualMaximum(int) + */ + abstract public int getMinimum(int field); + + /** + * Returns the maximum value for the given calendar field of this + * Calendar instance. The maximum value is defined as + * the largest value returned by the {@link #get(int) get} method + * for any possible time value. The maximum value depends on + * calendar system specific parameters of the instance. + * + * @param field the calendar field. + * @return the maximum value for the given calendar field. + * @see #getMinimum(int) + * @see #getGreatestMinimum(int) + * @see #getLeastMaximum(int) + * @see #getActualMinimum(int) + * @see #getActualMaximum(int) + */ + abstract public int getMaximum(int field); + + /** + * Returns the highest minimum value for the given calendar field + * of this Calendar instance. The highest minimum + * value is defined as the largest value returned by {@link + * #getActualMinimum(int)} for any possible time value. The + * greatest minimum value depends on calendar system specific + * parameters of the instance. + * + * @param field the calendar field. + * @return the highest minimum value for the given calendar field. + * @see #getMinimum(int) + * @see #getMaximum(int) + * @see #getLeastMaximum(int) + * @see #getActualMinimum(int) + * @see #getActualMaximum(int) + */ + abstract public int getGreatestMinimum(int field); + + /** + * Returns the lowest maximum value for the given calendar field + * of this Calendar instance. The lowest maximum + * value is defined as the smallest value returned by {@link + * #getActualMaximum(int)} for any possible time value. The least + * maximum value depends on calendar system specific parameters of + * the instance. For example, a Calendar for the + * Gregorian calendar system returns 28 for the + * DAY_OF_MONTH field, because the 28th is the last + * day of the shortest month of this calendar, February in a + * common year. + * + * @param field the calendar field. + * @return the lowest maximum value for the given calendar field. + * @see #getMinimum(int) + * @see #getMaximum(int) + * @see #getGreatestMinimum(int) + * @see #getActualMinimum(int) + * @see #getActualMaximum(int) + */ + abstract public int getLeastMaximum(int field); + + /** + * Returns the minimum value that the specified calendar field + * could have, given the time value of this Calendar. + * + *

The default implementation of this method uses an iterative + * algorithm to determine the actual minimum value for the + * calendar field. Subclasses should, if possible, override this + * with a more efficient implementation - in many cases, they can + * simply return getMinimum(). + * + * @param field the calendar field + * @return the minimum of the given calendar field for the time + * value of this Calendar + * @see #getMinimum(int) + * @see #getMaximum(int) + * @see #getGreatestMinimum(int) + * @see #getLeastMaximum(int) + * @see #getActualMaximum(int) + * @since 1.2 + */ + public int getActualMinimum(int field) { + int fieldValue = getGreatestMinimum(field); + int endValue = getMinimum(field); + + // if we know that the minimum value is always the same, just return it + if (fieldValue == endValue) { + return fieldValue; + } + + // clone the calendar so we don't mess with the real one, and set it to + // accept anything for the field values + Calendar work = (Calendar)this.clone(); + work.setLenient(true); + + // now try each value from getLeastMaximum() to getMaximum() one by one until + // we get a value that normalizes to another value. The last value that + // normalizes to itself is the actual minimum for the current date + int result = fieldValue; + + do { + work.set(field, fieldValue); + if (work.get(field) != fieldValue) { + break; + } else { + result = fieldValue; + fieldValue--; + } + } while (fieldValue >= endValue); + + return result; + } + + /** + * Returns the maximum value that the specified calendar field + * could have, given the time value of this + * Calendar. For example, the actual maximum value of + * the MONTH field is 12 in some years, and 13 in + * other years in the Hebrew calendar system. + * + *

The default implementation of this method uses an iterative + * algorithm to determine the actual maximum value for the + * calendar field. Subclasses should, if possible, override this + * with a more efficient implementation. + * + * @param field the calendar field + * @return the maximum of the given calendar field for the time + * value of this Calendar + * @see #getMinimum(int) + * @see #getMaximum(int) + * @see #getGreatestMinimum(int) + * @see #getLeastMaximum(int) + * @see #getActualMinimum(int) + * @since 1.2 + */ + public int getActualMaximum(int field) { + int fieldValue = getLeastMaximum(field); + int endValue = getMaximum(field); + + // if we know that the maximum value is always the same, just return it. + if (fieldValue == endValue) { + return fieldValue; + } + + // clone the calendar so we don't mess with the real one, and set it to + // accept anything for the field values. + Calendar work = (Calendar)this.clone(); + work.setLenient(true); + + // if we're counting weeks, set the day of the week to Sunday. We know the + // last week of a month or year will contain the first day of the week. + if (field == WEEK_OF_YEAR || field == WEEK_OF_MONTH) + work.set(DAY_OF_WEEK, firstDayOfWeek); + + // now try each value from getLeastMaximum() to getMaximum() one by one until + // we get a value that normalizes to another value. The last value that + // normalizes to itself is the actual maximum for the current date + int result = fieldValue; + + do { + work.set(field, fieldValue); + if (work.get(field) != fieldValue) { + break; + } else { + result = fieldValue; + fieldValue++; + } + } while (fieldValue <= endValue); + + return result; + } + + /** + * Creates and returns a copy of this object. + * + * @return a copy of this object. + */ + public Object clone() + { + try { + Calendar other = (Calendar) super.clone(); + + other.fields = new int[FIELD_COUNT]; + other.isSet = new boolean[FIELD_COUNT]; + other.stamp = new int[FIELD_COUNT]; + for (int i = 0; i < FIELD_COUNT; i++) { + other.fields[i] = fields[i]; + other.stamp[i] = stamp[i]; + other.isSet[i] = isSet[i]; + } + other.zone = (TimeZone) zone.clone(); + return other; + } + catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } + } + + private static final String[] FIELD_NAME = { + "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH", "DAY_OF_MONTH", + "DAY_OF_YEAR", "DAY_OF_WEEK", "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", + "HOUR_OF_DAY", "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET", + "DST_OFFSET" + }; + + /** + * Returns the name of the specified calendar field. + * + * @param field the calendar field + * @return the calendar field name + * @exception IndexOutOfBoundsException if field is negative, + * equal to or greater then FIELD_COUNT. + */ + static final String getFieldName(int field) { + return FIELD_NAME[field]; + } + + /** + * Return a string representation of this calendar. This method + * is intended to be used only for debugging purposes, and the + * format of the returned string may vary between implementations. + * The returned string may be empty but may not be null. + * + * @return a string representation of this calendar. + */ + public String toString() { + // NOTE: BuddhistCalendar.toString() interprets the string + // produced by this method so that the Gregorian year number + // is substituted by its B.E. year value. It relies on + // "...,YEAR=,..." or "...,YEAR=?,...". + StringBuilder buffer = new StringBuilder(800); + buffer.append(getClass().getName()).append('['); + appendValue(buffer, "time", isTimeSet, time); + buffer.append(",areFieldsSet=").append(areFieldsSet); + buffer.append(",areAllFieldsSet=").append(areAllFieldsSet); + buffer.append(",lenient=").append(lenient); + buffer.append(",zone=").append(zone); + appendValue(buffer, ",firstDayOfWeek", true, (long) firstDayOfWeek); + appendValue(buffer, ",minimalDaysInFirstWeek", true, (long) minimalDaysInFirstWeek); + for (int i = 0; i < FIELD_COUNT; ++i) { + buffer.append(','); + appendValue(buffer, FIELD_NAME[i], isSet(i), (long) fields[i]); + } + buffer.append(']'); + return buffer.toString(); + } + + // =======================privates=============================== + + private static final void appendValue(StringBuilder sb, String item, boolean valid, long value) { + sb.append(item).append('='); + if (valid) { + sb.append(value); + } else { + sb.append('?'); + } + } + + /** + * Both firstDayOfWeek and minimalDaysInFirstWeek are locale-dependent. + * They are used to figure out the week count for a specific date for + * a given locale. These must be set when a Calendar is constructed. + * @param desiredLocale the given locale. + */ + private void setWeekCountData(Locale desiredLocale) + { + /* try to get the Locale data from the cache */ + int[] data = cachedLocaleData.get(desiredLocale); + if (data == null) { /* cache miss */ +// ResourceBundle bundle = LocaleData.getCalendarData(desiredLocale); + data = new int[2]; +// data[0] = Integer.parseInt(bundle.getString("firstDayOfWeek")); +// data[1] = Integer.parseInt(bundle.getString("minimalDaysInFirstWeek")); + cachedLocaleData.putIfAbsent(desiredLocale, data); + } + firstDayOfWeek = data[0]; + minimalDaysInFirstWeek = data[1]; + } + + /** + * Recomputes the time and updates the status fields isTimeSet + * and areFieldsSet. Callers should check isTimeSet and only + * call this method if isTimeSet is false. + */ + private void updateTime() { + computeTime(); + // The areFieldsSet and areAllFieldsSet values are no longer + // controlled here (as of 1.5). + isTimeSet = true; + } + + private int compareTo(long t) { + long thisTime = getMillisOf(this); + return (thisTime > t) ? 1 : (thisTime == t) ? 0 : -1; + } + + private static final long getMillisOf(Calendar calendar) { + if (calendar.isTimeSet) { + return calendar.time; + } + Calendar cal = (Calendar) calendar.clone(); + cal.setLenient(true); + return cal.getTimeInMillis(); + } + + /** + * Adjusts the stamp[] values before nextStamp overflow. nextStamp + * is set to the next stamp value upon the return. + */ + private final void adjustStamp() { + int max = MINIMUM_USER_STAMP; + int newStamp = MINIMUM_USER_STAMP; + + for (;;) { + int min = Integer.MAX_VALUE; + for (int i = 0; i < stamp.length; i++) { + int v = stamp[i]; + if (v >= newStamp && min > v) { + min = v; + } + if (max < v) { + max = v; + } + } + if (max != min && min == Integer.MAX_VALUE) { + break; + } + for (int i = 0; i < stamp.length; i++) { + if (stamp[i] == min) { + stamp[i] = newStamp; + } + } + newStamp++; + if (min == max) { + break; + } + } + nextStamp = newStamp; + } + + /** + * Sets the WEEK_OF_MONTH and WEEK_OF_YEAR fields to new values with the + * new parameter value if they have been calculated internally. + */ + private void invalidateWeekFields() + { + if (stamp[WEEK_OF_MONTH] != COMPUTED && + stamp[WEEK_OF_YEAR] != COMPUTED) { + return; + } + + // We have to check the new values of these fields after changing + // firstDayOfWeek and/or minimalDaysInFirstWeek. If the field values + // have been changed, then set the new values. (4822110) + Calendar cal = (Calendar) clone(); + cal.setLenient(true); + cal.clear(WEEK_OF_MONTH); + cal.clear(WEEK_OF_YEAR); + + if (stamp[WEEK_OF_MONTH] == COMPUTED) { + int weekOfMonth = cal.get(WEEK_OF_MONTH); + if (fields[WEEK_OF_MONTH] != weekOfMonth) { + fields[WEEK_OF_MONTH] = weekOfMonth; + } + } + + if (stamp[WEEK_OF_YEAR] == COMPUTED) { + int weekOfYear = cal.get(WEEK_OF_YEAR); + if (fields[WEEK_OF_YEAR] != weekOfYear) { + fields[WEEK_OF_YEAR] = weekOfYear; + } + } + } + + /** + * Save the state of this object to a stream (i.e., serialize it). + * + * Ideally, Calendar would only write out its state data and + * the current time, and not write any field data out, such as + * fields[], isTimeSet, areFieldsSet, + * and isSet[]. nextStamp also should not be part + * of the persistent state. Unfortunately, this didn't happen before JDK 1.1 + * shipped. To be compatible with JDK 1.1, we will always have to write out + * the field values and state flags. However, nextStamp can be + * removed from the serialization stream; this will probably happen in the + * near future. + */ + private void writeObject(ObjectOutputStream stream) + throws IOException + { + // Try to compute the time correctly, for the future (stream + // version 2) in which we don't write out fields[] or isSet[]. + if (!isTimeSet) { + try { + updateTime(); + } + catch (IllegalArgumentException e) {} + } + + // If this Calendar has a ZoneInfo, save it and set a + // SimpleTimeZone equivalent (as a single DST schedule) for + // backward compatibility. + TimeZone savedZone = null; +// if (zone instanceof ZoneInfo) { +// SimpleTimeZone stz = ((ZoneInfo)zone).getLastRuleInstance(); +// if (stz == null) { +// stz = new SimpleTimeZone(zone.getRawOffset(), zone.getID()); +// } +// savedZone = zone; +// zone = stz; +// } + + // Write out the 1.1 FCS object. + stream.defaultWriteObject(); + + // Write out the ZoneInfo object + // 4802409: we write out even if it is null, a temporary workaround + // the real fix for bug 4844924 in corba-iiop + stream.writeObject(savedZone); + if (savedZone != null) { + zone = savedZone; + } + } + + /** + * Reconstitutes this object from a stream (i.e., deserialize it). + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + final ObjectInputStream input = stream; + input.defaultReadObject(); + + stamp = new int[FIELD_COUNT]; + + // Starting with version 2 (not implemented yet), we expect that + // fields[], isSet[], isTimeSet, and areFieldsSet may not be + // streamed out anymore. We expect 'time' to be correct. + if (serialVersionOnStream >= 2) + { + isTimeSet = true; + if (fields == null) fields = new int[FIELD_COUNT]; + if (isSet == null) isSet = new boolean[FIELD_COUNT]; + } + else if (serialVersionOnStream >= 0) + { + for (int i=0; i() { +// public ZoneInfo run() throws Exception { +// return (ZoneInfo) input.readObject(); +// } +// }, +// CalendarAccessControlContext.INSTANCE); +// } catch (PrivilegedActionException pae) { +// Exception e = pae.getException(); +// if (!(e instanceof OptionalDataException)) { +// if (e instanceof RuntimeException) { +// throw (RuntimeException) e; +// } else if (e instanceof IOException) { +// throw (IOException) e; +// } else if (e instanceof ClassNotFoundException) { +// throw (ClassNotFoundException) e; +// } +// throw new RuntimeException(e); +// } +// } + if (zi != null) { + zone = zi; + } + + // If the deserialized object has a SimpleTimeZone, try to + // replace it with a ZoneInfo equivalent (as of 1.4) in order + // to be compatible with the SimpleTimeZone-based + // implementation as much as possible. + if (zone instanceof SimpleTimeZone) { + String id = zone.getID(); + TimeZone tz = TimeZone.getTimeZone(id); + if (tz != null && tz.hasSameRules(zone) && tz.getID().equals(id)) { + zone = tz; + } + } + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/Currency.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/Currency.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,735 @@ +/* + * 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.BufferedInputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.Serializable; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Represents a currency. Currencies are identified by their ISO 4217 currency + * codes. Visit the + * ISO web site for more information, including a table of + * currency codes. + *

+ * The class is designed so that there's never more than one + * Currency instance for any given currency. Therefore, there's + * no public constructor. You obtain a Currency instance using + * the getInstance methods. + *

+ * Users can supersede the Java runtime currency data by creating a properties + * file named <JAVA_HOME>/lib/currency.properties. The contents + * of the properties file are key/value pairs of the ISO 3166 country codes + * and the ISO 4217 currency data respectively. The value part consists of + * three ISO 4217 values of a currency, i.e., an alphabetic code, a numeric + * code, and a minor unit. Those three ISO 4217 values are separated by commas. + * The lines which start with '#'s are considered comment lines. For example, + *

+ * + * #Sample currency properties
+ * JP=JPZ,999,0 + *
+ *

+ * will supersede the currency data for Japan. + * + * @since 1.4 + */ +public final class Currency implements Serializable { + + private static final long serialVersionUID = -158308464356906721L; + + /** + * ISO 4217 currency code for this currency. + * + * @serial + */ + private final String currencyCode; + + /** + * Default fraction digits for this currency. + * Set from currency data tables. + */ + transient private final int defaultFractionDigits; + + /** + * ISO 4217 numeric code for this currency. + * Set from currency data tables. + */ + transient private final int numericCode; + + + // class data: instance map + + private static HashMap instances = new HashMap(7); + private static HashSet available; + + + // Class data: currency data obtained from currency.data file. + // Purpose: + // - determine valid country codes + // - determine valid currency codes + // - map country codes to currency codes + // - obtain default fraction digits for currency codes + // + // sc = special case; dfd = default fraction digits + // Simple countries are those where the country code is a prefix of the + // currency code, and there are no known plans to change the currency. + // + // table formats: + // - mainTable: + // - maps country code to 32-bit int + // - 26*26 entries, corresponding to [A-Z]*[A-Z] + // - \u007F -> not valid country + // - bits 18-31: unused + // - bits 8-17: numeric code (0 to 1023) + // - bit 7: 1 - special case, bits 0-4 indicate which one + // 0 - simple country, bits 0-4 indicate final char of currency code + // - bits 5-6: fraction digits for simple countries, 0 for special cases + // - bits 0-4: final char for currency code for simple country, or ID of special case + // - special case IDs: + // - 0: country has no currency + // - other: index into sc* arrays + 1 + // - scCutOverTimes: cut-over time in millis as returned by + // System.currentTimeMillis for special case countries that are changing + // currencies; Long.MAX_VALUE for countries that are not changing currencies + // - scOldCurrencies: old currencies for special case countries + // - scNewCurrencies: new currencies for special case countries that are + // changing currencies; null for others + // - scOldCurrenciesDFD: default fraction digits for old currencies + // - scNewCurrenciesDFD: default fraction digits for new currencies, 0 for + // countries that are not changing currencies + // - otherCurrencies: concatenation of all currency codes that are not the + // main currency of a simple country, separated by "-" + // - otherCurrenciesDFD: decimal format digits for currencies in otherCurrencies, same order + + static int formatVersion; + static int dataVersion; + static int[] mainTable; + static long[] scCutOverTimes; + static String[] scOldCurrencies; + static String[] scNewCurrencies; + static int[] scOldCurrenciesDFD; + static int[] scNewCurrenciesDFD; + static int[] scOldCurrenciesNumericCode; + static int[] scNewCurrenciesNumericCode; + static String otherCurrencies; + static int[] otherCurrenciesDFD; + static int[] otherCurrenciesNumericCode; + + // handy constants - must match definitions in GenerateCurrencyData + // magic number + private static final int MAGIC_NUMBER = 0x43757244; + // number of characters from A to Z + private static final int A_TO_Z = ('Z' - 'A') + 1; + // entry for invalid country codes + private static final int INVALID_COUNTRY_ENTRY = 0x007F; + // entry for countries without currency + private static final int COUNTRY_WITHOUT_CURRENCY_ENTRY = 0x0080; + // mask for simple case country entries + private static final int SIMPLE_CASE_COUNTRY_MASK = 0x0000; + // mask for simple case country entry final character + private static final int SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK = 0x001F; + // mask for simple case country entry default currency digits + private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK = 0x0060; + // shift count for simple case country entry default currency digits + private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT = 5; + // mask for special case country entries + private static final int SPECIAL_CASE_COUNTRY_MASK = 0x0080; + // mask for special case country index + private static final int SPECIAL_CASE_COUNTRY_INDEX_MASK = 0x001F; + // delta from entry index component in main table to index into special case tables + private static final int SPECIAL_CASE_COUNTRY_INDEX_DELTA = 1; + // mask for distinguishing simple and special case countries + private static final int COUNTRY_TYPE_MASK = SIMPLE_CASE_COUNTRY_MASK | SPECIAL_CASE_COUNTRY_MASK; + // mask for the numeric code of the currency + private static final int NUMERIC_CODE_MASK = 0x0003FF00; + // shift count for the numeric code of the currency + private static final int NUMERIC_CODE_SHIFT = 8; + + // Currency data format version + private static final int VALID_FORMAT_VERSION = 1; + + static { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + String homeDir = System.getProperty("java.home"); + try { + String dataFile = homeDir + File.separator + + "lib" + File.separator + "currency.data"; + DataInputStream dis = new DataInputStream( + new BufferedInputStream( + new FileInputStream(dataFile))); + if (dis.readInt() != MAGIC_NUMBER) { + throw new InternalError("Currency data is possibly corrupted"); + } + formatVersion = dis.readInt(); + if (formatVersion != VALID_FORMAT_VERSION) { + throw new InternalError("Currency data format is incorrect"); + } + dataVersion = dis.readInt(); + mainTable = readIntArray(dis, A_TO_Z * A_TO_Z); + int scCount = dis.readInt(); + scCutOverTimes = readLongArray(dis, scCount); + scOldCurrencies = readStringArray(dis, scCount); + scNewCurrencies = readStringArray(dis, scCount); + scOldCurrenciesDFD = readIntArray(dis, scCount); + scNewCurrenciesDFD = readIntArray(dis, scCount); + scOldCurrenciesNumericCode = readIntArray(dis, scCount); + scNewCurrenciesNumericCode = readIntArray(dis, scCount); + int ocCount = dis.readInt(); + otherCurrencies = dis.readUTF(); + otherCurrenciesDFD = readIntArray(dis, ocCount); + otherCurrenciesNumericCode = readIntArray(dis, ocCount); + dis.close(); + } catch (IOException e) { + InternalError ie = new InternalError(); + ie.initCause(e); + throw ie; + } + + // look for the properties file for overrides +// try { + File propFile = new File(homeDir + File.separator + + "lib" + File.separator + + "currency.properties"); +// if (propFile.exists()) { +// Properties props = new Properties(); +// try (FileReader fr = new FileReader(propFile)) { +// props.load(fr); +// } +// Set keys = props.stringPropertyNames(); +// Pattern propertiesPattern = +// Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*([0-3])"); +// for (String key : keys) { +// replaceCurrencyData(propertiesPattern, +// key.toUpperCase(Locale.ROOT), +// props.getProperty(key).toUpperCase(Locale.ROOT)); +// } +// } +// } catch (IOException e) { +// info("currency.properties is ignored because of an IOException", e); +// } + return null; + } + }); + } + + /** + * Constants for retrieving localized names from the name providers. + */ + private static final int SYMBOL = 0; + private static final int DISPLAYNAME = 1; + + + /** + * Constructs a Currency instance. The constructor is private + * so that we can insure that there's never more than one instance for a + * given currency. + */ + private Currency(String currencyCode, int defaultFractionDigits, int numericCode) { + this.currencyCode = currencyCode; + this.defaultFractionDigits = defaultFractionDigits; + this.numericCode = numericCode; + } + + /** + * Returns the Currency instance for the given currency code. + * + * @param currencyCode the ISO 4217 code of the currency + * @return the Currency instance for the given currency code + * @exception NullPointerException if currencyCode is null + * @exception IllegalArgumentException if currencyCode is not + * a supported ISO 4217 code. + */ + public static Currency getInstance(String currencyCode) { + return getInstance(currencyCode, Integer.MIN_VALUE, 0); + } + + private static Currency getInstance(String currencyCode, int defaultFractionDigits, + int numericCode) { + synchronized (instances) { + // Try to look up the currency code in the instances table. + // This does the null pointer check as a side effect. + // Also, if there already is an entry, the currencyCode must be valid. + Currency instance = instances.get(currencyCode); + if (instance != null) { + return instance; + } + + if (defaultFractionDigits == Integer.MIN_VALUE) { + // Currency code not internally generated, need to verify first + // A currency code must have 3 characters and exist in the main table + // or in the list of other currencies. + if (currencyCode.length() != 3) { + throw new IllegalArgumentException(); + } + char char1 = currencyCode.charAt(0); + char char2 = currencyCode.charAt(1); + int tableEntry = getMainTableEntry(char1, char2); + if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK + && tableEntry != INVALID_COUNTRY_ENTRY + && currencyCode.charAt(2) - 'A' == (tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK)) { + defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT; + numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT; + } else { + // Check for '-' separately so we don't get false hits in the table. + if (currencyCode.charAt(2) == '-') { + throw new IllegalArgumentException(); + } + int index = otherCurrencies.indexOf(currencyCode); + if (index == -1) { + throw new IllegalArgumentException(); + } + defaultFractionDigits = otherCurrenciesDFD[index / 4]; + numericCode = otherCurrenciesNumericCode[index / 4]; + } + } + + instance = new Currency(currencyCode, defaultFractionDigits, numericCode); + instances.put(currencyCode, instance); + return instance; + } + } + + /** + * Returns the Currency instance for the country of the + * given locale. The language and variant components of the locale + * are ignored. The result may vary over time, as countries change their + * currencies. For example, for the original member countries of the + * European Monetary Union, the method returns the old national currencies + * until December 31, 2001, and the Euro from January 1, 2002, local time + * of the respective countries. + *

+ * The method returns null for territories that don't + * have a currency, such as Antarctica. + * + * @param locale the locale for whose country a Currency + * instance is needed + * @return the Currency instance for the country of the given + * locale, or null + * @exception NullPointerException if locale or its country + * code is null + * @exception IllegalArgumentException if the country of the given locale + * is not a supported ISO 3166 country code. + */ + public static Currency getInstance(Locale locale) { + String country = locale.getCountry(); + if (country == null) { + throw new NullPointerException(); + } + + if (country.length() != 2) { + throw new IllegalArgumentException(); + } + + char char1 = country.charAt(0); + char char2 = country.charAt(1); + int tableEntry = getMainTableEntry(char1, char2); + if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK + && tableEntry != INVALID_COUNTRY_ENTRY) { + char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A'); + int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT; + int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT; + StringBuffer sb = new StringBuffer(country); + sb.append(finalChar); + return getInstance(sb.toString(), defaultFractionDigits, numericCode); + } else { + // special cases + if (tableEntry == INVALID_COUNTRY_ENTRY) { + throw new IllegalArgumentException(); + } + if (tableEntry == COUNTRY_WITHOUT_CURRENCY_ENTRY) { + return null; + } else { + int index = (tableEntry & SPECIAL_CASE_COUNTRY_INDEX_MASK) - SPECIAL_CASE_COUNTRY_INDEX_DELTA; + if (scCutOverTimes[index] == Long.MAX_VALUE || System.currentTimeMillis() < scCutOverTimes[index]) { + return getInstance(scOldCurrencies[index], scOldCurrenciesDFD[index], + scOldCurrenciesNumericCode[index]); + } else { + return getInstance(scNewCurrencies[index], scNewCurrenciesDFD[index], + scNewCurrenciesNumericCode[index]); + } + } + } + } + + /** + * Gets the set of available currencies. The returned set of currencies + * contains all of the available currencies, which may include currencies + * that represent obsolete ISO 4217 codes. The set can be modified + * without affecting the available currencies in the runtime. + * + * @return the set of available currencies. If there is no currency + * available in the runtime, the returned set is empty. + * @since 1.7 + */ + public static Set getAvailableCurrencies() { + synchronized(Currency.class) { + if (available == null) { + available = new HashSet(256); + + // Add simple currencies first + for (char c1 = 'A'; c1 <= 'Z'; c1 ++) { + for (char c2 = 'A'; c2 <= 'Z'; c2 ++) { + int tableEntry = getMainTableEntry(c1, c2); + if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK + && tableEntry != INVALID_COUNTRY_ENTRY) { + char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A'); + int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT; + int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT; + StringBuilder sb = new StringBuilder(); + sb.append(c1); + sb.append(c2); + sb.append(finalChar); + available.add(getInstance(sb.toString(), defaultFractionDigits, numericCode)); + } + } + } + + // Now add other currencies + StringTokenizer st = new StringTokenizer(otherCurrencies, "-"); + while (st.hasMoreElements()) { + available.add(getInstance((String)st.nextElement())); + } + } + } + + return (Set) available.clone(); + } + + /** + * Gets the ISO 4217 currency code of this currency. + * + * @return the ISO 4217 currency code of this currency. + */ + public String getCurrencyCode() { + return currencyCode; + } + + /** + * Gets the symbol of this currency for the default locale. + * For example, for the US Dollar, the symbol is "$" if the default + * locale is the US, while for other locales it may be "US$". If no + * symbol can be determined, the ISO 4217 currency code is returned. + * + * @return the symbol of this currency for the default locale + */ + public String getSymbol() { + return getSymbol(Locale.getDefault(Locale.Category.DISPLAY)); + } + + /** + * Gets the symbol of this currency for the specified locale. + * For example, for the US Dollar, the symbol is "$" if the specified + * locale is the US, while for other locales it may be "US$". If no + * symbol can be determined, the ISO 4217 currency code is returned. + * + * @param locale the locale for which a display name for this currency is + * needed + * @return the symbol of this currency for the specified locale + * @exception NullPointerException if locale is null + */ + public String getSymbol(Locale locale) { + try { + // Check whether a provider can provide an implementation that's closer + // to the requested locale than what the Java runtime itself can provide. + /* + LocaleServiceProviderPool pool = + LocaleServiceProviderPool.getPool(CurrencyNameProvider.class); + + if (pool.hasProviders()) { + // Assuming that all the country locales include necessary currency + // symbols in the Java runtime's resources, so there is no need to + // examine whether Java runtime's currency resource bundle is missing + // names. Therefore, no resource bundle is provided for calling this + // method. + String symbol = pool.getLocalizedObject( + CurrencyNameGetter.INSTANCE, + locale, (OpenListResourceBundle)null, + currencyCode, SYMBOL); + if (symbol != null) { + return symbol; + } + } + */ + ResourceBundle bundle = null; //LocaleData.getCurrencyNames(locale); + return bundle.getString(currencyCode); + } catch (MissingResourceException e) { + // use currency code as symbol of last resort + return currencyCode; + } + } + + /** + * Gets the default number of fraction digits used with this currency. + * For example, the default number of fraction digits for the Euro is 2, + * while for the Japanese Yen it's 0. + * In the case of pseudo-currencies, such as IMF Special Drawing Rights, + * -1 is returned. + * + * @return the default number of fraction digits used with this currency + */ + public int getDefaultFractionDigits() { + return defaultFractionDigits; + } + + /** + * Returns the ISO 4217 numeric code of this currency. + * + * @return the ISO 4217 numeric code of this currency + * @since 1.7 + */ + public int getNumericCode() { + return numericCode; + } + + /** + * Gets the name that is suitable for displaying this currency for + * the default locale. If there is no suitable display name found + * for the default locale, the ISO 4217 currency code is returned. + * + * @return the display name of this currency for the default locale + * @since 1.7 + */ + public String getDisplayName() { + return getDisplayName(Locale.getDefault(Locale.Category.DISPLAY)); + } + + /** + * Gets the name that is suitable for displaying this currency for + * the specified locale. If there is no suitable display name found + * for the specified locale, the ISO 4217 currency code is returned. + * + * @param locale the locale for which a display name for this currency is + * needed + * @return the display name of this currency for the specified locale + * @exception NullPointerException if locale is null + * @since 1.7 + */ + public String getDisplayName(Locale locale) { +// try { +// OpenListResourceBundle bundle = LocaleData.getCurrencyNames(locale); +// String result = null; +// String bundleKey = currencyCode.toLowerCase(Locale.ROOT); +// +// // Check whether a provider can provide an implementation that's closer +// // to the requested locale than what the Java runtime itself can provide. +// LocaleServiceProviderPool pool = +// LocaleServiceProviderPool.getPool(CurrencyNameProvider.class); +// if (pool.hasProviders()) { +// result = pool.getLocalizedObject( +// CurrencyNameGetter.INSTANCE, +// locale, bundleKey, bundle, currencyCode, DISPLAYNAME); +// } +// +// if (result == null) { +// result = bundle.getString(bundleKey); +// } +// +// if (result != null) { +// return result; +// } +// } catch (MissingResourceException e) { +// // fall through +// } + + // use currency code as symbol of last resort + return currencyCode; + } + + /** + * Returns the ISO 4217 currency code of this currency. + * + * @return the ISO 4217 currency code of this currency + */ + public String toString() { + return currencyCode; + } + + /** + * Resolves instances being deserialized to a single instance per currency. + */ + private Object readResolve() { + return getInstance(currencyCode); + } + + /** + * Gets the main table entry for the country whose country code consists + * of char1 and char2. + */ + private static int getMainTableEntry(char char1, char char2) { + if (char1 < 'A' || char1 > 'Z' || char2 < 'A' || char2 > 'Z') { + throw new IllegalArgumentException(); + } + return mainTable[(char1 - 'A') * A_TO_Z + (char2 - 'A')]; + } + + /** + * Sets the main table entry for the country whose country code consists + * of char1 and char2. + */ + private static void setMainTableEntry(char char1, char char2, int entry) { + if (char1 < 'A' || char1 > 'Z' || char2 < 'A' || char2 > 'Z') { + throw new IllegalArgumentException(); + } + mainTable[(char1 - 'A') * A_TO_Z + (char2 - 'A')] = entry; + } + + /** + * Obtains a localized currency names from a CurrencyNameProvider + * implementation. + private static class CurrencyNameGetter + implements LocaleServiceProviderPool.LocalizedObjectGetter { + private static final CurrencyNameGetter INSTANCE = new CurrencyNameGetter(); + + public String getObject(CurrencyNameProvider currencyNameProvider, + Locale locale, + String key, + Object... params) { + assert params.length == 1; + int type = (Integer)params[0]; + + switch(type) { + case SYMBOL: + return currencyNameProvider.getSymbol(key, locale); + case DISPLAYNAME: + return currencyNameProvider.getDisplayName(key, locale); + default: + assert false; // shouldn't happen + } + + return null; + } + } + */ + + private static int[] readIntArray(DataInputStream dis, int count) throws IOException { + int[] ret = new int[count]; + for (int i = 0; i < count; i++) { + ret[i] = dis.readInt(); + } + + return ret; + } + + private static long[] readLongArray(DataInputStream dis, int count) throws IOException { + long[] ret = new long[count]; + for (int i = 0; i < count; i++) { + ret[i] = dis.readLong(); + } + + return ret; + } + + private static String[] readStringArray(DataInputStream dis, int count) throws IOException { + String[] ret = new String[count]; + for (int i = 0; i < count; i++) { + ret[i] = dis.readUTF(); + } + + return ret; + } + + /** + * Replaces currency data found in the currencydata.properties file + * + * @param pattern regex pattern for the properties + * @param ctry country code + * @param data currency data. This is a comma separated string that + * consists of "three-letter alphabet code", "three-digit numeric code", + * and "one-digit (0,1,2, or 3) default fraction digit". + * For example, "JPZ,392,0". + * @throws + private static void replaceCurrencyData(Pattern pattern, String ctry, String curdata) { + + if (ctry.length() != 2) { + // ignore invalid country code + String message = new StringBuilder() + .append("The entry in currency.properties for ") + .append(ctry).append(" is ignored because of the invalid country code.") + .toString(); + info(message, null); + return; + } + + Matcher m = pattern.matcher(curdata); + if (!m.find()) { + // format is not recognized. ignore the data + String message = new StringBuilder() + .append("The entry in currency.properties for ") + .append(ctry) + .append(" is ignored because the value format is not recognized.") + .toString(); + info(message, null); + return; + } + + String code = m.group(1); + int numeric = Integer.parseInt(m.group(2)); + int fraction = Integer.parseInt(m.group(3)); + int entry = numeric << NUMERIC_CODE_SHIFT; + + int index; + for (index = 0; index < scOldCurrencies.length; index++) { + if (scOldCurrencies[index].equals(code)) { + break; + } + } + + if (index == scOldCurrencies.length) { + // simple case + entry |= (fraction << SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT) | + (code.charAt(2) - 'A'); + } else { + // special case + entry |= SPECIAL_CASE_COUNTRY_MASK | + (index + SPECIAL_CASE_COUNTRY_INDEX_DELTA); + } + setMainTableEntry(ctry.charAt(0), ctry.charAt(1), entry); + } + */ + + private static void info(String message, Throwable t) { + Logger logger = Logger.getLogger("java.util.Currency"); + if (logger.isLoggable(Level.INFO)) { + if (t != null) { + logger.log(Level.INFO, message, t); + } else { + logger.info(message); + } + } + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/CurrencyData.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/CurrencyData.properties Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,586 @@ +# +# 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. +# + +formatVersion=1 + +# Version of the currency code information in this class. +# It is a serial number that accompanies with each amendment, such as +# 'MAxxx.doc' + +dataVersion=140 + +# List of all valid ISO 4217 currency codes. +# To ensure compatibility, do not remove codes. + +all=ADP020-AED784-AFA004-AFN971-ALL008-AMD051-ANG532-AOA973-ARS032-ATS040-AUD036-\ + AWG533-AYM945-AZM031-AZN944-BAM977-BBD052-BDT050-BEF056-BGL100-BGN975-BHD048-BIF108-\ + BMD060-BND096-BOB068-BOV984-BRL986-BSD044-BTN064-BWP072-BYB112-BYR974-\ + BZD084-CAD124-CDF976-CHF756-CLF990-CLP152-CNY156-COP170-CRC188-CSD891-CUP192-\ + CVE132-CYP196-CZK203-DEM276-DJF262-DKK208-DOP214-DZD012-EEK233-EGP818-\ + ERN232-ESP724-ETB230-EUR978-FIM246-FJD242-FKP238-FRF250-GBP826-GEL981-\ + GHC288-GHS936-GIP292-GMD270-GNF324-GRD300-GTQ320-GWP624-GYD328-HKD344-HNL340-\ + HRK191-HTG332-HUF348-IDR360-IEP372-ILS376-INR356-IQD368-IRR364-ISK352-\ + ITL380-JMD388-JOD400-JPY392-KES404-KGS417-KHR116-KMF174-KPW408-KRW410-\ + KWD414-KYD136-KZT398-LAK418-LBP422-LKR144-LRD430-LSL426-LTL440-LUF442-\ + LVL428-LYD434-MAD504-MDL498-MGA969-MGF450-MKD807-MMK104-MNT496-MOP446-MRO478-\ + MTL470-MUR480-MVR462-MWK454-MXN484-MXV979-MYR458-MZM508-MZN943-NAD516-NGN566-\ + NIO558-NLG528-NOK578-NPR524-NZD554-OMR512-PAB590-PEN604-PGK598-PHP608-\ + PKR586-PLN985-PTE620-PYG600-QAR634-ROL946-RON946-RSD941-RUB643-RUR810-RWF646-SAR682-\ + SBD090-SCR690-SDD736-SDG938-SEK752-SGD702-SHP654-SIT705-SKK703-SLL694-SOS706-\ + SRD968-SRG740-STD678-SVC222-SYP760-SZL748-THB764-TJS972-TMM795-TND788-TOP776-\ + TPE626-TRL792-TRY949-TTD780-TWD901-TZS834-UAH980-UGX800-USD840-USN997-USS998-\ + UYU858-UZS860-VEB862-VEF937-VND704-VUV548-WST882-XAF950-XAG961-XAU959-XBA955-\ + XBB956-XBC957-XBD958-XCD951-XDR960-XFO000-XFU000-XOF952-XPD964-XPF953-\ + XPT962-XTS963-XXX999-YER886-YUM891-ZAR710-ZMK894-ZWD716-ZWN942 + + +# Mappings from ISO 3166 country codes to ISO 4217 currency codes. +# +# Three forms are used: +# Form 1: = +# Form 2: =;

+ * Prior to JDK 1.1, the class Date had two additional + * functions. It allowed the interpretation of dates as year, month, day, hour, + * minute, and second values. It also allowed the formatting and parsing + * of date strings. Unfortunately, the API for these functions was not + * amenable to internationalization. As of JDK 1.1, the + * Calendar class should be used to convert between dates and time + * fields and the DateFormat class should be used to format and + * parse date strings. + * The corresponding methods in Date are deprecated. + *

+ * Although the Date class is intended to reflect + * coordinated universal time (UTC), it may not do so exactly, + * depending on the host environment of the Java Virtual Machine. + * Nearly all modern operating systems assume that 1 day = + * 24 × 60 × 60 = 86400 seconds + * in all cases. In UTC, however, about once every year or two there + * is an extra second, called a "leap second." The leap + * second is always added as the last second of the day, and always + * on December 31 or June 30. For example, the last minute of the + * year 1995 was 61 seconds long, thanks to an added leap second. + * Most computer clocks are not accurate enough to be able to reflect + * the leap-second distinction. + *

+ * Some computer standards are defined in terms of Greenwich mean + * time (GMT), which is equivalent to universal time (UT). GMT is + * the "civil" name for the standard; UT is the + * "scientific" name for the same standard. The + * distinction between UTC and UT is that UTC is based on an atomic + * clock and UT is based on astronomical observations, which for all + * practical purposes is an invisibly fine hair to split. Because the + * earth's rotation is not uniform (it slows down and speeds up + * in complicated ways), UT does not always flow uniformly. Leap + * seconds are introduced as needed into UTC so as to keep UTC within + * 0.9 seconds of UT1, which is a version of UT with certain + * corrections applied. There are other time and date systems as + * well; for example, the time scale used by the satellite-based + * global positioning system (GPS) is synchronized to UTC but is + * not adjusted for leap seconds. An interesting source of + * further information is the U.S. Naval Observatory, particularly + * the Directorate of Time at: + *

+ *     http://tycho.usno.navy.mil
+ * 
+ *

+ * and their definitions of "Systems of Time" at: + *

+ *     http://tycho.usno.navy.mil/systime.html
+ * 
+ *

+ * In all methods of class Date that accept or return + * year, month, date, hours, minutes, and seconds values, the + * following representations are used: + *

    + *
  • A year y is represented by the integer + * y - 1900. + *
  • A month is represented by an integer from 0 to 11; 0 is January, + * 1 is February, and so forth; thus 11 is December. + *
  • A date (day of month) is represented by an integer from 1 to 31 + * in the usual manner. + *
  • An hour is represented by an integer from 0 to 23. Thus, the hour + * from midnight to 1 a.m. is hour 0, and the hour from noon to 1 + * p.m. is hour 12. + *
  • A minute is represented by an integer from 0 to 59 in the usual manner. + *
  • A second is represented by an integer from 0 to 61; the values 60 and + * 61 occur only for leap seconds and even then only in Java + * implementations that actually track leap seconds correctly. Because + * of the manner in which leap seconds are currently introduced, it is + * extremely unlikely that two leap seconds will occur in the same + * minute, but this specification follows the date and time conventions + * for ISO C. + *
+ *

+ * In all cases, arguments given to methods for these purposes need + * not fall within the indicated ranges; for example, a date may be + * specified as January 32 and is interpreted as meaning February 1. + * + * @author James Gosling + * @author Arthur van Hoff + * @author Alan Liu + * @see java.text.DateFormat + * @see java.util.Calendar + * @see java.util.TimeZone + * @since JDK1.0 + */ +public class Date + implements java.io.Serializable, Cloneable, Comparable +{ + private static final BaseCalendar gcal = new BaseCalendar(); + + private static BaseCalendar jcal; + + private transient long fastTime; + + /* + * If cdate is null, then fastTime indicates the time in millis. + * If cdate.isNormalized() is true, then fastTime and cdate are in + * synch. Otherwise, fastTime is ignored, and cdate indicates the + * time. + */ + private transient BaseCalendar.Datum cdate; + + // Initialized just before the value is used. See parse(). + private static int defaultCenturyStart; + + /* use serialVersionUID from modified java.util.Date for + * interoperability with JDK1.1. The Date was modified to write + * and read only the UTC time. + */ + private static final long serialVersionUID = 7523967970034938905L; + + /** + * Allocates a Date object and initializes it so that + * it represents the time at which it was allocated, measured to the + * nearest millisecond. + * + * @see java.lang.System#currentTimeMillis() + */ + public Date() { + this(System.currentTimeMillis()); + } + + /** + * Allocates a Date object and initializes it to + * represent the specified number of milliseconds since the + * standard base time known as "the epoch", namely January 1, + * 1970, 00:00:00 GMT. + * + * @param date the milliseconds since January 1, 1970, 00:00:00 GMT. + * @see java.lang.System#currentTimeMillis() + */ + public Date(long date) { + fastTime = date; + } + + /** + * Allocates a Date object and initializes it so that + * it represents midnight, local time, at the beginning of the day + * specified by the year, month, and + * date arguments. + * + * @param year the year minus 1900. + * @param month the month between 0-11. + * @param date the day of the month between 1-31. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(year + 1900, month, date) + * or GregorianCalendar(year + 1900, month, date). + */ + @Deprecated + public Date(int year, int month, int date) { + this(year, month, date, 0, 0, 0); + } + + /** + * Allocates a Date object and initializes it so that + * it represents the instant at the start of the minute specified by + * the year, month, date, + * hrs, and min arguments, in the local + * time zone. + * + * @param year the year minus 1900. + * @param month the month between 0-11. + * @param date the day of the month between 1-31. + * @param hrs the hours between 0-23. + * @param min the minutes between 0-59. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(year + 1900, month, date, + * hrs, min) or GregorianCalendar(year + 1900, + * month, date, hrs, min). + */ + @Deprecated + public Date(int year, int month, int date, int hrs, int min) { + this(year, month, date, hrs, min, 0); + } + + /** + * Allocates a Date object and initializes it so that + * it represents the instant at the start of the second specified + * by the year, month, date, + * hrs, min, and sec arguments, + * in the local time zone. + * + * @param year the year minus 1900. + * @param month the month between 0-11. + * @param date the day of the month between 1-31. + * @param hrs the hours between 0-23. + * @param min the minutes between 0-59. + * @param sec the seconds between 0-59. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(year + 1900, month, date, + * hrs, min, sec) or GregorianCalendar(year + 1900, + * month, date, hrs, min, sec). + */ + @Deprecated + public Date(int year, int month, int date, int hrs, int min, int sec) { + int y = year + 1900; + // month is 0-based. So we have to normalize month to support Long.MAX_VALUE. + if (month >= 12) { + y += month / 12; + month %= 12; + } else if (month < 0) { + y += month / 12; + month = month % 12; + } + BaseCalendar cal = getCalendarSystem(y); + cdate = (BaseCalendar.Datum) cal.newCalendarDate(TimeZone.getDefaultRef()); + cdate.setNormalizedDate(y, month + 1, date).setTimeOfDay(hrs, min, sec, 0); + getTimeImpl(); + cdate = null; + } + + /** + * Allocates a Date object and initializes it so that + * it represents the date and time indicated by the string + * s, which is interpreted as if by the + * {@link Date#parse} method. + * + * @param s a string representation of the date. + * @see java.text.DateFormat + * @see java.util.Date#parse(java.lang.String) + * @deprecated As of JDK version 1.1, + * replaced by DateFormat.parse(String s). + */ + @Deprecated + public Date(String s) { + this(parse(s)); + } + + /** + * Return a copy of this object. + */ + public Object clone() { + Date d = null; + try { + d = (Date)super.clone(); + if (cdate != null) { + d.cdate = (BaseCalendar.Datum) cdate.clone(); + } + } catch (CloneNotSupportedException e) {} // Won't happen + return d; + } + + /** + * Determines the date and time based on the arguments. The + * arguments are interpreted as a year, month, day of the month, + * hour of the day, minute within the hour, and second within the + * minute, exactly as for the Date constructor with six + * arguments, except that the arguments are interpreted relative + * to UTC rather than to the local time zone. The time indicated is + * returned represented as the distance, measured in milliseconds, + * of that time from the epoch (00:00:00 GMT on January 1, 1970). + * + * @param year the year minus 1900. + * @param month the month between 0-11. + * @param date the day of the month between 1-31. + * @param hrs the hours between 0-23. + * @param min the minutes between 0-59. + * @param sec the seconds between 0-59. + * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT for + * the date and time specified by the arguments. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(year + 1900, month, date, + * hrs, min, sec) or GregorianCalendar(year + 1900, + * month, date, hrs, min, sec), using a UTC + * TimeZone, followed by Calendar.getTime().getTime(). + */ + @Deprecated + public static long UTC(int year, int month, int date, + int hrs, int min, int sec) { + int y = year + 1900; + // month is 0-based. So we have to normalize month to support Long.MAX_VALUE. + if (month >= 12) { + y += month / 12; + month %= 12; + } else if (month < 0) { + y += month / 12; + month = month % 12; + } + int m = month + 1; + BaseCalendar cal = getCalendarSystem(y); + BaseCalendar.Datum udate = (BaseCalendar.Datum) cal.newCalendarDate(null); + udate.setNormalizedDate(y, m, date).setTimeOfDay(hrs, min, sec, 0); + + // Use a Date instance to perform normalization. Its fastTime + // is the UTC value after the normalization. + Date d = new Date(0); + d.normalize(udate); + return d.fastTime; + } + + /** + * Attempts to interpret the string s as a representation + * of a date and time. If the attempt is successful, the time + * indicated is returned represented as the distance, measured in + * milliseconds, of that time from the epoch (00:00:00 GMT on + * January 1, 1970). If the attempt fails, an + * IllegalArgumentException is thrown. + *

+ * It accepts many syntaxes; in particular, it recognizes the IETF + * standard date syntax: "Sat, 12 Aug 1995 13:30:00 GMT". It also + * understands the continental U.S. time-zone abbreviations, but for + * general use, a time-zone offset should be used: "Sat, 12 Aug 1995 + * 13:30:00 GMT+0430" (4 hours, 30 minutes west of the Greenwich + * meridian). If no time zone is specified, the local time zone is + * assumed. GMT and UTC are considered equivalent. + *

+ * The string s is processed from left to right, looking for + * data of interest. Any material in s that is within the + * ASCII parenthesis characters ( and ) is ignored. + * Parentheses may be nested. Otherwise, the only characters permitted + * within s are these ASCII characters: + *

+     * abcdefghijklmnopqrstuvwxyz
+     * ABCDEFGHIJKLMNOPQRSTUVWXYZ
+     * 0123456789,+-:/
+ * and whitespace characters.

+ * A consecutive sequence of decimal digits is treated as a decimal + * number:

    + *
  • If a number is preceded by + or - and a year + * has already been recognized, then the number is a time-zone + * offset. If the number is less than 24, it is an offset measured + * in hours. Otherwise, it is regarded as an offset in minutes, + * expressed in 24-hour time format without punctuation. A + * preceding - means a westward offset. Time zone offsets + * are always relative to UTC (Greenwich). Thus, for example, + * -5 occurring in the string would mean "five hours west + * of Greenwich" and +0430 would mean "four hours and + * thirty minutes east of Greenwich." It is permitted for the + * string to specify GMT, UT, or UTC + * redundantly-for example, GMT-5 or utc+0430. + *
  • The number is regarded as a year number if one of the + * following conditions is true: + *
      + *
    • The number is equal to or greater than 70 and followed by a + * space, comma, slash, or end of string + *
    • The number is less than 70, and both a month and a day of + * the month have already been recognized
    • + *
    + * If the recognized year number is less than 100, it is + * interpreted as an abbreviated year relative to a century of + * which dates are within 80 years before and 19 years after + * the time when the Date class is initialized. + * After adjusting the year number, 1900 is subtracted from + * it. For example, if the current year is 1999 then years in + * the range 19 to 99 are assumed to mean 1919 to 1999, while + * years from 0 to 18 are assumed to mean 2000 to 2018. Note + * that this is slightly different from the interpretation of + * years less than 100 that is used in {@link java.text.SimpleDateFormat}. + *
  • If the number is followed by a colon, it is regarded as an hour, + * unless an hour has already been recognized, in which case it is + * regarded as a minute. + *
  • If the number is followed by a slash, it is regarded as a month + * (it is decreased by 1 to produce a number in the range 0 + * to 11), unless a month has already been recognized, in + * which case it is regarded as a day of the month. + *
  • If the number is followed by whitespace, a comma, a hyphen, or + * end of string, then if an hour has been recognized but not a + * minute, it is regarded as a minute; otherwise, if a minute has + * been recognized but not a second, it is regarded as a second; + * otherwise, it is regarded as a day of the month.

+ * A consecutive sequence of letters is regarded as a word and treated + * as follows:

    + *
  • A word that matches AM, ignoring case, is ignored (but + * the parse fails if an hour has not been recognized or is less + * than 1 or greater than 12). + *
  • A word that matches PM, ignoring case, adds 12 + * to the hour (but the parse fails if an hour has not been + * recognized or is less than 1 or greater than 12). + *
  • Any word that matches any prefix of SUNDAY, MONDAY, TUESDAY, + * WEDNESDAY, THURSDAY, FRIDAY, or SATURDAY, ignoring + * case, is ignored. For example, sat, Friday, TUE, and + * Thurs are ignored. + *
  • Otherwise, any word that matches any prefix of JANUARY, + * FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, + * OCTOBER, NOVEMBER, or DECEMBER, ignoring case, and + * considering them in the order given here, is recognized as + * specifying a month and is converted to a number (0 to + * 11). For example, aug, Sept, april, and + * NOV are recognized as months. So is Ma, which + * is recognized as MARCH, not MAY. + *
  • Any word that matches GMT, UT, or UTC, ignoring + * case, is treated as referring to UTC. + *
  • Any word that matches EST, CST, MST, or PST, + * ignoring case, is recognized as referring to the time zone in + * North America that is five, six, seven, or eight hours west of + * Greenwich, respectively. Any word that matches EDT, CDT, + * MDT, or PDT, ignoring case, is recognized as + * referring to the same time zone, respectively, during daylight + * saving time.

+ * Once the entire string s has been scanned, it is converted to a time + * result in one of two ways. If a time zone or time-zone offset has been + * recognized, then the year, month, day of month, hour, minute, and + * second are interpreted in UTC and then the time-zone offset is + * applied. Otherwise, the year, month, day of month, hour, minute, and + * second are interpreted in the local time zone. + * + * @param s a string to be parsed as a date. + * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT + * represented by the string argument. + * @see java.text.DateFormat + * @deprecated As of JDK version 1.1, + * replaced by DateFormat.parse(String s). + */ + @Deprecated + public static long parse(String s) { + int year = Integer.MIN_VALUE; + int mon = -1; + int mday = -1; + int hour = -1; + int min = -1; + int sec = -1; + int millis = -1; + int c = -1; + int i = 0; + int n = -1; + int wst = -1; + int tzoffset = -1; + int prevc = 0; + syntax: + { + if (s == null) + break syntax; + int limit = s.length(); + while (i < limit) { + c = s.charAt(i); + i++; + if (c <= ' ' || c == ',') + continue; + if (c == '(') { // skip comments + int depth = 1; + while (i < limit) { + c = s.charAt(i); + i++; + if (c == '(') depth++; + else if (c == ')') + if (--depth <= 0) + break; + } + continue; + } + if ('0' <= c && c <= '9') { + n = c - '0'; + while (i < limit && '0' <= (c = s.charAt(i)) && c <= '9') { + n = n * 10 + c - '0'; + i++; + } + if (prevc == '+' || prevc == '-' && year != Integer.MIN_VALUE) { + // timezone offset + if (n < 24) + n = n * 60; // EG. "GMT-3" + else + n = n % 100 + n / 100 * 60; // eg "GMT-0430" + if (prevc == '+') // plus means east of GMT + n = -n; + if (tzoffset != 0 && tzoffset != -1) + break syntax; + tzoffset = n; + } else if (n >= 70) + if (year != Integer.MIN_VALUE) + break syntax; + else if (c <= ' ' || c == ',' || c == '/' || i >= limit) + // year = n < 1900 ? n : n - 1900; + year = n; + else + break syntax; + else if (c == ':') + if (hour < 0) + hour = (byte) n; + else if (min < 0) + min = (byte) n; + else + break syntax; + else if (c == '/') + if (mon < 0) + mon = (byte) (n - 1); + else if (mday < 0) + mday = (byte) n; + else + break syntax; + else if (i < limit && c != ',' && c > ' ' && c != '-') + break syntax; + else if (hour >= 0 && min < 0) + min = (byte) n; + else if (min >= 0 && sec < 0) + sec = (byte) n; + else if (mday < 0) + mday = (byte) n; + // Handle two-digit years < 70 (70-99 handled above). + else if (year == Integer.MIN_VALUE && mon >= 0 && mday >= 0) + year = n; + else + break syntax; + prevc = 0; + } else if (c == '/' || c == ':' || c == '+' || c == '-') + prevc = c; + else { + int st = i - 1; + while (i < limit) { + c = s.charAt(i); + if (!('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z')) + break; + i++; + } + if (i <= st + 1) + break syntax; + int k; + for (k = wtb.length; --k >= 0;) + if (wtb[k].regionMatches(true, 0, s, st, i - st)) { + int action = ttb[k]; + if (action != 0) { + if (action == 1) { // pm + if (hour > 12 || hour < 1) + break syntax; + else if (hour < 12) + hour += 12; + } else if (action == 14) { // am + if (hour > 12 || hour < 1) + break syntax; + else if (hour == 12) + hour = 0; + } else if (action <= 13) { // month! + if (mon < 0) + mon = (byte) (action - 2); + else + break syntax; + } else { + tzoffset = action - 10000; + } + } + break; + } + if (k < 0) + break syntax; + prevc = 0; + } + } + if (year == Integer.MIN_VALUE || mon < 0 || mday < 0) + break syntax; + // Parse 2-digit years within the correct default century. + if (year < 100) { + synchronized (Date.class) { + if (defaultCenturyStart == 0) { + defaultCenturyStart = gcal.getCalendarDate().getYear() - 80; + } + } + year += (defaultCenturyStart / 100) * 100; + if (year < defaultCenturyStart) year += 100; + } + if (sec < 0) + sec = 0; + if (min < 0) + min = 0; + if (hour < 0) + hour = 0; + BaseCalendar cal = getCalendarSystem(year); + if (tzoffset == -1) { // no time zone specified, have to use local + BaseCalendar.Datum ldate = (BaseCalendar.Datum) cal.newCalendarDate(TimeZone.getDefaultRef()); + ldate.setDate(year, mon + 1, mday); + ldate.setTimeOfDay(hour, min, sec, 0); + return cal.getTime(ldate); + } + BaseCalendar.Datum udate = (BaseCalendar.Datum) cal.newCalendarDate(null); // no time zone + udate.setDate(year, mon + 1, mday); + udate.setTimeOfDay(hour, min, sec, 0); + return cal.getTime(udate) + tzoffset * (60 * 1000); + } + // syntax error + throw new IllegalArgumentException(); + } + private final static String wtb[] = { + "am", "pm", + "monday", "tuesday", "wednesday", "thursday", "friday", + "saturday", "sunday", + "january", "february", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december", + "gmt", "ut", "utc", "est", "edt", "cst", "cdt", + "mst", "mdt", "pst", "pdt" + }; + private final static int ttb[] = { + 14, 1, 0, 0, 0, 0, 0, 0, 0, + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 10000 + 0, 10000 + 0, 10000 + 0, // GMT/UT/UTC + 10000 + 5 * 60, 10000 + 4 * 60, // EST/EDT + 10000 + 6 * 60, 10000 + 5 * 60, // CST/CDT + 10000 + 7 * 60, 10000 + 6 * 60, // MST/MDT + 10000 + 8 * 60, 10000 + 7 * 60 // PST/PDT + }; + + /** + * Returns a value that is the result of subtracting 1900 from the + * year that contains or begins with the instant in time represented + * by this Date object, as interpreted in the local + * time zone. + * + * @return the year represented by this date, minus 1900. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.YEAR) - 1900. + */ + @Deprecated + public int getYear() { + return normalize().getYear() - 1900; + } + + /** + * Sets the year of this Date object to be the specified + * value plus 1900. This Date object is modified so + * that it represents a point in time within the specified year, + * with the month, date, hour, minute, and second the same as + * before, as interpreted in the local time zone. (Of course, if + * the date was February 29, for example, and the year is set to a + * non-leap year, then the new date will be treated as if it were + * on March 1.) + * + * @param year the year value. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(Calendar.YEAR, year + 1900). + */ + @Deprecated + public void setYear(int year) { + getCalendarDate().setNormalizedYear(year + 1900); + } + + /** + * Returns a number representing the month that contains or begins + * with the instant in time represented by this Date object. + * The value returned is between 0 and 11, + * with the value 0 representing January. + * + * @return the month represented by this date. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.MONTH). + */ + @Deprecated + public int getMonth() { + return normalize().getMonth() - 1; // adjust 1-based to 0-based + } + + /** + * Sets the month of this date to the specified value. This + * Date object is modified so that it represents a point + * in time within the specified month, with the year, date, hour, + * minute, and second the same as before, as interpreted in the + * local time zone. If the date was October 31, for example, and + * the month is set to June, then the new date will be treated as + * if it were on July 1, because June has only 30 days. + * + * @param month the month value between 0-11. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(Calendar.MONTH, int month). + */ + @Deprecated + public void setMonth(int month) { + int y = 0; + if (month >= 12) { + y = month / 12; + month %= 12; + } else if (month < 0) { + y = month / 12; + month = month % 12; + } + BaseCalendar.Datum d = getCalendarDate(); + if (y != 0) { + d.setNormalizedYear(d.getNormalizedYear() + y); + } + d.setMonth(month + 1); // adjust 0-based to 1-based month numbering + } + + /** + * Returns the day of the month represented by this Date object. + * The value returned is between 1 and 31 + * representing the day of the month that contains or begins with the + * instant in time represented by this Date object, as + * interpreted in the local time zone. + * + * @return the day of the month represented by this date. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.DAY_OF_MONTH). + * @deprecated + */ + @Deprecated + public int getDate() { + return normalize().getDayOfMonth(); + } + + /** + * Sets the day of the month of this Date object to the + * specified value. This Date object is modified so that + * it represents a point in time within the specified day of the + * month, with the year, month, hour, minute, and second the same + * as before, as interpreted in the local time zone. If the date + * was April 30, for example, and the date is set to 31, then it + * will be treated as if it were on May 1, because April has only + * 30 days. + * + * @param date the day of the month value between 1-31. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(Calendar.DAY_OF_MONTH, int date). + */ + @Deprecated + public void setDate(int date) { + getCalendarDate().setDayOfMonth(date); + } + + /** + * Returns the day of the week represented by this date. The + * returned value (0 = Sunday, 1 = Monday, + * 2 = Tuesday, 3 = Wednesday, 4 = + * Thursday, 5 = Friday, 6 = Saturday) + * represents the day of the week that contains or begins with + * the instant in time represented by this Date object, + * as interpreted in the local time zone. + * + * @return the day of the week represented by this date. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.DAY_OF_WEEK). + */ + @Deprecated + public int getDay() { + return normalize().getDayOfWeek() - 7;//gcal.SUNDAY; + } + + /** + * Returns the hour represented by this Date object. The + * returned value is a number (0 through 23) + * representing the hour within the day that contains or begins + * with the instant in time represented by this Date + * object, as interpreted in the local time zone. + * + * @return the hour represented by this date. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.HOUR_OF_DAY). + */ + @Deprecated + public int getHours() { + return normalize().getHours(); + } + + /** + * Sets the hour of this Date object to the specified value. + * This Date object is modified so that it represents a point + * in time within the specified hour of the day, with the year, month, + * date, minute, and second the same as before, as interpreted in the + * local time zone. + * + * @param hours the hour value. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(Calendar.HOUR_OF_DAY, int hours). + */ + @Deprecated + public void setHours(int hours) { + getCalendarDate().setHours(hours); + } + + /** + * Returns the number of minutes past the hour represented by this date, + * as interpreted in the local time zone. + * The value returned is between 0 and 59. + * + * @return the number of minutes past the hour represented by this date. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.MINUTE). + */ + @Deprecated + public int getMinutes() { + return normalize().getMinutes(); + } + + /** + * Sets the minutes of this Date object to the specified value. + * This Date object is modified so that it represents a point + * in time within the specified minute of the hour, with the year, month, + * date, hour, and second the same as before, as interpreted in the + * local time zone. + * + * @param minutes the value of the minutes. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(Calendar.MINUTE, int minutes). + */ + @Deprecated + public void setMinutes(int minutes) { + getCalendarDate().setMinutes(minutes); + } + + /** + * Returns the number of seconds past the minute represented by this date. + * The value returned is between 0 and 61. The + * values 60 and 61 can only occur on those + * Java Virtual Machines that take leap seconds into account. + * + * @return the number of seconds past the minute represented by this date. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.SECOND). + */ + @Deprecated + public int getSeconds() { + return normalize().getSeconds(); + } + + /** + * Sets the seconds of this Date to the specified value. + * This Date object is modified so that it represents a + * point in time within the specified second of the minute, with + * the year, month, date, hour, and minute the same as before, as + * interpreted in the local time zone. + * + * @param seconds the seconds value. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(Calendar.SECOND, int seconds). + */ + @Deprecated + public void setSeconds(int seconds) { + getCalendarDate().setSeconds(seconds); + } + + /** + * Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT + * represented by this Date object. + * + * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT + * represented by this date. + */ + public long getTime() { + return getTimeImpl(); + } + + private final long getTimeImpl() { + if (cdate != null && !cdate.isNormalized()) { + normalize(); + } + return fastTime; + } + + /** + * Sets this Date object to represent a point in time that is + * time milliseconds after January 1, 1970 00:00:00 GMT. + * + * @param time the number of milliseconds. + */ + public void setTime(long time) { + fastTime = time; + cdate = null; + } + + /** + * Tests if this date is before the specified date. + * + * @param when a date. + * @return true if and only if the instant of time + * represented by this Date object is strictly + * earlier than the instant represented by when; + * false otherwise. + * @exception NullPointerException if when is null. + */ + public boolean before(Date when) { + return getMillisOf(this) < getMillisOf(when); + } + + /** + * Tests if this date is after the specified date. + * + * @param when a date. + * @return true if and only if the instant represented + * by this Date object is strictly later than the + * instant represented by when; + * false otherwise. + * @exception NullPointerException if when is null. + */ + public boolean after(Date when) { + return getMillisOf(this) > getMillisOf(when); + } + + /** + * Compares two dates for equality. + * The result is true if and only if the argument is + * not null and is a Date object that + * represents the same point in time, to the millisecond, as this object. + *

+ * Thus, two Date objects are equal if and only if the + * getTime method returns the same long + * value for both. + * + * @param obj the object to compare with. + * @return true if the objects are the same; + * false otherwise. + * @see java.util.Date#getTime() + */ + public boolean equals(Object obj) { + return obj instanceof Date && getTime() == ((Date) obj).getTime(); + } + + /** + * Returns the millisecond value of this Date object + * without affecting its internal state. + */ + static final long getMillisOf(Date date) { + if (date.cdate == null || date.cdate.isNormalized()) { + return date.fastTime; + } + BaseCalendar.Datum d = (BaseCalendar.Datum) date.cdate.clone(); + return gcal.getTime(d); + } + + /** + * Compares two Dates for ordering. + * + * @param anotherDate the Date to be compared. + * @return the value 0 if the argument Date is equal to + * this Date; a value less than 0 if this Date + * is before the Date argument; and a value greater than + * 0 if this Date is after the Date argument. + * @since 1.2 + * @exception NullPointerException if anotherDate is null. + */ + public int compareTo(Date anotherDate) { + long thisTime = getMillisOf(this); + long anotherTime = getMillisOf(anotherDate); + return (thisTimelong + * value returned by the {@link Date#getTime} + * method. That is, the hash code is the value of the expression: + *

+     * (int)(this.getTime()^(this.getTime() >>> 32))
+ * + * @return a hash code value for this object. + */ + public int hashCode() { + long ht = this.getTime(); + return (int) ht ^ (int) (ht >> 32); + } + + /** + * Converts this Date object to a String + * of the form: + *
+     * dow mon dd hh:mm:ss zzz yyyy
+ * where:
    + *
  • dow is the day of the week (Sun, Mon, Tue, Wed, + * Thu, Fri, Sat). + *
  • mon is the month (Jan, Feb, Mar, Apr, May, Jun, + * Jul, Aug, Sep, Oct, Nov, Dec). + *
  • dd is the day of the month (01 through + * 31), as two decimal digits. + *
  • hh is the hour of the day (00 through + * 23), as two decimal digits. + *
  • mm is the minute within the hour (00 through + * 59), as two decimal digits. + *
  • ss is the second within the minute (00 through + * 61, as two decimal digits. + *
  • zzz is the time zone (and may reflect daylight saving + * time). Standard time zone abbreviations include those + * recognized by the method parse. If time zone + * information is not available, then zzz is empty - + * that is, it consists of no characters at all. + *
  • yyyy is the year, as four decimal digits. + *
+ * + * @return a string representation of this date. + * @see java.util.Date#toLocaleString() + * @see java.util.Date#toGMTString() + */ + public String toString() { + // "EEE MMM dd HH:mm:ss zzz yyyy"; + BaseCalendar.Datum date = normalize(); + StringBuilder sb = new StringBuilder(28); + int index = date.getDayOfWeek(); + if (index == 7) { + index = 8; + } + convertToAbbr(sb, wtb[index]).append(' '); // EEE + convertToAbbr(sb, wtb[date.getMonth() - 1 + 2 + 7]).append(' '); // MMM +// CalendarUtils.sprintf0d(sb, date.getDayOfMonth(), 2).append(' '); // dd +// +// CalendarUtils.sprintf0d(sb, date.getHours(), 2).append(':'); // HH +// CalendarUtils.sprintf0d(sb, date.getMinutes(), 2).append(':'); // mm +// CalendarUtils.sprintf0d(sb, date.getSeconds(), 2).append(' '); // ss +// TimeZone zi = date.getZone(); +// if (zi != null) { +// sb.append(zi.getDisplayName(date.isDaylightTime(), zi.SHORT, Locale.US)); // zzz +// } else { +// sb.append("GMT"); +// } + sb.append(' ').append(date.getYear()); // yyyy + return sb.toString(); + } + + /** + * Converts the given name to its 3-letter abbreviation (e.g., + * "monday" -> "Mon") and stored the abbreviation in the given + * StringBuilder. + */ + private static final StringBuilder convertToAbbr(StringBuilder sb, String name) { + sb.append(Character.toUpperCase(name.charAt(0))); + sb.append(name.charAt(1)).append(name.charAt(2)); + return sb; + } + + /** + * Creates a string representation of this Date object in an + * implementation-dependent form. The intent is that the form should + * be familiar to the user of the Java application, wherever it may + * happen to be running. The intent is comparable to that of the + * "%c" format supported by the strftime() + * function of ISO C. + * + * @return a string representation of this date, using the locale + * conventions. + * @see java.text.DateFormat + * @see java.util.Date#toString() + * @see java.util.Date#toGMTString() + * @deprecated As of JDK version 1.1, + * replaced by DateFormat.format(Date date). + */ + @Deprecated + public String toLocaleString() { + DateFormat formatter = DateFormat.getDateTimeInstance(); + return formatter.format(this); + } + + /** + * Creates a string representation of this Date object of + * the form: + * + * d mon yyyy hh:mm:ss GMT + * where:
    + *
  • d is the day of the month (1 through 31), + * as one or two decimal digits. + *
  • mon is the month (Jan, Feb, Mar, Apr, May, Jun, Jul, + * Aug, Sep, Oct, Nov, Dec). + *
  • yyyy is the year, as four decimal digits. + *
  • hh is the hour of the day (00 through 23), + * as two decimal digits. + *
  • mm is the minute within the hour (00 through + * 59), as two decimal digits. + *
  • ss is the second within the minute (00 through + * 61), as two decimal digits. + *
  • GMT is exactly the ASCII letters "GMT" to indicate + * Greenwich Mean Time. + *

+ * The result does not depend on the local time zone. + * + * @return a string representation of this date, using the Internet GMT + * conventions. + * @see java.text.DateFormat + * @see java.util.Date#toString() + * @see java.util.Date#toLocaleString() + * @deprecated As of JDK version 1.1, + * replaced by DateFormat.format(Date date), using a + * GMT TimeZone. + */ + @Deprecated + public String toGMTString() { + // d MMM yyyy HH:mm:ss 'GMT' + long t = getTime(); + BaseCalendar cal = getCalendarSystem(t); + StringBuilder sb = new StringBuilder(32); +// BaseCalendar.Datum date = +// (BaseCalendar.Datum) cal.getCalendarDate(getTime(), (TimeZone)null); +// CalendarUtils.sprintf0d(sb, date.getDayOfMonth(), 1).append(' '); // d +// convertToAbbr(sb, wtb[date.getMonth() - 1 + 2 + 7]).append(' '); // MMM +// sb.append(date.getYear()).append(' '); // yyyy +// CalendarUtils.sprintf0d(sb, date.getHours(), 2).append(':'); // HH +// CalendarUtils.sprintf0d(sb, date.getMinutes(), 2).append(':'); // mm +// CalendarUtils.sprintf0d(sb, date.getSeconds(), 2); // ss + sb.append(" GMT"); // ' GMT' + return sb.toString(); + } + + /** + * Returns the offset, measured in minutes, for the local time zone + * relative to UTC that is appropriate for the time represented by + * this Date object. + *

+ * For example, in Massachusetts, five time zones west of Greenwich: + *

+     * new Date(96, 1, 14).getTimezoneOffset() returns 300
+ * because on February 14, 1996, standard time (Eastern Standard Time) + * is in use, which is offset five hours from UTC; but: + *
+     * new Date(96, 5, 1).getTimezoneOffset() returns 240
+ * because on June 1, 1996, daylight saving time (Eastern Daylight Time) + * is in use, which is offset only four hours from UTC.

+ * This method produces the same result as if it computed: + *

+     * (this.getTime() - UTC(this.getYear(),
+     *                       this.getMonth(),
+     *                       this.getDate(),
+     *                       this.getHours(),
+     *                       this.getMinutes(),
+     *                       this.getSeconds())) / (60 * 1000)
+     * 
+ * + * @return the time-zone offset, in minutes, for the current time zone. + * @see java.util.Calendar#ZONE_OFFSET + * @see java.util.Calendar#DST_OFFSET + * @see java.util.TimeZone#getDefault + * @deprecated As of JDK version 1.1, + * replaced by -(Calendar.get(Calendar.ZONE_OFFSET) + + * Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000). + */ + @Deprecated + public int getTimezoneOffset() { + int zoneOffset; + if (cdate == null) { + TimeZone tz = TimeZone.getDefaultRef(); + zoneOffset = tz.getOffset(fastTime); + } else { + normalize(); + zoneOffset = cdate.getZoneOffset(); + } + return -zoneOffset/60000; // convert to minutes + } + + private final BaseCalendar.Datum getCalendarDate() { + if (cdate == null) { +// BaseCalendar cal = getCalendarSystem(fastTime); +// cdate = (BaseCalendar.Datum) cal.getCalendarDate(fastTime, +// TimeZone.getDefaultRef()); + } + return cdate; + } + + private final BaseCalendar.Datum normalize() { + if (cdate == null) { +// BaseCalendar cal = getCalendarSystem(fastTime); +// cdate = (BaseCalendar.Datum) cal.getCalendarDate(fastTime, +// TimeZone.getDefaultRef()); +// return cdate; + } + + // Normalize cdate with the TimeZone in cdate first. This is + // required for the compatible behavior. + if (!cdate.isNormalized()) { + cdate = normalize(cdate); + } + + // If the default TimeZone has changed, then recalculate the + // fields with the new TimeZone. + TimeZone tz = TimeZone.getDefaultRef(); + if (tz != cdate.getZone()) { +// cdate.setZone(tz); +// CalendarSystem cal = getCalendarSystem(cdate); +// cal.getCalendarDate(fastTime, cdate); + } + return cdate; + } + + // fastTime and the returned data are in sync upon return. + private final BaseCalendar.Datum normalize(BaseCalendar.Datum date) { + int y = date.getNormalizedYear(); + int m = date.getMonth(); + int d = date.getDayOfMonth(); + int hh = date.getHours(); + int mm = date.getMinutes(); + int ss = date.getSeconds(); + int ms = date.getMillis(); + TimeZone tz = date.getZone(); + + // If the specified year can't be handled using a long value + // in milliseconds, GregorianCalendar is used for full + // compatibility with underflow and overflow. This is required + // by some JCK tests. The limits are based max year values - + // years that can be represented by max values of d, hh, mm, + // ss and ms. Also, let GregorianCalendar handle the default + // cutover year so that we don't need to worry about the + // transition here. +// if (y == 1582 || y > 280000000 || y < -280000000) { +// if (tz == null) { +// tz = TimeZone.getTimeZone("GMT"); +// } +// GregorianCalendar gc = new GregorianCalendar(tz); +// gc.clear(); +// gc.set(gc.MILLISECOND, ms); +// gc.set(y, m-1, d, hh, mm, ss); +// fastTime = gc.getTimeInMillis(); +// BaseCalendar cal = getCalendarSystem(fastTime); +// date = (BaseCalendar.Datum) cal.getCalendarDate(fastTime, tz); +// return date; +// } + + BaseCalendar cal = getCalendarSystem(y); + if (cal != getCalendarSystem(date)) { + date = (BaseCalendar.Datum) cal.newCalendarDate(tz); + date.setNormalizedDate(y, m, d).setTimeOfDay(hh, mm, ss, ms); + } + // Perform the GregorianCalendar-style normalization. + fastTime = cal.getTime(date); + + // In case the normalized date requires the other calendar + // system, we need to recalculate it using the other one. + BaseCalendar ncal = getCalendarSystem(fastTime); + if (ncal != cal) { + date = (BaseCalendar.Datum) ncal.newCalendarDate(tz); + date.setNormalizedDate(y, m, d).setTimeOfDay(hh, mm, ss, ms); + fastTime = ncal.getTime(date); + } + return date; + } + + /** + * Returns the Gregorian or Julian calendar system to use with the + * given date. Use Gregorian from October 15, 1582. + * + * @param year normalized calendar year (not -1900) + * @return the CalendarSystem to use for the specified date + */ + private static final BaseCalendar getCalendarSystem(int year) { + if (year >= 1582) { + return gcal; + } + return getJulianCalendar(); + } + + private static final BaseCalendar getCalendarSystem(long utc) { + // Quickly check if the time stamp given by `utc' is the Epoch + // or later. If it's before 1970, we convert the cutover to + // local time to compare. +// if (utc >= 0 +// || utc >= GregorianCalendar.DEFAULT_GREGORIAN_CUTOVER +// - TimeZone.getDefaultRef().getOffset(utc)) { + return gcal; +// } +// return getJulianCalendar(); + } + + private static final BaseCalendar getCalendarSystem(BaseCalendar.Datum cdate) { + if (jcal == null) { + return gcal; + } + if (cdate.getEra() != null) { + return jcal; + } + return gcal; + } + + synchronized private static final BaseCalendar getJulianCalendar() { + if (jcal == null) { +// jcal = (BaseCalendar) CalendarSystem.forName("julian"); + } + return jcal; + } + + /** + * Save the state of this object to a stream (i.e., serialize it). + * + * @serialData The value returned by getTime() + * is emitted (long). This represents the offset from + * January 1, 1970, 00:00:00 GMT in milliseconds. + */ + private void writeObject(ObjectOutputStream s) + throws IOException + { + s.writeLong(getTimeImpl()); + } + + /** + * Reconstitute this object from a stream (i.e., deserialize it). + */ + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException + { + fastTime = s.readLong(); + } + + static final class BaseCalendar { + Datum newCalendarDate(TimeZone t) { + return new Datum(); + } + + Datum getNthDayOfWeek(int a, int b, Datum c) { + return new Datum(); + } + + Datum getCalendarDate() { + return new Datum(); + } + + int getTime(Datum udate) { + return 0; + } + + int getMonthLength(Datum cdate) { + return 0; + } + + void getCalendarDate(long l, Datum cdate) { + } + + static class Datum implements Cloneable { + public Datum clone() { + return new Datum(); + } + + Datum setNormalizedDate(int y, int i, int date) { + return this; + } + + void setTimeOfDay(int hrs, int min, int sec, int i) { + } + + int getYear() { + return 0; + } + + void setDate(int year, int i, int mday) { + } + + void setNormalizedYear(int i) { + } + + int getMonth() { + return 0; + } + + int getNormalizedYear() { + return 0; + } + + void setMonth(int i) { + } + + int getDayOfMonth() { + return 0; + } + + void setDayOfMonth(int date) { + } + + int getDayOfWeek() { + return 0; + } + + int getHours() { + return 0; + } + + void setHours(int hours) { + } + + int getMinutes() { + return 0; + } + + void setMinutes(int minutes) { + } + + int getSeconds() { + return 0; + } + + void setSeconds(int seconds) { + } + + boolean isNormalized() { + return false; + } + + Object getEra() { + return this; + } + + int getMillis() { + return 0; + } + + TimeZone getZone() { + return TimeZone.NO_TIMEZONE; + } + + int getZoneOffset() { + return 0; + } + } + } +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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.fld_$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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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.fld_$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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/Locale.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/Locale.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1012 @@ +/* + * 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation + * is copyrighted and owned by Taligent, Inc., a wholly-owned + * subsidiary of IBM. These materials are provided under terms + * of a License Agreement between Taligent and Sun. This technology + * is protected by multiple US and International patents. + * + * This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.util; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.io.Serializable; + +/** + * A Locale object represents a specific geographical, political, + * or cultural region. An operation that requires a Locale to perform + * its task is called locale-sensitive and uses the Locale + * to tailor information for the user. For example, displaying a number + * is a locale-sensitive operation— the number should be formatted + * according to the customs and conventions of the user's native country, + * region, or culture. + * + *

The Locale class implements identifiers + * interchangeable with BCP 47 (IETF BCP 47, "Tags for Identifying + * Languages"), with support for the LDML (UTS#35, "Unicode Locale + * Data Markup Language") BCP 47-compatible extensions for locale data + * exchange. + * + *

A Locale object logically consists of the fields + * described below. + * + *

+ *
language
+ * + *
ISO 639 alpha-2 or alpha-3 language code, or registered + * language subtags up to 8 alpha letters (for future enhancements). + * When a language has both an alpha-2 code and an alpha-3 code, the + * alpha-2 code must be used. You can find a full list of valid + * language codes in the IANA Language Subtag Registry (search for + * "Type: language"). The language field is case insensitive, but + * Locale always canonicalizes to lower case.

+ * + *
Well-formed language values have the form + * [a-zA-Z]{2,8}. Note that this is not the the full + * BCP47 language production, since it excludes extlang. They are + * not needed since modern three-letter language codes replace + * them.

+ * + *
Example: "en" (English), "ja" (Japanese), "kok" (Konkani)

+ * + *
script
+ * + *
ISO 15924 alpha-4 script code. You can find a full list of + * valid script codes in the IANA Language Subtag Registry (search + * for "Type: script"). The script field is case insensitive, but + * Locale always canonicalizes to title case (the first + * letter is upper case and the rest of the letters are lower + * case).

+ * + *
Well-formed script values have the form + * [a-zA-Z]{4}

+ * + *
Example: "Latn" (Latin), "Cyrl" (Cyrillic)

+ * + *
country (region)
+ * + *
ISO 3166 alpha-2 country code or UN M.49 numeric-3 area code. + * You can find a full list of valid country and region codes in the + * IANA Language Subtag Registry (search for "Type: region"). The + * country (region) field is case insensitive, but + * Locale always canonicalizes to upper case.

+ * + *
Well-formed country/region values have + * the form [a-zA-Z]{2} | [0-9]{3}

+ * + *
Example: "US" (United States), "FR" (France), "029" + * (Caribbean)

+ * + *
variant
+ * + *
Any arbitrary value used to indicate a variation of a + * Locale. Where there are two or more variant values + * each indicating its own semantics, these values should be ordered + * by importance, with most important first, separated by + * underscore('_'). The variant field is case sensitive.

+ * + *
Note: IETF BCP 47 places syntactic restrictions on variant + * subtags. Also BCP 47 subtags are strictly used to indicate + * additional variations that define a language or its dialects that + * are not covered by any combinations of language, script and + * region subtags. You can find a full list of valid variant codes + * in the IANA Language Subtag Registry (search for "Type: variant"). + * + *

However, the variant field in Locale has + * historically been used for any kind of variation, not just + * language variations. For example, some supported variants + * available in Java SE Runtime Environments indicate alternative + * cultural behaviors such as calendar type or number script. In + * BCP 47 this kind of information, which does not identify the + * language, is supported by extension subtags or private use + * subtags.


+ * + *
Well-formed variant values have the form SUBTAG + * (('_'|'-') SUBTAG)* where SUBTAG = + * [0-9][0-9a-zA-Z]{3} | [0-9a-zA-Z]{5,8}. (Note: BCP 47 only + * uses hyphen ('-') as a delimiter, this is more lenient).

+ * + *
Example: "polyton" (Polytonic Greek), "POSIX"

+ * + *
extensions
+ * + *
A map from single character keys to string values, indicating + * extensions apart from language identification. The extensions in + * Locale implement the semantics and syntax of BCP 47 + * extension subtags and private use subtags. The extensions are + * case insensitive, but Locale canonicalizes all + * extension keys and values to lower case. Note that extensions + * cannot have empty values.

+ * + *
Well-formed keys are single characters from the set + * [0-9a-zA-Z]. Well-formed values have the form + * SUBTAG ('-' SUBTAG)* where for the key 'x' + * SUBTAG = [0-9a-zA-Z]{1,8} and for other keys + * SUBTAG = [0-9a-zA-Z]{2,8} (that is, 'x' allows + * single-character subtags).

+ * + *
Example: key="u"/value="ca-japanese" (Japanese Calendar), + * key="x"/value="java-1-7"
+ *
+ * + * Note: Although BCP 47 requires field values to be registered + * in the IANA Language Subtag Registry, the Locale class + * does not provide any validation features. The Builder + * only checks if an individual field satisfies the syntactic + * requirement (is well-formed), but does not validate the value + * itself. See {@link Builder} for details. + * + *

Unicode locale/language extension

+ * + *

UTS#35, "Unicode Locale Data Markup Language" defines optional + * attributes and keywords to override or refine the default behavior + * associated with a locale. A keyword is represented by a pair of + * key and type. For example, "nu-thai" indicates that Thai local + * digits (value:"thai") should be used for formatting numbers + * (key:"nu"). + * + *

The keywords are mapped to a BCP 47 extension value using the + * extension key 'u' ({@link #UNICODE_LOCALE_EXTENSION}). The above + * example, "nu-thai", becomes the extension "u-nu-thai".code + * + *

Thus, when a Locale object contains Unicode locale + * attributes and keywords, + * getExtension(UNICODE_LOCALE_EXTENSION) will return a + * String representing this information, for example, "nu-thai". The + * Locale class also provides {@link + * #getUnicodeLocaleAttributes}, {@link #getUnicodeLocaleKeys}, and + * {@link #getUnicodeLocaleType} which allow you to access Unicode + * locale attributes and key/type pairs directly. When represented as + * a string, the Unicode Locale Extension lists attributes + * alphabetically, followed by key/type sequences with keys listed + * alphabetically (the order of subtags comprising a key's type is + * fixed when the type is defined) + * + *

A well-formed locale key has the form + * [0-9a-zA-Z]{2}. A well-formed locale type has the + * form "" | [0-9a-zA-Z]{3,8} ('-' [0-9a-zA-Z]{3,8})* (it + * can be empty, or a series of subtags 3-8 alphanums in length). A + * well-formed locale attribute has the form + * [0-9a-zA-Z]{3,8} (it is a single subtag with the same + * form as a locale type subtag). + * + *

The Unicode locale extension specifies optional behavior in + * locale-sensitive services. Although the LDML specification defines + * various keys and values, actual locale-sensitive service + * implementations in a Java Runtime Environment might not support any + * particular Unicode locale attributes or key/type pairs. + * + *

Creating a Locale

+ * + *

There are several different ways to create a Locale + * object. + * + *

Builder
+ * + *

Using {@link Builder} you can construct a Locale object + * that conforms to BCP 47 syntax. + * + *

Constructors
+ * + *

The Locale class provides three constructors: + *

+ *
+ *     {@link #Locale(String language)}
+ *     {@link #Locale(String language, String country)}
+ *     {@link #Locale(String language, String country, String variant)}
+ * 
+ *
+ * These constructors allow you to create a Locale object + * with language, country and variant, but you cannot specify + * script or extensions. + * + *
Factory Methods
+ * + *

The method {@link #forLanguageTag} creates a Locale + * object for a well-formed BCP 47 language tag. + * + *

Locale Constants
+ * + *

The Locale class provides a number of convenient constants + * that you can use to create Locale objects for commonly used + * locales. For example, the following creates a Locale object + * for the United States: + *

+ *
+ *     Locale.US
+ * 
+ *
+ * + *

Use of Locale

+ * + *

Once you've created a Locale you can query it for information + * about itself. Use getCountry to get the country (or region) + * code and getLanguage to get the language code. + * You can use getDisplayCountry to get the + * name of the country suitable for displaying to the user. Similarly, + * you can use getDisplayLanguage to get the name of + * the language suitable for displaying to the user. Interestingly, + * the getDisplayXXX methods are themselves locale-sensitive + * and have two versions: one that uses the default locale and one + * that uses the locale specified as an argument. + * + *

The Java Platform provides a number of classes that perform locale-sensitive + * operations. For example, the NumberFormat class formats + * numbers, currency, and percentages in a locale-sensitive manner. Classes + * such as NumberFormat have several convenience methods + * for creating a default object of that type. For example, the + * NumberFormat class provides these three convenience methods + * for creating a default NumberFormat object: + *

+ *
+ *     NumberFormat.getInstance()
+ *     NumberFormat.getCurrencyInstance()
+ *     NumberFormat.getPercentInstance()
+ * 
+ *
+ * Each of these methods has two variants; one with an explicit locale + * and one without; the latter uses the default locale: + *
+ *
+ *     NumberFormat.getInstance(myLocale)
+ *     NumberFormat.getCurrencyInstance(myLocale)
+ *     NumberFormat.getPercentInstance(myLocale)
+ * 
+ *
+ * A Locale is the mechanism for identifying the kind of object + * (NumberFormat) that you would like to get. The locale is + * just a mechanism for identifying objects, + * not a container for the objects themselves. + * + *

Compatibility

+ * + *

In order to maintain compatibility with existing usage, Locale's + * constructors retain their behavior prior to the Java Runtime + * Environment version 1.7. The same is largely true for the + * toString method. Thus Locale objects can continue to + * be used as they were. In particular, clients who parse the output + * of toString into language, country, and variant fields can continue + * to do so (although this is strongly discouraged), although the + * variant field will have additional information in it if script or + * extensions are present. + * + *

In addition, BCP 47 imposes syntax restrictions that are not + * imposed by Locale's constructors. This means that conversions + * between some Locales and BCP 47 language tags cannot be made without + * losing information. Thus toLanguageTag cannot + * represent the state of locales whose language, country, or variant + * do not conform to BCP 47. + * + *

Because of these issues, it is recommended that clients migrate + * away from constructing non-conforming locales and use the + * forLanguageTag and Locale.Builder APIs instead. + * Clients desiring a string representation of the complete locale can + * then always rely on toLanguageTag for this purpose. + * + *

Special cases
+ * + *

For compatibility reasons, two + * non-conforming locales are treated as special cases. These are + * ja_JP_JP and th_TH_TH. These are ill-formed + * in BCP 47 since the variants are too short. To ease migration to BCP 47, + * these are treated specially during construction. These two cases (and only + * these) cause a constructor to generate an extension, all other values behave + * exactly as they did prior to Java 7. + * + *

Java has used ja_JP_JP to represent Japanese as used in + * Japan together with the Japanese Imperial calendar. This is now + * representable using a Unicode locale extension, by specifying the + * Unicode locale key ca (for "calendar") and type + * japanese. When the Locale constructor is called with the + * arguments "ja", "JP", "JP", the extension "u-ca-japanese" is + * automatically added. + * + *

Java has used th_TH_TH to represent Thai as used in + * Thailand together with Thai digits. This is also now representable using + * a Unicode locale extension, by specifying the Unicode locale key + * nu (for "number") and value thai. When the Locale + * constructor is called with the arguments "th", "TH", "TH", the + * extension "u-nu-thai" is automatically added. + * + *

Serialization
+ * + *

During serialization, writeObject writes all fields to the output + * stream, including extensions. + * + *

During deserialization, readResolve adds extensions as described + * in Special Cases, only + * for the two cases th_TH_TH and ja_JP_JP. + * + *

Legacy language codes
+ * + *

Locale's constructor has always converted three language codes to + * their earlier, obsoleted forms: he maps to iw, + * yi maps to ji, and id maps to + * in. This continues to be the case, in order to not break + * backwards compatibility. + * + *

The APIs added in 1.7 map between the old and new language codes, + * maintaining the old codes internal to Locale (so that + * getLanguage and toString reflect the old + * code), but using the new codes in the BCP 47 language tag APIs (so + * that toLanguageTag reflects the new one). This + * preserves the equivalence between Locales no matter which code or + * API is used to construct them. Java's default resource bundle + * lookup mechanism also implements this mapping, so that resources + * can be named using either convention, see {@link ResourceBundle.Control}. + * + *

Three-letter language/country(region) codes
+ * + *

The Locale constructors have always specified that the language + * and the country param be two characters in length, although in + * practice they have accepted any length. The specification has now + * been relaxed to allow language codes of two to eight characters and + * country (region) codes of two to three characters, and in + * particular, three-letter language codes and three-digit region + * codes as specified in the IANA Language Subtag Registry. For + * compatibility, the implementation still does not impose a length + * constraint. + * + * @see Builder + * @see ResourceBundle + * @see java.text.Format + * @see java.text.NumberFormat + * @see java.text.Collator + * @author Mark Davis + * @since 1.1 + */ +public final class Locale implements Cloneable, Serializable { + + /** Useful constant for language. + */ + static public final Locale ENGLISH = createConstant("en", ""); + + /** Useful constant for language. + */ + static public final Locale FRENCH = createConstant("fr", ""); + + /** Useful constant for language. + */ + static public final Locale GERMAN = createConstant("de", ""); + + /** Useful constant for language. + */ + static public final Locale ITALIAN = createConstant("it", ""); + + /** Useful constant for language. + */ + static public final Locale JAPANESE = createConstant("ja", ""); + + /** Useful constant for language. + */ + static public final Locale KOREAN = createConstant("ko", ""); + + /** Useful constant for language. + */ + static public final Locale CHINESE = createConstant("zh", ""); + + /** Useful constant for language. + */ + static public final Locale SIMPLIFIED_CHINESE = createConstant("zh", "CN"); + + /** Useful constant for language. + */ + static public final Locale TRADITIONAL_CHINESE = createConstant("zh", "TW"); + + /** Useful constant for country. + */ + static public final Locale FRANCE = createConstant("fr", "FR"); + + /** Useful constant for country. + */ + static public final Locale GERMANY = createConstant("de", "DE"); + + /** Useful constant for country. + */ + static public final Locale ITALY = createConstant("it", "IT"); + + /** Useful constant for country. + */ + static public final Locale JAPAN = createConstant("ja", "JP"); + + /** Useful constant for country. + */ + static public final Locale KOREA = createConstant("ko", "KR"); + + /** Useful constant for country. + */ + static public final Locale CHINA = SIMPLIFIED_CHINESE; + + /** Useful constant for country. + */ + static public final Locale PRC = SIMPLIFIED_CHINESE; + + /** Useful constant for country. + */ + static public final Locale TAIWAN = TRADITIONAL_CHINESE; + + /** Useful constant for country. + */ + static public final Locale UK = createConstant("en", "GB"); + + /** Useful constant for country. + */ + static public final Locale US = createConstant("en", "US"); + + /** Useful constant for country. + */ + static public final Locale CANADA = createConstant("en", "CA"); + + /** Useful constant for country. + */ + static public final Locale CANADA_FRENCH = createConstant("fr", "CA"); + + /** + * Useful constant for the root locale. The root locale is the locale whose + * language, country, and variant are empty ("") strings. This is regarded + * as the base locale of all locales, and is used as the language/country + * neutral locale for the locale sensitive operations. + * + * @since 1.6 + */ + static public final Locale ROOT = createConstant("", ""); + + /** + * The key for the private use extension ('x'). + * + * @see #getExtension(char) + * @see Builder#setExtension(char, String) + * @since 1.7 + */ + static public final char PRIVATE_USE_EXTENSION = 'x'; + + /** + * The key for Unicode locale extension ('u'). + * + * @see #getExtension(char) + * @see Builder#setExtension(char, String) + * @since 1.7 + */ + static public final char UNICODE_LOCALE_EXTENSION = 'u'; + + /** serialization ID + */ + static final long serialVersionUID = 9149081749638150636L; + + /** + * Display types for retrieving localized names from the name providers. + */ + private static final int DISPLAY_LANGUAGE = 0; + private static final int DISPLAY_COUNTRY = 1; + private static final int DISPLAY_VARIANT = 2; + private static final int DISPLAY_SCRIPT = 3; + + static Locale getInstance(String language, String script, String region, String v, Object object) { + return new Locale(language, script, region); + } + + static Locale getInstance(String no, String no0, String ny) { + return new Locale(no, no0, ny); + } + + private String language; + private String country; + private String variant; + + + /** + * Construct a locale from language, country and variant. + * This constructor normalizes the language value to lowercase and + * the country value to uppercase. + *

+ * Note: + *

    + *
  • ISO 639 is not a stable standard; some of the language codes it defines + * (specifically "iw", "ji", and "in") have changed. This constructor accepts both the + * old codes ("iw", "ji", and "in") and the new codes ("he", "yi", and "id"), but all other + * API on Locale will return only the OLD codes. + *
  • For backward compatibility reasons, this constructor does not make + * any syntactic checks on the input. + *
  • The two cases ("ja", "JP", "JP") and ("th", "TH", "TH") are handled specially, + * see Special Cases for more information. + *
+ * + * @param language An ISO 639 alpha-2 or alpha-3 language code, or a language subtag + * up to 8 characters in length. See the Locale class description about + * valid language values. + * @param country An ISO 3166 alpha-2 country code or a UN M.49 numeric-3 area code. + * See the Locale class description about valid country values. + * @param variant Any arbitrary value used to indicate a variation of a Locale. + * See the Locale class description for the details. + * @exception NullPointerException thrown if any argument is null. + */ + public Locale(String language, String country, String variant) { + if (language== null || country == null || variant == null) { + throw new NullPointerException(); + } + this.language = language; + this.country = country; + this.variant = variant; + } + + /** + * Construct a locale from language and country. + * This constructor normalizes the language value to lowercase and + * the country value to uppercase. + *

+ * Note: + *

    + *
  • ISO 639 is not a stable standard; some of the language codes it defines + * (specifically "iw", "ji", and "in") have changed. This constructor accepts both the + * old codes ("iw", "ji", and "in") and the new codes ("he", "yi", and "id"), but all other + * API on Locale will return only the OLD codes. + *
  • For backward compatibility reasons, this constructor does not make + * any syntactic checks on the input. + *
+ * + * @param language An ISO 639 alpha-2 or alpha-3 language code, or a language subtag + * up to 8 characters in length. See the Locale class description about + * valid language values. + * @param country An ISO 3166 alpha-2 country code or a UN M.49 numeric-3 area code. + * See the Locale class description about valid country values. + * @exception NullPointerException thrown if either argument is null. + */ + public Locale(String language, String country) { + this(language, country, ""); + } + + /** + * Construct a locale from a language code. + * This constructor normalizes the language value to lowercase. + *

+ * Note: + *

    + *
  • ISO 639 is not a stable standard; some of the language codes it defines + * (specifically "iw", "ji", and "in") have changed. This constructor accepts both the + * old codes ("iw", "ji", and "in") and the new codes ("he", "yi", and "id"), but all other + * API on Locale will return only the OLD codes. + *
  • For backward compatibility reasons, this constructor does not make + * any syntactic checks on the input. + *
+ * + * @param language An ISO 639 alpha-2 or alpha-3 language code, or a language subtag + * up to 8 characters in length. See the Locale class description about + * valid language values. + * @exception NullPointerException thrown if argument is null. + * @since 1.4 + */ + public Locale(String language) { + this(language, "", ""); + } + + /** + * This method must be called only for creating the Locale.* + * constants due to making shortcuts. + */ + private static Locale createConstant(String lang, String country) { + return new Locale(lang, country); + } + + /** + * Gets the current value of the default locale for this instance + * of the Java Virtual Machine. + *

+ * The Java Virtual Machine sets the default locale during startup + * based on the host environment. It is used by many locale-sensitive + * methods if no locale is explicitly specified. + * It can be changed using the + * {@link #setDefault(java.util.Locale) setDefault} method. + * + * @return the default locale for this instance of the Java Virtual Machine + */ + public static Locale getDefault() { + return Locale.US; + } + + /** + * Gets the current value of the default locale for the specified Category + * for this instance of the Java Virtual Machine. + *

+ * The Java Virtual Machine sets the default locale during startup based + * on the host environment. It is used by many locale-sensitive methods + * if no locale is explicitly specified. It can be changed using the + * setDefault(Locale.Category, Locale) method. + * + * @param category - the specified category to get the default locale + * @throws NullPointerException - if category is null + * @return the default locale for the specified Category for this instance + * of the Java Virtual Machine + * @see #setDefault(Locale.Category, Locale) + * @since 1.7 + */ + public static Locale getDefault(Locale.Category category) { + return Locale.US; + } + + /** + * Sets the default locale for this instance of the Java Virtual Machine. + * This does not affect the host locale. + *

+ * If there is a security manager, its checkPermission + * method is called with a PropertyPermission("user.language", "write") + * permission before the default locale is changed. + *

+ * The Java Virtual Machine sets the default locale during startup + * based on the host environment. It is used by many locale-sensitive + * methods if no locale is explicitly specified. + *

+ * Since changing the default locale may affect many different areas + * of functionality, this method should only be used if the caller + * is prepared to reinitialize locale-sensitive code running + * within the same Java Virtual Machine. + *

+ * By setting the default locale with this method, all of the default + * locales for each Category are also set to the specified default locale. + * + * @throws SecurityException + * if a security manager exists and its + * checkPermission method doesn't allow the operation. + * @throws NullPointerException if newLocale is null + * @param newLocale the new default locale + * @see SecurityManager#checkPermission + * @see java.util.PropertyPermission + */ + public static void setDefault(Locale newLocale) { + throw new SecurityException(); + } + + /** + * Returns an array of all installed locales. + * The returned array represents the union of locales supported + * by the Java runtime environment and by installed + * {@link java.util.spi.LocaleServiceProvider LocaleServiceProvider} + * implementations. It must contain at least a Locale + * instance equal to {@link java.util.Locale#US Locale.US}. + * + * @return An array of installed locales. + */ + public static Locale[] getAvailableLocales() { + return new Locale[] { Locale.US }; + } + + /** + * Returns the language code of this Locale. + * + *

Note: ISO 639 is not a stable standard— some languages' codes have changed. + * Locale's constructor recognizes both the new and the old codes for the languages + * whose codes have changed, but this function always returns the old code. If you + * want to check for a specific language whose code has changed, don't do + *

+     * if (locale.getLanguage().equals("he")) // BAD!
+     *    ...
+     * 
+ * Instead, do + *
+     * if (locale.getLanguage().equals(new Locale("he").getLanguage()))
+     *    ...
+     * 
+ * @return The language code, or the empty string if none is defined. + * @see #getDisplayLanguage + */ + public String getLanguage() { + return language; + } + + /** + * Returns the script for this locale, which should + * either be the empty string or an ISO 15924 4-letter script + * code. The first letter is uppercase and the rest are + * lowercase, for example, 'Latn', 'Cyrl'. + * + * @return The script code, or the empty string if none is defined. + * @see #getDisplayScript + * @since 1.7 + */ + public String getScript() { + return ""; + } + + /** + * Returns the country/region code for this locale, which should + * either be the empty string, an uppercase ISO 3166 2-letter code, + * or a UN M.49 3-digit code. + * + * @return The country/region code, or the empty string if none is defined. + * @see #getDisplayCountry + */ + public String getCountry() { + return country; + } + + /** + * Returns the variant code for this locale. + * + * @return The variant code, or the empty string if none is defined. + * @see #getDisplayVariant + */ + public String getVariant() { + return variant; + } + + String getRegion() { + return getCountry(); + } + + /** + * Returns the extension (or private use) value associated with + * the specified key, or null if there is no extension + * associated with the key. To be well-formed, the key must be one + * of [0-9A-Za-z]. Keys are case-insensitive, so + * for example 'z' and 'Z' represent the same extension. + * + * @param key the extension key + * @return The extension, or null if this locale defines no + * extension for the specified key. + * @throws IllegalArgumentException if key is not well-formed + * @see #PRIVATE_USE_EXTENSION + * @see #UNICODE_LOCALE_EXTENSION + * @since 1.7 + */ + public String getExtension(char key) { + return null; + } + + /** + * Returns the set of extension keys associated with this locale, or the + * empty set if it has no extensions. The returned set is unmodifiable. + * The keys will all be lower-case. + * + * @return The set of extension keys, or the empty set if this locale has + * no extensions. + * @since 1.7 + */ + public Set getExtensionKeys() { + return Collections.emptySet(); + } + + /** + * Returns the set of unicode locale attributes associated with + * this locale, or the empty set if it has no attributes. The + * returned set is unmodifiable. + * + * @return The set of attributes. + * @since 1.7 + */ + public Set getUnicodeLocaleAttributes() { + return Collections.emptySet(); + } + + /** + * Returns the Unicode locale type associated with the specified Unicode locale key + * for this locale. Returns the empty string for keys that are defined with no type. + * Returns null if the key is not defined. Keys are case-insensitive. The key must + * be two alphanumeric characters ([0-9a-zA-Z]), or an IllegalArgumentException is + * thrown. + * + * @param key the Unicode locale key + * @return The Unicode locale type associated with the key, or null if the + * locale does not define the key. + * @throws IllegalArgumentException if the key is not well-formed + * @throws NullPointerException if key is null + * @since 1.7 + */ + public String getUnicodeLocaleType(String key) { + return null; + } + + /** + * Returns the set of Unicode locale keys defined by this locale, or the empty set if + * this locale has none. The returned set is immutable. Keys are all lower case. + * + * @return The set of Unicode locale keys, or the empty set if this locale has + * no Unicode locale keywords. + * @since 1.7 + */ + public Set getUnicodeLocaleKeys() { + return Collections.emptySet(); + } + + /** + * Returns a string representation of this Locale + * object, consisting of language, country, variant, script, + * and extensions as below: + *

+ * language + "_" + country + "_" + (variant + "_#" | "#") + script + "-" + extensions + *
+ * + * Language is always lower case, country is always upper case, script is always title + * case, and extensions are always lower case. Extensions and private use subtags + * will be in canonical order as explained in {@link #toLanguageTag}. + * + *

When the locale has neither script nor extensions, the result is the same as in + * Java 6 and prior. + * + *

If both the language and country fields are missing, this function will return + * the empty string, even if the variant, script, or extensions field is present (you + * can't have a locale with just a variant, the variant must accompany a well-formed + * language or country code). + * + *

If script or extensions are present and variant is missing, no underscore is + * added before the "#". + * + *

This behavior is designed to support debugging and to be compatible with + * previous uses of toString that expected language, country, and variant + * fields only. To represent a Locale as a String for interchange purposes, use + * {@link #toLanguageTag}. + * + *

Examples:

    + *
  • en + *
  • de_DE + *
  • _GB + *
  • en_US_WIN + *
  • de__POSIX + *
  • zh_CN_#Hans + *
  • zh_TW_#Hant-x-java + *
  • th_TH_TH_#u-nu-thai
+ * + * @return A string representation of the Locale, for debugging. + * @see #getDisplayName + * @see #toLanguageTag + */ + @Override + public final String toString() { + Locale baseLocale = this; + boolean l = (baseLocale.getLanguage().length() != 0); + boolean s = (baseLocale.getScript().length() != 0); + boolean r = (baseLocale.getRegion().length() != 0); + boolean v = (baseLocale.getVariant().length() != 0); + boolean e = false; //(localeExtensions != null && localeExtensions.getID().length() != 0); + + StringBuilder result = new StringBuilder(baseLocale.getLanguage()); + if (r || (l && (v || s || e))) { + result.append('_') + .append(baseLocale.getRegion()); // This may just append '_' + } + if (v && (l || r)) { + result.append('_') + .append(baseLocale.getVariant()); + } + + if (s && (l || r)) { + result.append("_#") + .append(baseLocale.getScript()); + } + + if (e && (l || r)) { + result.append('_'); + if (!s) { + result.append('#'); + } +// result.append(localeExtensions.getID()); + } + + return result.toString(); + } + + + /** + * Overrides Cloneable. + */ + public Object clone() + { + try { + Locale that = (Locale)super.clone(); + return that; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * Override hashCode. + * Since Locales are often used in hashtables, caches the value + * for speed. + */ + @Override + public int hashCode() { + int hash = 3; + hash = 43 * hash + Objects.hashCode(this.language); + hash = 43 * hash + Objects.hashCode(this.country); + hash = 43 * hash + Objects.hashCode(this.variant); + return hash; + } + + // Overrides + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Locale other = (Locale) obj; + if (!Objects.equals(this.language, other.language)) { + return false; + } + if (!Objects.equals(this.country, other.country)) { + return false; + } + if (!Objects.equals(this.variant, other.variant)) { + return false; + } + return true; + } + + /** + * Enum for locale categories. These locale categories are used to get/set + * the default locale for the specific functionality represented by the + * category. + * + * @see #getDefault(Locale.Category) + * @see #setDefault(Locale.Category, Locale) + * @since 1.7 + */ + public enum Category { + + /** + * Category used to represent the default locale for + * displaying user interfaces. + */ + DISPLAY("user.language.display", + "user.script.display", + "user.country.display", + "user.variant.display"), + + /** + * Category used to represent the default locale for + * formatting dates, numbers, and/or currencies. + */ + FORMAT("user.language.format", + "user.script.format", + "user.country.format", + "user.variant.format"); + + Category(String languageKey, String scriptKey, String countryKey, String variantKey) { + this.languageKey = languageKey; + this.scriptKey = scriptKey; + this.countryKey = countryKey; + this.variantKey = variantKey; + } + + final String languageKey; + final String scriptKey; + final String countryKey; + final String variantKey; + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/MissingResourceException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/MissingResourceException.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,124 @@ +/* + * 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation + * is copyrighted and owned by Taligent, Inc., a wholly-owned + * subsidiary of IBM. These materials are provided under terms + * of a License Agreement between Taligent and Sun. This technology + * is protected by multiple US and International patents. + * + * This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.util; + +/** + * Signals that a resource is missing. + * @see java.lang.Exception + * @see ResourceBundle + * @author Mark Davis + * @since JDK1.1 + */ +public +class MissingResourceException extends RuntimeException { + + /** + * Constructs a MissingResourceException with the specified information. + * A detail message is a String that describes this particular exception. + * @param s the detail message + * @param className the name of the resource class + * @param key the key for the missing resource. + */ + public MissingResourceException(String s, String className, String key) { + super(s); + this.className = className; + this.key = key; + } + + /** + * Constructs a MissingResourceException with + * message, className, key, + * and cause. This constructor is package private for + * use by ResourceBundle.getBundle. + * + * @param message + * the detail message + * @param className + * the name of the resource class + * @param key + * the key for the missing resource. + * @param cause + * the cause (which is saved for later retrieval by the + * {@link Throwable.getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent + * or unknown.) + */ + MissingResourceException(String message, String className, String key, Throwable cause) { + super(message, cause); + this.className = className; + this.key = key; + } + + /** + * Gets parameter passed by constructor. + * + * @return the name of the resource class + */ + public String getClassName() { + return className; + } + + /** + * Gets parameter passed by constructor. + * + * @return the key for the missing resource + */ + public String getKey() { + return key; + } + + //============ privates ============ + + // serialization compatibility with JDK1.1 + private static final long serialVersionUID = -4876345176062000401L; + + /** + * The class name of the resource bundle requested by the user. + * @serial + */ + private String className; + + /** + * The name of the specific resource requested by the user. + * @serial + */ + private String key; +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/Properties.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/Properties.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1113 @@ +/* + * Copyright (c) 1995, 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.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.io.OutputStreamWriter; +import java.io.BufferedWriter; + +/** + * The Properties class represents a persistent set of + * properties. The Properties can be saved to a stream + * or loaded from a stream. Each key and its corresponding value in + * the property list is a string. + *

+ * A property list can contain another property list as its + * "defaults"; this second property list is searched if + * the property key is not found in the original property list. + *

+ * Because Properties inherits from Hashtable, the + * put and putAll methods can be applied to a + * Properties object. Their use is strongly discouraged as they + * allow the caller to insert entries whose keys or values are not + * Strings. The setProperty method should be used + * instead. If the store or save method is called + * on a "compromised" Properties object that contains a + * non-String key or value, the call will fail. Similarly, + * the call to the propertyNames or list method + * will fail if it is called on a "compromised" Properties + * object that contains a non-String key. + * + *

+ * The {@link #load(java.io.Reader) load(Reader)} / + * {@link #store(java.io.Writer, java.lang.String) store(Writer, String)} + * methods load and store properties from and to a character based stream + * in a simple line-oriented format specified below. + * + * The {@link #load(java.io.InputStream) load(InputStream)} / + * {@link #store(java.io.OutputStream, java.lang.String) store(OutputStream, String)} + * methods work the same way as the load(Reader)/store(Writer, String) pair, except + * the input/output stream is encoded in ISO 8859-1 character encoding. + * Characters that cannot be directly represented in this encoding can be written using + * Unicode escapes as defined in section 3.3 of + * The Java™ Language Specification; + * only a single 'u' character is allowed in an escape + * sequence. The native2ascii tool can be used to convert property files to and + * from other character encodings. + * + *

The {@link #loadFromXML(InputStream)} and {@link + * #storeToXML(OutputStream, String, String)} methods load and store properties + * in a simple XML format. By default the UTF-8 character encoding is used, + * however a specific encoding may be specified if required. An XML properties + * document has the following DOCTYPE declaration: + * + *

+ * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+ * 
+ * Note that the system URI (http://java.sun.com/dtd/properties.dtd) is + * not accessed when exporting or importing properties; it merely + * serves as a string to uniquely identify the DTD, which is: + *
+ *    <?xml version="1.0" encoding="UTF-8"?>
+ *
+ *    <!-- DTD for properties -->
+ *
+ *    <!ELEMENT properties ( comment?, entry* ) >
+ *
+ *    <!ATTLIST properties version CDATA #FIXED "1.0">
+ *
+ *    <!ELEMENT comment (#PCDATA) >
+ *
+ *    <!ELEMENT entry (#PCDATA) >
+ *
+ *    <!ATTLIST entry key CDATA #REQUIRED>
+ * 
+ * + *

This class is thread-safe: multiple threads can share a single + * Properties object without the need for external synchronization. + * + * @see native2ascii tool for Solaris + * @see native2ascii tool for Windows + * + * @author Arthur van Hoff + * @author Michael McCloskey + * @author Xueming Shen + * @since JDK1.0 + */ +public +class Properties extends Hashtable { + /** + * use serialVersionUID from JDK 1.1.X for interoperability + */ + private static final long serialVersionUID = 4112578634029874840L; + + /** + * A property list that contains default values for any keys not + * found in this property list. + * + * @serial + */ + protected Properties defaults; + + /** + * Creates an empty property list with no default values. + */ + public Properties() { + this(null); + } + + /** + * Creates an empty property list with the specified defaults. + * + * @param defaults the defaults. + */ + public Properties(Properties defaults) { + this.defaults = defaults; + } + + /** + * Calls the Hashtable method put. Provided for + * parallelism with the getProperty method. Enforces use of + * strings for property keys and values. The value returned is the + * result of the Hashtable call to put. + * + * @param key the key to be placed into this property list. + * @param value the value corresponding to key. + * @return the previous value of the specified key in this property + * list, or null if it did not have one. + * @see #getProperty + * @since 1.2 + */ + public synchronized Object setProperty(String key, String value) { + return put(key, value); + } + + + /** + * Reads a property list (key and element pairs) from the input + * character stream in a simple line-oriented format. + *

+ * Properties are processed in terms of lines. There are two + * kinds of line, natural lines and logical lines. + * A natural line is defined as a line of + * characters that is terminated either by a set of line terminator + * characters (\n or \r or \r\n) + * or by the end of the stream. A natural line may be either a blank line, + * a comment line, or hold all or some of a key-element pair. A logical + * line holds all the data of a key-element pair, which may be spread + * out across several adjacent natural lines by escaping + * the line terminator sequence with a backslash character + * \. Note that a comment line cannot be extended + * in this manner; every natural line that is a comment must have + * its own comment indicator, as described below. Lines are read from + * input until the end of the stream is reached. + * + *

+ * A natural line that contains only white space characters is + * considered blank and is ignored. A comment line has an ASCII + * '#' or '!' as its first non-white + * space character; comment lines are also ignored and do not + * encode key-element information. In addition to line + * terminators, this format considers the characters space + * (' ', '\u0020'), tab + * ('\t', '\u0009'), and form feed + * ('\f', '\u000C') to be white + * space. + * + *

+ * If a logical line is spread across several natural lines, the + * backslash escaping the line terminator sequence, the line + * terminator sequence, and any white space at the start of the + * following line have no affect on the key or element values. + * The remainder of the discussion of key and element parsing + * (when loading) will assume all the characters constituting + * the key and element appear on a single natural line after + * line continuation characters have been removed. Note that + * it is not sufficient to only examine the character + * preceding a line terminator sequence to decide if the line + * terminator is escaped; there must be an odd number of + * contiguous backslashes for the line terminator to be escaped. + * Since the input is processed from left to right, a + * non-zero even number of 2n contiguous backslashes + * before a line terminator (or elsewhere) encodes n + * backslashes after escape processing. + * + *

+ * The key contains all of the characters in the line starting + * with the first non-white space character and up to, but not + * including, the first unescaped '=', + * ':', or white space character other than a line + * terminator. All of these key termination characters may be + * included in the key by escaping them with a preceding backslash + * character; for example,

+ * + * \:\=

+ * + * would be the two-character key ":=". Line + * terminator characters can be included using \r and + * \n escape sequences. Any white space after the + * key is skipped; if the first non-white space character after + * the key is '=' or ':', then it is + * ignored and any white space characters after it are also + * skipped. All remaining characters on the line become part of + * the associated element string; if there are no remaining + * characters, the element is the empty string + * "". Once the raw character sequences + * constituting the key and element are identified, escape + * processing is performed as described above. + * + *

+ * As an example, each of the following three lines specifies the key + * "Truth" and the associated element value + * "Beauty": + *

+ *

+     * Truth = Beauty
+     *  Truth:Beauty
+     * Truth                    :Beauty
+     * 
+ * As another example, the following three lines specify a single + * property: + *

+ *

+     * fruits                           apple, banana, pear, \
+     *                                  cantaloupe, watermelon, \
+     *                                  kiwi, mango
+     * 
+ * The key is "fruits" and the associated element is: + *

+ *

"apple, banana, pear, cantaloupe, watermelon, kiwi, mango"
+ * Note that a space appears before each \ so that a space + * will appear after each comma in the final result; the \, + * line terminator, and leading white space on the continuation line are + * merely discarded and are not replaced by one or more other + * characters. + *

+ * As a third example, the line: + *

+ *

cheeses
+     * 
+ * specifies that the key is "cheeses" and the associated + * element is the empty string "".

+ *

+ * + * + * Characters in keys and elements can be represented in escape + * sequences similar to those used for character and string literals + * (see sections 3.3 and 3.10.6 of + * The Java™ Language Specification). + * + * The differences from the character escape sequences and Unicode + * escapes used for characters and strings are: + * + *

    + *
  • Octal escapes are not recognized. + * + *
  • The character sequence \b does not + * represent a backspace character. + * + *
  • The method does not treat a backslash character, + * \, before a non-valid escape character as an + * error; the backslash is silently dropped. For example, in a + * Java string the sequence "\z" would cause a + * compile time error. In contrast, this method silently drops + * the backslash. Therefore, this method treats the two character + * sequence "\b" as equivalent to the single + * character 'b'. + * + *
  • Escapes are not necessary for single and double quotes; + * however, by the rule above, single and double quote characters + * preceded by a backslash still yield single and double quote + * characters, respectively. + * + *
  • Only a single 'u' character is allowed in a Uniocde escape + * sequence. + * + *
+ *

+ * The specified stream remains open after this method returns. + * + * @param reader the input character stream. + * @throws IOException if an error occurred when reading from the + * input stream. + * @throws IllegalArgumentException if a malformed Unicode escape + * appears in the input. + * @since 1.6 + */ + public synchronized void load(Reader reader) throws IOException { + load0(new LineReader(reader)); + } + + /** + * Reads a property list (key and element pairs) from the input + * byte stream. The input stream is in a simple line-oriented + * format as specified in + * {@link #load(java.io.Reader) load(Reader)} and is assumed to use + * the ISO 8859-1 character encoding; that is each byte is one Latin1 + * character. Characters not in Latin1, and certain special characters, + * are represented in keys and elements using Unicode escapes as defined in + * section 3.3 of + * The Java™ Language Specification. + *

+ * The specified stream remains open after this method returns. + * + * @param inStream the input stream. + * @exception IOException if an error occurred when reading from the + * input stream. + * @throws IllegalArgumentException if the input stream contains a + * malformed Unicode escape sequence. + * @since 1.2 + */ + public synchronized void load(InputStream inStream) throws IOException { + load0(new LineReader(inStream)); + } + + private void load0 (LineReader lr) throws IOException { + char[] convtBuf = new char[1024]; + int limit; + int keyLen; + int valueStart; + char c; + boolean hasSep; + boolean precedingBackslash; + + while ((limit = lr.readLine()) >= 0) { + c = 0; + keyLen = 0; + valueStart = limit; + hasSep = false; + + //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">"); + precedingBackslash = false; + while (keyLen < limit) { + c = lr.lineBuf[keyLen]; + //need check if escaped. + if ((c == '=' || c == ':') && !precedingBackslash) { + valueStart = keyLen + 1; + hasSep = true; + break; + } else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { + valueStart = keyLen + 1; + break; + } + if (c == '\\') { + precedingBackslash = !precedingBackslash; + } else { + precedingBackslash = false; + } + keyLen++; + } + while (valueStart < limit) { + c = lr.lineBuf[valueStart]; + if (c != ' ' && c != '\t' && c != '\f') { + if (!hasSep && (c == '=' || c == ':')) { + hasSep = true; + } else { + break; + } + } + valueStart++; + } + String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); + String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); + put(key, value); + } + } + + /* Read in a "logical line" from an InputStream/Reader, skip all comment + * and blank lines and filter out those leading whitespace characters + * (\u0020, \u0009 and \u000c) from the beginning of a "natural line". + * Method returns the char length of the "logical line" and stores + * the line in "lineBuf". + */ + class LineReader { + public LineReader(InputStream inStream) { + this.inStream = inStream; + inByteBuf = new byte[8192]; + } + + public LineReader(Reader reader) { + this.reader = reader; + inCharBuf = new char[8192]; + } + + byte[] inByteBuf; + char[] inCharBuf; + char[] lineBuf = new char[1024]; + int inLimit = 0; + int inOff = 0; + InputStream inStream; + Reader reader; + + int readLine() throws IOException { + int len = 0; + char c = 0; + + boolean skipWhiteSpace = true; + boolean isCommentLine = false; + boolean isNewLine = true; + boolean appendedLineBegin = false; + boolean precedingBackslash = false; + boolean skipLF = false; + + while (true) { + if (inOff >= inLimit) { + inLimit = (inStream==null)?reader.read(inCharBuf) + :inStream.read(inByteBuf); + inOff = 0; + if (inLimit <= 0) { + if (len == 0 || isCommentLine) { + return -1; + } + return len; + } + } + if (inStream != null) { + //The line below is equivalent to calling a + //ISO8859-1 decoder. + c = (char) (0xff & inByteBuf[inOff++]); + } else { + c = inCharBuf[inOff++]; + } + if (skipLF) { + skipLF = false; + if (c == '\n') { + continue; + } + } + if (skipWhiteSpace) { + if (c == ' ' || c == '\t' || c == '\f') { + continue; + } + if (!appendedLineBegin && (c == '\r' || c == '\n')) { + continue; + } + skipWhiteSpace = false; + appendedLineBegin = false; + } + if (isNewLine) { + isNewLine = false; + if (c == '#' || c == '!') { + isCommentLine = true; + continue; + } + } + + if (c != '\n' && c != '\r') { + lineBuf[len++] = c; + if (len == lineBuf.length) { + int newLength = lineBuf.length * 2; + if (newLength < 0) { + newLength = Integer.MAX_VALUE; + } + char[] buf = new char[newLength]; + System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length); + lineBuf = buf; + } + //flip the preceding backslash flag + if (c == '\\') { + precedingBackslash = !precedingBackslash; + } else { + precedingBackslash = false; + } + } + else { + // reached EOL + if (isCommentLine || len == 0) { + isCommentLine = false; + isNewLine = true; + skipWhiteSpace = true; + len = 0; + continue; + } + if (inOff >= inLimit) { + inLimit = (inStream==null) + ?reader.read(inCharBuf) + :inStream.read(inByteBuf); + inOff = 0; + if (inLimit <= 0) { + return len; + } + } + if (precedingBackslash) { + len -= 1; + //skip the leading whitespace characters in following line + skipWhiteSpace = true; + appendedLineBegin = true; + precedingBackslash = false; + if (c == '\r') { + skipLF = true; + } + } else { + return len; + } + } + } + } + } + + /* + * Converts encoded \uxxxx to unicode chars + * and changes special saved chars to their original forms + */ + private String loadConvert (char[] in, int off, int len, char[] convtBuf) { + if (convtBuf.length < len) { + int newLen = len * 2; + if (newLen < 0) { + newLen = Integer.MAX_VALUE; + } + convtBuf = new char[newLen]; + } + char aChar; + char[] out = convtBuf; + int outLen = 0; + int end = off + len; + + while (off < end) { + aChar = in[off++]; + if (aChar == '\\') { + aChar = in[off++]; + if(aChar == 'u') { + // Read the xxxx + int value=0; + for (int i=0; i<4; i++) { + aChar = in[off++]; + switch (aChar) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + value = (value << 4) + aChar - '0'; + break; + case 'a': case 'b': case 'c': + case 'd': case 'e': case 'f': + value = (value << 4) + 10 + aChar - 'a'; + break; + case 'A': case 'B': case 'C': + case 'D': case 'E': case 'F': + value = (value << 4) + 10 + aChar - 'A'; + break; + default: + throw new IllegalArgumentException( + "Malformed \\uxxxx encoding."); + } + } + out[outLen++] = (char)value; + } else { + if (aChar == 't') aChar = '\t'; + else if (aChar == 'r') aChar = '\r'; + else if (aChar == 'n') aChar = '\n'; + else if (aChar == 'f') aChar = '\f'; + out[outLen++] = aChar; + } + } else { + out[outLen++] = aChar; + } + } + return new String (out, 0, outLen); + } + + /* + * Converts unicodes to encoded \uxxxx and escapes + * special characters with a preceding slash + */ + private String saveConvert(String theString, + boolean escapeSpace, + boolean escapeUnicode) { + int len = theString.length(); + int bufLen = len * 2; + if (bufLen < 0) { + bufLen = Integer.MAX_VALUE; + } + StringBuffer outBuffer = new StringBuffer(bufLen); + + for(int x=0; x 61) && (aChar < 127)) { + if (aChar == '\\') { + outBuffer.append('\\'); outBuffer.append('\\'); + continue; + } + outBuffer.append(aChar); + continue; + } + switch(aChar) { + case ' ': + if (x == 0 || escapeSpace) + outBuffer.append('\\'); + outBuffer.append(' '); + break; + case '\t':outBuffer.append('\\'); outBuffer.append('t'); + break; + case '\n':outBuffer.append('\\'); outBuffer.append('n'); + break; + case '\r':outBuffer.append('\\'); outBuffer.append('r'); + break; + case '\f':outBuffer.append('\\'); outBuffer.append('f'); + break; + case '=': // Fall through + case ':': // Fall through + case '#': // Fall through + case '!': + outBuffer.append('\\'); outBuffer.append(aChar); + break; + default: + if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode ) { + outBuffer.append('\\'); + outBuffer.append('u'); + outBuffer.append(toHex((aChar >> 12) & 0xF)); + outBuffer.append(toHex((aChar >> 8) & 0xF)); + outBuffer.append(toHex((aChar >> 4) & 0xF)); + outBuffer.append(toHex( aChar & 0xF)); + } else { + outBuffer.append(aChar); + } + } + } + return outBuffer.toString(); + } + + private static void writeComments(BufferedWriter bw, String comments) + throws IOException { + bw.write("#"); + int len = comments.length(); + int current = 0; + int last = 0; + char[] uu = new char[6]; + uu[0] = '\\'; + uu[1] = 'u'; + while (current < len) { + char c = comments.charAt(current); + if (c > '\u00ff' || c == '\n' || c == '\r') { + if (last != current) + bw.write(comments.substring(last, current)); + if (c > '\u00ff') { + uu[2] = toHex((c >> 12) & 0xf); + uu[3] = toHex((c >> 8) & 0xf); + uu[4] = toHex((c >> 4) & 0xf); + uu[5] = toHex( c & 0xf); + bw.write(new String(uu)); + } else { + bw.newLine(); + if (c == '\r' && + current != len - 1 && + comments.charAt(current + 1) == '\n') { + current++; + } + if (current == len - 1 || + (comments.charAt(current + 1) != '#' && + comments.charAt(current + 1) != '!')) + bw.write("#"); + } + last = current + 1; + } + current++; + } + if (last != current) + bw.write(comments.substring(last, current)); + bw.newLine(); + } + + /** + * Calls the store(OutputStream out, String comments) method + * and suppresses IOExceptions that were thrown. + * + * @deprecated This method does not throw an IOException if an I/O error + * occurs while saving the property list. The preferred way to save a + * properties list is via the store(OutputStream out, + * String comments) method or the + * storeToXML(OutputStream os, String comment) method. + * + * @param out an output stream. + * @param comments a description of the property list. + * @exception ClassCastException if this Properties object + * contains any keys or values that are not + * Strings. + */ + @Deprecated + public void save(OutputStream out, String comments) { + try { + store(out, comments); + } catch (IOException e) { + } + } + + /** + * Writes this property list (key and element pairs) in this + * Properties table to the output character stream in a + * format suitable for using the {@link #load(java.io.Reader) load(Reader)} + * method. + *

+ * Properties from the defaults table of this Properties + * table (if any) are not written out by this method. + *

+ * If the comments argument is not null, then an ASCII # + * character, the comments string, and a line separator are first written + * to the output stream. Thus, the comments can serve as an + * identifying comment. Any one of a line feed ('\n'), a carriage + * return ('\r'), or a carriage return followed immediately by a line feed + * in comments is replaced by a line separator generated by the Writer + * and if the next character in comments is not character # or + * character ! then an ASCII # is written out + * after that line separator. + *

+ * Next, a comment line is always written, consisting of an ASCII + * # character, the current date and time (as if produced + * by the toString method of Date for the + * current time), and a line separator as generated by the Writer. + *

+ * Then every entry in this Properties table is + * written out, one per line. For each entry the key string is + * written, then an ASCII =, then the associated + * element string. For the key, all space characters are + * written with a preceding \ character. For the + * element, leading space characters, but not embedded or trailing + * space characters, are written with a preceding \ + * character. The key and element characters #, + * !, =, and : are written + * with a preceding backslash to ensure that they are properly loaded. + *

+ * After the entries have been written, the output stream is flushed. + * The output stream remains open after this method returns. + *

+ * + * @param writer an output character stream writer. + * @param comments a description of the property list. + * @exception IOException if writing this property list to the specified + * output stream throws an IOException. + * @exception ClassCastException if this Properties object + * contains any keys or values that are not Strings. + * @exception NullPointerException if writer is null. + * @since 1.6 + */ + public void store(Writer writer, String comments) + throws IOException + { + store0((writer instanceof BufferedWriter)?(BufferedWriter)writer + : new BufferedWriter(writer), + comments, + false); + } + + /** + * Writes this property list (key and element pairs) in this + * Properties table to the output stream in a format suitable + * for loading into a Properties table using the + * {@link #load(InputStream) load(InputStream)} method. + *

+ * Properties from the defaults table of this Properties + * table (if any) are not written out by this method. + *

+ * This method outputs the comments, properties keys and values in + * the same format as specified in + * {@link #store(java.io.Writer, java.lang.String) store(Writer)}, + * with the following differences: + *

    + *
  • The stream is written using the ISO 8859-1 character encoding. + * + *
  • Characters not in Latin-1 in the comments are written as + * \uxxxx for their appropriate unicode + * hexadecimal value xxxx. + * + *
  • Characters less than \u0020 and characters greater + * than \u007E in property keys or values are written + * as \uxxxx for the appropriate hexadecimal + * value xxxx. + *
+ *

+ * After the entries have been written, the output stream is flushed. + * The output stream remains open after this method returns. + *

+ * @param out an output stream. + * @param comments a description of the property list. + * @exception IOException if writing this property list to the specified + * output stream throws an IOException. + * @exception ClassCastException if this Properties object + * contains any keys or values that are not Strings. + * @exception NullPointerException if out is null. + * @since 1.2 + */ + public void store(OutputStream out, String comments) + throws IOException + { + store0(new BufferedWriter(new OutputStreamWriter(out, "8859_1")), + comments, + true); + } + + private void store0(BufferedWriter bw, String comments, boolean escUnicode) + throws IOException + { + if (comments != null) { + writeComments(bw, comments); + } + bw.write("#" + new Date().toString()); + bw.newLine(); + synchronized (this) { + for (Enumeration e = keys(); e.hasMoreElements();) { + String key = (String)e.nextElement(); + String val = (String)get(key); + key = saveConvert(key, true, escUnicode); + /* No need to escape embedded and trailing spaces for value, hence + * pass false to flag. + */ + val = saveConvert(val, false, escUnicode); + bw.write(key + "=" + val); + bw.newLine(); + } + } + bw.flush(); + } + + /** + * Loads all of the properties represented by the XML document on the + * specified input stream into this properties table. + * + *

The XML document must have the following DOCTYPE declaration: + *

+     * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+     * 
+ * Furthermore, the document must satisfy the properties DTD described + * above. + * + *

The specified stream is closed after this method returns. + * + * @param in the input stream from which to read the XML document. + * @throws IOException if reading from the specified input stream + * results in an IOException. + * @throws InvalidPropertiesFormatException Data on input stream does not + * constitute a valid XML document with the mandated document type. + * @throws NullPointerException if in is null. + * @see #storeToXML(OutputStream, String, String) + * @since 1.5 + */ + public synchronized void loadFromXML(InputStream in) + throws IOException + { + if (in == null) + throw new NullPointerException(); + throw new IOException(); + } + + /** + * Emits an XML document representing all of the properties contained + * in this table. + * + *

An invocation of this method of the form props.storeToXML(os, + * comment) behaves in exactly the same way as the invocation + * props.storeToXML(os, comment, "UTF-8");. + * + * @param os the output stream on which to emit the XML document. + * @param comment a description of the property list, or null + * if no comment is desired. + * @throws IOException if writing to the specified output stream + * results in an IOException. + * @throws NullPointerException if os is null. + * @throws ClassCastException if this Properties object + * contains any keys or values that are not + * Strings. + * @see #loadFromXML(InputStream) + * @since 1.5 + */ + public void storeToXML(OutputStream os, String comment) + throws IOException + { + if (os == null) + throw new NullPointerException(); + storeToXML(os, comment, "UTF-8"); + } + + /** + * Emits an XML document representing all of the properties contained + * in this table, using the specified encoding. + * + *

The XML document will have the following DOCTYPE declaration: + *

+     * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+     * 
+ * + *

If the specified comment is null then no comment + * will be stored in the document. + * + *

The specified stream remains open after this method returns. + * + * @param os the output stream on which to emit the XML document. + * @param comment a description of the property list, or null + * if no comment is desired. + * @param encoding the name of a supported + * + * character encoding + * + * @throws IOException if writing to the specified output stream + * results in an IOException. + * @throws NullPointerException if os is null, + * or if encoding is null. + * @throws ClassCastException if this Properties object + * contains any keys or values that are not + * Strings. + * @see #loadFromXML(InputStream) + * @since 1.5 + */ + public void storeToXML(OutputStream os, String comment, String encoding) + throws IOException + { + if (os == null) + throw new NullPointerException(); + throw new IOException(); + } + + /** + * Searches for the property with the specified key in this property list. + * If the key is not found in this property list, the default property list, + * and its defaults, recursively, are then checked. The method returns + * null if the property is not found. + * + * @param key the property key. + * @return the value in this property list with the specified key value. + * @see #setProperty + * @see #defaults + */ + public String getProperty(String key) { + Object oval = super.get(key); + String sval = (oval instanceof String) ? (String)oval : null; + return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval; + } + + /** + * Searches for the property with the specified key in this property list. + * If the key is not found in this property list, the default property list, + * and its defaults, recursively, are then checked. The method returns the + * default value argument if the property is not found. + * + * @param key the hashtable key. + * @param defaultValue a default value. + * + * @return the value in this property list with the specified key value. + * @see #setProperty + * @see #defaults + */ + public String getProperty(String key, String defaultValue) { + String val = getProperty(key); + return (val == null) ? defaultValue : val; + } + + /** + * Returns an enumeration of all the keys in this property list, + * including distinct keys in the default property list if a key + * of the same name has not already been found from the main + * properties list. + * + * @return an enumeration of all the keys in this property list, including + * the keys in the default property list. + * @throws ClassCastException if any key in this property list + * is not a string. + * @see java.util.Enumeration + * @see java.util.Properties#defaults + * @see #stringPropertyNames + */ + public Enumeration propertyNames() { + Hashtable h = new Hashtable(); + enumerate(h); + return h.keys(); + } + + /** + * Returns a set of keys in this property list where + * the key and its corresponding value are strings, + * including distinct keys in the default property list if a key + * of the same name has not already been found from the main + * properties list. Properties whose key or value is not + * of type String are omitted. + *

+ * The returned set is not backed by the Properties object. + * Changes to this Properties are not reflected in the set, + * or vice versa. + * + * @return a set of keys in this property list where + * the key and its corresponding value are strings, + * including the keys in the default property list. + * @see java.util.Properties#defaults + * @since 1.6 + */ + public Set stringPropertyNames() { + Hashtable h = new Hashtable<>(); + enumerateStringProperties(h); + return h.keySet(); + } + + /** + * Prints this property list out to the specified output stream. + * This method is useful for debugging. + * + * @param out an output stream. + * @throws ClassCastException if any key in this property list + * is not a string. + */ + public void list(PrintStream out) { + out.println("-- listing properties --"); + Hashtable h = new Hashtable(); + enumerate(h); + for (Enumeration e = h.keys() ; e.hasMoreElements() ;) { + String key = (String)e.nextElement(); + String val = (String)h.get(key); + if (val.length() > 40) { + val = val.substring(0, 37) + "..."; + } + out.println(key + "=" + val); + } + } + + /** + * Prints this property list out to the specified output stream. + * This method is useful for debugging. + * + * @param out an output stream. + * @throws ClassCastException if any key in this property list + * is not a string. + * @since JDK1.1 + */ + /* + * Rather than use an anonymous inner class to share common code, this + * method is duplicated in order to ensure that a non-1.1 compiler can + * compile this file. + */ + public void list(PrintWriter out) { + out.println("-- listing properties --"); + Hashtable h = new Hashtable(); + enumerate(h); + for (Enumeration e = h.keys() ; e.hasMoreElements() ;) { + String key = (String)e.nextElement(); + String val = (String)h.get(key); + if (val.length() > 40) { + val = val.substring(0, 37) + "..."; + } + out.println(key + "=" + val); + } + } + + /** + * Enumerates all key/value pairs in the specified hashtable. + * @param h the hashtable + * @throws ClassCastException if any of the property keys + * is not of String type. + */ + private synchronized void enumerate(Hashtable h) { + if (defaults != null) { + defaults.enumerate(h); + } + for (Enumeration e = keys() ; e.hasMoreElements() ;) { + String key = (String)e.nextElement(); + h.put(key, get(key)); + } + } + + /** + * Enumerates all key/value pairs in the specified hashtable + * and omits the property if the key or value is not a string. + * @param h the hashtable + */ + private synchronized void enumerateStringProperties(Hashtable h) { + if (defaults != null) { + defaults.enumerateStringProperties(h); + } + for (Enumeration e = keys() ; e.hasMoreElements() ;) { + Object k = e.nextElement(); + Object v = get(k); + if (k instanceof String && v instanceof String) { + h.put((String) k, (String) v); + } + } + } + + /** + * Convert a nibble to a hex character + * @param nibble the nibble to convert. + */ + private static char toHex(int nibble) { + return hexDigit[(nibble & 0xF)]; + } + + /** A table of hex digits */ + private static final char[] hexDigit = { + '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' + }; +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/PropertyResourceBundle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/PropertyResourceBundle.java Wed Apr 30 15:04:10 2014 +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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation + * is copyrighted and owned by Taligent, Inc., a wholly-owned + * subsidiary of IBM. These materials are provided under terms + * of a License Agreement between Taligent and Sun. This technology + * is protected by multiple US and International patents. + * + * This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + */ + +package java.util; + +import java.io.InputStream; +import java.io.Reader; +import java.io.IOException; + +/** + * PropertyResourceBundle is a concrete subclass of + * ResourceBundle that manages resources for a locale + * using a set of static strings from a property file. See + * {@link ResourceBundle ResourceBundle} for more information about resource + * bundles. + * + *

+ * Unlike other types of resource bundle, you don't subclass + * PropertyResourceBundle. Instead, you supply properties + * files containing the resource data. ResourceBundle.getBundle + * will automatically look for the appropriate properties file and create a + * PropertyResourceBundle that refers to it. See + * {@link ResourceBundle#getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader) ResourceBundle.getBundle} + * for a complete description of the search and instantiation strategy. + * + *

+ * The following example shows a member of a resource + * bundle family with the base name "MyResources". + * The text defines the bundle "MyResources_de", + * the German member of the bundle family. + * This member is based on PropertyResourceBundle, and the text + * therefore is the content of the file "MyResources_de.properties" + * (a related example shows + * how you can add bundles to this family that are implemented as subclasses + * of ListResourceBundle). + * The keys in this example are of the form "s1" etc. The actual + * keys are entirely up to your choice, so long as they are the same as + * the keys you use in your program to retrieve the objects from the bundle. + * Keys are case-sensitive. + *

+ *
+ * # MessageFormat pattern
+ * s1=Die Platte \"{1}\" enthält {0}.
+ *
+ * # location of {0} in pattern
+ * s2=1
+ *
+ * # sample disk name
+ * s3=Meine Platte
+ *
+ * # first ChoiceFormat choice
+ * s4=keine Dateien
+ *
+ * # second ChoiceFormat choice
+ * s5=eine Datei
+ *
+ * # third ChoiceFormat choice
+ * s6={0,number} Dateien
+ *
+ * # sample date
+ * s7=3. März 1996
+ * 
+ *
+ * + *

+ * Note: PropertyResourceBundle can be constructed either + * from an InputStream or a Reader, which represents a property file. + * Constructing a PropertyResourceBundle instance from an InputStream requires + * that the input stream be encoded in ISO-8859-1. In that case, characters + * that cannot be represented in ISO-8859-1 encoding must be represented by Unicode Escapes + * as defined in section 3.3 of + * The Java™ Language Specification + * whereas the other constructor which takes a Reader does not have that limitation. + * + * @see ResourceBundle + * @see ListResourceBundle + * @see Properties + * @since JDK1.1 + */ +public class PropertyResourceBundle extends ResourceBundle { + /** + * Creates a property resource bundle from an {@link java.io.InputStream + * InputStream}. The property file read with this constructor + * must be encoded in ISO-8859-1. + * + * @param stream an InputStream that represents a property file + * to read from. + * @throws IOException if an I/O error occurs + * @throws NullPointerException if stream is null + */ + public PropertyResourceBundle (InputStream stream) throws IOException { + Properties properties = new Properties(); + properties.load(stream); + lookup = new HashMap(properties); + } + + /** + * Creates a property resource bundle from a {@link java.io.Reader + * Reader}. Unlike the constructor + * {@link #PropertyResourceBundle(java.io.InputStream) PropertyResourceBundle(InputStream)}, + * there is no limitation as to the encoding of the input property file. + * + * @param reader a Reader that represents a property file to + * read from. + * @throws IOException if an I/O error occurs + * @throws NullPointerException if reader is null + * @since 1.6 + */ + public PropertyResourceBundle (Reader reader) throws IOException { + Properties properties = new Properties(); + properties.load(reader); + lookup = new HashMap(properties); + } + + // Implements java.util.ResourceBundle.handleGetObject; inherits javadoc specification. + public Object handleGetObject(String key) { + if (key == null) { + throw new NullPointerException(); + } + return lookup.get(key); + } + + /** + * Returns an Enumeration of the keys contained in + * this ResourceBundle and its parent bundles. + * + * @return an Enumeration of the keys contained in + * this ResourceBundle and its parent bundles. + * @see #keySet() + */ + public Enumeration getKeys() { + ResourceBundle parent = this.parent; + return new ResourceBundleEnumeration(lookup.keySet(), + (parent != null) ? parent.getKeys() : null); + } + + /** + * Returns a Set of the keys contained + * only in this ResourceBundle. + * + * @return a Set of the keys contained only in this + * ResourceBundle + * @since 1.6 + * @see #keySet() + */ + protected Set handleKeySet() { + return lookup.keySet(); + } + + // ==================privates==================== + + private Map lookup; + + + /** + * Implements an Enumeration that combines elements from a Set and + * an Enumeration. Used by ListResourceBundle and PropertyResourceBundle. + */ + static class ResourceBundleEnumeration implements Enumeration { + + Set set; + Iterator iterator; + Enumeration enumeration; // may remain null + + /** + * Constructs a resource bundle enumeration. + * @param set an set providing some elements of the enumeration + * @param enumeration an enumeration providing more elements of the enumeration. + * enumeration may be null. + */ + public ResourceBundleEnumeration(Set set, Enumeration enumeration) { + this.set = set; + this.iterator = set.iterator(); + this.enumeration = enumeration; + } + + String next = null; + + public boolean hasMoreElements() { + if (next == null) { + if (iterator.hasNext()) { + next = iterator.next(); + } else if (enumeration != null) { + while (next == null && enumeration.hasMoreElements()) { + next = enumeration.nextElement(); + if (set.contains(next)) { + next = null; + } + } + } + } + return next != null; + } + + public String nextElement() { + if (hasMoreElements()) { + String result = next; + next = null; + return result; + } else { + throw new NoSuchElementException(); + } + } + } + +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/ResourceBundle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/ResourceBundle.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,2778 @@ +/* + * 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved + * + * The original version of this source code and documentation + * is copyrighted and owned by Taligent, Inc., a wholly-owned + * subsidiary of IBM. These materials are provided under terms + * of a License Agreement between Taligent and Sun. This technology + * is protected by multiple US and International patents. + * + * This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.util; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.net.URL; + + +/** + * + * Resource bundles contain locale-specific objects. When your program needs a + * locale-specific resource, a String for example, your program can + * load it from the resource bundle that is appropriate for the current user's + * locale. In this way, you can write program code that is largely independent + * of the user's locale isolating most, if not all, of the locale-specific + * information in resource bundles. + * + *

+ * This allows you to write programs that can: + *

    + *
  • be easily localized, or translated, into different languages + *
  • handle multiple locales at once + *
  • be easily modified later to support even more locales + *
+ * + *

+ * Resource bundles belong to families whose members share a common base + * name, but whose names also have additional components that identify + * their locales. For example, the base name of a family of resource + * bundles might be "MyResources". The family should have a default + * resource bundle which simply has the same name as its family - + * "MyResources" - and will be used as the bundle of last resort if a + * specific locale is not supported. The family can then provide as + * many locale-specific members as needed, for example a German one + * named "MyResources_de". + * + *

+ * Each resource bundle in a family contains the same items, but the items have + * been translated for the locale represented by that resource bundle. + * For example, both "MyResources" and "MyResources_de" may have a + * String that's used on a button for canceling operations. + * In "MyResources" the String may contain "Cancel" and in + * "MyResources_de" it may contain "Abbrechen". + * + *

+ * If there are different resources for different countries, you + * can make specializations: for example, "MyResources_de_CH" contains objects for + * the German language (de) in Switzerland (CH). If you want to only + * modify some of the resources + * in the specialization, you can do so. + * + *

+ * When your program needs a locale-specific object, it loads + * the ResourceBundle class using the + * {@link #getBundle(java.lang.String, java.util.Locale) getBundle} + * method: + *

+ *
+ * ResourceBundle myResources =
+ *      ResourceBundle.getBundle("MyResources", currentLocale);
+ * 
+ *
+ * + *

+ * Resource bundles contain key/value pairs. The keys uniquely + * identify a locale-specific object in the bundle. Here's an + * example of a ListResourceBundle that contains + * two key/value pairs: + *

+ *
+ * public class MyResources extends ListResourceBundle {
+ *     protected Object[][] getContents() {
+ *         return new Object[][] {
+ *             // LOCALIZE THE SECOND STRING OF EACH ARRAY (e.g., "OK")
+ *             {"OkKey", "OK"},
+ *             {"CancelKey", "Cancel"},
+ *             // END OF MATERIAL TO LOCALIZE
+ *        };
+ *     }
+ * }
+ * 
+ *
+ * Keys are always Strings. + * In this example, the keys are "OkKey" and "CancelKey". + * In the above example, the values + * are also Strings--"OK" and "Cancel"--but + * they don't have to be. The values can be any type of object. + * + *

+ * You retrieve an object from resource bundle using the appropriate + * getter method. Because "OkKey" and "CancelKey" + * are both strings, you would use getString to retrieve them: + *

+ *
+ * button1 = new Button(myResources.getString("OkKey"));
+ * button2 = new Button(myResources.getString("CancelKey"));
+ * 
+ *
+ * The getter methods all require the key as an argument and return + * the object if found. If the object is not found, the getter method + * throws a MissingResourceException. + * + *

+ * Besides getString, ResourceBundle also provides + * a method for getting string arrays, getStringArray, + * as well as a generic getObject method for any other + * type of object. When using getObject, you'll + * have to cast the result to the appropriate type. For example: + *

+ *
+ * int[] myIntegers = (int[]) myResources.getObject("intList");
+ * 
+ *
+ * + *

+ * The Java Platform provides two subclasses of ResourceBundle, + * ListResourceBundle and PropertyResourceBundle, + * that provide a fairly simple way to create resources. + * As you saw briefly in a previous example, ListResourceBundle + * manages its resource as a list of key/value pairs. + * PropertyResourceBundle uses a properties file to manage + * its resources. + * + *

+ * If ListResourceBundle or PropertyResourceBundle + * do not suit your needs, you can write your own ResourceBundle + * subclass. Your subclasses must override two methods: handleGetObject + * and getKeys(). + * + *

ResourceBundle.Control

+ * + * The {@link ResourceBundle.Control} class provides information necessary + * to perform the bundle loading process by the getBundle + * factory methods that take a ResourceBundle.Control + * instance. You can implement your own subclass in order to enable + * non-standard resource bundle formats, change the search strategy, or + * define caching parameters. Refer to the descriptions of the class and the + * {@link #getBundle(String, Locale, ClassLoader, Control) getBundle} + * factory method for details. + * + *

Cache Management

+ * + * Resource bundle instances created by the getBundle factory + * methods are cached by default, and the factory methods return the same + * resource bundle instance multiple times if it has been + * cached. getBundle clients may clear the cache, manage the + * lifetime of cached resource bundle instances using time-to-live values, + * or specify not to cache resource bundle instances. Refer to the + * descriptions of the {@linkplain #getBundle(String, Locale, ClassLoader, + * Control) getBundle factory method}, {@link + * #clearCache(ClassLoader) clearCache}, {@link + * Control#getTimeToLive(String, Locale) + * ResourceBundle.Control.getTimeToLive}, and {@link + * Control#needsReload(String, Locale, String, ClassLoader, ResourceBundle, + * long) ResourceBundle.Control.needsReload} for details. + * + *

Example

+ * + * The following is a very simple example of a ResourceBundle + * subclass, MyResources, that manages two resources (for a larger number of + * resources you would probably use a Map). + * Notice that you don't need to supply a value if + * a "parent-level" ResourceBundle handles the same + * key with the same value (as for the okKey below). + *
+ *
+ * // default (English language, United States)
+ * public class MyResources extends ResourceBundle {
+ *     public Object handleGetObject(String key) {
+ *         if (key.equals("okKey")) return "Ok";
+ *         if (key.equals("cancelKey")) return "Cancel";
+ *         return null;
+ *     }
+ *
+ *     public Enumeration<String> getKeys() {
+ *         return Collections.enumeration(keySet());
+ *     }
+ *
+ *     // Overrides handleKeySet() so that the getKeys() implementation
+ *     // can rely on the keySet() value.
+ *     protected Set<String> handleKeySet() {
+ *         return new HashSet<String>(Arrays.asList("okKey", "cancelKey"));
+ *     }
+ * }
+ *
+ * // German language
+ * public class MyResources_de extends MyResources {
+ *     public Object handleGetObject(String key) {
+ *         // don't need okKey, since parent level handles it.
+ *         if (key.equals("cancelKey")) return "Abbrechen";
+ *         return null;
+ *     }
+ *
+ *     protected Set<String> handleKeySet() {
+ *         return new HashSet<String>(Arrays.asList("cancelKey"));
+ *     }
+ * }
+ * 
+ *
+ * You do not have to restrict yourself to using a single family of + * ResourceBundles. For example, you could have a set of bundles for + * exception messages, ExceptionResources + * (ExceptionResources_fr, ExceptionResources_de, ...), + * and one for widgets, WidgetResource (WidgetResources_fr, + * WidgetResources_de, ...); breaking up the resources however you like. + * + * @see ListResourceBundle + * @see PropertyResourceBundle + * @see MissingResourceException + * @since JDK1.1 + */ +public abstract class ResourceBundle { + + /** initial size of the bundle cache */ + private static final int INITIAL_CACHE_SIZE = 32; + + /** constant indicating that no resource bundle exists */ + private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() { + public Enumeration getKeys() { return null; } + protected Object handleGetObject(String key) { return null; } + public String toString() { return "NONEXISTENT_BUNDLE"; } + }; + + + /** + * The cache is a map from cache keys (with bundle base name, locale, and + * class loader) to either a resource bundle or NONEXISTENT_BUNDLE wrapped by a + * BundleReference. + * + * The cache is a ConcurrentMap, allowing the cache to be searched + * concurrently by multiple threads. This will also allow the cache keys + * to be reclaimed along with the ClassLoaders they reference. + * + * This variable would be better named "cache", but we keep the old + * name for compatibility with some workarounds for bug 4212439. + */ + private static final Map cacheList + = new HashMap<>(INITIAL_CACHE_SIZE); + + /** + * Queue for reference objects referring to class loaders or bundles. + */ + private static final ReferenceQueue referenceQueue = new ReferenceQueue(); + + /** + * The parent bundle of this bundle. + * The parent bundle is searched by {@link #getObject getObject} + * when this bundle does not contain a particular resource. + */ + protected ResourceBundle parent = null; + + /** + * The locale for this bundle. + */ + private Locale locale = null; + + /** + * The base bundle name for this bundle. + */ + private String name; + + /** + * The flag indicating this bundle has expired in the cache. + */ + private volatile boolean expired; + + /** + * The back link to the cache key. null if this bundle isn't in + * the cache (yet) or has expired. + */ + private volatile CacheKey cacheKey; + + /** + * A Set of the keys contained only in this ResourceBundle. + */ + private volatile Set keySet; + + /** + * Sole constructor. (For invocation by subclass constructors, typically + * implicit.) + */ + public ResourceBundle() { + } + + /** + * Gets a string for the given key from this resource bundle or one of its parents. + * Calling this method is equivalent to calling + *
+ * (String) {@link #getObject(java.lang.String) getObject}(key). + *
+ * + * @param key the key for the desired string + * @exception NullPointerException if key is null + * @exception MissingResourceException if no object for the given key can be found + * @exception ClassCastException if the object found for the given key is not a string + * @return the string for the given key + */ + public final String getString(String key) { + return (String) getObject(key); + } + + /** + * Gets a string array for the given key from this resource bundle or one of its parents. + * Calling this method is equivalent to calling + *
+ * (String[]) {@link #getObject(java.lang.String) getObject}(key). + *
+ * + * @param key the key for the desired string array + * @exception NullPointerException if key is null + * @exception MissingResourceException if no object for the given key can be found + * @exception ClassCastException if the object found for the given key is not a string array + * @return the string array for the given key + */ + public final String[] getStringArray(String key) { + return (String[]) getObject(key); + } + + /** + * Gets an object for the given key from this resource bundle or one of its parents. + * This method first tries to obtain the object from this resource bundle using + * {@link #handleGetObject(java.lang.String) handleGetObject}. + * If not successful, and the parent resource bundle is not null, + * it calls the parent's getObject method. + * If still not successful, it throws a MissingResourceException. + * + * @param key the key for the desired object + * @exception NullPointerException if key is null + * @exception MissingResourceException if no object for the given key can be found + * @return the object for the given key + */ + public final Object getObject(String key) { + Object obj = handleGetObject(key); + if (obj == null) { + if (parent != null) { + obj = parent.getObject(key); + } + if (obj == null) + throw new MissingResourceException("Can't find resource for bundle " + +this.getClass().getName() + +", key "+key, + this.getClass().getName(), + key); + } + return obj; + } + + /** + * Returns the locale of this resource bundle. This method can be used after a + * call to getBundle() to determine whether the resource bundle returned really + * corresponds to the requested locale or is a fallback. + * + * @return the locale of this resource bundle + */ + public Locale getLocale() { + return locale; + } + + /** + * Sets the parent bundle of this bundle. + * The parent bundle is searched by {@link #getObject getObject} + * when this bundle does not contain a particular resource. + * + * @param parent this bundle's parent bundle. + */ + protected void setParent(ResourceBundle parent) { + assert parent != NONEXISTENT_BUNDLE; + this.parent = parent; + } + + /** + * Key used for cached resource bundles. The key checks the base + * name, the locale, and the class loader to determine if the + * resource is a match to the requested one. The loader may be + * null, but the base name and the locale must have a non-null + * value. + */ + private static final class CacheKey implements Cloneable { + // These three are the actual keys for lookup in Map. + private String name; + private Locale locale; + + // bundle format which is necessary for calling + // Control.needsReload(). + private String format; + + // These time values are in CacheKey so that NONEXISTENT_BUNDLE + // doesn't need to be cloned for caching. + + // The time when the bundle has been loaded + private volatile long loadTime; + + // The time when the bundle expires in the cache, or either + // Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL. + private volatile long expirationTime; + + // Placeholder for an error report by a Throwable + private Throwable cause; + + // Hash code value cache to avoid recalculating the hash code + // of this instance. + private int hashCodeCache; + + CacheKey(String baseName, Locale locale) { + this.name = baseName; + this.locale = locale; + calculateHashCode(); + } + + String getName() { + return name; + } + + CacheKey setName(String baseName) { + if (!this.name.equals(baseName)) { + this.name = baseName; + calculateHashCode(); + } + return this; + } + + Locale getLocale() { + return locale; + } + + CacheKey setLocale(Locale locale) { + if (!this.locale.equals(locale)) { + this.locale = locale; + calculateHashCode(); + } + return this; + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + try { + final CacheKey otherEntry = (CacheKey)other; + //quick check to see if they are not equal + if (hashCodeCache != otherEntry.hashCodeCache) { + return false; + } + //are the names the same? + if (!name.equals(otherEntry.name)) { + return false; + } + // are the locales the same? + if (!locale.equals(otherEntry.locale)) { + return false; + } + return true; + } catch (NullPointerException e) { + } catch (ClassCastException e) { + } + return false; + } + + public int hashCode() { + return hashCodeCache; + } + + private void calculateHashCode() { + hashCodeCache = name.hashCode() << 3; + hashCodeCache ^= locale.hashCode(); + } + + public Object clone() { + try { + CacheKey clone = (CacheKey) super.clone(); + // Clear the reference to a Throwable + clone.cause = null; + return clone; + } catch (CloneNotSupportedException e) { + //this should never happen + throw new InternalError(); + } + } + + String getFormat() { + return format; + } + + void setFormat(String format) { + this.format = format; + } + + private void setCause(Throwable cause) { + if (this.cause == null) { + this.cause = cause; + } else { + // Override the cause if the previous one is + // ClassNotFoundException. + if (this.cause instanceof ClassNotFoundException) { + this.cause = cause; + } + } + } + + private Throwable getCause() { + return cause; + } + + public String toString() { + String l = locale.toString(); + if (l.length() == 0) { + if (locale.getVariant().length() != 0) { + l = "__" + locale.getVariant(); + } else { + l = "\"\""; + } + } + return "CacheKey[" + name + ", lc=" + l + + "(format=" + format + ")]"; + } + } + + /** + * The common interface to get a CacheKey in LoaderReference and + * BundleReference. + */ + private static interface CacheKeyReference { + public CacheKey getCacheKey(); + } + + /** + * References to bundles are soft references so that they can be garbage + * collected when they have no hard references. + */ + private static final class BundleReference extends SoftReference + implements CacheKeyReference { + private CacheKey cacheKey; + + BundleReference(ResourceBundle referent, ReferenceQueue q, CacheKey key) { + super(referent, q); + cacheKey = key; + } + + public CacheKey getCacheKey() { + return cacheKey; + } + } + + /** + * Gets a resource bundle using the specified base name, the default locale, + * and the caller's class loader. Calling this method is equivalent to calling + *
+ * getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader()), + *
+ * except that getClassLoader() is run with the security + * privileges of ResourceBundle. + * See {@link #getBundle(String, Locale, ClassLoader) getBundle} + * for a complete description of the search and instantiation strategy. + * + * @param baseName the base name of the resource bundle, a fully qualified class name + * @exception java.lang.NullPointerException + * if baseName is null + * @exception MissingResourceException + * if no resource bundle for the specified base name can be found + * @return a resource bundle for the given base name and the default locale + */ + public static final ResourceBundle getBundle(String baseName) + { + return getBundleImpl(baseName, Locale.getDefault(), + Control.INSTANCE); + } + + /** + * Returns a resource bundle using the specified base name, the + * default locale and the specified control. Calling this method + * is equivalent to calling + *
+     * getBundle(baseName, Locale.getDefault(),
+     *           this.getClass().getClassLoader(), control),
+     * 
+ * except that getClassLoader() is run with the security + * privileges of ResourceBundle. See {@link + * #getBundle(String, Locale, ClassLoader, Control) getBundle} for the + * complete description of the resource bundle loading process with a + * ResourceBundle.Control. + * + * @param baseName + * the base name of the resource bundle, a fully qualified class + * name + * @param control + * the control which gives information for the resource bundle + * loading process + * @return a resource bundle for the given base name and the default + * locale + * @exception NullPointerException + * if baseName or control is + * null + * @exception MissingResourceException + * if no resource bundle for the specified base name can be found + * @exception IllegalArgumentException + * if the given control doesn't perform properly + * (e.g., control.getCandidateLocales returns null.) + * Note that validation of control is performed as + * needed. + * @since 1.6 + */ + public static final ResourceBundle getBundle(String baseName, + Control control) { + return getBundleImpl(baseName, Locale.getDefault(), + /* must determine loader here, else we break stack invariant */ + control); + } + + /** + * Gets a resource bundle using the specified base name and locale, + * and the caller's class loader. Calling this method is equivalent to calling + *
+ * getBundle(baseName, locale, this.getClass().getClassLoader()), + *
+ * except that getClassLoader() is run with the security + * privileges of ResourceBundle. + * See {@link #getBundle(String, Locale, ClassLoader) getBundle} + * for a complete description of the search and instantiation strategy. + * + * @param baseName + * the base name of the resource bundle, a fully qualified class name + * @param locale + * the locale for which a resource bundle is desired + * @exception NullPointerException + * if baseName or locale is null + * @exception MissingResourceException + * if no resource bundle for the specified base name can be found + * @return a resource bundle for the given base name and locale + */ + public static final ResourceBundle getBundle(String baseName, + Locale locale) + { + return getBundleImpl(baseName, locale, + /* must determine loader here, else we break stack invariant */ + Control.INSTANCE); + } + + /** + * Returns a resource bundle using the specified base name, target + * locale and control, and the caller's class loader. Calling this + * method is equivalent to calling + *
+     * getBundle(baseName, targetLocale, this.getClass().getClassLoader(),
+     *           control),
+     * 
+ * except that getClassLoader() is run with the security + * privileges of ResourceBundle. See {@link + * #getBundle(String, Locale, ClassLoader, Control) getBundle} for the + * complete description of the resource bundle loading process with a + * ResourceBundle.Control. + * + * @param baseName + * the base name of the resource bundle, a fully qualified + * class name + * @param targetLocale + * the locale for which a resource bundle is desired + * @param control + * the control which gives information for the resource + * bundle loading process + * @return a resource bundle for the given base name and a + * Locale in locales + * @exception NullPointerException + * if baseName, locales or + * control is null + * @exception MissingResourceException + * if no resource bundle for the specified base name in any + * of the locales can be found. + * @exception IllegalArgumentException + * if the given control doesn't perform properly + * (e.g., control.getCandidateLocales returns null.) + * Note that validation of control is performed as + * needed. + * @since 1.6 + */ + public static final ResourceBundle getBundle(String baseName, Locale targetLocale, + Control control) { + return getBundleImpl(baseName, targetLocale, + /* must determine loader here, else we break stack invariant */ + control); + } + + /** + * Gets a resource bundle using the specified base name, locale, and class + * loader. + * + *

This method behaves the same as calling + * {@link #getBundle(String, Locale, ClassLoader, Control)} passing a + * default instance of {@link Control}. The following describes this behavior. + * + *

getBundle uses the base name, the specified locale, and + * the default locale (obtained from {@link java.util.Locale#getDefault() + * Locale.getDefault}) to generate a sequence of candidate bundle names. If the specified + * locale's language, script, country, and variant are all empty strings, + * then the base name is the only candidate bundle name. Otherwise, a list + * of candidate locales is generated from the attribute values of the + * specified locale (language, script, country and variant) and appended to + * the base name. Typically, this will look like the following: + * + *

+     *     baseName + "_" + language + "_" + script + "_" + country + "_" + variant
+     *     baseName + "_" + language + "_" + script + "_" + country
+     *     baseName + "_" + language + "_" + script
+     *     baseName + "_" + language + "_" + country + "_" + variant
+     *     baseName + "_" + language + "_" + country
+     *     baseName + "_" + language
+     * 
+ * + *

Candidate bundle names where the final component is an empty string + * are omitted, along with the underscore. For example, if country is an + * empty string, the second and the fifth candidate bundle names above + * would be omitted. Also, if script is an empty string, the candidate names + * including script are omitted. For example, a locale with language "de" + * and variant "JAVA" will produce candidate names with base name + * "MyResource" below. + * + *

+     *     MyResource_de__JAVA
+     *     MyResource_de
+     * 
+ * + * In the case that the variant contains one or more underscores ('_'), a + * sequence of bundle names generated by truncating the last underscore and + * the part following it is inserted after a candidate bundle name with the + * original variant. For example, for a locale with language "en", script + * "Latn, country "US" and variant "WINDOWS_VISTA", and bundle base name + * "MyResource", the list of candidate bundle names below is generated: + * + *
+     * MyResource_en_Latn_US_WINDOWS_VISTA
+     * MyResource_en_Latn_US_WINDOWS
+     * MyResource_en_Latn_US
+     * MyResource_en_Latn
+     * MyResource_en_US_WINDOWS_VISTA
+     * MyResource_en_US_WINDOWS
+     * MyResource_en_US
+     * MyResource_en
+     * 
+ * + *
Note: For some Locales, the list of + * candidate bundle names contains extra names, or the order of bundle names + * is slightly modified. See the description of the default implementation + * of {@link Control#getCandidateLocales(String, Locale) + * getCandidateLocales} for details.
+ * + *

getBundle then iterates over the candidate bundle names + * to find the first one for which it can instantiate an actual + * resource bundle. It uses the default controls' {@link Control#getFormats + * getFormats} method, which generates two bundle names for each generated + * name, the first a class name and the second a properties file name. For + * each candidate bundle name, it attempts to create a resource bundle: + * + *

  • First, it attempts to load a class using the generated class name. + * If such a class can be found and loaded using the specified class + * loader, is assignment compatible with ResourceBundle, is accessible from + * ResourceBundle, and can be instantiated, getBundle creates a + * new instance of this class and uses it as the result resource + * bundle. + * + *
  • Otherwise, getBundle attempts to locate a property + * resource file using the generated properties file name. It generates a + * path name from the candidate bundle name by replacing all "." characters + * with "/" and appending the string ".properties". It attempts to find a + * "resource" with this name using {@link + * java.lang.ClassLoader#getResource(java.lang.String) + * ClassLoader.getResource}. (Note that a "resource" in the sense of + * getResource has nothing to do with the contents of a + * resource bundle, it is just a container of data, such as a file.) If it + * finds a "resource", it attempts to create a new {@link + * PropertyResourceBundle} instance from its contents. If successful, this + * instance becomes the result resource bundle.
+ * + *

This continues until a result resource bundle is instantiated or the + * list of candidate bundle names is exhausted. If no matching resource + * bundle is found, the default control's {@link Control#getFallbackLocale + * getFallbackLocale} method is called, which returns the current default + * locale. A new sequence of candidate locale names is generated using this + * locale and and searched again, as above. + * + *

If still no result bundle is found, the base name alone is looked up. If + * this still fails, a MissingResourceException is thrown. + * + *

Once a result resource bundle has been found, + * its parent chain is instantiated. If the result bundle already + * has a parent (perhaps because it was returned from a cache) the chain is + * complete. + * + *

Otherwise, getBundle examines the remainder of the + * candidate locale list that was used during the pass that generated the + * result resource bundle. (As before, candidate bundle names where the + * final component is an empty string are omitted.) When it comes to the + * end of the candidate list, it tries the plain bundle name. With each of the + * candidate bundle names it attempts to instantiate a resource bundle (first + * looking for a class and then a properties file, as described above). + * + *

Whenever it succeeds, it calls the previously instantiated resource + * bundle's {@link #setParent(java.util.ResourceBundle) setParent} method + * with the new resource bundle. This continues until the list of names + * is exhausted or the current bundle already has a non-null parent. + * + *

Once the parent chain is complete, the bundle is returned. + * + *

Note: getBundle caches instantiated resource + * bundles and might return the same resource bundle instance multiple times. + * + *

Note:The baseName argument should be a fully + * qualified class name. However, for compatibility with earlier versions, + * Sun's Java SE Runtime Environments do not verify this, and so it is + * possible to access PropertyResourceBundles by specifying a + * path name (using "/") instead of a fully qualified class name (using + * "."). + * + *

+ * Example: + *

+ * The following class and property files are provided: + *

+     *     MyResources.class
+     *     MyResources.properties
+     *     MyResources_fr.properties
+     *     MyResources_fr_CH.class
+     *     MyResources_fr_CH.properties
+     *     MyResources_en.properties
+     *     MyResources_es_ES.class
+     * 
+ * + * The contents of all files are valid (that is, public non-abstract + * subclasses of ResourceBundle for the ".class" files, + * syntactically correct ".properties" files). The default locale is + * Locale("en", "GB"). + * + *

Calling getBundle with the locale arguments below will + * instantiate resource bundles as follows: + * + * + * + * + * + * + * + *
Locale("fr", "CH")MyResources_fr_CH.class, parent MyResources_fr.properties, parent MyResources.class
Locale("fr", "FR")MyResources_fr.properties, parent MyResources.class
Locale("de", "DE")MyResources_en.properties, parent MyResources.class
Locale("en", "US")MyResources_en.properties, parent MyResources.class
Locale("es", "ES")MyResources_es_ES.class, parent MyResources.class
+ * + *

The file MyResources_fr_CH.properties is never used because it is + * hidden by the MyResources_fr_CH.class. Likewise, MyResources.properties + * is also hidden by MyResources.class. + * + * @param baseName the base name of the resource bundle, a fully qualified class name + * @param locale the locale for which a resource bundle is desired + * @param loader the class loader from which to load the resource bundle + * @return a resource bundle for the given base name and locale + * @exception java.lang.NullPointerException + * if baseName, locale, or loader is null + * @exception MissingResourceException + * if no resource bundle for the specified base name can be found + * @since 1.2 + */ + public static ResourceBundle getBundle(String baseName, Locale locale, + ClassLoader loader) + { + if (loader == null) { + throw new NullPointerException(); + } + return getBundleImpl(baseName, locale, Control.INSTANCE); + } + + /** + * Returns a resource bundle using the specified base name, target + * locale, class loader and control. Unlike the {@linkplain + * #getBundle(String, Locale, ClassLoader) getBundle + * factory methods with no control argument}, the given + * control specifies how to locate and instantiate resource + * bundles. Conceptually, the bundle loading process with the given + * control is performed in the following steps. + * + *

+ *

    + *
  1. This factory method looks up the resource bundle in the cache for + * the specified baseName, targetLocale and + * loader. If the requested resource bundle instance is + * found in the cache and the time-to-live periods of the instance and + * all of its parent instances have not expired, the instance is returned + * to the caller. Otherwise, this factory method proceeds with the + * loading process below.
  2. + * + *
  3. The {@link ResourceBundle.Control#getFormats(String) + * control.getFormats} method is called to get resource bundle formats + * to produce bundle or resource names. The strings + * "java.class" and "java.properties" + * designate class-based and {@linkplain PropertyResourceBundle + * property}-based resource bundles, respectively. Other strings + * starting with "java." are reserved for future extensions + * and must not be used for application-defined formats. Other strings + * designate application-defined formats.
  4. + * + *
  5. The {@link ResourceBundle.Control#getCandidateLocales(String, + * Locale) control.getCandidateLocales} method is called with the target + * locale to get a list of candidate Locales for + * which resource bundles are searched.
  6. + * + *
  7. The {@link ResourceBundle.Control#newBundle(String, Locale, + * String, ClassLoader, boolean) control.newBundle} method is called to + * instantiate a ResourceBundle for the base bundle name, a + * candidate locale, and a format. (Refer to the note on the cache + * lookup below.) This step is iterated over all combinations of the + * candidate locales and formats until the newBundle method + * returns a ResourceBundle instance or the iteration has + * used up all the combinations. For example, if the candidate locales + * are Locale("de", "DE"), Locale("de") and + * Locale("") and the formats are "java.class" + * and "java.properties", then the following is the + * sequence of locale-format combinations to be used to call + * control.newBundle. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Locale
    + *
    format
    + *
    Locale("de", "DE")
    + *
    java.class
    + *
    Locale("de", "DE")java.properties
    + *
    Locale("de")java.class
    Locale("de")java.properties
    Locale("")
    + *
    java.class
    Locale("")java.properties
    + *
  8. + * + *
  9. If the previous step has found no resource bundle, proceed to + * Step 6. If a bundle has been found that is a base bundle (a bundle + * for Locale("")), and the candidate locale list only contained + * Locale(""), return the bundle to the caller. If a bundle + * has been found that is a base bundle, but the candidate locale list + * contained locales other than Locale(""), put the bundle on hold and + * proceed to Step 6. If a bundle has been found that is not a base + * bundle, proceed to Step 7.
  10. + * + *
  11. The {@link ResourceBundle.Control#getFallbackLocale(String, + * Locale) control.getFallbackLocale} method is called to get a fallback + * locale (alternative to the current target locale) to try further + * finding a resource bundle. If the method returns a non-null locale, + * it becomes the next target locale and the loading process starts over + * from Step 3. Otherwise, if a base bundle was found and put on hold in + * a previous Step 5, it is returned to the caller now. Otherwise, a + * MissingResourceException is thrown.
  12. + * + *
  13. At this point, we have found a resource bundle that's not the + * base bundle. If this bundle set its parent during its instantiation, + * it is returned to the caller. Otherwise, its parent chain is + * instantiated based on the list of candidate locales from which it was + * found. Finally, the bundle is returned to the caller.
  14. + *
+ * + *

During the resource bundle loading process above, this factory + * method looks up the cache before calling the {@link + * Control#newBundle(String, Locale, String, ClassLoader, boolean) + * control.newBundle} method. If the time-to-live period of the + * resource bundle found in the cache has expired, the factory method + * calls the {@link ResourceBundle.Control#needsReload(String, Locale, + * String, ClassLoader, ResourceBundle, long) control.needsReload} + * method to determine whether the resource bundle needs to be reloaded. + * If reloading is required, the factory method calls + * control.newBundle to reload the resource bundle. If + * control.newBundle returns null, the factory + * method puts a dummy resource bundle in the cache as a mark of + * nonexistent resource bundles in order to avoid lookup overhead for + * subsequent requests. Such dummy resource bundles are under the same + * expiration control as specified by control. + * + *

All resource bundles loaded are cached by default. Refer to + * {@link Control#getTimeToLive(String,Locale) + * control.getTimeToLive} for details. + * + *

The following is an example of the bundle loading process with the + * default ResourceBundle.Control implementation. + * + *

Conditions: + *

    + *
  • Base bundle name: foo.bar.Messages + *
  • Requested Locale: {@link Locale#ITALY}
  • + *
  • Default Locale: {@link Locale#FRENCH}
  • + *
  • Available resource bundles: + * foo/bar/Messages_fr.properties and + * foo/bar/Messages.properties
  • + *
+ * + *

First, getBundle tries loading a resource bundle in + * the following sequence. + * + *

    + *
  • class foo.bar.Messages_it_IT + *
  • file foo/bar/Messages_it_IT.properties + *
  • class foo.bar.Messages_it
  • + *
  • file foo/bar/Messages_it.properties
  • + *
  • class foo.bar.Messages
  • + *
  • file foo/bar/Messages.properties
  • + *
+ * + *

At this point, getBundle finds + * foo/bar/Messages.properties, which is put on hold + * because it's the base bundle. getBundle calls {@link + * Control#getFallbackLocale(String, Locale) + * control.getFallbackLocale("foo.bar.Messages", Locale.ITALY)} which + * returns Locale.FRENCH. Next, getBundle + * tries loading a bundle in the following sequence. + * + *

    + *
  • class foo.bar.Messages_fr
  • + *
  • file foo/bar/Messages_fr.properties
  • + *
  • class foo.bar.Messages
  • + *
  • file foo/bar/Messages.properties
  • + *
+ * + *

getBundle finds + * foo/bar/Messages_fr.properties and creates a + * ResourceBundle instance. Then, getBundle + * sets up its parent chain from the list of the candiate locales. Only + * foo/bar/Messages.properties is found in the list and + * getBundle creates a ResourceBundle instance + * that becomes the parent of the instance for + * foo/bar/Messages_fr.properties. + * + * @param baseName + * the base name of the resource bundle, a fully qualified + * class name + * @param targetLocale + * the locale for which a resource bundle is desired + * @param loader + * the class loader from which to load the resource bundle + * @param control + * the control which gives information for the resource + * bundle loading process + * @return a resource bundle for the given base name and locale + * @exception NullPointerException + * if baseName, targetLocale, + * loader, or control is + * null + * @exception MissingResourceException + * if no resource bundle for the specified base name can be found + * @exception IllegalArgumentException + * if the given control doesn't perform properly + * (e.g., control.getCandidateLocales returns null.) + * Note that validation of control is performed as + * needed. + * @since 1.6 + */ + public static ResourceBundle getBundle(String baseName, Locale targetLocale, + ClassLoader loader, Control control) { + if (loader == null || control == null) { + throw new NullPointerException(); + } + return getBundleImpl(baseName, targetLocale, control); + } + + private static ResourceBundle getBundleImpl(String baseName, Locale locale, + Control control) { + if (locale == null || control == null) { + throw new NullPointerException(); + } + + // We create a CacheKey here for use by this call. The base + // name and loader will never change during the bundle loading + // process. We have to make sure that the locale is set before + // using it as a cache key. + CacheKey cacheKey = new CacheKey(baseName, locale); + ResourceBundle bundle = null; + + // Quick lookup of the cache. + BundleReference bundleRef = cacheList.get(cacheKey); + if (bundleRef != null) { + bundle = bundleRef.get(); + bundleRef = null; + } + + // If this bundle and all of its parents are valid (not expired), + // then return this bundle. If any of the bundles is expired, we + // don't call control.needsReload here but instead drop into the + // complete loading process below. + if (isValidBundle(bundle) && hasValidParentChain(bundle)) { + return bundle; + } + + // No valid bundle was found in the cache, so we need to load the + // resource bundle and its parents. + + boolean isKnownControl = (control == Control.INSTANCE) || + (control instanceof SingleFormatControl); + List formats = control.getFormats(baseName); + if (!isKnownControl && !checkList(formats)) { + throw new IllegalArgumentException("Invalid Control: getFormats"); + } + + ResourceBundle baseBundle = null; + for (Locale targetLocale = locale; + targetLocale != null; + targetLocale = control.getFallbackLocale(baseName, targetLocale)) { + List candidateLocales = control.getCandidateLocales(baseName, targetLocale); + if (!isKnownControl && !checkList(candidateLocales)) { + throw new IllegalArgumentException("Invalid Control: getCandidateLocales"); + } + + bundle = findBundle(cacheKey, candidateLocales, formats, 0, control, baseBundle); + + // If the loaded bundle is the base bundle and exactly for the + // requested locale or the only candidate locale, then take the + // bundle as the resulting one. If the loaded bundle is the base + // bundle, it's put on hold until we finish processing all + // fallback locales. + if (isValidBundle(bundle)) { + boolean isBaseBundle = Locale.ROOT.equals(bundle.locale); + if (!isBaseBundle || bundle.locale.equals(locale) + || (candidateLocales.size() == 1 + && bundle.locale.equals(candidateLocales.get(0)))) { + break; + } + + // If the base bundle has been loaded, keep the reference in + // baseBundle so that we can avoid any redundant loading in case + // the control specify not to cache bundles. + if (isBaseBundle && baseBundle == null) { + baseBundle = bundle; + } + } + } + + if (bundle == null) { + if (baseBundle == null) { + throwMissingResourceException(baseName, locale, cacheKey.getCause()); + } + bundle = baseBundle; + } + + return bundle; + } + + /** + * Checks if the given List is not null, not empty, + * not having null in its elements. + */ + private static final boolean checkList(List a) { + boolean valid = (a != null && a.size() != 0); + if (valid) { + int size = a.size(); + for (int i = 0; valid && i < size; i++) { + valid = (a.get(i) != null); + } + } + return valid; + } + + private static final ResourceBundle findBundle(CacheKey cacheKey, + List candidateLocales, + List formats, + int index, + Control control, + ResourceBundle baseBundle) { + Locale targetLocale = candidateLocales.get(index); + ResourceBundle parent = null; + if (index != candidateLocales.size() - 1) { + parent = findBundle(cacheKey, candidateLocales, formats, index + 1, + control, baseBundle); + } else if (baseBundle != null && Locale.ROOT.equals(targetLocale)) { + return baseBundle; + } + + // Before we do the real loading work, see whether we need to + // do some housekeeping: If references to class loaders or + // resource bundles have been nulled out, remove all related + // information from the cache. + Object ref; + while ((ref = referenceQueue.poll()) != null) { + cacheList.remove(((CacheKeyReference)ref).getCacheKey()); + } + + // flag indicating the resource bundle has expired in the cache + boolean expiredBundle = false; + + // First, look up the cache to see if it's in the cache, without + // attempting to load bundle. + cacheKey.setLocale(targetLocale); + ResourceBundle bundle = findBundleInCache(cacheKey, control); + if (isValidBundle(bundle)) { + expiredBundle = bundle.expired; + if (!expiredBundle) { + // If its parent is the one asked for by the candidate + // locales (the runtime lookup path), we can take the cached + // one. (If it's not identical, then we'd have to check the + // parent's parents to be consistent with what's been + // requested.) + if (bundle.parent == parent) { + return bundle; + } + // Otherwise, remove the cached one since we can't keep + // the same bundles having different parents. + BundleReference bundleRef = cacheList.get(cacheKey); + if (bundleRef != null && bundleRef.get() == bundle) { + cacheList.remove(cacheKey); + } + } + } + + if (bundle != NONEXISTENT_BUNDLE) { + CacheKey constKey = (CacheKey) cacheKey.clone(); + + try { + bundle = loadBundle(cacheKey, formats, control, expiredBundle); + if (bundle != null) { + if (bundle.parent == null) { + bundle.setParent(parent); + } + bundle.locale = targetLocale; + bundle = putBundleInCache(cacheKey, bundle, control); + return bundle; + } + + // Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle + // instance for the locale. + putBundleInCache(cacheKey, NONEXISTENT_BUNDLE, control); + } finally { + if (constKey.getCause() instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + } + } + return parent; + } + + private static final ResourceBundle loadBundle(CacheKey cacheKey, + List formats, + Control control, + boolean reload) { + + // Here we actually load the bundle in the order of formats + // specified by the getFormats() value. + Locale targetLocale = cacheKey.getLocale(); + + ResourceBundle bundle = null; + int size = formats.size(); + for (int i = 0; i < size; i++) { + String format = formats.get(i); + try { + bundle = control.newBundle(cacheKey.getName(), targetLocale, format, + null, reload); + } catch (LinkageError error) { + // We need to handle the LinkageError case due to + // inconsistent case-sensitivity in ClassLoader. + // See 6572242 for details. + cacheKey.setCause(error); + } catch (Exception cause) { + cacheKey.setCause(cause); + } + if (bundle != null) { + // Set the format in the cache key so that it can be + // used when calling needsReload later. + cacheKey.setFormat(format); + bundle.name = cacheKey.getName(); + bundle.locale = targetLocale; + // Bundle provider might reuse instances. So we should make + // sure to clear the expired flag here. + bundle.expired = false; + break; + } + } + + return bundle; + } + + private static final boolean isValidBundle(ResourceBundle bundle) { + return bundle != null && bundle != NONEXISTENT_BUNDLE; + } + + /** + * Determines whether any of resource bundles in the parent chain, + * including the leaf, have expired. + */ + private static final boolean hasValidParentChain(ResourceBundle bundle) { + long now = System.currentTimeMillis(); + while (bundle != null) { + if (bundle.expired) { + return false; + } + CacheKey key = bundle.cacheKey; + if (key != null) { + long expirationTime = key.expirationTime; + if (expirationTime >= 0 && expirationTime <= now) { + return false; + } + } + bundle = bundle.parent; + } + return true; + } + + /** + * Throw a MissingResourceException with proper message + */ + private static final void throwMissingResourceException(String baseName, + Locale locale, + Throwable cause) { + // If the cause is a MissingResourceException, avoid creating + // a long chain. (6355009) + if (cause instanceof MissingResourceException) { + cause = null; + } + throw new MissingResourceException("Can't find bundle for base name " + + baseName + ", locale " + locale, + baseName + "_" + locale, // className + "", // key + cause); + } + + /** + * Finds a bundle in the cache. Any expired bundles are marked as + * `expired' and removed from the cache upon return. + * + * @param cacheKey the key to look up the cache + * @param control the Control to be used for the expiration control + * @return the cached bundle, or null if the bundle is not found in the + * cache or its parent has expired. bundle.expire is true + * upon return if the bundle in the cache has expired. + */ + private static final ResourceBundle findBundleInCache(CacheKey cacheKey, + Control control) { + BundleReference bundleRef = cacheList.get(cacheKey); + if (bundleRef == null) { + return null; + } + ResourceBundle bundle = bundleRef.get(); + if (bundle == null) { + return null; + } + ResourceBundle p = bundle.parent; + assert p != NONEXISTENT_BUNDLE; + // If the parent has expired, then this one must also expire. We + // check only the immediate parent because the actual loading is + // done from the root (base) to leaf (child) and the purpose of + // checking is to propagate expiration towards the leaf. For + // example, if the requested locale is ja_JP_JP and there are + // bundles for all of the candidates in the cache, we have a list, + // + // base <- ja <- ja_JP <- ja_JP_JP + // + // If ja has expired, then it will reload ja and the list becomes a + // tree. + // + // base <- ja (new) + // " <- ja (expired) <- ja_JP <- ja_JP_JP + // + // When looking up ja_JP in the cache, it finds ja_JP in the cache + // which references to the expired ja. Then, ja_JP is marked as + // expired and removed from the cache. This will be propagated to + // ja_JP_JP. + // + // Now, it's possible, for example, that while loading new ja_JP, + // someone else has started loading the same bundle and finds the + // base bundle has expired. Then, what we get from the first + // getBundle call includes the expired base bundle. However, if + // someone else didn't start its loading, we wouldn't know if the + // base bundle has expired at the end of the loading process. The + // expiration control doesn't guarantee that the returned bundle and + // its parents haven't expired. + // + // We could check the entire parent chain to see if there's any in + // the chain that has expired. But this process may never end. An + // extreme case would be that getTimeToLive returns 0 and + // needsReload always returns true. + if (p != null && p.expired) { + assert bundle != NONEXISTENT_BUNDLE; + bundle.expired = true; + bundle.cacheKey = null; + cacheList.remove(cacheKey); + bundle = null; + } else { + CacheKey key = bundleRef.getCacheKey(); + long expirationTime = key.expirationTime; + if (!bundle.expired && expirationTime >= 0 && + expirationTime <= System.currentTimeMillis()) { + // its TTL period has expired. + if (bundle != NONEXISTENT_BUNDLE) { + // Synchronize here to call needsReload to avoid + // redundant concurrent calls for the same bundle. + synchronized (bundle) { + expirationTime = key.expirationTime; + if (!bundle.expired && expirationTime >= 0 && + expirationTime <= System.currentTimeMillis()) { + try { + bundle.expired = control.needsReload(key.getName(), + key.getLocale(), + key.getFormat(), + null, + bundle, + key.loadTime); + } catch (Exception e) { + cacheKey.setCause(e); + } + if (bundle.expired) { + // If the bundle needs to be reloaded, then + // remove the bundle from the cache, but + // return the bundle with the expired flag + // on. + bundle.cacheKey = null; + cacheList.remove(cacheKey); + } else { + // Update the expiration control info. and reuse + // the same bundle instance + setExpirationTime(key, control); + } + } + } + } else { + // We just remove NONEXISTENT_BUNDLE from the cache. + cacheList.remove(cacheKey); + bundle = null; + } + } + } + return bundle; + } + + /** + * Put a new bundle in the cache. + * + * @param cacheKey the key for the resource bundle + * @param bundle the resource bundle to be put in the cache + * @return the ResourceBundle for the cacheKey; if someone has put + * the bundle before this call, the one found in the cache is + * returned. + */ + private static final ResourceBundle putBundleInCache(CacheKey cacheKey, + ResourceBundle bundle, + Control control) { + setExpirationTime(cacheKey, control); + if (cacheKey.expirationTime != Control.TTL_DONT_CACHE) { + CacheKey key = (CacheKey) cacheKey.clone(); + BundleReference bundleRef = new BundleReference(bundle, referenceQueue, key); + bundle.cacheKey = key; + + // Put the bundle in the cache if it's not been in the cache. + BundleReference result = cacheList.put(key, bundleRef); + + // If someone else has put the same bundle in the cache before + // us and it has not expired, we should use the one in the cache. + if (result != null) { + ResourceBundle rb = result.get(); + if (rb != null && !rb.expired) { + // Clear the back link to the cache key + bundle.cacheKey = null; + bundle = rb; + // Clear the reference in the BundleReference so that + // it won't be enqueued. + bundleRef.clear(); + } else { + // Replace the invalid (garbage collected or expired) + // instance with the valid one. + cacheList.put(key, bundleRef); + } + } + } + return bundle; + } + + private static final void setExpirationTime(CacheKey cacheKey, Control control) { + long ttl = control.getTimeToLive(cacheKey.getName(), + cacheKey.getLocale()); + if (ttl >= 0) { + // If any expiration time is specified, set the time to be + // expired in the cache. + long now = System.currentTimeMillis(); + cacheKey.loadTime = now; + cacheKey.expirationTime = now + ttl; + } else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) { + cacheKey.expirationTime = ttl; + } else { + throw new IllegalArgumentException("Invalid Control: TTL=" + ttl); + } + } + + /** + * Removes all resource bundles from the cache that have been loaded + * using the caller's class loader. + * + * @since 1.6 + * @see ResourceBundle.Control#getTimeToLive(String,Locale) + */ + public static final void clearCache() { + clearCache(null); + } + + /** + * Removes all resource bundles from the cache that have been loaded + * using the given class loader. + * + * @param loader the class loader + * @exception NullPointerException if loader is null + * @since 1.6 + * @see ResourceBundle.Control#getTimeToLive(String,Locale) + */ + public static final void clearCache(ClassLoader loader) { + if (loader == null) { + throw new NullPointerException(); + } + Set set = cacheList.keySet(); + for (CacheKey key : set) { + set.remove(key); + } + } + + /** + * Gets an object for the given key from this resource bundle. + * Returns null if this resource bundle does not contain an + * object for the given key. + * + * @param key the key for the desired object + * @exception NullPointerException if key is null + * @return the object for the given key, or null + */ + protected abstract Object handleGetObject(String key); + + /** + * Returns an enumeration of the keys. + * + * @return an Enumeration of the keys contained in + * this ResourceBundle and its parent bundles. + */ + public abstract Enumeration getKeys(); + + /** + * Determines whether the given key is contained in + * this ResourceBundle or its parent bundles. + * + * @param key + * the resource key + * @return true if the given key is + * contained in this ResourceBundle or its + * parent bundles; false otherwise. + * @exception NullPointerException + * if key is null + * @since 1.6 + */ + public boolean containsKey(String key) { + if (key == null) { + throw new NullPointerException(); + } + for (ResourceBundle rb = this; rb != null; rb = rb.parent) { + if (rb.handleKeySet().contains(key)) { + return true; + } + } + return false; + } + + /** + * Returns a Set of all keys contained in this + * ResourceBundle and its parent bundles. + * + * @return a Set of all keys contained in this + * ResourceBundle and its parent bundles. + * @since 1.6 + */ + public Set keySet() { + Set keys = new HashSet<>(); + for (ResourceBundle rb = this; rb != null; rb = rb.parent) { + keys.addAll(rb.handleKeySet()); + } + return keys; + } + + /** + * Returns a Set of the keys contained only + * in this ResourceBundle. + * + *

The default implementation returns a Set of the + * keys returned by the {@link #getKeys() getKeys} method except + * for the ones for which the {@link #handleGetObject(String) + * handleGetObject} method returns null. Once the + * Set has been created, the value is kept in this + * ResourceBundle in order to avoid producing the + * same Set in subsequent calls. Subclasses can + * override this method for faster handling. + * + * @return a Set of the keys contained only in this + * ResourceBundle + * @since 1.6 + */ + protected Set handleKeySet() { + if (keySet == null) { + synchronized (this) { + if (keySet == null) { + Set keys = new HashSet<>(); + Enumeration enumKeys = getKeys(); + while (enumKeys.hasMoreElements()) { + String key = enumKeys.nextElement(); + if (handleGetObject(key) != null) { + keys.add(key); + } + } + keySet = keys; + } + } + } + return keySet; + } + + + + /** + * ResourceBundle.Control defines a set of callback methods + * that are invoked by the {@link ResourceBundle#getBundle(String, + * Locale, ClassLoader, Control) ResourceBundle.getBundle} factory + * methods during the bundle loading process. In other words, a + * ResourceBundle.Control collaborates with the factory + * methods for loading resource bundles. The default implementation of + * the callback methods provides the information necessary for the + * factory methods to perform the default behavior. + * + *

In addition to the callback methods, the {@link + * #toBundleName(String, Locale) toBundleName} and {@link + * #toResourceName(String, String) toResourceName} methods are defined + * primarily for convenience in implementing the callback + * methods. However, the toBundleName method could be + * overridden to provide different conventions in the organization and + * packaging of localized resources. The toResourceName + * method is final to avoid use of wrong resource and class + * name separators. + * + *

Two factory methods, {@link #getControl(List)} and {@link + * #getNoFallbackControl(List)}, provide + * ResourceBundle.Control instances that implement common + * variations of the default bundle loading process. + * + *

The formats returned by the {@link Control#getFormats(String) + * getFormats} method and candidate locales returned by the {@link + * ResourceBundle.Control#getCandidateLocales(String, Locale) + * getCandidateLocales} method must be consistent in all + * ResourceBundle.getBundle invocations for the same base + * bundle. Otherwise, the ResourceBundle.getBundle methods + * may return unintended bundles. For example, if only + * "java.class" is returned by the getFormats + * method for the first call to ResourceBundle.getBundle + * and only "java.properties" for the second call, then the + * second call will return the class-based one that has been cached + * during the first call. + * + *

A ResourceBundle.Control instance must be thread-safe + * if it's simultaneously used by multiple threads. + * ResourceBundle.getBundle does not synchronize to call + * the ResourceBundle.Control methods. The default + * implementations of the methods are thread-safe. + * + *

Applications can specify ResourceBundle.Control + * instances returned by the getControl factory methods or + * created from a subclass of ResourceBundle.Control to + * customize the bundle loading process. The following are examples of + * changing the default bundle loading process. + * + *

Example 1 + * + *

The following code lets ResourceBundle.getBundle look + * up only properties-based resources. + * + *

+     * import java.util.*;
+     * import static java.util.ResourceBundle.Control.*;
+     * ...
+     * ResourceBundle bundle =
+     *   ResourceBundle.getBundle("MyResources", new Locale("fr", "CH"),
+     *                            ResourceBundle.Control.getControl(FORMAT_PROPERTIES));
+     * 
+ * + * Given the resource bundles in the example in + * the ResourceBundle.getBundle description, this + * ResourceBundle.getBundle call loads + * MyResources_fr_CH.properties whose parent is + * MyResources_fr.properties whose parent is + * MyResources.properties. (MyResources_fr_CH.properties + * is not hidden, but MyResources_fr_CH.class is.) + * + *

Example 2 + * + *

The following is an example of loading XML-based bundles + * using {@link Properties#loadFromXML(java.io.InputStream) + * Properties.loadFromXML}. + * + *

+     * ResourceBundle rb = ResourceBundle.getBundle("Messages",
+     *     new ResourceBundle.Control() {
+     *         public List<String> getFormats(String baseName) {
+     *             if (baseName == null)
+     *                 throw new NullPointerException();
+     *             return Arrays.asList("xml");
+     *         }
+     *         public ResourceBundle newBundle(String baseName,
+     *                                         Locale locale,
+     *                                         String format,
+     *                                         ClassLoader loader,
+     *                                         boolean reload)
+     *                          throws IllegalAccessException,
+     *                                 InstantiationException,
+     *                                 IOException {
+     *             if (baseName == null || locale == null
+     *                   || format == null || loader == null)
+     *                 throw new NullPointerException();
+     *             ResourceBundle bundle = null;
+     *             if (format.equals("xml")) {
+     *                 String bundleName = toBundleName(baseName, locale);
+     *                 String resourceName = toResourceName(bundleName, format);
+     *                 InputStream stream = null;
+     *                 if (reload) {
+     *                     URL url = loader.getResource(resourceName);
+     *                     if (url != null) {
+     *                         URLConnection connection = url.openConnection();
+     *                         if (connection != null) {
+     *                             // Disable caches to get fresh data for
+     *                             // reloading.
+     *                             connection.setUseCaches(false);
+     *                             stream = connection.getInputStream();
+     *                         }
+     *                     }
+     *                 } else {
+     *                     stream = loader.getResourceAsStream(resourceName);
+     *                 }
+     *                 if (stream != null) {
+     *                     BufferedInputStream bis = new BufferedInputStream(stream);
+     *                     bundle = new XMLResourceBundle(bis);
+     *                     bis.close();
+     *                 }
+     *             }
+     *             return bundle;
+     *         }
+     *     });
+     *
+     * ...
+     *
+     * private static class XMLResourceBundle extends ResourceBundle {
+     *     private Properties props;
+     *     XMLResourceBundle(InputStream stream) throws IOException {
+     *         props = new Properties();
+     *         props.loadFromXML(stream);
+     *     }
+     *     protected Object handleGetObject(String key) {
+     *         return props.getProperty(key);
+     *     }
+     *     public Enumeration<String> getKeys() {
+     *         ...
+     *     }
+     * }
+     * 
+ * + * @since 1.6 + */ + public static class Control { + /** + * The default format List, which contains the strings + * "java.class" and "java.properties", in + * this order. This List is {@linkplain + * Collections#unmodifiableList(List) unmodifiable}. + * + * @see #getFormats(String) + */ + public static final List FORMAT_DEFAULT + = Collections.unmodifiableList(Arrays.asList("java.class", + "java.properties")); + + /** + * The class-only format List containing + * "java.class". This List is {@linkplain + * Collections#unmodifiableList(List) unmodifiable}. + * + * @see #getFormats(String) + */ + public static final List FORMAT_CLASS + = Collections.unmodifiableList(Arrays.asList("java.class")); + + /** + * The properties-only format List containing + * "java.properties". This List is + * {@linkplain Collections#unmodifiableList(List) unmodifiable}. + * + * @see #getFormats(String) + */ + public static final List FORMAT_PROPERTIES + = Collections.unmodifiableList(Arrays.asList("java.properties")); + + /** + * The time-to-live constant for not caching loaded resource bundle + * instances. + * + * @see #getTimeToLive(String, Locale) + */ + public static final long TTL_DONT_CACHE = -1; + + /** + * The time-to-live constant for disabling the expiration control + * for loaded resource bundle instances in the cache. + * + * @see #getTimeToLive(String, Locale) + */ + public static final long TTL_NO_EXPIRATION_CONTROL = -2; + + private static final Control INSTANCE = new Control(); + + /** + * Sole constructor. (For invocation by subclass constructors, + * typically implicit.) + */ + protected Control() { + } + + /** + * Returns a ResourceBundle.Control in which the {@link + * #getFormats(String) getFormats} method returns the specified + * formats. The formats must be equal to + * one of {@link Control#FORMAT_PROPERTIES}, {@link + * Control#FORMAT_CLASS} or {@link + * Control#FORMAT_DEFAULT}. ResourceBundle.Control + * instances returned by this method are singletons and thread-safe. + * + *

Specifying {@link Control#FORMAT_DEFAULT} is equivalent to + * instantiating the ResourceBundle.Control class, + * except that this method returns a singleton. + * + * @param formats + * the formats to be returned by the + * ResourceBundle.Control.getFormats method + * @return a ResourceBundle.Control supporting the + * specified formats + * @exception NullPointerException + * if formats is null + * @exception IllegalArgumentException + * if formats is unknown + */ + public static final Control getControl(List formats) { + if (formats.equals(Control.FORMAT_PROPERTIES)) { + return SingleFormatControl.PROPERTIES_ONLY; + } + if (formats.equals(Control.FORMAT_CLASS)) { + return SingleFormatControl.CLASS_ONLY; + } + if (formats.equals(Control.FORMAT_DEFAULT)) { + return Control.INSTANCE; + } + throw new IllegalArgumentException(); + } + + /** + * Returns a ResourceBundle.Control in which the {@link + * #getFormats(String) getFormats} method returns the specified + * formats and the {@link + * Control#getFallbackLocale(String, Locale) getFallbackLocale} + * method returns null. The formats must + * be equal to one of {@link Control#FORMAT_PROPERTIES}, {@link + * Control#FORMAT_CLASS} or {@link Control#FORMAT_DEFAULT}. + * ResourceBundle.Control instances returned by this + * method are singletons and thread-safe. + * + * @param formats + * the formats to be returned by the + * ResourceBundle.Control.getFormats method + * @return a ResourceBundle.Control supporting the + * specified formats with no fallback + * Locale support + * @exception NullPointerException + * if formats is null + * @exception IllegalArgumentException + * if formats is unknown + */ + public static final Control getNoFallbackControl(List formats) { + if (formats.equals(Control.FORMAT_DEFAULT)) { + return NoFallbackControl.NO_FALLBACK; + } + if (formats.equals(Control.FORMAT_PROPERTIES)) { + return NoFallbackControl.PROPERTIES_ONLY_NO_FALLBACK; + } + if (formats.equals(Control.FORMAT_CLASS)) { + return NoFallbackControl.CLASS_ONLY_NO_FALLBACK; + } + throw new IllegalArgumentException(); + } + + /** + * Returns a List of Strings containing + * formats to be used to load resource bundles for the given + * baseName. The ResourceBundle.getBundle + * factory method tries to load resource bundles with formats in the + * order specified by the list. The list returned by this method + * must have at least one String. The predefined + * formats are "java.class" for class-based resource + * bundles and "java.properties" for {@linkplain + * PropertyResourceBundle properties-based} ones. Strings starting + * with "java." are reserved for future extensions and + * must not be used by application-defined formats. + * + *

It is not a requirement to return an immutable (unmodifiable) + * List. However, the returned List must + * not be mutated after it has been returned by + * getFormats. + * + *

The default implementation returns {@link #FORMAT_DEFAULT} so + * that the ResourceBundle.getBundle factory method + * looks up first class-based resource bundles, then + * properties-based ones. + * + * @param baseName + * the base name of the resource bundle, a fully qualified class + * name + * @return a List of Strings containing + * formats for loading resource bundles. + * @exception NullPointerException + * if baseName is null + * @see #FORMAT_DEFAULT + * @see #FORMAT_CLASS + * @see #FORMAT_PROPERTIES + */ + public List getFormats(String baseName) { + if (baseName == null) { + throw new NullPointerException(); + } + return FORMAT_DEFAULT; + } + + /** + * Returns a List of Locales as candidate + * locales for baseName and locale. This + * method is called by the ResourceBundle.getBundle + * factory method each time the factory method tries finding a + * resource bundle for a target Locale. + * + *

The sequence of the candidate locales also corresponds to the + * runtime resource lookup path (also known as the parent + * chain), if the corresponding resource bundles for the + * candidate locales exist and their parents are not defined by + * loaded resource bundles themselves. The last element of the list + * must be a {@linkplain Locale#ROOT root locale} if it is desired to + * have the base bundle as the terminal of the parent chain. + * + *

If the given locale is equal to Locale.ROOT (the + * root locale), a List containing only the root + * Locale must be returned. In this case, the + * ResourceBundle.getBundle factory method loads only + * the base bundle as the resulting resource bundle. + * + *

It is not a requirement to return an immutable (unmodifiable) + * List. However, the returned List must not + * be mutated after it has been returned by + * getCandidateLocales. + * + *

The default implementation returns a List containing + * Locales using the rules described below. In the + * description below, L, S, C and V + * respectively represent non-empty language, script, country, and + * variant. For example, [L, C] represents a + * Locale that has non-empty values only for language and + * country. The form L("xx") represents the (non-empty) + * language value is "xx". For all cases, Locales whose + * final component values are empty strings are omitted. + * + *

  1. For an input Locale with an empty script value, + * append candidate Locales by omitting the final component + * one by one as below: + * + *
      + *
    • [L, C, V] + *
    • [L, C] + *
    • [L] + *
    • Locale.ROOT + *
    + * + *
  2. For an input Locale with a non-empty script value, + * append candidate Locales by omitting the final component + * up to language, then append candidates generated from the + * Locale with country and variant restored: + * + *
      + *
    • [L, S, C, V] + *
    • [L, S, C] + *
    • [L, S] + *
    • [L, C, V] + *
    • [L, C] + *
    • [L] + *
    • Locale.ROOT + *
    + * + *
  3. For an input Locale with a variant value consisting + * of multiple subtags separated by underscore, generate candidate + * Locales by omitting the variant subtags one by one, then + * insert them after every occurence of Locales with the + * full variant value in the original list. For example, if the + * the variant consists of two subtags V1 and V2: + * + *
      + *
    • [L, S, C, V1, V2] + *
    • [L, S, C, V1] + *
    • [L, S, C] + *
    • [L, S] + *
    • [L, C, V1, V2] + *
    • [L, C, V1] + *
    • [L, C] + *
    • [L] + *
    • Locale.ROOT + *
    + * + *
  4. Special cases for Chinese. When an input Locale has the + * language "zh" (Chinese) and an empty script value, either "Hans" (Simplified) or + * "Hant" (Traditional) might be supplied, depending on the country. + * When the country is "CN" (China) or "SG" (Singapore), "Hans" is supplied. + * When the country is "HK" (Hong Kong SAR China), "MO" (Macau SAR China), + * or "TW" (Taiwan), "Hant" is supplied. For all other countries or when the country + * is empty, no script is supplied. For example, for Locale("zh", "CN") + * , the candidate list will be: + *
      + *
    • [L("zh"), S("Hans"), C("CN")] + *
    • [L("zh"), S("Hans")] + *
    • [L("zh"), C("CN")] + *
    • [L("zh")] + *
    • Locale.ROOT + *
    + * + * For Locale("zh", "TW"), the candidate list will be: + *
      + *
    • [L("zh"), S("Hant"), C("TW")] + *
    • [L("zh"), S("Hant")] + *
    • [L("zh"), C("TW")] + *
    • [L("zh")] + *
    • Locale.ROOT + *
    + * + *
  5. Special cases for Norwegian. Both Locale("no", "NO", + * "NY") and Locale("nn", "NO") represent Norwegian + * Nynorsk. When a locale's language is "nn", the standard candidate + * list is generated up to [L("nn")], and then the following + * candidates are added: + * + *
    • [L("no"), C("NO"), V("NY")] + *
    • [L("no"), C("NO")] + *
    • [L("no")] + *
    • Locale.ROOT + *
    + * + * If the locale is exactly Locale("no", "NO", "NY"), it is first + * converted to Locale("nn", "NO") and then the above procedure is + * followed. + * + *

    Also, Java treats the language "no" as a synonym of Norwegian + * Bokmål "nb". Except for the single case Locale("no", + * "NO", "NY") (handled above), when an input Locale + * has language "no" or "nb", candidate Locales with + * language code "no" and "nb" are interleaved, first using the + * requested language, then using its synonym. For example, + * Locale("nb", "NO", "POSIX") generates the following + * candidate list: + * + *

      + *
    • [L("nb"), C("NO"), V("POSIX")] + *
    • [L("no"), C("NO"), V("POSIX")] + *
    • [L("nb"), C("NO")] + *
    • [L("no"), C("NO")] + *
    • [L("nb")] + *
    • [L("no")] + *
    • Locale.ROOT + *
    + * + * Locale("no", "NO", "POSIX") would generate the same list + * except that locales with "no" would appear before the corresponding + * locales with "nb".
  6. + * + * + *
+ * + *

The default implementation uses an {@link ArrayList} that + * overriding implementations may modify before returning it to the + * caller. However, a subclass must not modify it after it has + * been returned by getCandidateLocales. + * + *

For example, if the given baseName is "Messages" + * and the given locale is + * Locale("ja", "", "XX"), then a + * List of Locales: + *

+         *     Locale("ja", "", "XX")
+         *     Locale("ja")
+         *     Locale.ROOT
+         * 
+ * is returned. And if the resource bundles for the "ja" and + * "" Locales are found, then the runtime resource + * lookup path (parent chain) is: + *
+         *     Messages_ja -> Messages
+         * 
+ * + * @param baseName + * the base name of the resource bundle, a fully + * qualified class name + * @param locale + * the locale for which a resource bundle is desired + * @return a List of candidate + * Locales for the given locale + * @exception NullPointerException + * if baseName or locale is + * null + */ + public List getCandidateLocales(String baseName, Locale locale) { + if (baseName == null) { + throw new NullPointerException(); + } + return new ArrayList<>(CANDIDATES_CACHE.get(locale)); + } + + private static final CandidateListCache CANDIDATES_CACHE = new CandidateListCache(); + + private static class CandidateListCache { + private Locale prevQuery; + private List prevResult; + + public List get(Locale l) { + if (prevQuery == l) { + return prevResult; + } + prevResult = createObject(l); + prevQuery = l; + return prevResult; + } + + protected List createObject(Locale base) { + String language = base.getLanguage(); + String script = base.getScript(); + String region = base.getRegion(); + String variant = base.getVariant(); + + // Special handling for Norwegian + boolean isNorwegianBokmal = false; + boolean isNorwegianNynorsk = false; + if (language.equals("no")) { + if (region.equals("NO") && variant.equals("NY")) { + variant = ""; + isNorwegianNynorsk = true; + } else { + isNorwegianBokmal = true; + } + } + if (language.equals("nb") || isNorwegianBokmal) { + List tmpList = getDefaultList("nb", script, region, variant); + // Insert a locale replacing "nb" with "no" for every list entry + List bokmalList = new LinkedList<>(); + for (Locale l : tmpList) { + bokmalList.add(l); + if (l.getLanguage().length() == 0) { + break; + } + bokmalList.add(Locale.getInstance("no", l.getScript(), l.getCountry(), + l.getVariant(), null)); + } + return bokmalList; + } else if (language.equals("nn") || isNorwegianNynorsk) { + // Insert no_NO_NY, no_NO, no after nn + List nynorskList = getDefaultList("nn", script, region, variant); + int idx = nynorskList.size() - 1; + nynorskList.add(idx++, Locale.getInstance("no", "NO", "NY")); + nynorskList.add(idx++, Locale.getInstance("no", "NO", "")); + nynorskList.add(idx++, Locale.getInstance("no", "", "")); + return nynorskList; + } + // Special handling for Chinese + else if (language.equals("zh")) { + if (script.length() == 0 && region.length() > 0) { + // Supply script for users who want to use zh_Hans/zh_Hant + // as bundle names (recommended for Java7+) + if (region.equals("TW") || region.equals("HK") || region.equals("MO")) { + script = "Hant"; + } else if (region.equals("CN") || region.equals("SG")) { + script = "Hans"; + } + } else if (script.length() > 0 && region.length() == 0) { + // Supply region(country) for users who still package Chinese + // bundles using old convension. + if (script.equals("Hans")) { + region = "CN"; + } else if (script.equals("Hant")) { + region = "TW"; + } + } + } + + return getDefaultList(language, script, region, variant); + } + + private static List getDefaultList(String language, String script, String region, String variant) { + List variants = null; + + if (variant.length() > 0) { + variants = new LinkedList<>(); + int idx = variant.length(); + while (idx != -1) { + variants.add(variant.substring(0, idx)); + idx = variant.lastIndexOf('_', --idx); + } + } + + List list = new LinkedList<>(); + + if (variants != null) { + for (String v : variants) { + list.add(Locale.getInstance(language, script, region, v, null)); + } + } + if (region.length() > 0) { + list.add(Locale.getInstance(language, script, region, "", null)); + } + if (script.length() > 0) { + list.add(Locale.getInstance(language, script, "", "", null)); + + // With script, after truncating variant, region and script, + // start over without script. + if (variants != null) { + for (String v : variants) { + list.add(Locale.getInstance(language, "", region, v, null)); + } + } + if (region.length() > 0) { + list.add(Locale.getInstance(language, "", region, "", null)); + } + } + if (language.length() > 0) { + list.add(Locale.getInstance(language, "", "", "", null)); + } + // Add root locale at the end + list.add(Locale.ROOT); + + return list; + } + } + + /** + * Returns a Locale to be used as a fallback locale for + * further resource bundle searches by the + * ResourceBundle.getBundle factory method. This method + * is called from the factory method every time when no resulting + * resource bundle has been found for baseName and + * locale, where locale is either the parameter for + * ResourceBundle.getBundle or the previous fallback + * locale returned by this method. + * + *

The method returns null if no further fallback + * search is desired. + * + *

The default implementation returns the {@linkplain + * Locale#getDefault() default Locale} if the given + * locale isn't the default one. Otherwise, + * null is returned. + * + * @param baseName + * the base name of the resource bundle, a fully + * qualified class name for which + * ResourceBundle.getBundle has been + * unable to find any resource bundles (except for the + * base bundle) + * @param locale + * the Locale for which + * ResourceBundle.getBundle has been + * unable to find any resource bundles (except for the + * base bundle) + * @return a Locale for the fallback search, + * or null if no further fallback search + * is desired. + * @exception NullPointerException + * if baseName or locale + * is null + */ + public Locale getFallbackLocale(String baseName, Locale locale) { + if (baseName == null) { + throw new NullPointerException(); + } + Locale defaultLocale = Locale.getDefault(); + return locale.equals(defaultLocale) ? null : defaultLocale; + } + + /** + * Instantiates a resource bundle for the given bundle name of the + * given format and locale, using the given class loader if + * necessary. This method returns null if there is no + * resource bundle available for the given parameters. If a resource + * bundle can't be instantiated due to an unexpected error, the + * error must be reported by throwing an Error or + * Exception rather than simply returning + * null. + * + *

If the reload flag is true, it + * indicates that this method is being called because the previously + * loaded resource bundle has expired. + * + *

The default implementation instantiates a + * ResourceBundle as follows. + * + *

    + * + *
  • The bundle name is obtained by calling {@link + * #toBundleName(String, Locale) toBundleName(baseName, + * locale)}.
  • + * + *
  • If format is "java.class", the + * {@link Class} specified by the bundle name is loaded by calling + * {@link ClassLoader#loadClass(String)}. Then, a + * ResourceBundle is instantiated by calling {@link + * Class#newInstance()}. Note that the reload flag is + * ignored for loading class-based resource bundles in this default + * implementation.
  • + * + *
  • If format is "java.properties", + * {@link #toResourceName(String, String) toResourceName(bundlename, + * "properties")} is called to get the resource name. + * If reload is true, {@link + * ClassLoader#getResource(String) load.getResource} is called + * to get a {@link URL} for creating a {@link + * URLConnection}. This URLConnection is used to + * {@linkplain URLConnection#setUseCaches(boolean) disable the + * caches} of the underlying resource loading layers, + * and to {@linkplain URLConnection#getInputStream() get an + * InputStream}. + * Otherwise, {@link ClassLoader#getResourceAsStream(String) + * loader.getResourceAsStream} is called to get an {@link + * InputStream}. Then, a {@link + * PropertyResourceBundle} is constructed with the + * InputStream.
  • + * + *
  • If format is neither "java.class" + * nor "java.properties", an + * IllegalArgumentException is thrown.
  • + * + *
+ * + * @param baseName + * the base bundle name of the resource bundle, a fully + * qualified class name + * @param locale + * the locale for which the resource bundle should be + * instantiated + * @param format + * the resource bundle format to be loaded + * @param loader + * the ClassLoader to use to load the bundle + * @param reload + * the flag to indicate bundle reloading; true + * if reloading an expired resource bundle, + * false otherwise + * @return the resource bundle instance, + * or null if none could be found. + * @exception NullPointerException + * if bundleName, locale, + * format, or loader is + * null, or if null is returned by + * {@link #toBundleName(String, Locale) toBundleName} + * @exception IllegalArgumentException + * if format is unknown, or if the resource + * found for the given parameters contains malformed data. + * @exception ClassCastException + * if the loaded class cannot be cast to ResourceBundle + * @exception IllegalAccessException + * if the class or its nullary constructor is not + * accessible. + * @exception InstantiationException + * if the instantiation of a class fails for some other + * reason. + * @exception ExceptionInInitializerError + * if the initialization provoked by this method fails. + * @exception SecurityException + * If a security manager is present and creation of new + * instances is denied. See {@link Class#newInstance()} + * for details. + * @exception IOException + * if an error occurred when reading resources using + * any I/O operations + */ + public ResourceBundle newBundle(String baseName, Locale locale, String format, + ClassLoader loader, boolean reload) + throws IllegalAccessException, InstantiationException, IOException { + String bundleName = toBundleName(baseName, locale); + ResourceBundle bundle = null; + if (format.equals("java.class")) { + try { + Class bundleClass + = (Class)(loader != null ? + loader.loadClass(bundleName) : + Class.forName(bundleName)); + + // If the class isn't a ResourceBundle subclass, throw a + // ClassCastException. + if (ResourceBundle.class.isAssignableFrom(bundleClass)) { + bundle = bundleClass.newInstance(); + } else { + throw new ClassCastException(bundleClass.getName() + + " cannot be cast to ResourceBundle"); + } + } catch (ClassNotFoundException e) { + } + } else if (format.equals("java.properties")) { + final String resourceName = toResourceName(bundleName, "properties"); + final ClassLoader classLoader = loader; + final boolean reloadFlag = reload; + InputStream stream = classLoader != null ? classLoader.getResourceAsStream(resourceName) : + ResourceBundle.class.getResourceAsStream("/" + resourceName); + if (stream != null) { + try { + bundle = new PropertyResourceBundle(stream); + } finally { + stream.close(); + } + } + } else { + throw new IllegalArgumentException("unknown format: " + format); + } + return bundle; + } + + /** + * Returns the time-to-live (TTL) value for resource bundles that + * are loaded under this + * ResourceBundle.Control. Positive time-to-live values + * specify the number of milliseconds a bundle can remain in the + * cache without being validated against the source data from which + * it was constructed. The value 0 indicates that a bundle must be + * validated each time it is retrieved from the cache. {@link + * #TTL_DONT_CACHE} specifies that loaded resource bundles are not + * put in the cache. {@link #TTL_NO_EXPIRATION_CONTROL} specifies + * that loaded resource bundles are put in the cache with no + * expiration control. + * + *

The expiration affects only the bundle loading process by the + * ResourceBundle.getBundle factory method. That is, + * if the factory method finds a resource bundle in the cache that + * has expired, the factory method calls the {@link + * #needsReload(String, Locale, String, ClassLoader, ResourceBundle, + * long) needsReload} method to determine whether the resource + * bundle needs to be reloaded. If needsReload returns + * true, the cached resource bundle instance is removed + * from the cache. Otherwise, the instance stays in the cache, + * updated with the new TTL value returned by this method. + * + *

All cached resource bundles are subject to removal from the + * cache due to memory constraints of the runtime environment. + * Returning a large positive value doesn't mean to lock loaded + * resource bundles in the cache. + * + *

The default implementation returns {@link #TTL_NO_EXPIRATION_CONTROL}. + * + * @param baseName + * the base name of the resource bundle for which the + * expiration value is specified. + * @param locale + * the locale of the resource bundle for which the + * expiration value is specified. + * @return the time (0 or a positive millisecond offset from the + * cached time) to get loaded bundles expired in the cache, + * {@link #TTL_NO_EXPIRATION_CONTROL} to disable the + * expiration control, or {@link #TTL_DONT_CACHE} to disable + * caching. + * @exception NullPointerException + * if baseName or locale is + * null + */ + public long getTimeToLive(String baseName, Locale locale) { + if (baseName == null || locale == null) { + throw new NullPointerException(); + } + return TTL_NO_EXPIRATION_CONTROL; + } + + /** + * Determines if the expired bundle in the cache needs + * to be reloaded based on the loading time given by + * loadTime or some other criteria. The method returns + * true if reloading is required; false + * otherwise. loadTime is a millisecond offset since + * the Calendar + * Epoch. + * + * The calling ResourceBundle.getBundle factory method + * calls this method on the ResourceBundle.Control + * instance used for its current invocation, not on the instance + * used in the invocation that originally loaded the resource + * bundle. + * + *

The default implementation compares loadTime and + * the last modified time of the source data of the resource + * bundle. If it's determined that the source data has been modified + * since loadTime, true is + * returned. Otherwise, false is returned. This + * implementation assumes that the given format is the + * same string as its file suffix if it's not one of the default + * formats, "java.class" or + * "java.properties". + * + * @param baseName + * the base bundle name of the resource bundle, a + * fully qualified class name + * @param locale + * the locale for which the resource bundle + * should be instantiated + * @param format + * the resource bundle format to be loaded + * @param loader + * the ClassLoader to use to load the bundle + * @param bundle + * the resource bundle instance that has been expired + * in the cache + * @param loadTime + * the time when bundle was loaded and put + * in the cache + * @return true if the expired bundle needs to be + * reloaded; false otherwise. + * @exception NullPointerException + * if baseName, locale, + * format, loader, or + * bundle is null + */ + public boolean needsReload(String baseName, Locale locale, + String format, ClassLoader loader, + ResourceBundle bundle, long loadTime) { + if (bundle == null) { + throw new NullPointerException(); + } + if (format.equals("java.class") || format.equals("java.properties")) { + format = format.substring(5); + } + boolean result = false; + try { +/* + String resourceName = toResourceName(toBundleName(baseName, locale), format); + URL url = loader.getResource(resourceName); + if (url != null) { + long lastModified = 0; + URLConnection connection = url.openConnection(); + if (connection != null) { + // disable caches to get the correct data + connection.setUseCaches(false); + if (connection instanceof JarURLConnection) { + JarEntry ent = ((JarURLConnection)connection).getJarEntry(); + if (ent != null) { + lastModified = ent.getTime(); + if (lastModified == -1) { + lastModified = 0; + } + } + } else { + lastModified = connection.getLastModified(); + } + } + result = lastModified >= loadTime; + } + */ + } catch (NullPointerException npe) { + throw npe; + } catch (Exception e) { + // ignore other exceptions + } + return result; + } + + /** + * Converts the given baseName and locale + * to the bundle name. This method is called from the default + * implementation of the {@link #newBundle(String, Locale, String, + * ClassLoader, boolean) newBundle} and {@link #needsReload(String, + * Locale, String, ClassLoader, ResourceBundle, long) needsReload} + * methods. + * + *

This implementation returns the following value: + *

+         *     baseName + "_" + language + "_" + script + "_" + country + "_" + variant
+         * 
+ * where language, script, country, + * and variant are the language, script, country, and variant + * values of locale, respectively. Final component values that + * are empty Strings are omitted along with the preceding '_'. When the + * script is empty, the script value is ommitted along with the preceding '_'. + * If all of the values are empty strings, then baseName + * is returned. + * + *

For example, if baseName is + * "baseName" and locale is + * Locale("ja", "", "XX"), then + * "baseName_ja_ _XX" is returned. If the given + * locale is Locale("en"), then + * "baseName_en" is returned. + * + *

Overriding this method allows applications to use different + * conventions in the organization and packaging of localized + * resources. + * + * @param baseName + * the base name of the resource bundle, a fully + * qualified class name + * @param locale + * the locale for which a resource bundle should be + * loaded + * @return the bundle name for the resource bundle + * @exception NullPointerException + * if baseName or locale + * is null + */ + public String toBundleName(String baseName, Locale locale) { + if (locale == Locale.ROOT) { + return baseName; + } + + String language = locale.getLanguage(); + String script = locale.getScript(); + String country = locale.getCountry(); + String variant = locale.getVariant(); + + if (language == "" && country == "" && variant == "") { + return baseName; + } + + StringBuilder sb = new StringBuilder(baseName); + sb.append('_'); + if (script != "") { + if (variant != "") { + sb.append(language).append('_').append(script).append('_').append(country).append('_').append(variant); + } else if (country != "") { + sb.append(language).append('_').append(script).append('_').append(country); + } else { + sb.append(language).append('_').append(script); + } + } else { + if (variant != "") { + sb.append(language).append('_').append(country).append('_').append(variant); + } else if (country != "") { + sb.append(language).append('_').append(country); + } else { + sb.append(language); + } + } + return sb.toString(); + + } + + /** + * Converts the given bundleName to the form required + * by the {@link ClassLoader#getResource ClassLoader.getResource} + * method by replacing all occurrences of '.' in + * bundleName with '/' and appending a + * '.' and the given file suffix. For + * example, if bundleName is + * "foo.bar.MyResources_ja_JP" and suffix + * is "properties", then + * "foo/bar/MyResources_ja_JP.properties" is returned. + * + * @param bundleName + * the bundle name + * @param suffix + * the file type suffix + * @return the converted resource name + * @exception NullPointerException + * if bundleName or suffix + * is null + */ + public final String toResourceName(String bundleName, String suffix) { + StringBuilder sb = new StringBuilder(bundleName.length() + 1 + suffix.length()); + sb.append(bundleName.replace('.', '/')).append('.').append(suffix); + return sb.toString(); + } + } + + private static class SingleFormatControl extends Control { + private static final Control PROPERTIES_ONLY + = new SingleFormatControl(FORMAT_PROPERTIES); + + private static final Control CLASS_ONLY + = new SingleFormatControl(FORMAT_CLASS); + + private final List formats; + + protected SingleFormatControl(List formats) { + this.formats = formats; + } + + public List getFormats(String baseName) { + if (baseName == null) { + throw new NullPointerException(); + } + return formats; + } + } + + private static final class NoFallbackControl extends SingleFormatControl { + private static final Control NO_FALLBACK + = new NoFallbackControl(FORMAT_DEFAULT); + + private static final Control PROPERTIES_ONLY_NO_FALLBACK + = new NoFallbackControl(FORMAT_PROPERTIES); + + private static final Control CLASS_ONLY_NO_FALLBACK + = new NoFallbackControl(FORMAT_CLASS); + + protected NoFallbackControl(List formats) { + super(formats); + } + + public Locale getFallbackLocale(String baseName, Locale locale) { + if (baseName == null || locale == null) { + throw new NullPointerException(); + } + return null; + } + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/SimpleTimeZone.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/SimpleTimeZone.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1737 @@ +/* + * 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.util; + +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.IOException; +import java.util.Date.BaseCalendar; + +/** + * SimpleTimeZone is a concrete subclass of TimeZone + * that represents a time zone for use with a Gregorian calendar. + * The class holds an offset from GMT, called raw offset, and start + * and end rules for a daylight saving time schedule. Since it only holds + * single values for each, it cannot handle historical changes in the offset + * from GMT and the daylight saving schedule, except that the {@link + * #setStartYear setStartYear} method can specify the year when the daylight + * saving time schedule starts in effect. + *

+ * To construct a SimpleTimeZone with a daylight saving time + * schedule, the schedule can be described with a set of rules, + * start-rule and end-rule. A day when daylight saving time + * starts or ends is specified by a combination of month, + * day-of-month, and day-of-week values. The month + * value is represented by a Calendar {@link Calendar#MONTH MONTH} field + * value, such as {@link Calendar#MARCH}. The day-of-week value is + * represented by a Calendar {@link Calendar#DAY_OF_WEEK DAY_OF_WEEK} value, + * such as {@link Calendar#SUNDAY SUNDAY}. The meanings of value combinations + * are as follows. + * + *

    + *
  • Exact day of month
    + * To specify an exact day of month, set the month and + * day-of-month to an exact value, and day-of-week to zero. For + * example, to specify March 1, set the month to {@link Calendar#MARCH + * MARCH}, day-of-month to 1, and day-of-week to 0.
  • + * + *
  • Day of week on or after day of month
    + * To specify a day of week on or after an exact day of month, set the + * month to an exact month value, day-of-month to the day on + * or after which the rule is applied, and day-of-week to a negative {@link + * Calendar#DAY_OF_WEEK DAY_OF_WEEK} field value. For example, to specify the + * second Sunday of April, set month to {@link Calendar#APRIL APRIL}, + * day-of-month to 8, and day-of-week to -{@link + * Calendar#SUNDAY SUNDAY}.
  • + * + *
  • Day of week on or before day of month
    + * To specify a day of the week on or before an exact day of the month, set + * day-of-month and day-of-week to a negative value. For + * example, to specify the last Wednesday on or before the 21st of March, set + * month to {@link Calendar#MARCH MARCH}, day-of-month is -21 + * and day-of-week is -{@link Calendar#WEDNESDAY WEDNESDAY}.
  • + * + *
  • Last day-of-week of month
    + * To specify, the last day-of-week of the month, set day-of-week to a + * {@link Calendar#DAY_OF_WEEK DAY_OF_WEEK} value and day-of-month to + * -1. For example, to specify the last Sunday of October, set month + * to {@link Calendar#OCTOBER OCTOBER}, day-of-week to {@link + * Calendar#SUNDAY SUNDAY} and day-of-month to -1.
  • + * + *
+ * The time of the day at which daylight saving time starts or ends is + * specified by a millisecond value within the day. There are three kinds of + * modes to specify the time: {@link #WALL_TIME}, {@link + * #STANDARD_TIME} and {@link #UTC_TIME}. For example, if daylight + * saving time ends + * at 2:00 am in the wall clock time, it can be specified by 7200000 + * milliseconds in the {@link #WALL_TIME} mode. In this case, the wall clock time + * for an end-rule means the same thing as the daylight time. + *

+ * The following are examples of parameters for constructing time zone objects. + *


+ *      // Base GMT offset: -8:00
+ *      // DST starts:      at 2:00am in standard time
+ *      //                  on the first Sunday in April
+ *      // DST ends:        at 2:00am in daylight time
+ *      //                  on the last Sunday in October
+ *      // Save:            1 hour
+ *      SimpleTimeZone(-28800000,
+ *                     "America/Los_Angeles",
+ *                     Calendar.APRIL, 1, -Calendar.SUNDAY,
+ *                     7200000,
+ *                     Calendar.OCTOBER, -1, Calendar.SUNDAY,
+ *                     7200000,
+ *                     3600000)
+ *
+ *      // Base GMT offset: +1:00
+ *      // DST starts:      at 1:00am in UTC time
+ *      //                  on the last Sunday in March
+ *      // DST ends:        at 1:00am in UTC time
+ *      //                  on the last Sunday in October
+ *      // Save:            1 hour
+ *      SimpleTimeZone(3600000,
+ *                     "Europe/Paris",
+ *                     Calendar.MARCH, -1, Calendar.SUNDAY,
+ *                     3600000, SimpleTimeZone.UTC_TIME,
+ *                     Calendar.OCTOBER, -1, Calendar.SUNDAY,
+ *                     3600000, SimpleTimeZone.UTC_TIME,
+ *                     3600000)
+ * 
+ * These parameter rules are also applicable to the set rule methods, such as + * setStartRule. + * + * @since 1.1 + * @see Calendar + * @see GregorianCalendar + * @see TimeZone + * @author David Goldsmith, Mark Davis, Chen-Lieh Huang, Alan Liu + */ + +public class SimpleTimeZone extends TimeZone { + /** + * Constructs a SimpleTimeZone with the given base time zone offset from GMT + * and time zone ID with no daylight saving time schedule. + * + * @param rawOffset The base time zone offset in milliseconds to GMT. + * @param ID The time zone name that is given to this instance. + */ + public SimpleTimeZone(int rawOffset, String ID) + { + this.rawOffset = rawOffset; + setID (ID); + dstSavings = millisPerHour; // In case user sets rules later + } + + /** + * Constructs a SimpleTimeZone with the given base time zone offset from + * GMT, time zone ID, and rules for starting and ending the daylight + * time. + * Both startTime and endTime are specified to be + * represented in the wall clock time. The amount of daylight saving is + * assumed to be 3600000 milliseconds (i.e., one hour). This constructor is + * equivalent to: + *

+     *     SimpleTimeZone(rawOffset,
+     *                    ID,
+     *                    startMonth,
+     *                    startDay,
+     *                    startDayOfWeek,
+     *                    startTime,
+     *                    SimpleTimeZone.{@link #WALL_TIME},
+     *                    endMonth,
+     *                    endDay,
+     *                    endDayOfWeek,
+     *                    endTime,
+     *                    SimpleTimeZone.{@link #WALL_TIME},
+     *                    3600000)
+     * 
+ * + * @param rawOffset The given base time zone offset from GMT. + * @param ID The time zone ID which is given to this object. + * @param startMonth The daylight saving time starting month. Month is + * a {@link Calendar#MONTH MONTH} field value (0-based. e.g., 0 + * for January). + * @param startDay The day of the month on which the daylight saving time starts. + * See the class description for the special cases of this parameter. + * @param startDayOfWeek The daylight saving time starting day-of-week. + * See the class description for the special cases of this parameter. + * @param startTime The daylight saving time starting time in local wall clock + * time (in milliseconds within the day), which is local + * standard time in this case. + * @param endMonth The daylight saving time ending month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 9 for October). + * @param endDay The day of the month on which the daylight saving time ends. + * See the class description for the special cases of this parameter. + * @param endDayOfWeek The daylight saving time ending day-of-week. + * See the class description for the special cases of this parameter. + * @param endTime The daylight saving ending time in local wall clock time, + * (in milliseconds within the day) which is local daylight + * time in this case. + * @exception IllegalArgumentException if the month, day, dayOfWeek, or time + * parameters are out of range for the start or end rule + */ + public SimpleTimeZone(int rawOffset, String ID, + int startMonth, int startDay, int startDayOfWeek, int startTime, + int endMonth, int endDay, int endDayOfWeek, int endTime) + { + this(rawOffset, ID, + startMonth, startDay, startDayOfWeek, startTime, WALL_TIME, + endMonth, endDay, endDayOfWeek, endTime, WALL_TIME, + millisPerHour); + } + + /** + * Constructs a SimpleTimeZone with the given base time zone offset from + * GMT, time zone ID, and rules for starting and ending the daylight + * time. + * Both startTime and endTime are assumed to be + * represented in the wall clock time. This constructor is equivalent to: + *

+     *     SimpleTimeZone(rawOffset,
+     *                    ID,
+     *                    startMonth,
+     *                    startDay,
+     *                    startDayOfWeek,
+     *                    startTime,
+     *                    SimpleTimeZone.{@link #WALL_TIME},
+     *                    endMonth,
+     *                    endDay,
+     *                    endDayOfWeek,
+     *                    endTime,
+     *                    SimpleTimeZone.{@link #WALL_TIME},
+     *                    dstSavings)
+     * 
+ * + * @param rawOffset The given base time zone offset from GMT. + * @param ID The time zone ID which is given to this object. + * @param startMonth The daylight saving time starting month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 0 for January). + * @param startDay The day of the month on which the daylight saving time starts. + * See the class description for the special cases of this parameter. + * @param startDayOfWeek The daylight saving time starting day-of-week. + * See the class description for the special cases of this parameter. + * @param startTime The daylight saving time starting time in local wall clock + * time, which is local standard time in this case. + * @param endMonth The daylight saving time ending month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 9 for October). + * @param endDay The day of the month on which the daylight saving time ends. + * See the class description for the special cases of this parameter. + * @param endDayOfWeek The daylight saving time ending day-of-week. + * See the class description for the special cases of this parameter. + * @param endTime The daylight saving ending time in local wall clock time, + * which is local daylight time in this case. + * @param dstSavings The amount of time in milliseconds saved during + * daylight saving time. + * @exception IllegalArgumentException if the month, day, dayOfWeek, or time + * parameters are out of range for the start or end rule + * @since 1.2 + */ + public SimpleTimeZone(int rawOffset, String ID, + int startMonth, int startDay, int startDayOfWeek, int startTime, + int endMonth, int endDay, int endDayOfWeek, int endTime, + int dstSavings) + { + this(rawOffset, ID, + startMonth, startDay, startDayOfWeek, startTime, WALL_TIME, + endMonth, endDay, endDayOfWeek, endTime, WALL_TIME, + dstSavings); + } + + /** + * Constructs a SimpleTimeZone with the given base time zone offset from + * GMT, time zone ID, and rules for starting and ending the daylight + * time. + * This constructor takes the full set of the start and end rules + * parameters, including modes of startTime and + * endTime. The mode specifies either {@link #WALL_TIME wall + * time} or {@link #STANDARD_TIME standard time} or {@link #UTC_TIME UTC + * time}. + * + * @param rawOffset The given base time zone offset from GMT. + * @param ID The time zone ID which is given to this object. + * @param startMonth The daylight saving time starting month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 0 for January). + * @param startDay The day of the month on which the daylight saving time starts. + * See the class description for the special cases of this parameter. + * @param startDayOfWeek The daylight saving time starting day-of-week. + * See the class description for the special cases of this parameter. + * @param startTime The daylight saving time starting time in the time mode + * specified by startTimeMode. + * @param startTimeMode The mode of the start time specified by startTime. + * @param endMonth The daylight saving time ending month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 9 for October). + * @param endDay The day of the month on which the daylight saving time ends. + * See the class description for the special cases of this parameter. + * @param endDayOfWeek The daylight saving time ending day-of-week. + * See the class description for the special cases of this parameter. + * @param endTime The daylight saving ending time in time time mode + * specified by endTimeMode. + * @param endTimeMode The mode of the end time specified by endTime + * @param dstSavings The amount of time in milliseconds saved during + * daylight saving time. + * + * @exception IllegalArgumentException if the month, day, dayOfWeek, time more, or + * time parameters are out of range for the start or end rule, or if a time mode + * value is invalid. + * + * @see #WALL_TIME + * @see #STANDARD_TIME + * @see #UTC_TIME + * + * @since 1.4 + */ + public SimpleTimeZone(int rawOffset, String ID, + int startMonth, int startDay, int startDayOfWeek, + int startTime, int startTimeMode, + int endMonth, int endDay, int endDayOfWeek, + int endTime, int endTimeMode, + int dstSavings) { + + setID(ID); + this.rawOffset = rawOffset; + this.startMonth = startMonth; + this.startDay = startDay; + this.startDayOfWeek = startDayOfWeek; + this.startTime = startTime; + this.startTimeMode = startTimeMode; + this.endMonth = endMonth; + this.endDay = endDay; + this.endDayOfWeek = endDayOfWeek; + this.endTime = endTime; + this.endTimeMode = endTimeMode; + this.dstSavings = dstSavings; + + // this.useDaylight is set by decodeRules + decodeRules(); + if (dstSavings <= 0) { + throw new IllegalArgumentException("Illegal daylight saving value: " + dstSavings); + } + } + + /** + * Sets the daylight saving time starting year. + * + * @param year The daylight saving starting year. + */ + public void setStartYear(int year) + { + startYear = year; + invalidateCache(); + } + + /** + * Sets the daylight saving time start rule. For example, if daylight saving + * time starts on the first Sunday in April at 2 am in local wall clock + * time, you can set the start rule by calling: + *
setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
+ * + * @param startMonth The daylight saving time starting month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 0 for January). + * @param startDay The day of the month on which the daylight saving time starts. + * See the class description for the special cases of this parameter. + * @param startDayOfWeek The daylight saving time starting day-of-week. + * See the class description for the special cases of this parameter. + * @param startTime The daylight saving time starting time in local wall clock + * time, which is local standard time in this case. + * @exception IllegalArgumentException if the startMonth, startDay, + * startDayOfWeek, or startTime parameters are out of range + */ + public void setStartRule(int startMonth, int startDay, int startDayOfWeek, int startTime) + { + this.startMonth = startMonth; + this.startDay = startDay; + this.startDayOfWeek = startDayOfWeek; + this.startTime = startTime; + startTimeMode = WALL_TIME; + decodeStartRule(); + invalidateCache(); + } + + /** + * Sets the daylight saving time start rule to a fixed date within a month. + * This method is equivalent to: + *
setStartRule(startMonth, startDay, 0, startTime)
+ * + * @param startMonth The daylight saving time starting month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 0 for January). + * @param startDay The day of the month on which the daylight saving time starts. + * @param startTime The daylight saving time starting time in local wall clock + * time, which is local standard time in this case. + * See the class description for the special cases of this parameter. + * @exception IllegalArgumentException if the startMonth, + * startDayOfMonth, or startTime parameters are out of range + * @since 1.2 + */ + public void setStartRule(int startMonth, int startDay, int startTime) { + setStartRule(startMonth, startDay, 0, startTime); + } + + /** + * Sets the daylight saving time start rule to a weekday before or after the given date within + * a month, e.g., the first Monday on or after the 8th. + * + * @param startMonth The daylight saving time starting month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 0 for January). + * @param startDay The day of the month on which the daylight saving time starts. + * @param startDayOfWeek The daylight saving time starting day-of-week. + * @param startTime The daylight saving time starting time in local wall clock + * time, which is local standard time in this case. + * @param after If true, this rule selects the first dayOfWeek on or + * after dayOfMonth. If false, this rule + * selects the last dayOfWeek on or before + * dayOfMonth. + * @exception IllegalArgumentException if the startMonth, startDay, + * startDayOfWeek, or startTime parameters are out of range + * @since 1.2 + */ + public void setStartRule(int startMonth, int startDay, int startDayOfWeek, + int startTime, boolean after) + { + // TODO: this method doesn't check the initial values of dayOfMonth or dayOfWeek. + if (after) { + setStartRule(startMonth, startDay, -startDayOfWeek, startTime); + } else { + setStartRule(startMonth, -startDay, -startDayOfWeek, startTime); + } + } + + /** + * Sets the daylight saving time end rule. For example, if daylight saving time + * ends on the last Sunday in October at 2 am in wall clock time, + * you can set the end rule by calling: + * setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000); + * + * @param endMonth The daylight saving time ending month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 9 for October). + * @param endDay The day of the month on which the daylight saving time ends. + * See the class description for the special cases of this parameter. + * @param endDayOfWeek The daylight saving time ending day-of-week. + * See the class description for the special cases of this parameter. + * @param endTime The daylight saving ending time in local wall clock time, + * (in milliseconds within the day) which is local daylight + * time in this case. + * @exception IllegalArgumentException if the endMonth, endDay, + * endDayOfWeek, or endTime parameters are out of range + */ + public void setEndRule(int endMonth, int endDay, int endDayOfWeek, + int endTime) + { + this.endMonth = endMonth; + this.endDay = endDay; + this.endDayOfWeek = endDayOfWeek; + this.endTime = endTime; + this.endTimeMode = WALL_TIME; + decodeEndRule(); + invalidateCache(); + } + + /** + * Sets the daylight saving time end rule to a fixed date within a month. + * This method is equivalent to: + *
setEndRule(endMonth, endDay, 0, endTime)
+ * + * @param endMonth The daylight saving time ending month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 9 for October). + * @param endDay The day of the month on which the daylight saving time ends. + * @param endTime The daylight saving ending time in local wall clock time, + * (in milliseconds within the day) which is local daylight + * time in this case. + * @exception IllegalArgumentException the endMonth, endDay, + * or endTime parameters are out of range + * @since 1.2 + */ + public void setEndRule(int endMonth, int endDay, int endTime) + { + setEndRule(endMonth, endDay, 0, endTime); + } + + /** + * Sets the daylight saving time end rule to a weekday before or after the given date within + * a month, e.g., the first Monday on or after the 8th. + * + * @param endMonth The daylight saving time ending month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 9 for October). + * @param endDay The day of the month on which the daylight saving time ends. + * @param endDayOfWeek The daylight saving time ending day-of-week. + * @param endTime The daylight saving ending time in local wall clock time, + * (in milliseconds within the day) which is local daylight + * time in this case. + * @param after If true, this rule selects the first endDayOfWeek on + * or after endDay. If false, this rule + * selects the last endDayOfWeek on or before + * endDay of the month. + * @exception IllegalArgumentException the endMonth, endDay, + * endDayOfWeek, or endTime parameters are out of range + * @since 1.2 + */ + public void setEndRule(int endMonth, int endDay, int endDayOfWeek, int endTime, boolean after) + { + if (after) { + setEndRule(endMonth, endDay, -endDayOfWeek, endTime); + } else { + setEndRule(endMonth, -endDay, -endDayOfWeek, endTime); + } + } + + /** + * Returns the offset of this time zone from UTC at the given + * time. If daylight saving time is in effect at the given time, + * the offset value is adjusted with the amount of daylight + * saving. + * + * @param date the time at which the time zone offset is found + * @return the amount of time in milliseconds to add to UTC to get + * local time. + * @since 1.4 + */ + public int getOffset(long date) { + return getOffsets(date, null); + } + + /** + * @see TimeZone#getOffsets + */ + int getOffsets(long date, int[] offsets) { + int offset = rawOffset; + + computeOffset: + if (useDaylight) { + synchronized (this) { + if (cacheStart != 0) { + if (date >= cacheStart && date < cacheEnd) { + offset += dstSavings; + break computeOffset; + } + } + } + BaseCalendar cal = gcal; +// date >= GregorianCalendar.DEFAULT_GREGORIAN_CUTOVER ? +// gcal : (BaseCalendar) CalendarSystem.forName("julian"); + BaseCalendar.Datum cdate = (BaseCalendar.Datum) cal.newCalendarDate(TimeZone.NO_TIMEZONE); + // Get the year in local time + cal.getCalendarDate(date + rawOffset, cdate); + int year = cdate.getNormalizedYear(); + if (year >= startYear) { + // Clear time elements for the transition calculations + cdate.setTimeOfDay(0, 0, 0, 0); + offset = getOffset(cal, cdate, year, date); + } + } + + if (offsets != null) { + offsets[0] = rawOffset; + offsets[1] = offset - rawOffset; + } + return offset; + } + + /** + * Returns the difference in milliseconds between local time and + * UTC, taking into account both the raw offset and the effect of + * daylight saving, for the specified date and time. This method + * assumes that the start and end month are distinct. It also + * uses a default {@link GregorianCalendar} object as its + * underlying calendar, such as for determining leap years. Do + * not use the result of this method with a calendar other than a + * default GregorianCalendar. + * + *

Note: In general, clients should use + * Calendar.get(ZONE_OFFSET) + Calendar.get(DST_OFFSET) + * instead of calling this method. + * + * @param era The era of the given date. + * @param year The year in the given date. + * @param month The month in the given date. Month is 0-based. e.g., + * 0 for January. + * @param day The day-in-month of the given date. + * @param dayOfWeek The day-of-week of the given date. + * @param millis The milliseconds in day in standard local time. + * @return The milliseconds to add to UTC to get local time. + * @exception IllegalArgumentException the era, + * month, day, dayOfWeek, + * or millis parameters are out of range + */ + public int getOffset(int era, int year, int month, int day, int dayOfWeek, + int millis) + { + if (era != GregorianCalendar.AD && era != GregorianCalendar.BC) { + throw new IllegalArgumentException("Illegal era " + era); + } + + int y = year; + if (era == GregorianCalendar.BC) { + // adjust y with the GregorianCalendar-style year numbering. + y = 1 - y; + } + + // If the year isn't representable with the 64-bit long + // integer in milliseconds, convert the year to an + // equivalent year. This is required to pass some JCK test cases + // which are actually useless though because the specified years + // can't be supported by the Java time system. + if (y >= 292278994) { + y = 2800 + y % 2800; + } else if (y <= -292269054) { + // y %= 28 also produces an equivalent year, but positive + // year numbers would be convenient to use the UNIX cal + // command. + y = (int) (long) y % 28; + } + + // convert year to its 1-based month value + int m = month + 1; + + // First, calculate time as a Gregorian date. + BaseCalendar cal = gcal; + BaseCalendar.Datum cdate = (BaseCalendar.Datum) cal.newCalendarDate(TimeZone.NO_TIMEZONE); + cdate.setDate(y, m, day); + long time = cal.getTime(cdate); // normalize cdate + time += millis - rawOffset; // UTC time + + // If the time value represents a time before the default + // Gregorian cutover, recalculate time using the Julian + // calendar system. For the Julian calendar system, the + // normalized year numbering is ..., -2 (BCE 2), -1 (BCE 1), + // 1, 2 ... which is different from the GregorianCalendar + // style year numbering (..., -1, 0 (BCE 1), 1, 2, ...). +// if (time < GregorianCalendar.DEFAULT_GREGORIAN_CUTOVER) { +// cal = (BaseCalendar) CalendarSystem.forName("julian"); +// cdate = (BaseCalendar.Datum) cal.newCalendarDate(TimeZone.NO_TIMEZONE); +// cdate.setNormalizedDate(y, m, day); +// time = cal.getTime(cdate) + millis - rawOffset; +// } + + if ((cdate.getNormalizedYear() != y) + || (cdate.getMonth() != m) + || (cdate.getDayOfMonth() != day) + // The validation should be cdate.getDayOfWeek() == + // dayOfWeek. However, we don't check dayOfWeek for + // compatibility. + || (dayOfWeek < Calendar.SUNDAY || dayOfWeek > Calendar.SATURDAY) + || (millis < 0 || millis >= (24*60*60*1000))) { + throw new IllegalArgumentException(); + } + + if (!useDaylight || year < startYear || era != GregorianCalendar.CE) { + return rawOffset; + } + + return getOffset(cal, cdate, y, time); + } + + private int getOffset(BaseCalendar cal, BaseCalendar.Datum cdate, int year, long time) { + synchronized (this) { + if (cacheStart != 0) { + if (time >= cacheStart && time < cacheEnd) { + return rawOffset + dstSavings; + } + if (year == cacheYear) { + return rawOffset; + } + } + } + + long start = getStart(cal, cdate, year); + long end = getEnd(cal, cdate, year); + int offset = rawOffset; + if (start <= end) { + if (time >= start && time < end) { + offset += dstSavings; + } + synchronized (this) { + cacheYear = year; + cacheStart = start; + cacheEnd = end; + } + } else { + if (time < end) { + // TODO: support Gregorian cutover. The previous year + // may be in the other calendar system. + start = getStart(cal, cdate, year - 1); + if (time >= start) { + offset += dstSavings; + } + } else if (time >= start) { + // TODO: support Gregorian cutover. The next year + // may be in the other calendar system. + end = getEnd(cal, cdate, year + 1); + if (time < end) { + offset += dstSavings; + } + } + if (start <= end) { + synchronized (this) { + // The start and end transitions are in multiple years. + cacheYear = (long) startYear - 1; + cacheStart = start; + cacheEnd = end; + } + } + } + return offset; + } + + private long getStart(BaseCalendar cal, BaseCalendar.Datum cdate, int year) { + int time = startTime; + if (startTimeMode != UTC_TIME) { + time -= rawOffset; + } + return getTransition(cal, cdate, startMode, year, startMonth, startDay, + startDayOfWeek, time); + } + + private long getEnd(BaseCalendar cal, BaseCalendar.Datum cdate, int year) { + int time = endTime; + if (endTimeMode != UTC_TIME) { + time -= rawOffset; + } + if (endTimeMode == WALL_TIME) { + time -= dstSavings; + } + return getTransition(cal, cdate, endMode, year, endMonth, endDay, + endDayOfWeek, time); + } + + private long getTransition(BaseCalendar cal, BaseCalendar.Datum cdate, + int mode, int year, int month, int dayOfMonth, + int dayOfWeek, int timeOfDay) { + cdate.setNormalizedYear(year); + cdate.setMonth(month + 1); + switch (mode) { + case DOM_MODE: + cdate.setDayOfMonth(dayOfMonth); + break; + + case DOW_IN_MONTH_MODE: + cdate.setDayOfMonth(1); + if (dayOfMonth < 0) { + cdate.setDayOfMonth(cal.getMonthLength(cdate)); + } + cdate = (BaseCalendar.Datum) cal.getNthDayOfWeek(dayOfMonth, dayOfWeek, cdate); + break; + + case DOW_GE_DOM_MODE: + cdate.setDayOfMonth(dayOfMonth); + cdate = (BaseCalendar.Datum) cal.getNthDayOfWeek(1, dayOfWeek, cdate); + break; + + case DOW_LE_DOM_MODE: + cdate.setDayOfMonth(dayOfMonth); + cdate = (BaseCalendar.Datum) cal.getNthDayOfWeek(-1, dayOfWeek, cdate); + break; + } + return cal.getTime(cdate) + timeOfDay; + } + + /** + * Gets the GMT offset for this time zone. + * @return the GMT offset value in milliseconds + * @see #setRawOffset + */ + public int getRawOffset() + { + // The given date will be taken into account while + // we have the historical time zone data in place. + return rawOffset; + } + + /** + * Sets the base time zone offset to GMT. + * This is the offset to add to UTC to get local time. + * @see #getRawOffset + */ + public void setRawOffset(int offsetMillis) + { + this.rawOffset = offsetMillis; + } + + /** + * Sets the amount of time in milliseconds that the clock is advanced + * during daylight saving time. + * @param millisSavedDuringDST the number of milliseconds the time is + * advanced with respect to standard time when the daylight saving time rules + * are in effect. A positive number, typically one hour (3600000). + * @see #getDSTSavings + * @since 1.2 + */ + public void setDSTSavings(int millisSavedDuringDST) { + if (millisSavedDuringDST <= 0) { + throw new IllegalArgumentException("Illegal daylight saving value: " + + millisSavedDuringDST); + } + dstSavings = millisSavedDuringDST; + } + + /** + * Returns the amount of time in milliseconds that the clock is + * advanced during daylight saving time. + * + * @return the number of milliseconds the time is advanced with + * respect to standard time when the daylight saving rules are in + * effect, or 0 (zero) if this time zone doesn't observe daylight + * saving time. + * + * @see #setDSTSavings + * @since 1.2 + */ + public int getDSTSavings() { + return useDaylight ? dstSavings : 0; + } + + /** + * Queries if this time zone uses daylight saving time. + * @return true if this time zone uses daylight saving time; + * false otherwise. + */ + public boolean useDaylightTime() + { + return useDaylight; + } + + /** + * Returns {@code true} if this {@code SimpleTimeZone} observes + * Daylight Saving Time. This method is equivalent to {@link + * #useDaylightTime()}. + * + * @return {@code true} if this {@code SimpleTimeZone} observes + * Daylight Saving Time; {@code false} otherwise. + * @since 1.7 + */ + @Override + public boolean observesDaylightTime() { + return useDaylightTime(); + } + + /** + * Queries if the given date is in daylight saving time. + * @return true if daylight saving time is in effective at the + * given date; false otherwise. + */ + public boolean inDaylightTime(Date date) + { + return (getOffset(date.getTime()) != rawOffset); + } + + /** + * Returns a clone of this SimpleTimeZone instance. + * @return a clone of this instance. + */ + public Object clone() + { + return super.clone(); + } + + /** + * Generates the hash code for the SimpleDateFormat object. + * @return the hash code for this object + */ + public synchronized int hashCode() + { + return startMonth ^ startDay ^ startDayOfWeek ^ startTime ^ + endMonth ^ endDay ^ endDayOfWeek ^ endTime ^ rawOffset; + } + + /** + * Compares the equality of two SimpleTimeZone objects. + * + * @param obj The SimpleTimeZone object to be compared with. + * @return True if the given obj is the same as this + * SimpleTimeZone object; false otherwise. + */ + public boolean equals(Object obj) + { + if (this == obj) { + return true; + } + if (!(obj instanceof SimpleTimeZone)) { + return false; + } + + SimpleTimeZone that = (SimpleTimeZone) obj; + + return getID().equals(that.getID()) && + hasSameRules(that); + } + + /** + * Returns true if this zone has the same rules and offset as another zone. + * @param other the TimeZone object to be compared with + * @return true if the given zone is a SimpleTimeZone and has the + * same rules and offset as this one + * @since 1.2 + */ + public boolean hasSameRules(TimeZone other) { + if (this == other) { + return true; + } + if (!(other instanceof SimpleTimeZone)) { + return false; + } + SimpleTimeZone that = (SimpleTimeZone) other; + return rawOffset == that.rawOffset && + useDaylight == that.useDaylight && + (!useDaylight + // Only check rules if using DST + || (dstSavings == that.dstSavings && + startMode == that.startMode && + startMonth == that.startMonth && + startDay == that.startDay && + startDayOfWeek == that.startDayOfWeek && + startTime == that.startTime && + startTimeMode == that.startTimeMode && + endMode == that.endMode && + endMonth == that.endMonth && + endDay == that.endDay && + endDayOfWeek == that.endDayOfWeek && + endTime == that.endTime && + endTimeMode == that.endTimeMode && + startYear == that.startYear)); + } + + /** + * Returns a string representation of this time zone. + * @return a string representation of this time zone. + */ + public String toString() { + return getClass().getName() + + "[id=" + getID() + + ",offset=" + rawOffset + + ",dstSavings=" + dstSavings + + ",useDaylight=" + useDaylight + + ",startYear=" + startYear + + ",startMode=" + startMode + + ",startMonth=" + startMonth + + ",startDay=" + startDay + + ",startDayOfWeek=" + startDayOfWeek + + ",startTime=" + startTime + + ",startTimeMode=" + startTimeMode + + ",endMode=" + endMode + + ",endMonth=" + endMonth + + ",endDay=" + endDay + + ",endDayOfWeek=" + endDayOfWeek + + ",endTime=" + endTime + + ",endTimeMode=" + endTimeMode + ']'; + } + + // =======================privates=============================== + + /** + * The month in which daylight saving time starts. This value must be + * between Calendar.JANUARY and + * Calendar.DECEMBER inclusive. This value must not equal + * endMonth. + *

If useDaylight is false, this value is ignored. + * @serial + */ + private int startMonth; + + /** + * This field has two possible interpretations: + *

+ *
startMode == DOW_IN_MONTH
+ *
+ * startDay indicates the day of the month of + * startMonth on which daylight + * saving time starts, from 1 to 28, 30, or 31, depending on the + * startMonth. + *
+ *
startMode != DOW_IN_MONTH
+ *
+ * startDay indicates which startDayOfWeek in the + * month startMonth daylight + * saving time starts on. For example, a value of +1 and a + * startDayOfWeek of Calendar.SUNDAY indicates the + * first Sunday of startMonth. Likewise, +2 would indicate the + * second Sunday, and -1 the last Sunday. A value of 0 is illegal. + *
+ *
+ *

If useDaylight is false, this value is ignored. + * @serial + */ + private int startDay; + + /** + * The day of the week on which daylight saving time starts. This value + * must be between Calendar.SUNDAY and + * Calendar.SATURDAY inclusive. + *

If useDaylight is false or + * startMode == DAY_OF_MONTH, this value is ignored. + * @serial + */ + private int startDayOfWeek; + + /** + * The time in milliseconds after midnight at which daylight saving + * time starts. This value is expressed as wall time, standard time, + * or UTC time, depending on the setting of startTimeMode. + *

If useDaylight is false, this value is ignored. + * @serial + */ + private int startTime; + + /** + * The format of startTime, either WALL_TIME, STANDARD_TIME, or UTC_TIME. + * @serial + * @since 1.3 + */ + private int startTimeMode; + + /** + * The month in which daylight saving time ends. This value must be + * between Calendar.JANUARY and + * Calendar.UNDECIMBER. This value must not equal + * startMonth. + *

If useDaylight is false, this value is ignored. + * @serial + */ + private int endMonth; + + /** + * This field has two possible interpretations: + *

+ *
endMode == DOW_IN_MONTH
+ *
+ * endDay indicates the day of the month of + * endMonth on which daylight + * saving time ends, from 1 to 28, 30, or 31, depending on the + * endMonth. + *
+ *
endMode != DOW_IN_MONTH
+ *
+ * endDay indicates which endDayOfWeek in th + * month endMonth daylight + * saving time ends on. For example, a value of +1 and a + * endDayOfWeek of Calendar.SUNDAY indicates the + * first Sunday of endMonth. Likewise, +2 would indicate the + * second Sunday, and -1 the last Sunday. A value of 0 is illegal. + *
+ *
+ *

If useDaylight is false, this value is ignored. + * @serial + */ + private int endDay; + + /** + * The day of the week on which daylight saving time ends. This value + * must be between Calendar.SUNDAY and + * Calendar.SATURDAY inclusive. + *

If useDaylight is false or + * endMode == DAY_OF_MONTH, this value is ignored. + * @serial + */ + private int endDayOfWeek; + + /** + * The time in milliseconds after midnight at which daylight saving + * time ends. This value is expressed as wall time, standard time, + * or UTC time, depending on the setting of endTimeMode. + *

If useDaylight is false, this value is ignored. + * @serial + */ + private int endTime; + + /** + * The format of endTime, either WALL_TIME, + * STANDARD_TIME, or UTC_TIME. + * @serial + * @since 1.3 + */ + private int endTimeMode; + + /** + * The year in which daylight saving time is first observed. This is an {@link GregorianCalendar#AD AD} + * value. If this value is less than 1 then daylight saving time is observed + * for all AD years. + *

If useDaylight is false, this value is ignored. + * @serial + */ + private int startYear; + + /** + * The offset in milliseconds between this zone and GMT. Negative offsets + * are to the west of Greenwich. To obtain local standard time, + * add the offset to GMT time. To obtain local wall time it may also be + * necessary to add dstSavings. + * @serial + */ + private int rawOffset; + + /** + * A boolean value which is true if and only if this zone uses daylight + * saving time. If this value is false, several other fields are ignored. + * @serial + */ + private boolean useDaylight=false; // indicate if this time zone uses DST + + private static final int millisPerHour = 60*60*1000; + private static final int millisPerDay = 24*millisPerHour; + + /** + * This field was serialized in JDK 1.1, so we have to keep it that way + * to maintain serialization compatibility. However, there's no need to + * recreate the array each time we create a new time zone. + * @serial An array of bytes containing the values {31, 28, 31, 30, 31, 30, + * 31, 31, 30, 31, 30, 31}. This is ignored as of the Java 2 platform v1.2, however, it must + * be streamed out for compatibility with JDK 1.1. + */ + private final byte monthLength[] = staticMonthLength; + private final static byte staticMonthLength[] = {31,28,31,30,31,30,31,31,30,31,30,31}; + private final static byte staticLeapMonthLength[] = {31,29,31,30,31,30,31,31,30,31,30,31}; + + /** + * Variables specifying the mode of the start rule. Takes the following + * values: + *

+ *
DOM_MODE
+ *
+ * Exact day of week; e.g., March 1. + *
+ *
DOW_IN_MONTH_MODE
+ *
+ * Day of week in month; e.g., last Sunday in March. + *
+ *
DOW_GE_DOM_MODE
+ *
+ * Day of week after day of month; e.g., Sunday on or after March 15. + *
+ *
DOW_LE_DOM_MODE
+ *
+ * Day of week before day of month; e.g., Sunday on or before March 15. + *
+ *
+ * The setting of this field affects the interpretation of the + * startDay field. + *

If useDaylight is false, this value is ignored. + * @serial + * @since 1.1.4 + */ + private int startMode; + + /** + * Variables specifying the mode of the end rule. Takes the following + * values: + *

+ *
DOM_MODE
+ *
+ * Exact day of week; e.g., March 1. + *
+ *
DOW_IN_MONTH_MODE
+ *
+ * Day of week in month; e.g., last Sunday in March. + *
+ *
DOW_GE_DOM_MODE
+ *
+ * Day of week after day of month; e.g., Sunday on or after March 15. + *
+ *
DOW_LE_DOM_MODE
+ *
+ * Day of week before day of month; e.g., Sunday on or before March 15. + *
+ *
+ * The setting of this field affects the interpretation of the + * endDay field. + *

If useDaylight is false, this value is ignored. + * @serial + * @since 1.1.4 + */ + private int endMode; + + /** + * A positive value indicating the amount of time saved during DST in + * milliseconds. + * Typically one hour (3600000); sometimes 30 minutes (1800000). + *

If useDaylight is false, this value is ignored. + * @serial + * @since 1.1.4 + */ + private int dstSavings; + + private static final BaseCalendar gcal = new BaseCalendar();//CalendarSystem.getGregorianCalendar(); + + /** + * Cache values representing a single period of daylight saving + * time. When the cache values are valid, cacheStart is the start + * time (inclusive) of daylight saving time and cacheEnd is the + * end time (exclusive). + * + * cacheYear has a year value if both cacheStart and cacheEnd are + * in the same year. cacheYear is set to startYear - 1 if + * cacheStart and cacheEnd are in different years. cacheStart is 0 + * if the cache values are void. cacheYear is a long to support + * Integer.MIN_VALUE - 1 (JCK requirement). + */ + private transient long cacheYear; + private transient long cacheStart; + private transient long cacheEnd; + + /** + * Constants specifying values of startMode and endMode. + */ + private static final int DOM_MODE = 1; // Exact day of month, "Mar 1" + private static final int DOW_IN_MONTH_MODE = 2; // Day of week in month, "lastSun" + private static final int DOW_GE_DOM_MODE = 3; // Day of week after day of month, "Sun>=15" + private static final int DOW_LE_DOM_MODE = 4; // Day of week before day of month, "Sun<=21" + + /** + * Constant for a mode of start or end time specified as wall clock + * time. Wall clock time is standard time for the onset rule, and + * daylight time for the end rule. + * @since 1.4 + */ + public static final int WALL_TIME = 0; // Zero for backward compatibility + + /** + * Constant for a mode of start or end time specified as standard time. + * @since 1.4 + */ + public static final int STANDARD_TIME = 1; + + /** + * Constant for a mode of start or end time specified as UTC. European + * Union rules are specified as UTC time, for example. + * @since 1.4 + */ + public static final int UTC_TIME = 2; + + // Proclaim compatibility with 1.1 + static final long serialVersionUID = -403250971215465050L; + + // the internal serial version which says which version was written + // - 0 (default) for version up to JDK 1.1.3 + // - 1 for version from JDK 1.1.4, which includes 3 new fields + // - 2 for JDK 1.3, which includes 2 new fields + static final int currentSerialVersion = 2; + + /** + * The version of the serialized data on the stream. Possible values: + *

+ *
0 or not present on stream
+ *
+ * JDK 1.1.3 or earlier. + *
+ *
1
+ *
+ * JDK 1.1.4 or later. Includes three new fields: startMode, + * endMode, and dstSavings. + *
+ *
2
+ *
+ * JDK 1.3 or later. Includes two new fields: startTimeMode + * and endTimeMode. + *
+ *
+ * When streaming out this class, the most recent format + * and the highest allowable serialVersionOnStream + * is written. + * @serial + * @since 1.1.4 + */ + private int serialVersionOnStream = currentSerialVersion; + + synchronized private void invalidateCache() { + cacheYear = startYear - 1; + cacheStart = cacheEnd = 0; + } + + //---------------------------------------------------------------------- + // Rule representation + // + // We represent the following flavors of rules: + // 5 the fifth of the month + // lastSun the last Sunday in the month + // lastMon the last Monday in the month + // Sun>=8 first Sunday on or after the eighth + // Sun<=25 last Sunday on or before the 25th + // This is further complicated by the fact that we need to remain + // backward compatible with the 1.1 FCS. Finally, we need to minimize + // API changes. In order to satisfy these requirements, we support + // three representation systems, and we translate between them. + // + // INTERNAL REPRESENTATION + // This is the format SimpleTimeZone objects take after construction or + // streaming in is complete. Rules are represented directly, using an + // unencoded format. We will discuss the start rule only below; the end + // rule is analogous. + // startMode Takes on enumerated values DAY_OF_MONTH, + // DOW_IN_MONTH, DOW_AFTER_DOM, or DOW_BEFORE_DOM. + // startDay The day of the month, or for DOW_IN_MONTH mode, a + // value indicating which DOW, such as +1 for first, + // +2 for second, -1 for last, etc. + // startDayOfWeek The day of the week. Ignored for DAY_OF_MONTH. + // + // ENCODED REPRESENTATION + // This is the format accepted by the constructor and by setStartRule() + // and setEndRule(). It uses various combinations of positive, negative, + // and zero values to encode the different rules. This representation + // allows us to specify all the different rule flavors without altering + // the API. + // MODE startMonth startDay startDayOfWeek + // DOW_IN_MONTH_MODE >=0 !=0 >0 + // DOM_MODE >=0 >0 ==0 + // DOW_GE_DOM_MODE >=0 >0 <0 + // DOW_LE_DOM_MODE >=0 <0 <0 + // (no DST) don't care ==0 don't care + // + // STREAMED REPRESENTATION + // We must retain binary compatibility with the 1.1 FCS. The 1.1 code only + // handles DOW_IN_MONTH_MODE and non-DST mode, the latter indicated by the + // flag useDaylight. When we stream an object out, we translate into an + // approximate DOW_IN_MONTH_MODE representation so the object can be parsed + // and used by 1.1 code. Following that, we write out the full + // representation separately so that contemporary code can recognize and + // parse it. The full representation is written in a "packed" format, + // consisting of a version number, a length, and an array of bytes. Future + // versions of this class may specify different versions. If they wish to + // include additional data, they should do so by storing them after the + // packed representation below. + //---------------------------------------------------------------------- + + /** + * Given a set of encoded rules in startDay and startDayOfMonth, decode + * them and set the startMode appropriately. Do the same for endDay and + * endDayOfMonth. Upon entry, the day of week variables may be zero or + * negative, in order to indicate special modes. The day of month + * variables may also be negative. Upon exit, the mode variables will be + * set, and the day of week and day of month variables will be positive. + * This method also recognizes a startDay or endDay of zero as indicating + * no DST. + */ + private void decodeRules() + { + decodeStartRule(); + decodeEndRule(); + } + + /** + * Decode the start rule and validate the parameters. The parameters are + * expected to be in encoded form, which represents the various rule modes + * by negating or zeroing certain values. Representation formats are: + *

+ *

+     *            DOW_IN_MONTH  DOM    DOW>=DOM  DOW<=DOM  no DST
+     *            ------------  -----  --------  --------  ----------
+     * month       0..11        same    same      same     don't care
+     * day        -5..5         1..31   1..31    -1..-31   0
+     * dayOfWeek   1..7         0      -1..-7    -1..-7    don't care
+     * time        0..ONEDAY    same    same      same     don't care
+     * 
+ * The range for month does not include UNDECIMBER since this class is + * really specific to GregorianCalendar, which does not use that month. + * The range for time includes ONEDAY (vs. ending at ONEDAY-1) because the + * end rule is an exclusive limit point. That is, the range of times that + * are in DST include those >= the start and < the end. For this reason, + * it should be possible to specify an end of ONEDAY in order to include the + * entire day. Although this is equivalent to time 0 of the following day, + * it's not always possible to specify that, for example, on December 31. + * While arguably the start range should still be 0..ONEDAY-1, we keep + * the start and end ranges the same for consistency. + */ + private void decodeStartRule() { + useDaylight = (startDay != 0) && (endDay != 0); + if (startDay != 0) { + if (startMonth < Calendar.JANUARY || startMonth > Calendar.DECEMBER) { + throw new IllegalArgumentException( + "Illegal start month " + startMonth); + } + if (startTime < 0 || startTime > millisPerDay) { + throw new IllegalArgumentException( + "Illegal start time " + startTime); + } + if (startDayOfWeek == 0) { + startMode = DOM_MODE; + } else { + if (startDayOfWeek > 0) { + startMode = DOW_IN_MONTH_MODE; + } else { + startDayOfWeek = -startDayOfWeek; + if (startDay > 0) { + startMode = DOW_GE_DOM_MODE; + } else { + startDay = -startDay; + startMode = DOW_LE_DOM_MODE; + } + } + if (startDayOfWeek > Calendar.SATURDAY) { + throw new IllegalArgumentException( + "Illegal start day of week " + startDayOfWeek); + } + } + if (startMode == DOW_IN_MONTH_MODE) { + if (startDay < -5 || startDay > 5) { + throw new IllegalArgumentException( + "Illegal start day of week in month " + startDay); + } + } else if (startDay < 1 || startDay > staticMonthLength[startMonth]) { + throw new IllegalArgumentException( + "Illegal start day " + startDay); + } + } + } + + /** + * Decode the end rule and validate the parameters. This method is exactly + * analogous to decodeStartRule(). + * @see decodeStartRule + */ + private void decodeEndRule() { + useDaylight = (startDay != 0) && (endDay != 0); + if (endDay != 0) { + if (endMonth < Calendar.JANUARY || endMonth > Calendar.DECEMBER) { + throw new IllegalArgumentException( + "Illegal end month " + endMonth); + } + if (endTime < 0 || endTime > millisPerDay) { + throw new IllegalArgumentException( + "Illegal end time " + endTime); + } + if (endDayOfWeek == 0) { + endMode = DOM_MODE; + } else { + if (endDayOfWeek > 0) { + endMode = DOW_IN_MONTH_MODE; + } else { + endDayOfWeek = -endDayOfWeek; + if (endDay > 0) { + endMode = DOW_GE_DOM_MODE; + } else { + endDay = -endDay; + endMode = DOW_LE_DOM_MODE; + } + } + if (endDayOfWeek > Calendar.SATURDAY) { + throw new IllegalArgumentException( + "Illegal end day of week " + endDayOfWeek); + } + } + if (endMode == DOW_IN_MONTH_MODE) { + if (endDay < -5 || endDay > 5) { + throw new IllegalArgumentException( + "Illegal end day of week in month " + endDay); + } + } else if (endDay < 1 || endDay > staticMonthLength[endMonth]) { + throw new IllegalArgumentException( + "Illegal end day " + endDay); + } + } + } + + /** + * Make rules compatible to 1.1 FCS code. Since 1.1 FCS code only understands + * day-of-week-in-month rules, we must modify other modes of rules to their + * approximate equivalent in 1.1 FCS terms. This method is used when streaming + * out objects of this class. After it is called, the rules will be modified, + * with a possible loss of information. startMode and endMode will NOT be + * altered, even though semantically they should be set to DOW_IN_MONTH_MODE, + * since the rule modification is only intended to be temporary. + */ + private void makeRulesCompatible() + { + switch (startMode) { + case DOM_MODE: + startDay = 1 + (startDay / 7); + startDayOfWeek = Calendar.SUNDAY; + break; + + case DOW_GE_DOM_MODE: + // A day-of-month of 1 is equivalent to DOW_IN_MONTH_MODE + // that is, Sun>=1 == firstSun. + if (startDay != 1) { + startDay = 1 + (startDay / 7); + } + break; + + case DOW_LE_DOM_MODE: + if (startDay >= 30) { + startDay = -1; + } else { + startDay = 1 + (startDay / 7); + } + break; + } + + switch (endMode) { + case DOM_MODE: + endDay = 1 + (endDay / 7); + endDayOfWeek = Calendar.SUNDAY; + break; + + case DOW_GE_DOM_MODE: + // A day-of-month of 1 is equivalent to DOW_IN_MONTH_MODE + // that is, Sun>=1 == firstSun. + if (endDay != 1) { + endDay = 1 + (endDay / 7); + } + break; + + case DOW_LE_DOM_MODE: + if (endDay >= 30) { + endDay = -1; + } else { + endDay = 1 + (endDay / 7); + } + break; + } + + /* + * Adjust the start and end times to wall time. This works perfectly + * well unless it pushes into the next or previous day. If that + * happens, we attempt to adjust the day rule somewhat crudely. The day + * rules have been forced into DOW_IN_MONTH mode already, so we change + * the day of week to move forward or back by a day. It's possible to + * make a more refined adjustment of the original rules first, but in + * most cases this extra effort will go to waste once we adjust the day + * rules anyway. + */ + switch (startTimeMode) { + case UTC_TIME: + startTime += rawOffset; + break; + } + while (startTime < 0) { + startTime += millisPerDay; + startDayOfWeek = 1 + ((startDayOfWeek+5) % 7); // Back 1 day + } + while (startTime >= millisPerDay) { + startTime -= millisPerDay; + startDayOfWeek = 1 + (startDayOfWeek % 7); // Forward 1 day + } + + switch (endTimeMode) { + case UTC_TIME: + endTime += rawOffset + dstSavings; + break; + case STANDARD_TIME: + endTime += dstSavings; + } + while (endTime < 0) { + endTime += millisPerDay; + endDayOfWeek = 1 + ((endDayOfWeek+5) % 7); // Back 1 day + } + while (endTime >= millisPerDay) { + endTime -= millisPerDay; + endDayOfWeek = 1 + (endDayOfWeek % 7); // Forward 1 day + } + } + + /** + * Pack the start and end rules into an array of bytes. Only pack + * data which is not preserved by makeRulesCompatible. + */ + private byte[] packRules() + { + byte[] rules = new byte[6]; + rules[0] = (byte)startDay; + rules[1] = (byte)startDayOfWeek; + rules[2] = (byte)endDay; + rules[3] = (byte)endDayOfWeek; + + // As of serial version 2, include time modes + rules[4] = (byte)startTimeMode; + rules[5] = (byte)endTimeMode; + + return rules; + } + + /** + * Given an array of bytes produced by packRules, interpret them + * as the start and end rules. + */ + private void unpackRules(byte[] rules) + { + startDay = rules[0]; + startDayOfWeek = rules[1]; + endDay = rules[2]; + endDayOfWeek = rules[3]; + + // As of serial version 2, include time modes + if (rules.length >= 6) { + startTimeMode = rules[4]; + endTimeMode = rules[5]; + } + } + + /** + * Pack the start and end times into an array of bytes. This is required + * as of serial version 2. + */ + private int[] packTimes() { + int[] times = new int[2]; + times[0] = startTime; + times[1] = endTime; + return times; + } + + /** + * Unpack the start and end times from an array of bytes. This is required + * as of serial version 2. + */ + private void unpackTimes(int[] times) { + startTime = times[0]; + endTime = times[1]; + } + + /** + * Save the state of this object to a stream (i.e., serialize it). + * + * @serialData We write out two formats, a JDK 1.1 compatible format, using + * DOW_IN_MONTH_MODE rules, in the required section, followed + * by the full rules, in packed format, in the optional section. The + * optional section will be ignored by JDK 1.1 code upon stream in. + *

Contents of the optional section: The length of a byte array is + * emitted (int); this is 4 as of this release. The byte array of the given + * length is emitted. The contents of the byte array are the true values of + * the fields startDay, startDayOfWeek, + * endDay, and endDayOfWeek. The values of these + * fields in the required section are approximate values suited to the rule + * mode DOW_IN_MONTH_MODE, which is the only mode recognized by + * JDK 1.1. + */ + private void writeObject(ObjectOutputStream stream) + throws IOException + { + // Construct a binary rule + byte[] rules = packRules(); + int[] times = packTimes(); + + // Convert to 1.1 FCS rules. This step may cause us to lose information. + makeRulesCompatible(); + + // Write out the 1.1 FCS rules + stream.defaultWriteObject(); + + // Write out the binary rules in the optional data area of the stream. + stream.writeInt(rules.length); + stream.write(rules); + stream.writeObject(times); + + // Recover the original rules. This recovers the information lost + // by makeRulesCompatible. + unpackRules(rules); + unpackTimes(times); + } + + /** + * Reconstitute this object from a stream (i.e., deserialize it). + * + * We handle both JDK 1.1 + * binary formats and full formats with a packed byte array. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + + if (serialVersionOnStream < 1) { + // Fix a bug in the 1.1 SimpleTimeZone code -- namely, + // startDayOfWeek and endDayOfWeek were usually uninitialized. We can't do + // too much, so we assume SUNDAY, which actually works most of the time. + if (startDayOfWeek == 0) { + startDayOfWeek = Calendar.SUNDAY; + } + if (endDayOfWeek == 0) { + endDayOfWeek = Calendar.SUNDAY; + } + + // The variables dstSavings, startMode, and endMode are post-1.1, so they + // won't be present if we're reading from a 1.1 stream. Fix them up. + startMode = endMode = DOW_IN_MONTH_MODE; + dstSavings = millisPerHour; + } else { + // For 1.1.4, in addition to the 3 new instance variables, we also + // store the actual rules (which have not be made compatible with 1.1) + // in the optional area. Read them in here and parse them. + int length = stream.readInt(); + byte[] rules = new byte[length]; + stream.readFully(rules); + unpackRules(rules); + } + + if (serialVersionOnStream >= 2) { + int[] times = (int[]) stream.readObject(); + unpackTimes(times); + } + + serialVersionOnStream = currentSerialVersion; + } + + static final class GregorianCalendar { + public static final int BC = 0; + + /** + * Value of the {@link #ERA} field indicating the period before the + * common era, the same value as {@link #BC}. + * + * @see #CE + */ + static final int BCE = 0; + + /** + * Value of the ERA field indicating the common era (Anno + * Domini), also known as CE. The sequence of years at the transition + * from BC to AD is ..., 2 BC, 1 BC, 1 AD, 2 + * AD,... + * + * @see #ERA + */ + public static final int AD = 1; + + // The default value of gregorianCutover. + static final long DEFAULT_GREGORIAN_CUTOVER = -12219292800000L; + /** + * Value of the {@link #ERA} field indicating + * the common era, the same value as {@link #AD}. + * + * @see #BCE + */ + static final int CE = 1; + + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/TimeZone.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/TimeZone.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,715 @@ +/* + * 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. + */ + +/* + * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.util; + +import java.io.Serializable; +import java.lang.ref.SoftReference; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.concurrent.ConcurrentHashMap; + +/** + * TimeZone represents a time zone offset, and also figures out daylight + * savings. + * + *

+ * Typically, you get a TimeZone using getDefault + * which creates a TimeZone based on the time zone where the program + * is running. For example, for a program running in Japan, getDefault + * creates a TimeZone object based on Japanese Standard Time. + * + *

+ * You can also get a TimeZone using getTimeZone + * along with a time zone ID. For instance, the time zone ID for the + * U.S. Pacific Time zone is "America/Los_Angeles". So, you can get a + * U.S. Pacific Time TimeZone object with: + *

+ * TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
+ * 
+ * You can use the getAvailableIDs method to iterate through + * all the supported time zone IDs. You can then choose a + * supported ID to get a TimeZone. + * If the time zone you want is not represented by one of the + * supported IDs, then a custom time zone ID can be specified to + * produce a TimeZone. The syntax of a custom time zone ID is: + * + *
+ * CustomID:
+ *         GMT Sign Hours : Minutes
+ *         GMT Sign Hours Minutes
+ *         GMT Sign Hours
+ * Sign: one of
+ *         + -
+ * Hours:
+ *         Digit
+ *         Digit Digit
+ * Minutes:
+ *         Digit Digit
+ * Digit: one of
+ *         0 1 2 3 4 5 6 7 8 9
+ * 
+ * + * Hours must be between 0 to 23 and Minutes must be + * between 00 to 59. For example, "GMT+10" and "GMT+0010" mean ten + * hours and ten minutes ahead of GMT, respectively. + *

+ * The format is locale independent and digits must be taken from the + * Basic Latin block of the Unicode standard. No daylight saving time + * transition schedule can be specified with a custom time zone ID. If + * the specified string doesn't match the syntax, "GMT" + * is used. + *

+ * When creating a TimeZone, the specified custom time + * zone ID is normalized in the following syntax: + *

+ * NormalizedCustomID:
+ *         GMT Sign TwoDigitHours : Minutes
+ * Sign: one of
+ *         + -
+ * TwoDigitHours:
+ *         Digit Digit
+ * Minutes:
+ *         Digit Digit
+ * Digit: one of
+ *         0 1 2 3 4 5 6 7 8 9
+ * 
+ * For example, TimeZone.getTimeZone("GMT-8").getID() returns "GMT-08:00". + * + *

Three-letter time zone IDs

+ * + * For compatibility with JDK 1.1.x, some other three-letter time zone IDs + * (such as "PST", "CTT", "AST") are also supported. However, their + * use is deprecated because the same abbreviation is often used + * for multiple time zones (for example, "CST" could be U.S. "Central Standard + * Time" and "China Standard Time"), and the Java platform can then only + * recognize one of them. + * + * + * @see Calendar + * @see GregorianCalendar + * @see SimpleTimeZone + * @author Mark Davis, David Goldsmith, Chen-Lieh Huang, Alan Liu + * @since JDK1.1 + */ +abstract public class TimeZone implements Serializable, Cloneable { + /** + * Sole constructor. (For invocation by subclass constructors, typically + * implicit.) + */ + public TimeZone() { + } + + /** + * A style specifier for getDisplayName() indicating + * a short name, such as "PST." + * @see #LONG + * @since 1.2 + */ + public static final int SHORT = 0; + + /** + * A style specifier for getDisplayName() indicating + * a long name, such as "Pacific Standard Time." + * @see #SHORT + * @since 1.2 + */ + public static final int LONG = 1; + + // Constants used internally; unit is milliseconds + private static final int ONE_MINUTE = 60*1000; + private static final int ONE_HOUR = 60*ONE_MINUTE; + private static final int ONE_DAY = 24*ONE_HOUR; + + // Proclaim serialization compatibility with JDK 1.1 + static final long serialVersionUID = 3581463369166924961L; + + /** + * Gets the time zone offset, for current date, modified in case of + * daylight savings. This is the offset to add to UTC to get local time. + *

+ * This method returns a historically correct offset if an + * underlying TimeZone implementation subclass + * supports historical Daylight Saving Time schedule and GMT + * offset changes. + * + * @param era the era of the given date. + * @param year the year in the given date. + * @param month the month in the given date. + * Month is 0-based. e.g., 0 for January. + * @param day the day-in-month of the given date. + * @param dayOfWeek the day-of-week of the given date. + * @param milliseconds the milliseconds in day in standard + * local time. + * + * @return the offset in milliseconds to add to GMT to get local time. + * + * @see Calendar#ZONE_OFFSET + * @see Calendar#DST_OFFSET + */ + public abstract int getOffset(int era, int year, int month, int day, + int dayOfWeek, int milliseconds); + + /** + * Returns the offset of this time zone from UTC at the specified + * date. If Daylight Saving Time is in effect at the specified + * date, the offset value is adjusted with the amount of daylight + * saving. + *

+ * This method returns a historically correct offset value if an + * underlying TimeZone implementation subclass supports historical + * Daylight Saving Time schedule and GMT offset changes. + * + * @param date the date represented in milliseconds since January 1, 1970 00:00:00 GMT + * @return the amount of time in milliseconds to add to UTC to get local time. + * + * @see Calendar#ZONE_OFFSET + * @see Calendar#DST_OFFSET + * @since 1.4 + */ + public int getOffset(long date) { + if (inDaylightTime(new Date(date))) { + return getRawOffset() + getDSTSavings(); + } + return getRawOffset(); + } + + /** + * Gets the raw GMT offset and the amount of daylight saving of this + * time zone at the given time. + * @param date the milliseconds (since January 1, 1970, + * 00:00:00.000 GMT) at which the time zone offset and daylight + * saving amount are found + * @param offset an array of int where the raw GMT offset + * (offset[0]) and daylight saving amount (offset[1]) are stored, + * or null if those values are not needed. The method assumes that + * the length of the given array is two or larger. + * @return the total amount of the raw GMT offset and daylight + * saving at the specified date. + * + * @see Calendar#ZONE_OFFSET + * @see Calendar#DST_OFFSET + */ + int getOffsets(long date, int[] offsets) { + int rawoffset = getRawOffset(); + int dstoffset = 0; + if (inDaylightTime(new Date(date))) { + dstoffset = getDSTSavings(); + } + if (offsets != null) { + offsets[0] = rawoffset; + offsets[1] = dstoffset; + } + return rawoffset + dstoffset; + } + + /** + * Sets the base time zone offset to GMT. + * This is the offset to add to UTC to get local time. + *

+ * If an underlying TimeZone implementation subclass + * supports historical GMT offset changes, the specified GMT + * offset is set as the latest GMT offset and the difference from + * the known latest GMT offset value is used to adjust all + * historical GMT offset values. + * + * @param offsetMillis the given base time zone offset to GMT. + */ + abstract public void setRawOffset(int offsetMillis); + + /** + * Returns the amount of time in milliseconds to add to UTC to get + * standard time in this time zone. Because this value is not + * affected by daylight saving time, it is called raw + * offset. + *

+ * If an underlying TimeZone implementation subclass + * supports historical GMT offset changes, the method returns the + * raw offset value of the current date. In Honolulu, for example, + * its raw offset changed from GMT-10:30 to GMT-10:00 in 1947, and + * this method always returns -36000000 milliseconds (i.e., -10 + * hours). + * + * @return the amount of raw offset time in milliseconds to add to UTC. + * @see Calendar#ZONE_OFFSET + */ + public abstract int getRawOffset(); + + /** + * Gets the ID of this time zone. + * @return the ID of this time zone. + */ + public String getID() + { + return ID; + } + + /** + * Sets the time zone ID. This does not change any other data in + * the time zone object. + * @param ID the new time zone ID. + */ + public void setID(String ID) + { + if (ID == null) { + throw new NullPointerException(); + } + this.ID = ID; + } + + /** + * Returns a long standard time name of this {@code TimeZone} suitable for + * presentation to the user in the default locale. + * + *

This method is equivalent to: + *

+ * getDisplayName(false, {@link #LONG}, + * Locale.getDefault({@link Locale.Category#DISPLAY})) + *
+ * + * @return the human-readable name of this time zone in the default locale. + * @since 1.2 + * @see #getDisplayName(boolean, int, Locale) + * @see Locale#getDefault(Locale.Category) + * @see Locale.Category + */ + public final String getDisplayName() { + return getDisplayName(false, LONG, + Locale.getDefault(Locale.Category.DISPLAY)); + } + + /** + * Returns a long standard time name of this {@code TimeZone} suitable for + * presentation to the user in the specified {@code locale}. + * + *

This method is equivalent to: + *

+ * getDisplayName(false, {@link #LONG}, locale) + *
+ * + * @param locale the locale in which to supply the display name. + * @return the human-readable name of this time zone in the given locale. + * @exception NullPointerException if {@code locale} is {@code null}. + * @since 1.2 + * @see #getDisplayName(boolean, int, Locale) + */ + public final String getDisplayName(Locale locale) { + return getDisplayName(false, LONG, locale); + } + + /** + * Returns a name in the specified {@code style} of this {@code TimeZone} + * suitable for presentation to the user in the default locale. If the + * specified {@code daylight} is {@code true}, a Daylight Saving Time name + * is returned (even if this {@code TimeZone} doesn't observe Daylight Saving + * Time). Otherwise, a Standard Time name is returned. + * + *

This method is equivalent to: + *

+ * getDisplayName(daylight, style, + * Locale.getDefault({@link Locale.Category#DISPLAY})) + *
+ * + * @param daylight {@code true} specifying a Daylight Saving Time name, or + * {@code false} specifying a Standard Time name + * @param style either {@link #LONG} or {@link #SHORT} + * @return the human-readable name of this time zone in the default locale. + * @exception IllegalArgumentException if {@code style} is invalid. + * @since 1.2 + * @see #getDisplayName(boolean, int, Locale) + * @see Locale#getDefault(Locale.Category) + * @see Locale.Category + * @see java.text.DateFormatSymbols#getZoneStrings() + */ + public final String getDisplayName(boolean daylight, int style) { + return getDisplayName(daylight, style, + Locale.getDefault(Locale.Category.DISPLAY)); + } + + /** + * Returns a name in the specified {@code style} of this {@code TimeZone} + * suitable for presentation to the user in the specified {@code + * locale}. If the specified {@code daylight} is {@code true}, a Daylight + * Saving Time name is returned (even if this {@code TimeZone} doesn't + * observe Daylight Saving Time). Otherwise, a Standard Time name is + * returned. + * + *

When looking up a time zone name, the {@linkplain + * ResourceBundle.Control#getCandidateLocales(String,Locale) default + * Locale search path of ResourceBundle} derived + * from the specified {@code locale} is used. (No {@linkplain + * ResourceBundle.Control#getFallbackLocale(String,Locale) fallback + * Locale} search is performed.) If a time zone name in any + * {@code Locale} of the search path, including {@link Locale#ROOT}, is + * found, the name is returned. Otherwise, a string in the + * normalized custom ID format is returned. + * + * @param daylight {@code true} specifying a Daylight Saving Time name, or + * {@code false} specifying a Standard Time name + * @param style either {@link #LONG} or {@link #SHORT} + * @param locale the locale in which to supply the display name. + * @return the human-readable name of this time zone in the given locale. + * @exception IllegalArgumentException if {@code style} is invalid. + * @exception NullPointerException if {@code locale} is {@code null}. + * @since 1.2 + * @see java.text.DateFormatSymbols#getZoneStrings() + */ + public String getDisplayName(boolean daylight, int style, Locale locale) { + if (style != SHORT && style != LONG) { + throw new IllegalArgumentException("Illegal style: " + style); + } + + String id = getID(); + String[] names = getDisplayNames(id, locale); + if (names == null) { + if (id.startsWith("GMT")) { + char sign = id.charAt(3); + if (sign == '+' || sign == '-') { + return id; + } + } + int offset = getRawOffset(); + if (daylight) { + offset += getDSTSavings(); + } + // return ZoneInfoFile.toCustomID(offset); + } + + int index = daylight ? 3 : 1; + if (style == SHORT) { + index++; + } + return names[index]; + } + + private static class DisplayNames { + // Cache for managing display names per timezone per locale + // The structure is: + // Map(key=id, value=SoftReference(Map(key=locale, value=displaynames))) + private static final Map>> CACHE = + new ConcurrentHashMap>>(); + } + + private static final String[] getDisplayNames(String id, Locale locale) { + Map>> displayNames = DisplayNames.CACHE; + + SoftReference> ref = displayNames.get(id); + if (ref != null) { + Map perLocale = ref.get(); + if (perLocale != null) { + String[] names = perLocale.get(locale); + if (names != null) { + return names; + } + names = null; // TimeZoneNameUtility.retrieveDisplayNames(id, locale); + if (names != null) { + perLocale.put(locale, names); + } + return names; + } + } + + String[] names = null; // TimeZoneNameUtility.retrieveDisplayNames(id, locale); + if (names != null) { + Map perLocale = new ConcurrentHashMap(); + perLocale.put(locale, names); + ref = new SoftReference>(perLocale); + displayNames.put(id, ref); + } + return names; + } + + /** + * Returns the amount of time to be added to local standard time + * to get local wall clock time. + * + *

The default implementation returns 3600000 milliseconds + * (i.e., one hour) if a call to {@link #useDaylightTime()} + * returns {@code true}. Otherwise, 0 (zero) is returned. + * + *

If an underlying {@code TimeZone} implementation subclass + * supports historical and future Daylight Saving Time schedule + * changes, this method returns the amount of saving time of the + * last known Daylight Saving Time rule that can be a future + * prediction. + * + *

If the amount of saving time at any given time stamp is + * required, construct a {@link Calendar} with this {@code + * TimeZone} and the time stamp, and call {@link Calendar#get(int) + * Calendar.get}{@code (}{@link Calendar#DST_OFFSET}{@code )}. + * + * @return the amount of saving time in milliseconds + * @since 1.4 + * @see #inDaylightTime(Date) + * @see #getOffset(long) + * @see #getOffset(int,int,int,int,int,int) + * @see Calendar#ZONE_OFFSET + */ + public int getDSTSavings() { + if (useDaylightTime()) { + return 3600000; + } + return 0; + } + + /** + * Queries if this {@code TimeZone} uses Daylight Saving Time. + * + *

If an underlying {@code TimeZone} implementation subclass + * supports historical and future Daylight Saving Time schedule + * changes, this method refers to the last known Daylight Saving Time + * rule that can be a future prediction and may not be the same as + * the current rule. Consider calling {@link #observesDaylightTime()} + * if the current rule should also be taken into account. + * + * @return {@code true} if this {@code TimeZone} uses Daylight Saving Time, + * {@code false}, otherwise. + * @see #inDaylightTime(Date) + * @see Calendar#DST_OFFSET + */ + public abstract boolean useDaylightTime(); + + /** + * Returns {@code true} if this {@code TimeZone} is currently in + * Daylight Saving Time, or if a transition from Standard Time to + * Daylight Saving Time occurs at any future time. + * + *

The default implementation returns {@code true} if + * {@code useDaylightTime()} or {@code inDaylightTime(new Date())} + * returns {@code true}. + * + * @return {@code true} if this {@code TimeZone} is currently in + * Daylight Saving Time, or if a transition from Standard Time to + * Daylight Saving Time occurs at any future time; {@code false} + * otherwise. + * @since 1.7 + * @see #useDaylightTime() + * @see #inDaylightTime(Date) + * @see Calendar#DST_OFFSET + */ + public boolean observesDaylightTime() { + return useDaylightTime() || inDaylightTime(new Date()); + } + + /** + * Queries if the given {@code date} is in Daylight Saving Time in + * this time zone. + * + * @param date the given Date. + * @return {@code true} if the given date is in Daylight Saving Time, + * {@code false}, otherwise. + */ + abstract public boolean inDaylightTime(Date date); + + /** + * Gets the TimeZone for the given ID. + * + * @param ID the ID for a TimeZone, either an abbreviation + * such as "PST", a full name such as "America/Los_Angeles", or a custom + * ID such as "GMT-8:00". Note that the support of abbreviations is + * for JDK 1.1.x compatibility only and full names should be used. + * + * @return the specified TimeZone, or the GMT zone if the given ID + * cannot be understood. + */ + public static synchronized TimeZone getTimeZone(String ID) { + return getTimeZone(ID, true); + } + + private static TimeZone getTimeZone(String ID, boolean fallback) { +// TimeZone tz = ZoneInfo.getTimeZone(ID); +// if (tz == null) { +// tz = parseCustomTimeZone(ID); +// if (tz == null && fallback) { +// tz = new ZoneInfo(GMT_ID, 0); +// } +// } +// return tz; + return TimeZone.NO_TIMEZONE; + } + + /** + * Gets the available IDs according to the given time zone offset in milliseconds. + * + * @param rawOffset the given time zone GMT offset in milliseconds. + * @return an array of IDs, where the time zone for that ID has + * the specified GMT offset. For example, "America/Phoenix" and "America/Denver" + * both have GMT-07:00, but differ in daylight saving behavior. + * @see #getRawOffset() + */ + public static synchronized String[] getAvailableIDs(int rawOffset) { + return new String[0];//ZoneInfo.getAvailableIDs(rawOffset); + } + + /** + * Gets all the available IDs supported. + * @return an array of IDs. + */ + public static synchronized String[] getAvailableIDs() { + return new String[0];//return ZoneInfo.getAvailableIDs(); + } + + /** + * Gets the platform defined TimeZone ID. + **/ + private static native String getSystemTimeZoneID(String javaHome, + String country); + + /** + * Gets the custom time zone ID based on the GMT offset of the + * platform. (e.g., "GMT+08:00") + */ + private static native String getSystemGMTOffsetID(); + + /** + * Gets the default TimeZone for this host. + * The source of the default TimeZone + * may vary with implementation. + * @return a default TimeZone. + * @see #setDefault + */ + public static TimeZone getDefault() { + return (TimeZone) getDefaultRef().clone(); + } + + /** + * Returns the reference to the default TimeZone object. This + * method doesn't create a clone. + */ + static TimeZone getDefaultRef() { + TimeZone defaultZone = null;//defaultZoneTL.get(); + if (defaultZone == null) { + defaultZone = defaultTimeZone; + if (defaultZone == null) { + // Need to initialize the default time zone. + defaultZone = TimeZone.NO_TIMEZONE; + assert defaultZone != null; + } + } + // Don't clone here. + return defaultZone; + } + + private static boolean hasPermission() { + boolean hasPermission = false; + return hasPermission; + } + + /** + * Sets the TimeZone that is + * returned by the getDefault method. If zone + * is null, reset the default to the value it had originally when the + * VM first started. + * @param zone the new default time zone + * @see #getDefault + */ + public static void setDefault(TimeZone zone) + { + if (hasPermission()) { + synchronized (TimeZone.class) { + defaultTimeZone = zone; + // defaultZoneTL.set(null); + } + } else { + //defaultZoneTL.set(zone); + } + } + + /** + * Returns true if this zone has the same rule and offset as another zone. + * That is, if this zone differs only in ID, if at all. Returns false + * if the other zone is null. + * @param other the TimeZone object to be compared with + * @return true if the other zone is not null and is the same as this one, + * with the possible exception of the ID + * @since 1.2 + */ + public boolean hasSameRules(TimeZone other) { + return other != null && getRawOffset() == other.getRawOffset() && + useDaylightTime() == other.useDaylightTime(); + } + + /** + * Creates a copy of this TimeZone. + * + * @return a clone of this TimeZone + */ + public Object clone() + { + try { + TimeZone other = (TimeZone) super.clone(); + other.ID = ID; + return other; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * The null constant as a TimeZone. + */ + static final TimeZone NO_TIMEZONE = null; + + // =======================privates=============================== + + /** + * The string identifier of this TimeZone. This is a + * programmatic identifier used internally to look up TimeZone + * objects from the system table and also to map them to their localized + * display names. ID values are unique in the system + * table but may not be for dynamically created zones. + * @serial + */ + private String ID; + private static volatile TimeZone defaultTimeZone; + + static final String GMT_ID = "GMT"; + private static final int GMT_ID_LENGTH = 3; + + /** + * Parses a custom time zone identifier and returns a corresponding zone. + * This method doesn't support the RFC 822 time zone format. (e.g., +hhmm) + * + * @param id a string of the custom ID form. + * @return a newly created TimeZone with the given offset and + * no daylight saving time, or null if the id cannot be parsed. + */ + private static final TimeZone parseCustomTimeZone(String id) { + return null; + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/Timer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/Timer.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,731 @@ +/* + * Copyright (c) 1999, 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.util; +import java.util.Date; +import java.util.concurrent.atomic.AtomicInteger; +import org.apidesign.bck2brwsr.core.JavaScriptBody; + +/** + * A facility for threads to schedule tasks for future execution in a + * background thread. Tasks may be scheduled for one-time execution, or for + * repeated execution at regular intervals. + * + *

Corresponding to each Timer object is a single background + * thread that is used to execute all of the timer's tasks, sequentially. + * Timer tasks should complete quickly. If a timer task takes excessive time + * to complete, it "hogs" the timer's task execution thread. This can, in + * turn, delay the execution of subsequent tasks, which may "bunch up" and + * execute in rapid succession when (and if) the offending task finally + * completes. + * + *

After the last live reference to a Timer object goes away + * and all outstanding tasks have completed execution, the timer's task + * execution thread terminates gracefully (and becomes subject to garbage + * collection). However, this can take arbitrarily long to occur. By + * default, the task execution thread does not run as a daemon thread, + * so it is capable of keeping an application from terminating. If a caller + * wants to terminate a timer's task execution thread rapidly, the caller + * should invoke the timer's cancel method. + * + *

If the timer's task execution thread terminates unexpectedly, for + * example, because its stop method is invoked, any further + * attempt to schedule a task on the timer will result in an + * IllegalStateException, as if the timer's cancel + * method had been invoked. + * + *

This class is thread-safe: multiple threads can share a single + * Timer object without the need for external synchronization. + * + *

This class does not offer real-time guarantees: it schedules + * tasks using the Object.wait(long) method. + * + *

Java 5.0 introduced the {@code java.util.concurrent} package and + * one of the concurrency utilities therein is the {@link + * java.util.concurrent.ScheduledThreadPoolExecutor + * ScheduledThreadPoolExecutor} which is a thread pool for repeatedly + * executing tasks at a given rate or delay. It is effectively a more + * versatile replacement for the {@code Timer}/{@code TimerTask} + * combination, as it allows multiple service threads, accepts various + * time units, and doesn't require subclassing {@code TimerTask} (just + * implement {@code Runnable}). Configuring {@code + * ScheduledThreadPoolExecutor} with one thread makes it equivalent to + * {@code Timer}. + * + *

Implementation note: This class scales to large numbers of concurrently + * scheduled tasks (thousands should present no problem). Internally, + * it uses a binary heap to represent its task queue, so the cost to schedule + * a task is O(log n), where n is the number of concurrently scheduled tasks. + * + *

Implementation note: All constructors start a timer thread. + * + * @author Josh Bloch + * @see TimerTask + * @see Object#wait(long) + * @since 1.3 + */ + +public class Timer { + /** + * The timer task queue. This data structure is shared with the timer + * thread. The timer produces tasks, via its various schedule calls, + * and the timer thread consumes, executing timer tasks as appropriate, + * and removing them from the queue when they're obsolete. + */ + private final TaskQueue queue = new TaskQueue(); + + /** + * The timer thread. + */ + private final TimerThread thread = new TimerThread(queue); + + /** + * This object causes the timer's task execution thread to exit + * gracefully when there are no live references to the Timer object and no + * tasks in the timer queue. It is used in preference to a finalizer on + * Timer as such a finalizer would be susceptible to a subclass's + * finalizer forgetting to call it. + */ + private final Object threadReaper = new Object() { + protected void finalize() throws Throwable { + synchronized(queue) { + thread.newTasksMayBeScheduled = false; + thread.notifyQueue(1); // In case queue is empty. + } + } + }; + + /** + * This ID is used to generate thread names. + */ + private final static AtomicInteger nextSerialNumber = new AtomicInteger(0); + private static int serialNumber() { + return nextSerialNumber.getAndIncrement(); + } + + /** + * Creates a new timer. The associated thread does not + * {@linkplain Thread#setDaemon run as a daemon}. + */ + public Timer() { + this("Timer-" + serialNumber()); + } + + /** + * Creates a new timer whose associated thread may be specified to + * {@linkplain Thread#setDaemon run as a daemon}. + * A daemon thread is called for if the timer will be used to + * schedule repeating "maintenance activities", which must be + * performed as long as the application is running, but should not + * prolong the lifetime of the application. + * + * @param isDaemon true if the associated thread should run as a daemon. + */ + public Timer(boolean isDaemon) { + this("Timer-" + serialNumber(), isDaemon); + } + + /** + * Creates a new timer whose associated thread has the specified name. + * The associated thread does not + * {@linkplain Thread#setDaemon run as a daemon}. + * + * @param name the name of the associated thread + * @throws NullPointerException if {@code name} is null + * @since 1.5 + */ + public Timer(String name) { + } + + /** + * Creates a new timer whose associated thread has the specified name, + * and may be specified to + * {@linkplain Thread#setDaemon run as a daemon}. + * + * @param name the name of the associated thread + * @param isDaemon true if the associated thread should run as a daemon + * @throws NullPointerException if {@code name} is null + * @since 1.5 + */ + public Timer(String name, boolean isDaemon) { + } + + /** + * Schedules the specified task for execution after the specified delay. + * + * @param task task to be scheduled. + * @param delay delay in milliseconds before task is to be executed. + * @throws IllegalArgumentException if delay is negative, or + * delay + System.currentTimeMillis() is negative. + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} is null + */ + public void schedule(TimerTask task, long delay) { + if (delay < 0) + throw new IllegalArgumentException("Negative delay."); + sched(task, System.currentTimeMillis()+delay, 0); + } + + /** + * Schedules the specified task for execution at the specified time. If + * the time is in the past, the task is scheduled for immediate execution. + * + * @param task task to be scheduled. + * @param time time at which task is to be executed. + * @throws IllegalArgumentException if time.getTime() is negative. + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} or {@code time} is null + */ + public void schedule(TimerTask task, Date time) { + sched(task, time.getTime(), 0); + } + + /** + * Schedules the specified task for repeated fixed-delay execution, + * beginning after the specified delay. Subsequent executions take place + * at approximately regular intervals separated by the specified period. + * + *

In fixed-delay execution, each execution is scheduled relative to + * the actual execution time of the previous execution. If an execution + * is delayed for any reason (such as garbage collection or other + * background activity), subsequent executions will be delayed as well. + * In the long run, the frequency of execution will generally be slightly + * lower than the reciprocal of the specified period (assuming the system + * clock underlying Object.wait(long) is accurate). + * + *

Fixed-delay execution is appropriate for recurring activities + * that require "smoothness." In other words, it is appropriate for + * activities where it is more important to keep the frequency accurate + * in the short run than in the long run. This includes most animation + * tasks, such as blinking a cursor at regular intervals. It also includes + * tasks wherein regular activity is performed in response to human + * input, such as automatically repeating a character as long as a key + * is held down. + * + * @param task task to be scheduled. + * @param delay delay in milliseconds before task is to be executed. + * @param period time in milliseconds between successive task executions. + * @throws IllegalArgumentException if {@code delay < 0}, or + * {@code delay + System.currentTimeMillis() < 0}, or + * {@code period <= 0} + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} is null + */ + public void schedule(TimerTask task, long delay, long period) { + if (delay < 0) + throw new IllegalArgumentException("Negative delay."); + if (period <= 0) + throw new IllegalArgumentException("Non-positive period."); + sched(task, System.currentTimeMillis()+delay, -period); + } + + /** + * Schedules the specified task for repeated fixed-delay execution, + * beginning at the specified time. Subsequent executions take place at + * approximately regular intervals, separated by the specified period. + * + *

In fixed-delay execution, each execution is scheduled relative to + * the actual execution time of the previous execution. If an execution + * is delayed for any reason (such as garbage collection or other + * background activity), subsequent executions will be delayed as well. + * In the long run, the frequency of execution will generally be slightly + * lower than the reciprocal of the specified period (assuming the system + * clock underlying Object.wait(long) is accurate). As a + * consequence of the above, if the scheduled first time is in the past, + * it is scheduled for immediate execution. + * + *

Fixed-delay execution is appropriate for recurring activities + * that require "smoothness." In other words, it is appropriate for + * activities where it is more important to keep the frequency accurate + * in the short run than in the long run. This includes most animation + * tasks, such as blinking a cursor at regular intervals. It also includes + * tasks wherein regular activity is performed in response to human + * input, such as automatically repeating a character as long as a key + * is held down. + * + * @param task task to be scheduled. + * @param firstTime First time at which task is to be executed. + * @param period time in milliseconds between successive task executions. + * @throws IllegalArgumentException if {@code firstTime.getTime() < 0}, or + * {@code period <= 0} + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} or {@code firstTime} is null + */ + public void schedule(TimerTask task, Date firstTime, long period) { + if (period <= 0) + throw new IllegalArgumentException("Non-positive period."); + sched(task, firstTime.getTime(), -period); + } + + /** + * Schedules the specified task for repeated fixed-rate execution, + * beginning after the specified delay. Subsequent executions take place + * at approximately regular intervals, separated by the specified period. + * + *

In fixed-rate execution, each execution is scheduled relative to the + * scheduled execution time of the initial execution. If an execution is + * delayed for any reason (such as garbage collection or other background + * activity), two or more executions will occur in rapid succession to + * "catch up." In the long run, the frequency of execution will be + * exactly the reciprocal of the specified period (assuming the system + * clock underlying Object.wait(long) is accurate). + * + *

Fixed-rate execution is appropriate for recurring activities that + * are sensitive to absolute time, such as ringing a chime every + * hour on the hour, or running scheduled maintenance every day at a + * particular time. It is also appropriate for recurring activities + * where the total time to perform a fixed number of executions is + * important, such as a countdown timer that ticks once every second for + * ten seconds. Finally, fixed-rate execution is appropriate for + * scheduling multiple repeating timer tasks that must remain synchronized + * with respect to one another. + * + * @param task task to be scheduled. + * @param delay delay in milliseconds before task is to be executed. + * @param period time in milliseconds between successive task executions. + * @throws IllegalArgumentException if {@code delay < 0}, or + * {@code delay + System.currentTimeMillis() < 0}, or + * {@code period <= 0} + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} is null + */ + public void scheduleAtFixedRate(TimerTask task, long delay, long period) { + if (delay < 0) + throw new IllegalArgumentException("Negative delay."); + if (period <= 0) + throw new IllegalArgumentException("Non-positive period."); + sched(task, System.currentTimeMillis()+delay, period); + } + + /** + * Schedules the specified task for repeated fixed-rate execution, + * beginning at the specified time. Subsequent executions take place at + * approximately regular intervals, separated by the specified period. + * + *

In fixed-rate execution, each execution is scheduled relative to the + * scheduled execution time of the initial execution. If an execution is + * delayed for any reason (such as garbage collection or other background + * activity), two or more executions will occur in rapid succession to + * "catch up." In the long run, the frequency of execution will be + * exactly the reciprocal of the specified period (assuming the system + * clock underlying Object.wait(long) is accurate). As a + * consequence of the above, if the scheduled first time is in the past, + * then any "missed" executions will be scheduled for immediate "catch up" + * execution. + * + *

Fixed-rate execution is appropriate for recurring activities that + * are sensitive to absolute time, such as ringing a chime every + * hour on the hour, or running scheduled maintenance every day at a + * particular time. It is also appropriate for recurring activities + * where the total time to perform a fixed number of executions is + * important, such as a countdown timer that ticks once every second for + * ten seconds. Finally, fixed-rate execution is appropriate for + * scheduling multiple repeating timer tasks that must remain synchronized + * with respect to one another. + * + * @param task task to be scheduled. + * @param firstTime First time at which task is to be executed. + * @param period time in milliseconds between successive task executions. + * @throws IllegalArgumentException if {@code firstTime.getTime() < 0} or + * {@code period <= 0} + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} or {@code firstTime} is null + */ + public void scheduleAtFixedRate(TimerTask task, Date firstTime, + long period) { + if (period <= 0) + throw new IllegalArgumentException("Non-positive period."); + sched(task, firstTime.getTime(), period); + } + + /** + * Schedule the specified timer task for execution at the specified + * time with the specified period, in milliseconds. If period is + * positive, the task is scheduled for repeated execution; if period is + * zero, the task is scheduled for one-time execution. Time is specified + * in Date.getTime() format. This method checks timer state, task state, + * and initial execution time, but not period. + * + * @throws IllegalArgumentException if time is negative. + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} is null + */ + private void sched(TimerTask task, long time, long period) { + if (time < 0) + throw new IllegalArgumentException("Illegal execution time."); + + // Constrain value of period sufficiently to prevent numeric + // overflow while still being effectively infinitely large. + if (Math.abs(period) > (Long.MAX_VALUE >> 1)) + period >>= 1; + + synchronized(queue) { + if (!thread.newTasksMayBeScheduled) + throw new IllegalStateException("Timer already cancelled."); + + synchronized(task.lock) { + if (task.state != TimerTask.VIRGIN) + throw new IllegalStateException( + "Task already scheduled or cancelled"); + task.nextExecutionTime = time; + task.period = period; + task.state = TimerTask.SCHEDULED; + } + + queue.add(task); + if (queue.getMin() == task) + thread.notifyQueue(1); + } + } + + /** + * Terminates this timer, discarding any currently scheduled tasks. + * Does not interfere with a currently executing task (if it exists). + * Once a timer has been terminated, its execution thread terminates + * gracefully, and no more tasks may be scheduled on it. + * + *

Note that calling this method from within the run method of a + * timer task that was invoked by this timer absolutely guarantees that + * the ongoing task execution is the last task execution that will ever + * be performed by this timer. + * + *

This method may be called repeatedly; the second and subsequent + * calls have no effect. + */ + public void cancel() { + synchronized(queue) { + thread.newTasksMayBeScheduled = false; + queue.clear(); + thread.notifyQueue(1); // In case queue was already empty. + } + } + + /** + * Removes all cancelled tasks from this timer's task queue. Calling + * this method has no effect on the behavior of the timer, but + * eliminates the references to the cancelled tasks from the queue. + * If there are no external references to these tasks, they become + * eligible for garbage collection. + * + *

Most programs will have no need to call this method. + * It is designed for use by the rare application that cancels a large + * number of tasks. Calling this method trades time for space: the + * runtime of the method may be proportional to n + c log n, where n + * is the number of tasks in the queue and c is the number of cancelled + * tasks. + * + *

Note that it is permissible to call this method from within a + * a task scheduled on this timer. + * + * @return the number of tasks removed from the queue. + * @since 1.5 + */ + public int purge() { + int result = 0; + + synchronized(queue) { + for (int i = queue.size(); i > 0; i--) { + if (queue.get(i).state == TimerTask.CANCELLED) { + queue.quickRemove(i); + result++; + } + } + + if (result != 0) + queue.heapify(); + } + + return result; + } +} + +/** + * This "helper class" implements the timer's task execution thread, which + * waits for tasks on the timer queue, executions them when they fire, + * reschedules repeating tasks, and removes cancelled tasks and spent + * non-repeating tasks from the queue. + */ +class TimerThread implements Runnable { + /** + * This flag is set to false by the reaper to inform us that there + * are no more live references to our Timer object. Once this flag + * is true and there are no more tasks in our queue, there is no + * work left for us to do, so we terminate gracefully. Note that + * this field is protected by queue's monitor! + */ + boolean newTasksMayBeScheduled = true; + + /** + * Our Timer's queue. We store this reference in preference to + * a reference to the Timer so the reference graph remains acyclic. + * Otherwise, the Timer would never be garbage-collected and this + * thread would never go away. + */ + private TaskQueue queue; + + TimerThread(TaskQueue queue) { + this.queue = queue; + } + + void notifyQueue(int delay) { + if (delay < 1) { + delay = 1; + } + setTimeout(delay, this); + } + + @JavaScriptBody(args = { "delay", "r" }, body = "window.setTimeout(function() { r.run__V(); }, delay);") + private static native void setTimeout(int delay, Runnable r); + + public void run() { + mainLoop(1); +// try { +// mainLoop(0); +// } finally { +// // Someone killed this Thread, behave as if Timer cancelled +// synchronized(queue) { +// newTasksMayBeScheduled = false; +// queue.clear(); // Eliminate obsolete references +// } +// } + } + + /** + * The main timer loop. (See class comment.) + */ + private void mainLoop(int inc) { + for (int i = 0; i < 1; i += inc) { + try { + TimerTask task; + boolean taskFired; + synchronized(queue) { + // Wait for queue to become non-empty + while (queue.isEmpty() && newTasksMayBeScheduled) + break; + if (queue.isEmpty()) + break; // Queue is empty and will forever remain; die + + // Queue nonempty; look at first evt and do the right thing + long currentTime, executionTime; + task = queue.getMin(); + synchronized(task.lock) { + if (task.state == TimerTask.CANCELLED) { + queue.removeMin(); + continue; // No action required, poll queue again + } + currentTime = System.currentTimeMillis(); + executionTime = task.nextExecutionTime; + if (taskFired = (executionTime<=currentTime)) { + if (task.period == 0) { // Non-repeating, remove + queue.removeMin(); + task.state = TimerTask.EXECUTED; + } else { // Repeating task, reschedule + queue.rescheduleMin( + task.period<0 ? currentTime - task.period + : executionTime + task.period); + } + } + } + if (!taskFired) { + // Task hasn't yet fired; wait + notifyQueue((int)(executionTime - currentTime)); + return; + } + } + if (taskFired) // Task fired; run it, holding no locks + task.run(); + } catch(Exception e) { + e.printStackTrace(); + } + } + } +} + +/** + * This class represents a timer task queue: a priority queue of TimerTasks, + * ordered on nextExecutionTime. Each Timer object has one of these, which it + * shares with its TimerThread. Internally this class uses a heap, which + * offers log(n) performance for the add, removeMin and rescheduleMin + * operations, and constant time performance for the getMin operation. + */ +class TaskQueue { + /** + * Priority queue represented as a balanced binary heap: the two children + * of queue[n] are queue[2*n] and queue[2*n+1]. The priority queue is + * ordered on the nextExecutionTime field: The TimerTask with the lowest + * nextExecutionTime is in queue[1] (assuming the queue is nonempty). For + * each node n in the heap, and each descendant of n, d, + * n.nextExecutionTime <= d.nextExecutionTime. + */ + private TimerTask[] queue = new TimerTask[128]; + + /** + * The number of tasks in the priority queue. (The tasks are stored in + * queue[1] up to queue[size]). + */ + private int size = 0; + + /** + * Returns the number of tasks currently on the queue. + */ + int size() { + return size; + } + + /** + * Adds a new task to the priority queue. + */ + void add(TimerTask task) { + // Grow backing store if necessary + if (size + 1 == queue.length) + queue = Arrays.copyOf(queue, 2*queue.length); + + queue[++size] = task; + fixUp(size); + } + + /** + * Return the "head task" of the priority queue. (The head task is an + * task with the lowest nextExecutionTime.) + */ + TimerTask getMin() { + return queue[1]; + } + + /** + * Return the ith task in the priority queue, where i ranges from 1 (the + * head task, which is returned by getMin) to the number of tasks on the + * queue, inclusive. + */ + TimerTask get(int i) { + return queue[i]; + } + + /** + * Remove the head task from the priority queue. + */ + void removeMin() { + queue[1] = queue[size]; + queue[size--] = null; // Drop extra reference to prevent memory leak + fixDown(1); + } + + /** + * Removes the ith element from queue without regard for maintaining + * the heap invariant. Recall that queue is one-based, so + * 1 <= i <= size. + */ + void quickRemove(int i) { + assert i <= size; + + queue[i] = queue[size]; + queue[size--] = null; // Drop extra ref to prevent memory leak + } + + /** + * Sets the nextExecutionTime associated with the head task to the + * specified value, and adjusts priority queue accordingly. + */ + void rescheduleMin(long newTime) { + queue[1].nextExecutionTime = newTime; + fixDown(1); + } + + /** + * Returns true if the priority queue contains no elements. + */ + boolean isEmpty() { + return size==0; + } + + /** + * Removes all elements from the priority queue. + */ + void clear() { + // Null out task references to prevent memory leak + for (int i=1; i<=size; i++) + queue[i] = null; + + size = 0; + } + + /** + * Establishes the heap invariant (described above) assuming the heap + * satisfies the invariant except possibly for the leaf-node indexed by k + * (which may have a nextExecutionTime less than its parent's). + * + * This method functions by "promoting" queue[k] up the hierarchy + * (by swapping it with its parent) repeatedly until queue[k]'s + * nextExecutionTime is greater than or equal to that of its parent. + */ + private void fixUp(int k) { + while (k > 1) { + int j = k >> 1; + if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime) + break; + TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; + k = j; + } + } + + /** + * Establishes the heap invariant (described above) in the subtree + * rooted at k, which is assumed to satisfy the heap invariant except + * possibly for node k itself (which may have a nextExecutionTime greater + * than its children's). + * + * This method functions by "demoting" queue[k] down the hierarchy + * (by swapping it with its smaller child) repeatedly until queue[k]'s + * nextExecutionTime is less than or equal to those of its children. + */ + private void fixDown(int k) { + int j; + while ((j = k << 1) <= size && j > 0) { + if (j < size && + queue[j].nextExecutionTime > queue[j+1].nextExecutionTime) + j++; // j indexes smallest kid + if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime) + break; + TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; + k = j; + } + } + + /** + * Establishes the heap invariant (described above) in the entire tree, + * assuming nothing about the order of the elements prior to the call. + */ + void heapify() { + for (int i = size/2; i >= 1; i--) + fixDown(i); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/TimerTask.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/TimerTask.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,158 @@ +/* + * Copyright (c) 1999, 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 java.util; + +/** + * A task that can be scheduled for one-time or repeated execution by a Timer. + * + * @author Josh Bloch + * @see Timer + * @since 1.3 + */ + +public abstract class TimerTask implements Runnable { + /** + * This object is used to control access to the TimerTask internals. + */ + final Object lock = new Object(); + + /** + * The state of this task, chosen from the constants below. + */ + int state = VIRGIN; + + /** + * This task has not yet been scheduled. + */ + static final int VIRGIN = 0; + + /** + * This task is scheduled for execution. If it is a non-repeating task, + * it has not yet been executed. + */ + static final int SCHEDULED = 1; + + /** + * This non-repeating task has already executed (or is currently + * executing) and has not been cancelled. + */ + static final int EXECUTED = 2; + + /** + * This task has been cancelled (with a call to TimerTask.cancel). + */ + static final int CANCELLED = 3; + + /** + * Next execution time for this task in the format returned by + * System.currentTimeMillis, assuming this task is scheduled for execution. + * For repeating tasks, this field is updated prior to each task execution. + */ + long nextExecutionTime; + + /** + * Period in milliseconds for repeating tasks. A positive value indicates + * fixed-rate execution. A negative value indicates fixed-delay execution. + * A value of 0 indicates a non-repeating task. + */ + long period = 0; + + /** + * Creates a new timer task. + */ + protected TimerTask() { + } + + /** + * The action to be performed by this timer task. + */ + public abstract void run(); + + /** + * Cancels this timer task. If the task has been scheduled for one-time + * execution and has not yet run, or has not yet been scheduled, it will + * never run. If the task has been scheduled for repeated execution, it + * will never run again. (If the task is running when this call occurs, + * the task will run to completion, but will never run again.) + * + *

Note that calling this method from within the run method of + * a repeating timer task absolutely guarantees that the timer task will + * not run again. + * + *

This method may be called repeatedly; the second and subsequent + * calls have no effect. + * + * @return true if this task is scheduled for one-time execution and has + * not yet run, or this task is scheduled for repeated execution. + * Returns false if the task was scheduled for one-time execution + * and has already run, or if the task was never scheduled, or if + * the task was already cancelled. (Loosely speaking, this method + * returns true if it prevents one or more scheduled + * executions from taking place.) + */ + public boolean cancel() { + synchronized(lock) { + boolean result = (state == SCHEDULED); + state = CANCELLED; + return result; + } + } + + /** + * Returns the scheduled execution time of the most recent + * actual execution of this task. (If this method is invoked + * while task execution is in progress, the return value is the scheduled + * execution time of the ongoing task execution.) + * + *

This method is typically invoked from within a task's run method, to + * determine whether the current execution of the task is sufficiently + * timely to warrant performing the scheduled activity: + *

+     *   public void run() {
+     *       if (System.currentTimeMillis() - scheduledExecutionTime() >=
+     *           MAX_TARDINESS)
+     *               return;  // Too late; skip this execution.
+     *       // Perform the task
+     *   }
+     * 
+ * This method is typically not used in conjunction with + * fixed-delay execution repeating tasks, as their scheduled + * execution times are allowed to drift over time, and so are not terribly + * significant. + * + * @return the time at which the most recent execution of this task was + * scheduled to occur, in the format returned by Date.getTime(). + * The return value is undefined if the task has yet to commence + * its first execution. + * @see Date#getTime() + */ + public long scheduledExecutionTime() { + synchronized(lock) { + return (period < 0 ? nextExecutionTime + period + : nextExecutionTime - period); + } + } +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/concurrent/ConcurrentHashMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/ConcurrentHashMap.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,327 @@ +/* + * 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; + +import java.util.*; +import java.io.Serializable; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * A hash table supporting full concurrency of retrievals and adjustable + * expected concurrency for updates. This class obeys the same functional + * specification as {@link java.util.Hashtable}, and includes versions of + * methods corresponding to each method of + * Hashtable. However, even though all operations are thread-safe, + * retrieval operations do not entail locking, and there is + * not any support for locking the entire table in a way that prevents + * all access. This class is fully interoperable with Hashtable in + * programs that rely on its thread safety but not on its synchronization + * details. + * + *

+ * Retrieval operations (including get) generally do not block, so may + * overlap with update operations (including + * put and remove). Retrievals reflect the results of the most + * recently completed update operations holding upon their onset. For + * aggregate operations such as putAll + * and clear, concurrent retrievals may reflect insertion or removal of + * only some entries. Similarly, Iterators and Enumerations return elements + * reflecting the state of the hash table at some point at or since the creation + * of the iterator/enumeration. They do not throw + * {@link ConcurrentModificationException}. However, iterators are designed to + * be used by only one thread at a time. + * + *

+ * The allowed concurrency among update operations is guided by the optional + * concurrencyLevel constructor argument (default 16), which + * is used as a hint for internal sizing. The table is internally partitioned to + * try to permit the indicated number of concurrent updates without contention. + * Because placement in hash tables is essentially random, the actual + * concurrency will vary. Ideally, you should choose a value to accommodate as + * many threads as will ever concurrently modify the table. Using a + * significantly higher value than you need can waste space and time, and a + * significantly lower value can lead to thread contention. But overestimates + * and underestimates within an order of magnitude do not usually have much + * noticeable impact. A value of one is appropriate when it is known that only + * one thread will modify and all others will only read. Also, resizing this or + * any other kind of hash table is a relatively slow operation, so, when + * possible, it is a good idea to provide estimates of expected table sizes in + * constructors. + * + *

+ * This class and its views and iterators implement all of the + * optional methods of the {@link Map} and {@link Iterator} interfaces. + * + *

+ * Like {@link Hashtable} but unlike {@link HashMap}, this class does + * not allow null to be used as a key or value. + * + *

+ * This class is a member of the + * + * Java Collections Framework. + * + * @since 1.5 + * @author Doug Lea + * @param the type of keys maintained by this map + * @param the type of mapped values + */ +public class ConcurrentHashMap extends AbstractMap + implements ConcurrentMap, Serializable { + + private static final long serialVersionUID = 7249069246763182397L; + /** + * The default initial capacity for this table, + * used when not otherwise specified in a constructor. + */ + static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * The default load factor for this table, used when not + * otherwise specified in a constructor. + */ + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * The default concurrency level for this table, used when not + * otherwise specified in a constructor. + */ + static final int DEFAULT_CONCURRENCY_LEVEL = 16; + + private final Map delegate; + + + /** + * Creates a new, empty map with the specified initial + * capacity, load factor and concurrency level. + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements. + * @param loadFactor the load factor threshold, used to control resizing. + * Resizing may be performed when the average number of elements per + * bin exceeds this threshold. + * @param concurrencyLevel the estimated number of concurrently + * updating threads. The implementation performs internal sizing + * to try to accommodate this many threads. + * @throws IllegalArgumentException if the initial capacity is + * negative or the load factor or concurrencyLevel are + * nonpositive. + */ + @SuppressWarnings("unchecked") + public ConcurrentHashMap(int initialCapacity, + float loadFactor, int concurrencyLevel) { + if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) + throw new IllegalArgumentException(); + delegate = new HashMap<>(initialCapacity, loadFactor); + } + + /** + * Creates a new, empty map with the specified initial capacity + * and load factor and with the default concurrencyLevel (16). + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @param loadFactor the load factor threshold, used to control resizing. + * Resizing may be performed when the average number of elements per + * bin exceeds this threshold. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative or the load factor is nonpositive + * + * @since 1.6 + */ + public ConcurrentHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL); + } + + /** + * Creates a new, empty map with the specified initial capacity, + * and with default load factor (0.75) and concurrencyLevel (16). + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative. + */ + public ConcurrentHashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); + } + + /** + * Creates a new, empty map with a default initial capacity (16), + * load factor (0.75) and concurrencyLevel (16). + */ + public ConcurrentHashMap() { + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); + } + + /** + * Creates a new map with the same mappings as the given map. + * The map is created with a capacity of 1.5 times the number + * of mappings in the given map or 16 (whichever is greater), + * and a default load factor (0.75) and concurrencyLevel (16). + * + * @param m the map + */ + public ConcurrentHashMap(Map m) { + this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, + DEFAULT_INITIAL_CAPACITY), + DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); + putAll(m); + } + + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return delegate.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + @Override + public V get(Object key) { + return delegate.get(key); + } + + @Override + public V put(K key, V value) { + return delegate.put(key, value); + } + + @Override + public V remove(Object key) { + return delegate.remove(key); + } + + @Override + public void putAll(Map m) { + delegate.putAll(m); + } + + @Override + public void clear() { + delegate.clear(); + } + + @Override + public Set keySet() { + return delegate.keySet(); + } + + @Override + public Collection values() { + return delegate.values(); + } + + @Override + public Set> entrySet() { + return delegate.entrySet(); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public V putIfAbsent(K key, V value) { + V old = delegate.get(key); + if (old == null) { + return delegate.put(key, value); + } + return old; + } + + @Override + public boolean remove(Object key, Object value) { + if (equals(value, delegate.get(key))) { + delegate.remove(key); + return true; + } else { + return false; + } + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + if (equals(oldValue, delegate.get(key))) { + delegate.put(key, newValue); + return true; + } else { + return false; + } + } + + @Override + public V replace(K key, V value) { + if (delegate.containsKey(key)) { + return delegate.put(key, value); + } else { + return null; + } + } + + private static boolean equals(Object a, Object b) { + if (a == null) { + return b == null; + } else { + return a.equals(b); + } + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/concurrent/ConcurrentMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/ConcurrentMap.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,165 @@ +/* + * 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; +import java.util.Map; + +/** + * A {@link java.util.Map} providing additional atomic + * putIfAbsent, remove, and replace methods. + * + *

Memory consistency effects: As with other concurrent + * collections, actions in a thread prior to placing an object into a + * {@code ConcurrentMap} as a key or value + * happen-before + * actions subsequent to the access or removal of that object from + * the {@code ConcurrentMap} in another thread. + * + *

This interface is a member of the + * + * Java Collections Framework. + * + * @since 1.5 + * @author Doug Lea + * @param the type of keys maintained by this map + * @param the type of mapped values + */ +public interface ConcurrentMap extends Map { + /** + * If the specified key is not already associated + * with a value, associate it with the given value. + * This is equivalent to + *

+     *   if (!map.containsKey(key))
+     *       return map.put(key, value);
+     *   else
+     *       return map.get(key);
+ * except that the action is performed atomically. + * + * @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 the specified key, or + * null if there was no mapping for the key. + * (A null return can also indicate that the map + * previously associated null with the key, + * if the implementation supports null values.) + * @throws UnsupportedOperationException if the put operation + * is not supported by this map + * @throws ClassCastException if the class of the specified key or value + * prevents it from being stored in this map + * @throws NullPointerException if the specified key or value is null, + * and this map does not permit null keys or values + * @throws IllegalArgumentException if some property of the specified key + * or value prevents it from being stored in this map + * + */ + V putIfAbsent(K key, V value); + + /** + * Removes the entry for a key only if currently mapped to a given value. + * This is equivalent to + *
+     *   if (map.containsKey(key) && map.get(key).equals(value)) {
+     *       map.remove(key);
+     *       return true;
+     *   } else return false;
+ * except that the action is performed atomically. + * + * @param key key with which the specified value is associated + * @param value value expected to be associated with the specified key + * @return true if the value was removed + * @throws UnsupportedOperationException if the remove operation + * is not supported by this map + * @throws ClassCastException if the key or value is of an inappropriate + * type for this map + * (optional) + * @throws NullPointerException if the specified key or value is null, + * and this map does not permit null keys or values + * (optional) + */ + boolean remove(Object key, Object value); + + /** + * Replaces the entry for a key only if currently mapped to a given value. + * This is equivalent to + *
+     *   if (map.containsKey(key) && map.get(key).equals(oldValue)) {
+     *       map.put(key, newValue);
+     *       return true;
+     *   } else return false;
+ * except that the action is performed atomically. + * + * @param key key with which the specified value is associated + * @param oldValue value expected to be associated with the specified key + * @param newValue value to be associated with the specified key + * @return true if the value was replaced + * @throws UnsupportedOperationException if the put operation + * is not supported by this map + * @throws ClassCastException if the class of a specified key or value + * prevents it from being stored in this map + * @throws NullPointerException if a specified key or value is null, + * and this map does not permit null keys or values + * @throws IllegalArgumentException if some property of a specified key + * or value prevents it from being stored in this map + */ + boolean replace(K key, V oldValue, V newValue); + + /** + * Replaces the entry for a key only if currently mapped to some value. + * This is equivalent to + *
+     *   if (map.containsKey(key)) {
+     *       return map.put(key, value);
+     *   } else return null;
+ * except that the action is performed atomically. + * + * @param key key with which the specified value is associated + * @param value value to be associated with the specified key + * @return the previous value associated with the specified key, or + * null if there was no mapping for the key. + * (A null return can also indicate that the map + * previously associated null with the key, + * if the implementation supports null values.) + * @throws UnsupportedOperationException if the put operation + * is not supported by this map + * @throws ClassCastException if the class of the specified key or value + * prevents it from being stored in this map + * @throws NullPointerException if the specified key or value is null, + * and this map does not permit null keys or values + * @throws IllegalArgumentException if some property of the specified key + * or value prevents it from being stored in this map + */ + V replace(K key, V value); +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicBoolean.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicBoolean.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,155 @@ +/* + * 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.atomic; + +/** + * A {@code boolean} value that may be updated atomically. See the + * {@link java.util.concurrent.atomic} package specification for + * description of the properties of atomic variables. An + * {@code AtomicBoolean} is used in applications such as atomically + * updated flags, and cannot be used as a replacement for a + * {@link java.lang.Boolean}. + * + * @since 1.5 + * @author Doug Lea + */ +public class AtomicBoolean implements java.io.Serializable { + private static final long serialVersionUID = 4654671469794556979L; + + private volatile int value; + + /** + * Creates a new {@code AtomicBoolean} with the given initial value. + * + * @param initialValue the initial value + */ + public AtomicBoolean(boolean initialValue) { + value = initialValue ? 1 : 0; + } + + /** + * Creates a new {@code AtomicBoolean} with initial value {@code false}. + */ + public AtomicBoolean() { + } + + /** + * Returns the current value. + * + * @return the current value + */ + public final boolean get() { + return value != 0; + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(boolean expect, boolean update) { + int e = expect ? 1 : 0; + int u = update ? 1 : 0; + if (this.value == e) { + this.value = u; + return true; + } else { + return false; + } + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public boolean weakCompareAndSet(boolean expect, boolean update) { + return compareAndSet(expect, update); + } + + /** + * Unconditionally sets to the given value. + * + * @param newValue the new value + */ + public final void set(boolean newValue) { + value = newValue ? 1 : 0; + } + + /** + * Eventually sets to the given value. + * + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(boolean newValue) { + set(newValue); + } + + /** + * Atomically sets to the given value and returns the previous value. + * + * @param newValue the new value + * @return the previous value + */ + public final boolean getAndSet(boolean newValue) { + for (;;) { + boolean current = get(); + if (compareAndSet(current, newValue)) + return current; + } + } + + /** + * Returns the String representation of the current value. + * @return the String representation of the current value. + */ + public String toString() { + return Boolean.toString(get()); + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicInteger.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicInteger.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,258 @@ +/* + * 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.atomic; + +/** + * An {@code int} value that may be updated atomically. See the + * {@link java.util.concurrent.atomic} package specification for + * description of the properties of atomic variables. An + * {@code AtomicInteger} is used in applications such as atomically + * incremented counters, and cannot be used as a replacement for an + * {@link java.lang.Integer}. However, this class does extend + * {@code Number} to allow uniform access by tools and utilities that + * deal with numerically-based classes. + * + * @since 1.5 + * @author Doug Lea +*/ +public class AtomicInteger extends Number implements java.io.Serializable { + private static final long serialVersionUID = 6214790243416807050L; + + private volatile int value; + + /** + * Creates a new AtomicInteger with the given initial value. + * + * @param initialValue the initial value + */ + public AtomicInteger(int initialValue) { + value = initialValue; + } + + /** + * Creates a new AtomicInteger with initial value {@code 0}. + */ + public AtomicInteger() { + } + + /** + * Gets the current value. + * + * @return the current value + */ + public final int get() { + return value; + } + + /** + * Sets to the given value. + * + * @param newValue the new value + */ + public final void set(int newValue) { + value = newValue; + } + + /** + * Eventually sets to the given value. + * + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(int newValue) { + value = newValue; + } + + /** + * Atomically sets to the given value and returns the old value. + * + * @param newValue the new value + * @return the previous value + */ + public final int getAndSet(int newValue) { + for (;;) { + int current = get(); + if (compareAndSet(current, newValue)) + return current; + } + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(int expect, int update) { + if (value == expect) { + value = update; + return true; + } else { + return false; + } + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public final boolean weakCompareAndSet(int expect, int update) { + return compareAndSet(expect, update); + } + + /** + * Atomically increments by one the current value. + * + * @return the previous value + */ + public final int getAndIncrement() { + for (;;) { + int current = get(); + int next = current + 1; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * Atomically decrements by one the current value. + * + * @return the previous value + */ + public final int getAndDecrement() { + for (;;) { + int current = get(); + int next = current - 1; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the previous value + */ + public final int getAndAdd(int delta) { + for (;;) { + int current = get(); + int next = current + delta; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * Atomically increments by one the current value. + * + * @return the updated value + */ + public final int incrementAndGet() { + for (;;) { + int current = get(); + int next = current + 1; + if (compareAndSet(current, next)) + return next; + } + } + + /** + * Atomically decrements by one the current value. + * + * @return the updated value + */ + public final int decrementAndGet() { + for (;;) { + int current = get(); + int next = current - 1; + if (compareAndSet(current, next)) + return next; + } + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the updated value + */ + public final int addAndGet(int delta) { + for (;;) { + int current = get(); + int next = current + delta; + if (compareAndSet(current, next)) + return next; + } + } + + /** + * Returns the String representation of the current value. + * @return the String representation of the current value. + */ + public String toString() { + return Integer.toString(get()); + } + + + public int intValue() { + return get(); + } + + public long longValue() { + return (long)get(); + } + + public float floatValue() { + return (float)get(); + } + + public double doubleValue() { + return (double)get(); + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicIntegerArray.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicIntegerArray.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,247 @@ +/* + * 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.atomic; + +/** + * An {@code int} array in which elements may be updated atomically. + * See the {@link java.util.concurrent.atomic} package + * specification for description of the properties of atomic + * variables. + * @since 1.5 + * @author Doug Lea + */ +public class AtomicIntegerArray implements java.io.Serializable { + private static final long serialVersionUID = 2862133569453604235L; + + private final int[] array; + + /** + * Creates a new AtomicIntegerArray of the given length, with all + * elements initially zero. + * + * @param length the length of the array + */ + public AtomicIntegerArray(int length) { + array = new int[length]; + } + + /** + * Creates a new AtomicIntegerArray with the same length as, and + * all elements copied from, the given array. + * + * @param array the array to copy elements from + * @throws NullPointerException if array is null + */ + public AtomicIntegerArray(int[] array) { + // Visibility guaranteed by final field guarantees + this.array = array.clone(); + } + + /** + * Returns the length of the array. + * + * @return the length of the array + */ + public final int length() { + return array.length; + } + + /** + * Gets the current value at position {@code i}. + * + * @param i the index + * @return the current value + */ + public final int get(int i) { + return array[i]; + } + + /** + * Sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + */ + public final void set(int i, int newValue) { + array[i] = newValue; + } + + /** + * Eventually sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(int i, int newValue) { + array[i] = newValue; + } + + /** + * Atomically sets the element at position {@code i} to the given + * value and returns the old value. + * + * @param i the index + * @param newValue the new value + * @return the previous value + */ + public final int getAndSet(int i, int newValue) { + int current = array[i]; + array[i] = newValue; + return current; + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value if the current value {@code ==} the expected value. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(int i, int expect, int update) { + if (array[i] == expect) { + array[i] = update; + return true; + } else { + return false; + } + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public final boolean weakCompareAndSet(int i, int expect, int update) { + return compareAndSet(i, expect, update); + } + + /** + * Atomically increments by one the element at index {@code i}. + * + * @param i the index + * @return the previous value + */ + public final int getAndIncrement(int i) { + return getAndAdd(i, 1); + } + + /** + * Atomically decrements by one the element at index {@code i}. + * + * @param i the index + * @return the previous value + */ + public final int getAndDecrement(int i) { + return getAndAdd(i, -1); + } + + /** + * Atomically adds the given value to the element at index {@code i}. + * + * @param i the index + * @param delta the value to add + * @return the previous value + */ + public final int getAndAdd(int i, int delta) { + int v = array[i]; + array[i] += delta; + return v; + } + + /** + * Atomically increments by one the element at index {@code i}. + * + * @param i the index + * @return the updated value + */ + public final int incrementAndGet(int i) { + return addAndGet(i, 1); + } + + /** + * Atomically decrements by one the element at index {@code i}. + * + * @param i the index + * @return the updated value + */ + public final int decrementAndGet(int i) { + return addAndGet(i, -1); + } + + /** + * Atomically adds the given value to the element at index {@code i}. + * + * @param i the index + * @param delta the value to add + * @return the updated value + */ + public final int addAndGet(int i, int delta) { + array[i] += delta; + return array[i]; + } + + /** + * Returns the String representation of the current values of array. + * @return the String representation of the current values of array + */ + public String toString() { + int iMax = array.length - 1; + if (iMax == -1) + return "[]"; + + StringBuilder b = new StringBuilder(); + b.append('['); + for (int i = 0; ; i++) { + b.append(get(i)); + if (i == iMax) + return b.append(']').toString(); + b.append(',').append(' '); + } + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicLong.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicLong.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,265 @@ +/* + * 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.atomic; + +/** + * A {@code long} value that may be updated atomically. See the + * {@link java.util.concurrent.atomic} package specification for + * description of the properties of atomic variables. An + * {@code AtomicLong} is used in applications such as atomically + * incremented sequence numbers, and cannot be used as a replacement + * for a {@link java.lang.Long}. However, this class does extend + * {@code Number} to allow uniform access by tools and utilities that + * deal with numerically-based classes. + * + * @since 1.5 + * @author Doug Lea + */ +public class AtomicLong extends Number implements java.io.Serializable { + private static final long serialVersionUID = 1927816293512124184L; + + + /** + * Returns whether underlying JVM supports lockless CompareAndSet + * for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS. + */ + private static native boolean VMSupportsCS8(); + + private volatile long value; + + /** + * Creates a new AtomicLong with the given initial value. + * + * @param initialValue the initial value + */ + public AtomicLong(long initialValue) { + value = initialValue; + } + + /** + * Creates a new AtomicLong with initial value {@code 0}. + */ + public AtomicLong() { + } + + /** + * Gets the current value. + * + * @return the current value + */ + public final long get() { + return value; + } + + /** + * Sets to the given value. + * + * @param newValue the new value + */ + public final void set(long newValue) { + value = newValue; + } + + /** + * Eventually sets to the given value. + * + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(long newValue) { + value = newValue; + } + + /** + * Atomically sets to the given value and returns the old value. + * + * @param newValue the new value + * @return the previous value + */ + public final long getAndSet(long newValue) { + while (true) { + long current = get(); + if (compareAndSet(current, newValue)) + return current; + } + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(long expect, long update) { + if (value == expect) { + value = update; + return true; + } else { + return false; + } + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public final boolean weakCompareAndSet(long expect, long update) { + return compareAndSet(expect, update); + } + + /** + * Atomically increments by one the current value. + * + * @return the previous value + */ + public final long getAndIncrement() { + while (true) { + long current = get(); + long next = current + 1; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * Atomically decrements by one the current value. + * + * @return the previous value + */ + public final long getAndDecrement() { + while (true) { + long current = get(); + long next = current - 1; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the previous value + */ + public final long getAndAdd(long delta) { + while (true) { + long current = get(); + long next = current + delta; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * Atomically increments by one the current value. + * + * @return the updated value + */ + public final long incrementAndGet() { + for (;;) { + long current = get(); + long next = current + 1; + if (compareAndSet(current, next)) + return next; + } + } + + /** + * Atomically decrements by one the current value. + * + * @return the updated value + */ + public final long decrementAndGet() { + for (;;) { + long current = get(); + long next = current - 1; + if (compareAndSet(current, next)) + return next; + } + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the updated value + */ + public final long addAndGet(long delta) { + for (;;) { + long current = get(); + long next = current + delta; + if (compareAndSet(current, next)) + return next; + } + } + + /** + * Returns the String representation of the current value. + * @return the String representation of the current value. + */ + public String toString() { + return Long.toString(get()); + } + + + public int intValue() { + return (int)get(); + } + + public long longValue() { + return get(); + } + + public float floatValue() { + return (float)get(); + } + + public double doubleValue() { + return (double)get(); + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicLongArray.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicLongArray.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,247 @@ +/* + * 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.atomic; + +/** + * A {@code long} array in which elements may be updated atomically. + * See the {@link java.util.concurrent.atomic} package specification + * for description of the properties of atomic variables. + * @since 1.5 + * @author Doug Lea + */ +public class AtomicLongArray implements java.io.Serializable { + private static final long serialVersionUID = -2308431214976778248L; + + private final long[] array; + + /** + * Creates a new AtomicLongArray of the given length, with all + * elements initially zero. + * + * @param length the length of the array + */ + public AtomicLongArray(int length) { + array = new long[length]; + } + + /** + * Creates a new AtomicLongArray with the same length as, and + * all elements copied from, the given array. + * + * @param array the array to copy elements from + * @throws NullPointerException if array is null + */ + public AtomicLongArray(long[] array) { + // Visibility guaranteed by final field guarantees + this.array = array.clone(); + } + + /** + * Returns the length of the array. + * + * @return the length of the array + */ + public final int length() { + return array.length; + } + + /** + * Gets the current value at position {@code i}. + * + * @param i the index + * @return the current value + */ + public final long get(int i) { + return array[i]; + } + + /** + * Sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + */ + public final void set(int i, long newValue) { + array[i] = newValue; + } + + /** + * Eventually sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(int i, long newValue) { + array[i] = newValue; + } + + + /** + * Atomically sets the element at position {@code i} to the given value + * and returns the old value. + * + * @param i the index + * @param newValue the new value + * @return the previous value + */ + public final long getAndSet(int i, long newValue) { + long v = array[i]; + array[i] = newValue; + return v; + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value if the current value {@code ==} the expected value. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(int i, long expect, long update) { + if (array[i] == expect) { + array[i] = update; + return true; + } else { + return false; + } + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public final boolean weakCompareAndSet(int i, long expect, long update) { + return compareAndSet(i, expect, update); + } + + /** + * Atomically increments by one the element at index {@code i}. + * + * @param i the index + * @return the previous value + */ + public final long getAndIncrement(int i) { + return getAndAdd(i, 1); + } + + /** + * Atomically decrements by one the element at index {@code i}. + * + * @param i the index + * @return the previous value + */ + public final long getAndDecrement(int i) { + return getAndAdd(i, -1); + } + + /** + * Atomically adds the given value to the element at index {@code i}. + * + * @param i the index + * @param delta the value to add + * @return the previous value + */ + public final long getAndAdd(int i, long delta) { + long v = array[i]; + array[i] += delta; + return v; + } + + /** + * Atomically increments by one the element at index {@code i}. + * + * @param i the index + * @return the updated value + */ + public final long incrementAndGet(int i) { + return addAndGet(i, 1); + } + + /** + * Atomically decrements by one the element at index {@code i}. + * + * @param i the index + * @return the updated value + */ + public final long decrementAndGet(int i) { + return addAndGet(i, -1); + } + + /** + * Atomically adds the given value to the element at index {@code i}. + * + * @param i the index + * @param delta the value to add + * @return the updated value + */ + public long addAndGet(int i, long delta) { + array[i] += delta; + return array[i]; + } + + /** + * Returns the String representation of the current values of array. + * @return the String representation of the current values of array + */ + public String toString() { + int iMax = array.length - 1; + if (iMax == -1) + return "[]"; + + StringBuilder b = new StringBuilder(); + b.append('['); + for (int i = 0; ; i++) { + b.append(get(i)); + if (i == iMax) + return b.append(']').toString(); + b.append(',').append(' '); + } + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicReference.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicReference.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,149 @@ +/* + * 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.atomic; + +/** + * An object reference that may be updated atomically. See the {@link + * java.util.concurrent.atomic} package specification for description + * of the properties of atomic variables. + * @since 1.5 + * @author Doug Lea + * @param The type of object referred to by this reference + */ +public class AtomicReference implements java.io.Serializable { + private static final long serialVersionUID = -1848883965231344442L; + + private volatile V value; + + /** + * Creates a new AtomicReference with the given initial value. + * + * @param initialValue the initial value + */ + public AtomicReference(V initialValue) { + value = initialValue; + } + + /** + * Creates a new AtomicReference with null initial value. + */ + public AtomicReference() { + } + + /** + * Gets the current value. + * + * @return the current value + */ + public final V get() { + return value; + } + + /** + * Sets to the given value. + * + * @param newValue the new value + */ + public final void set(V newValue) { + value = newValue; + } + + /** + * Eventually sets to the given value. + * + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(V newValue) { + value = newValue; + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(V expect, V update) { + if (value == expect) { + value = update; + return true; + } else { + return false; + } + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public final boolean weakCompareAndSet(V expect, V update) { + return compareAndSet(expect, update); + } + + /** + * Atomically sets to the given value and returns the old value. + * + * @param newValue the new value + * @return the previous value + */ + public final V getAndSet(V newValue) { + while (true) { + V x = get(); + if (compareAndSet(x, newValue)) + return x; + } + } + + /** + * Returns the String representation of the current value. + * @return the String representation of the current value. + */ + public String toString() { + return String.valueOf(get()); + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicReferenceArray.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicReferenceArray.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,184 @@ +/* + * 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.atomic; + +/** + * An array of object references in which elements may be updated + * atomically. See the {@link java.util.concurrent.atomic} package + * specification for description of the properties of atomic + * variables. + * @since 1.5 + * @author Doug Lea + * @param The base class of elements held in this array + */ +public class AtomicReferenceArray implements java.io.Serializable { + private static final long serialVersionUID = -6209656149925076980L; + + private final Object[] array; + + /** + * Creates a new AtomicReferenceArray of the given length, with all + * elements initially null. + * + * @param length the length of the array + */ + public AtomicReferenceArray(int length) { + array = new Object[length]; + } + + /** + * Creates a new AtomicReferenceArray with the same length as, and + * all elements copied from, the given array. + * + * @param array the array to copy elements from + * @throws NullPointerException if array is null + */ + public AtomicReferenceArray(E[] array) { + // Visibility guaranteed by final field guarantees + this.array = array.clone(); + } + + /** + * Returns the length of the array. + * + * @return the length of the array + */ + public final int length() { + return array.length; + } + + /** + * Gets the current value at position {@code i}. + * + * @param i the index + * @return the current value + */ + public final E get(int i) { + return (E)array[i]; + } + + /** + * Sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + */ + public final void set(int i, E newValue) { + array[i] = newValue; + } + + /** + * Eventually sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(int i, E newValue) { + array[i] = newValue; + } + + + /** + * Atomically sets the element at position {@code i} to the given + * value and returns the old value. + * + * @param i the index + * @param newValue the new value + * @return the previous value + */ + public final E getAndSet(int i, E newValue) { + E v = (E)array[i]; + array[i] = newValue; + return v; + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value if the current value {@code ==} the expected value. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(int i, E expect, E update) { + if (array[i] == expect) { + array[i] = update; + return true; + } else { + return false; + } + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public final boolean weakCompareAndSet(int i, E expect, E update) { + return compareAndSet(i, expect, update); + } + + /** + * Returns the String representation of the current values of array. + * @return the String representation of the current values of array + */ + public String toString() { + int iMax = array.length - 1; + if (iMax == -1) + return "[]"; + + StringBuilder b = new StringBuilder(); + b.append('['); + for (int i = 0; ; i++) { + b.append(get(i)); + if (i == iMax) + return b.append(']').toString(); + b.append(',').append(' '); + } + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/concurrent/atomic/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/package-info.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,199 @@ +/* + * 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/ + */ + +/** + * A small toolkit of classes that support lock-free thread-safe + * programming on single variables. In essence, the classes in this + * package extend the notion of {@code volatile} values, fields, and + * array elements to those that also provide an atomic conditional update + * operation of the form: + * + *

+ *   boolean compareAndSet(expectedValue, updateValue);
+ * 
+ * + *

This method (which varies in argument types across different + * classes) atomically sets a variable to the {@code updateValue} if it + * currently holds the {@code expectedValue}, reporting {@code true} on + * success. The classes in this package also contain methods to get and + * unconditionally set values, as well as a weaker conditional atomic + * update operation {@code weakCompareAndSet} described below. + * + *

The specifications of these methods enable implementations to + * employ efficient machine-level atomic instructions that are available + * on contemporary processors. However on some platforms, support may + * entail some form of internal locking. Thus the methods are not + * strictly guaranteed to be non-blocking -- + * a thread may block transiently before performing the operation. + * + *

Instances of classes + * {@link java.util.concurrent.atomic.AtomicBoolean}, + * {@link java.util.concurrent.atomic.AtomicInteger}, + * {@link java.util.concurrent.atomic.AtomicLong}, and + * {@link java.util.concurrent.atomic.AtomicReference} + * each provide access and updates to a single variable of the + * corresponding type. Each class also provides appropriate utility + * methods for that type. For example, classes {@code AtomicLong} and + * {@code AtomicInteger} provide atomic increment methods. One + * application is to generate sequence numbers, as in: + * + *

+ * class Sequencer {
+ *   private final AtomicLong sequenceNumber
+ *     = new AtomicLong(0);
+ *   public long next() {
+ *     return sequenceNumber.getAndIncrement();
+ *   }
+ * }
+ * 
+ * + *

The memory effects for accesses and updates of atomics generally + * follow the rules for volatiles, as stated in section 17.4 of + * The Java™ Language Specification. + * + *

    + * + *
  • {@code get} has the memory effects of reading a + * {@code volatile} variable. + * + *
  • {@code set} has the memory effects of writing (assigning) a + * {@code volatile} variable. + * + *
  • {@code lazySet} has the memory effects of writing (assigning) + * a {@code volatile} variable except that it permits reorderings with + * subsequent (but not previous) memory actions that do not themselves + * impose reordering constraints with ordinary non-{@code volatile} + * writes. Among other usage contexts, {@code lazySet} may apply when + * nulling out, for the sake of garbage collection, a reference that is + * never accessed again. + * + *
  • {@code weakCompareAndSet} atomically reads and conditionally + * writes a variable but does not + * create any happens-before orderings, so provides no guarantees + * with respect to previous or subsequent reads and writes of any + * variables other than the target of the {@code weakCompareAndSet}. + * + *
  • {@code compareAndSet} + * and all other read-and-update operations such as {@code getAndIncrement} + * have the memory effects of both reading and + * writing {@code volatile} variables. + *
+ * + *

In addition to classes representing single values, this package + * contains Updater classes that can be used to obtain + * {@code compareAndSet} operations on any selected {@code volatile} + * field of any selected class. + * + * {@link java.util.concurrent.atomic.AtomicReferenceFieldUpdater}, + * {@link java.util.concurrent.atomic.AtomicIntegerFieldUpdater}, and + * {@link java.util.concurrent.atomic.AtomicLongFieldUpdater} are + * reflection-based utilities that provide access to the associated + * field types. These are mainly of use in atomic data structures in + * which several {@code volatile} fields of the same node (for + * example, the links of a tree node) are independently subject to + * atomic updates. These classes enable greater flexibility in how + * and when to use atomic updates, at the expense of more awkward + * reflection-based setup, less convenient usage, and weaker + * guarantees. + * + *

The + * {@link java.util.concurrent.atomic.AtomicIntegerArray}, + * {@link java.util.concurrent.atomic.AtomicLongArray}, and + * {@link java.util.concurrent.atomic.AtomicReferenceArray} classes + * further extend atomic operation support to arrays of these types. + * These classes are also notable in providing {@code volatile} access + * semantics for their array elements, which is not supported for + * ordinary arrays. + * + * + *

The atomic classes also support method {@code weakCompareAndSet}, + * which has limited applicability. On some platforms, the weak version + * may be more efficient than {@code compareAndSet} in the normal case, + * but differs in that any given invocation of the + * {@code weakCompareAndSet} method may return {@code false} + * spuriously (that is, for no apparent reason). A + * {@code false} return means only that the operation may be retried if + * desired, relying on the guarantee that repeated invocation when the + * variable holds {@code expectedValue} and no other thread is also + * attempting to set the variable will eventually succeed. (Such + * spurious failures may for example be due to memory contention effects + * that are unrelated to whether the expected and current values are + * equal.) Additionally {@code weakCompareAndSet} does not provide + * ordering guarantees that are usually needed for synchronization + * control. However, the method may be useful for updating counters and + * statistics when such updates are unrelated to the other + * happens-before orderings of a program. When a thread sees an update + * to an atomic variable caused by a {@code weakCompareAndSet}, it does + * not necessarily see updates to any other variables that + * occurred before the {@code weakCompareAndSet}. This may be + * acceptable when, for example, updating performance statistics, but + * rarely otherwise. + * + *

The {@link java.util.concurrent.atomic.AtomicMarkableReference} + * class associates a single boolean with a reference. For example, this + * bit might be used inside a data structure to mean that the object + * being referenced has logically been deleted. + * + * The {@link java.util.concurrent.atomic.AtomicStampedReference} + * class associates an integer value with a reference. This may be + * used for example, to represent version numbers corresponding to + * series of updates. + * + *

Atomic classes are designed primarily as building blocks for + * implementing non-blocking data structures and related infrastructure + * classes. The {@code compareAndSet} method is not a general + * replacement for locking. It applies only when critical updates for an + * object are confined to a single variable. + * + *

Atomic classes are not general purpose replacements for + * {@code java.lang.Integer} and related classes. They do not + * define methods such as {@code hashCode} and + * {@code compareTo}. (Because atomic variables are expected to be + * mutated, they are poor choices for hash table keys.) Additionally, + * classes are provided only for those types that are commonly useful in + * intended applications. For example, there is no atomic class for + * representing {@code byte}. In those infrequent cases where you would + * like to do so, you can use an {@code AtomicInteger} to hold + * {@code byte} values, and cast appropriately. + * + * You can also hold floats using + * {@link java.lang.Float#floatToIntBits} and + * {@link java.lang.Float#intBitsToFloat} conversions, and doubles using + * {@link java.lang.Double#doubleToLongBits} and + * {@link java.lang.Double#longBitsToDouble} conversions. + * + * @since 1.5 + */ +package java.util.concurrent.atomic; diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1264 @@ +/* + * 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; + } + + String msg = record.getMessage(); + final Object[] params = record.getParameters(); + if (params != null && params.length != 0) { + for (int i = 0; i < params.length; i++) { + msg = msg.replace("{" + i + "}", params[i] == null ? "null" : params[i].toString()); + } + } + + consoleLog( + method, + record.getLoggerName(), msg); + } + + @JavaScriptBody(args = { "method", "logger", "msg" }, body = + "if (typeof console !== 'undefined') 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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/regex/ASCII.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/regex/ASCII.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,274 @@ +/* + * Copyright (c) 1999, 2000, 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.regex; + + +/** + * Utility class that implements the standard C ctype functionality. + * + * @author Hong Zhang + */ + +final class ASCII { + + static final int UPPER = 0x00000100; + + static final int LOWER = 0x00000200; + + static final int DIGIT = 0x00000400; + + static final int SPACE = 0x00000800; + + static final int PUNCT = 0x00001000; + + static final int CNTRL = 0x00002000; + + static final int BLANK = 0x00004000; + + static final int HEX = 0x00008000; + + static final int UNDER = 0x00010000; + + static final int ASCII = 0x0000FF00; + + static final int ALPHA = (UPPER|LOWER); + + static final int ALNUM = (UPPER|LOWER|DIGIT); + + static final int GRAPH = (PUNCT|UPPER|LOWER|DIGIT); + + static final int WORD = (UPPER|LOWER|UNDER|DIGIT); + + static final int XDIGIT = (HEX); + + private static final int[] ctype = new int[] { + CNTRL, /* 00 (NUL) */ + CNTRL, /* 01 (SOH) */ + CNTRL, /* 02 (STX) */ + CNTRL, /* 03 (ETX) */ + CNTRL, /* 04 (EOT) */ + CNTRL, /* 05 (ENQ) */ + CNTRL, /* 06 (ACK) */ + CNTRL, /* 07 (BEL) */ + CNTRL, /* 08 (BS) */ + SPACE+CNTRL+BLANK, /* 09 (HT) */ + SPACE+CNTRL, /* 0A (LF) */ + SPACE+CNTRL, /* 0B (VT) */ + SPACE+CNTRL, /* 0C (FF) */ + SPACE+CNTRL, /* 0D (CR) */ + CNTRL, /* 0E (SI) */ + CNTRL, /* 0F (SO) */ + CNTRL, /* 10 (DLE) */ + CNTRL, /* 11 (DC1) */ + CNTRL, /* 12 (DC2) */ + CNTRL, /* 13 (DC3) */ + CNTRL, /* 14 (DC4) */ + CNTRL, /* 15 (NAK) */ + CNTRL, /* 16 (SYN) */ + CNTRL, /* 17 (ETB) */ + CNTRL, /* 18 (CAN) */ + CNTRL, /* 19 (EM) */ + CNTRL, /* 1A (SUB) */ + CNTRL, /* 1B (ESC) */ + CNTRL, /* 1C (FS) */ + CNTRL, /* 1D (GS) */ + CNTRL, /* 1E (RS) */ + CNTRL, /* 1F (US) */ + SPACE+BLANK, /* 20 SPACE */ + PUNCT, /* 21 ! */ + PUNCT, /* 22 " */ + PUNCT, /* 23 # */ + PUNCT, /* 24 $ */ + PUNCT, /* 25 % */ + PUNCT, /* 26 & */ + PUNCT, /* 27 ' */ + PUNCT, /* 28 ( */ + PUNCT, /* 29 ) */ + PUNCT, /* 2A * */ + PUNCT, /* 2B + */ + PUNCT, /* 2C , */ + PUNCT, /* 2D - */ + PUNCT, /* 2E . */ + PUNCT, /* 2F / */ + DIGIT+HEX+0, /* 30 0 */ + DIGIT+HEX+1, /* 31 1 */ + DIGIT+HEX+2, /* 32 2 */ + DIGIT+HEX+3, /* 33 3 */ + DIGIT+HEX+4, /* 34 4 */ + DIGIT+HEX+5, /* 35 5 */ + DIGIT+HEX+6, /* 36 6 */ + DIGIT+HEX+7, /* 37 7 */ + DIGIT+HEX+8, /* 38 8 */ + DIGIT+HEX+9, /* 39 9 */ + PUNCT, /* 3A : */ + PUNCT, /* 3B ; */ + PUNCT, /* 3C < */ + PUNCT, /* 3D = */ + PUNCT, /* 3E > */ + PUNCT, /* 3F ? */ + PUNCT, /* 40 @ */ + UPPER+HEX+10, /* 41 A */ + UPPER+HEX+11, /* 42 B */ + UPPER+HEX+12, /* 43 C */ + UPPER+HEX+13, /* 44 D */ + UPPER+HEX+14, /* 45 E */ + UPPER+HEX+15, /* 46 F */ + UPPER+16, /* 47 G */ + UPPER+17, /* 48 H */ + UPPER+18, /* 49 I */ + UPPER+19, /* 4A J */ + UPPER+20, /* 4B K */ + UPPER+21, /* 4C L */ + UPPER+22, /* 4D M */ + UPPER+23, /* 4E N */ + UPPER+24, /* 4F O */ + UPPER+25, /* 50 P */ + UPPER+26, /* 51 Q */ + UPPER+27, /* 52 R */ + UPPER+28, /* 53 S */ + UPPER+29, /* 54 T */ + UPPER+30, /* 55 U */ + UPPER+31, /* 56 V */ + UPPER+32, /* 57 W */ + UPPER+33, /* 58 X */ + UPPER+34, /* 59 Y */ + UPPER+35, /* 5A Z */ + PUNCT, /* 5B [ */ + PUNCT, /* 5C \ */ + PUNCT, /* 5D ] */ + PUNCT, /* 5E ^ */ + PUNCT|UNDER, /* 5F _ */ + PUNCT, /* 60 ` */ + LOWER+HEX+10, /* 61 a */ + LOWER+HEX+11, /* 62 b */ + LOWER+HEX+12, /* 63 c */ + LOWER+HEX+13, /* 64 d */ + LOWER+HEX+14, /* 65 e */ + LOWER+HEX+15, /* 66 f */ + LOWER+16, /* 67 g */ + LOWER+17, /* 68 h */ + LOWER+18, /* 69 i */ + LOWER+19, /* 6A j */ + LOWER+20, /* 6B k */ + LOWER+21, /* 6C l */ + LOWER+22, /* 6D m */ + LOWER+23, /* 6E n */ + LOWER+24, /* 6F o */ + LOWER+25, /* 70 p */ + LOWER+26, /* 71 q */ + LOWER+27, /* 72 r */ + LOWER+28, /* 73 s */ + LOWER+29, /* 74 t */ + LOWER+30, /* 75 u */ + LOWER+31, /* 76 v */ + LOWER+32, /* 77 w */ + LOWER+33, /* 78 x */ + LOWER+34, /* 79 y */ + LOWER+35, /* 7A z */ + PUNCT, /* 7B { */ + PUNCT, /* 7C | */ + PUNCT, /* 7D } */ + PUNCT, /* 7E ~ */ + CNTRL, /* 7F (DEL) */ + }; + + static int getType(int ch) { + return ((ch & 0xFFFFFF80) == 0 ? ctype[ch] : 0); + } + + static boolean isType(int ch, int type) { + return (getType(ch) & type) != 0; + } + + static boolean isAscii(int ch) { + return ((ch & 0xFFFFFF80) == 0); + } + + static boolean isAlpha(int ch) { + return isType(ch, ALPHA); + } + + static boolean isDigit(int ch) { + return ((ch-'0')|('9'-ch)) >= 0; + } + + static boolean isAlnum(int ch) { + return isType(ch, ALNUM); + } + + static boolean isGraph(int ch) { + return isType(ch, GRAPH); + } + + static boolean isPrint(int ch) { + return ((ch-0x20)|(0x7E-ch)) >= 0; + } + + static boolean isPunct(int ch) { + return isType(ch, PUNCT); + } + + static boolean isSpace(int ch) { + return isType(ch, SPACE); + } + + static boolean isHexDigit(int ch) { + return isType(ch, HEX); + } + + static boolean isOctDigit(int ch) { + return ((ch-'0')|('7'-ch)) >= 0; + } + + static boolean isCntrl(int ch) { + return isType(ch, CNTRL); + } + + static boolean isLower(int ch) { + return ((ch-'a')|('z'-ch)) >= 0; + } + + static boolean isUpper(int ch) { + return ((ch-'A')|('Z'-ch)) >= 0; + } + + static boolean isWord(int ch) { + return isType(ch, WORD); + } + + static int toDigit(int ch) { + return (ctype[ch & 0x7F] & 0x3F); + } + + static int toLower(int ch) { + return isUpper(ch) ? (ch + 0x20) : ch; + } + + static int toUpper(int ch) { + return isLower(ch) ? (ch - 0x20) : ch; + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/regex/MatchResult.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/regex/MatchResult.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2003, 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 java.util.regex; + +/** + * The result of a match operation. + * + *

This interface contains query methods used to determine the + * results of a match against a regular expression. The match boundaries, + * groups and group boundaries can be seen but not modified through + * a MatchResult. + * + * @author Michael McCloskey + * @see Matcher + * @since 1.5 + */ +public interface MatchResult { + + /** + * Returns the start index of the match. + * + * @return The index of the first character matched + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + */ + public int start(); + + /** + * Returns the start index of the subsequence captured by the given group + * during this match. + * + *

Capturing groups are indexed from left + * to right, starting at one. Group zero denotes the entire pattern, so + * the expression m.start(0) is equivalent to + * m.start().

+ * + * @param group + * The index of a capturing group in this matcher's pattern + * + * @return The index of the first character captured by the group, + * or -1 if the match was successful but the group + * itself did not match anything + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + * + * @throws IndexOutOfBoundsException + * If there is no capturing group in the pattern + * with the given index + */ + public int start(int group); + + /** + * Returns the offset after the last character matched.

+ * + * @return @return The offset after the last character matched + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + */ + public int end(); + + /** + * Returns the offset after the last character of the subsequence + * captured by the given group during this match. + * + *

Capturing groups are indexed from left + * to right, starting at one. Group zero denotes the entire pattern, so + * the expression m.end(0) is equivalent to + * m.end().

+ * + * @param group + * The index of a capturing group in this matcher's pattern + * + * @return The offset after the last character captured by the group, + * or -1 if the match was successful + * but the group itself did not match anything + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + * + * @throws IndexOutOfBoundsException + * If there is no capturing group in the pattern + * with the given index + */ + public int end(int group); + + /** + * Returns the input subsequence matched by the previous match. + * + *

For a matcher m with input sequence s, + * the expressions m.group() and + * s.substring(m.start(), m.end()) + * are equivalent.

+ * + *

Note that some patterns, for example a*, match the empty + * string. This method will return the empty string when the pattern + * successfully matches the empty string in the input.

+ * + * @return The (possibly empty) subsequence matched by the previous match, + * in string form + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + */ + public String group(); + + /** + * Returns the input subsequence captured by the given group during the + * previous match operation. + * + *

For a matcher m, input sequence s, and group index + * g, the expressions m.group(g) and + * s.substring(m.start(g), m.end(g)) + * are equivalent.

+ * + *

Capturing groups are indexed from left + * to right, starting at one. Group zero denotes the entire pattern, so + * the expression m.group(0) is equivalent to m.group(). + *

+ * + *

If the match was successful but the group specified failed to match + * any part of the input sequence, then null is returned. Note + * that some groups, for example (a*), match the empty string. + * This method will return the empty string when such a group successfully + * matches the empty string in the input.

+ * + * @param group + * The index of a capturing group in this matcher's pattern + * + * @return The (possibly empty) subsequence captured by the group + * during the previous match, or null if the group + * failed to match part of the input + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + * + * @throws IndexOutOfBoundsException + * If there is no capturing group in the pattern + * with the given index + */ + public String group(int group); + + /** + * Returns the number of capturing groups in this match result's pattern. + * + *

Group zero denotes the entire pattern by convention. It is not + * included in this count. + * + *

Any non-negative integer smaller than or equal to the value + * returned by this method is guaranteed to be a valid group index for + * this matcher.

+ * + * @return The number of capturing groups in this matcher's pattern + */ + public int groupCount(); + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/regex/Matcher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/regex/Matcher.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1256 @@ +/* + * Copyright (c) 1999, 2009, 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.regex; + + +/** + * An engine that performs match operations on a {@link java.lang.CharSequence + * character sequence} by interpreting a {@link Pattern}. + * + *

A matcher is created from a pattern by invoking the pattern's {@link + * Pattern#matcher matcher} method. Once created, a matcher can be used to + * perform three different kinds of match operations: + * + *

    + * + *
  • The {@link #matches matches} method attempts to match the entire + * input sequence against the pattern.

  • + * + *
  • The {@link #lookingAt lookingAt} method attempts to match the + * input sequence, starting at the beginning, against the pattern.

  • + * + *
  • The {@link #find find} method scans the input sequence looking for + * the next subsequence that matches the pattern.

  • + * + *
+ * + *

Each of these methods returns a boolean indicating success or failure. + * More information about a successful match can be obtained by querying the + * state of the matcher. + * + *

A matcher finds matches in a subset of its input called the + * region. By default, the region contains all of the matcher's input. + * The region can be modified via the{@link #region region} method and queried + * via the {@link #regionStart regionStart} and {@link #regionEnd regionEnd} + * methods. The way that the region boundaries interact with some pattern + * constructs can be changed. See {@link #useAnchoringBounds + * useAnchoringBounds} and {@link #useTransparentBounds useTransparentBounds} + * for more details. + * + *

This class also defines methods for replacing matched subsequences with + * new strings whose contents can, if desired, be computed from the match + * result. The {@link #appendReplacement appendReplacement} and {@link + * #appendTail appendTail} methods can be used in tandem in order to collect + * the result into an existing string buffer, or the more convenient {@link + * #replaceAll replaceAll} method can be used to create a string in which every + * matching subsequence in the input sequence is replaced. + * + *

The explicit state of a matcher includes the start and end indices of + * the most recent successful match. It also includes the start and end + * indices of the input subsequence captured by each capturing group in the pattern as well as a total + * count of such subsequences. As a convenience, methods are also provided for + * returning these captured subsequences in string form. + * + *

The explicit state of a matcher is initially undefined; attempting to + * query any part of it before a successful match will cause an {@link + * IllegalStateException} to be thrown. The explicit state of a matcher is + * recomputed by every match operation. + * + *

The implicit state of a matcher includes the input character sequence as + * well as the append position, which is initially zero and is updated + * by the {@link #appendReplacement appendReplacement} method. + * + *

A matcher may be reset explicitly by invoking its {@link #reset()} + * method or, if a new input sequence is desired, its {@link + * #reset(java.lang.CharSequence) reset(CharSequence)} method. Resetting a + * matcher discards its explicit state information and sets the append position + * to zero. + * + *

Instances of this class are not safe for use by multiple concurrent + * threads.

+ * + * + * @author Mike McCloskey + * @author Mark Reinhold + * @author JSR-51 Expert Group + * @since 1.4 + * @spec JSR-51 + */ + +public final class Matcher implements MatchResult { + + /** + * The Pattern object that created this Matcher. + */ + Pattern parentPattern; + + /** + * The storage used by groups. They may contain invalid values if + * a group was skipped during the matching. + */ + int[] groups; + + /** + * The range within the sequence that is to be matched. Anchors + * will match at these "hard" boundaries. Changing the region + * changes these values. + */ + int from, to; + + /** + * Lookbehind uses this value to ensure that the subexpression + * match ends at the point where the lookbehind was encountered. + */ + int lookbehindTo; + + /** + * The original string being matched. + */ + CharSequence text; + + /** + * Matcher state used by the last node. NOANCHOR is used when a + * match does not have to consume all of the input. ENDANCHOR is + * the mode used for matching all the input. + */ + static final int ENDANCHOR = 1; + static final int NOANCHOR = 0; + int acceptMode = NOANCHOR; + + /** + * The range of string that last matched the pattern. If the last + * match failed then first is -1; last initially holds 0 then it + * holds the index of the end of the last match (which is where the + * next search starts). + */ + int first = -1, last = 0; + + /** + * The end index of what matched in the last match operation. + */ + int oldLast = -1; + + /** + * The index of the last position appended in a substitution. + */ + int lastAppendPosition = 0; + + /** + * Storage used by nodes to tell what repetition they are on in + * a pattern, and where groups begin. The nodes themselves are stateless, + * so they rely on this field to hold state during a match. + */ + int[] locals; + + /** + * Boolean indicating whether or not more input could change + * the results of the last match. + * + * If hitEnd is true, and a match was found, then more input + * might cause a different match to be found. + * If hitEnd is true and a match was not found, then more + * input could cause a match to be found. + * If hitEnd is false and a match was found, then more input + * will not change the match. + * If hitEnd is false and a match was not found, then more + * input will not cause a match to be found. + */ + boolean hitEnd; + + /** + * Boolean indicating whether or not more input could change + * a positive match into a negative one. + * + * If requireEnd is true, and a match was found, then more + * input could cause the match to be lost. + * If requireEnd is false and a match was found, then more + * input might change the match but the match won't be lost. + * If a match was not found, then requireEnd has no meaning. + */ + boolean requireEnd; + + /** + * If transparentBounds is true then the boundaries of this + * matcher's region are transparent to lookahead, lookbehind, + * and boundary matching constructs that try to see beyond them. + */ + boolean transparentBounds = false; + + /** + * If anchoringBounds is true then the boundaries of this + * matcher's region match anchors such as ^ and $. + */ + boolean anchoringBounds = true; + + /** + * No default constructor. + */ + Matcher() { + } + + /** + * All matchers have the state used by Pattern during a match. + */ + Matcher(Pattern parent, CharSequence text) { + this.parentPattern = parent; + this.text = text; + + // Allocate state storage + int parentGroupCount = Math.max(parent.capturingGroupCount, 10); + groups = new int[parentGroupCount * 2]; + locals = new int[parent.localCount]; + + // Put fields into initial states + reset(); + } + + /** + * Returns the pattern that is interpreted by this matcher. + * + * @return The pattern for which this matcher was created + */ + public Pattern pattern() { + return parentPattern; + } + + /** + * Returns the match state of this matcher as a {@link MatchResult}. + * The result is unaffected by subsequent operations performed upon this + * matcher. + * + * @return a MatchResult with the state of this matcher + * @since 1.5 + */ + public MatchResult toMatchResult() { + Matcher result = new Matcher(this.parentPattern, text.toString()); + result.first = this.first; + result.last = this.last; + result.groups = this.groups.clone(); + return result; + } + + /** + * Changes the Pattern that this Matcher uses to + * find matches with. + * + *

This method causes this matcher to lose information + * about the groups of the last match that occurred. The + * matcher's position in the input is maintained and its + * last append position is unaffected.

+ * + * @param newPattern + * The new pattern used by this matcher + * @return This matcher + * @throws IllegalArgumentException + * If newPattern is null + * @since 1.5 + */ + public Matcher usePattern(Pattern newPattern) { + if (newPattern == null) + throw new IllegalArgumentException("Pattern cannot be null"); + parentPattern = newPattern; + + // Reallocate state storage + int parentGroupCount = Math.max(newPattern.capturingGroupCount, 10); + groups = new int[parentGroupCount * 2]; + locals = new int[newPattern.localCount]; + for (int i = 0; i < groups.length; i++) + groups[i] = -1; + for (int i = 0; i < locals.length; i++) + locals[i] = -1; + return this; + } + + /** + * Resets this matcher. + * + *

Resetting a matcher discards all of its explicit state information + * and sets its append position to zero. The matcher's region is set to the + * default region, which is its entire character sequence. The anchoring + * and transparency of this matcher's region boundaries are unaffected. + * + * @return This matcher + */ + public Matcher reset() { + first = -1; + last = 0; + oldLast = -1; + for(int i=0; i Resetting a matcher discards all of its explicit state information + * and sets its append position to zero. The matcher's region is set to + * the default region, which is its entire character sequence. The + * anchoring and transparency of this matcher's region boundaries are + * unaffected. + * + * @param input + * The new input character sequence + * + * @return This matcher + */ + public Matcher reset(CharSequence input) { + text = input; + return reset(); + } + + /** + * Returns the start index of the previous match.

+ * + * @return The index of the first character matched + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + */ + public int start() { + if (first < 0) + throw new IllegalStateException("No match available"); + return first; + } + + /** + * Returns the start index of the subsequence captured by the given group + * during the previous match operation. + * + *

Capturing groups are indexed from left + * to right, starting at one. Group zero denotes the entire pattern, so + * the expression m.start(0) is equivalent to + * m.start().

+ * + * @param group + * The index of a capturing group in this matcher's pattern + * + * @return The index of the first character captured by the group, + * or -1 if the match was successful but the group + * itself did not match anything + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + * + * @throws IndexOutOfBoundsException + * If there is no capturing group in the pattern + * with the given index + */ + public int start(int group) { + if (first < 0) + throw new IllegalStateException("No match available"); + if (group > groupCount()) + throw new IndexOutOfBoundsException("No group " + group); + return groups[group * 2]; + } + + /** + * Returns the offset after the last character matched.

+ * + * @return The offset after the last character matched + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + */ + public int end() { + if (first < 0) + throw new IllegalStateException("No match available"); + return last; + } + + /** + * Returns the offset after the last character of the subsequence + * captured by the given group during the previous match operation. + * + *

Capturing groups are indexed from left + * to right, starting at one. Group zero denotes the entire pattern, so + * the expression m.end(0) is equivalent to + * m.end().

+ * + * @param group + * The index of a capturing group in this matcher's pattern + * + * @return The offset after the last character captured by the group, + * or -1 if the match was successful + * but the group itself did not match anything + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + * + * @throws IndexOutOfBoundsException + * If there is no capturing group in the pattern + * with the given index + */ + public int end(int group) { + if (first < 0) + throw new IllegalStateException("No match available"); + if (group > groupCount()) + throw new IndexOutOfBoundsException("No group " + group); + return groups[group * 2 + 1]; + } + + /** + * Returns the input subsequence matched by the previous match. + * + *

For a matcher m with input sequence s, + * the expressions m.group() and + * s.substring(m.start(), m.end()) + * are equivalent.

+ * + *

Note that some patterns, for example a*, match the empty + * string. This method will return the empty string when the pattern + * successfully matches the empty string in the input.

+ * + * @return The (possibly empty) subsequence matched by the previous match, + * in string form + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + */ + public String group() { + return group(0); + } + + /** + * Returns the input subsequence captured by the given group during the + * previous match operation. + * + *

For a matcher m, input sequence s, and group index + * g, the expressions m.group(g) and + * s.substring(m.start(g), m.end(g)) + * are equivalent.

+ * + *

Capturing groups are indexed from left + * to right, starting at one. Group zero denotes the entire pattern, so + * the expression m.group(0) is equivalent to m.group(). + *

+ * + *

If the match was successful but the group specified failed to match + * any part of the input sequence, then null is returned. Note + * that some groups, for example (a*), match the empty string. + * This method will return the empty string when such a group successfully + * matches the empty string in the input.

+ * + * @param group + * The index of a capturing group in this matcher's pattern + * + * @return The (possibly empty) subsequence captured by the group + * during the previous match, or null if the group + * failed to match part of the input + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + * + * @throws IndexOutOfBoundsException + * If there is no capturing group in the pattern + * with the given index + */ + public String group(int group) { + if (first < 0) + throw new IllegalStateException("No match found"); + if (group < 0 || group > groupCount()) + throw new IndexOutOfBoundsException("No group " + group); + if ((groups[group*2] == -1) || (groups[group*2+1] == -1)) + return null; + return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString(); + } + + /** + * Returns the input subsequence captured by the given + * named-capturing group during the previous + * match operation. + * + *

If the match was successful but the group specified failed to match + * any part of the input sequence, then null is returned. Note + * that some groups, for example (a*), match the empty string. + * This method will return the empty string when such a group successfully + * matches the empty string in the input.

+ * + * @param name + * The name of a named-capturing group in this matcher's pattern + * + * @return The (possibly empty) subsequence captured by the named group + * during the previous match, or null if the group + * failed to match part of the input + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + * + * @throws IllegalArgumentException + * If there is no capturing group in the pattern + * with the given name + */ + public String group(String name) { + if (name == null) + throw new NullPointerException("Null group name"); + if (first < 0) + throw new IllegalStateException("No match found"); + if (!parentPattern.namedGroups().containsKey(name)) + throw new IllegalArgumentException("No group with name <" + name + ">"); + int group = parentPattern.namedGroups().get(name); + if ((groups[group*2] == -1) || (groups[group*2+1] == -1)) + return null; + return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString(); + } + + /** + * Returns the number of capturing groups in this matcher's pattern. + * + *

Group zero denotes the entire pattern by convention. It is not + * included in this count. + * + *

Any non-negative integer smaller than or equal to the value + * returned by this method is guaranteed to be a valid group index for + * this matcher.

+ * + * @return The number of capturing groups in this matcher's pattern + */ + public int groupCount() { + return parentPattern.capturingGroupCount - 1; + } + + /** + * Attempts to match the entire region against the pattern. + * + *

If the match succeeds then more information can be obtained via the + * start, end, and group methods.

+ * + * @return true if, and only if, the entire region sequence + * matches this matcher's pattern + */ + public boolean matches() { + return match(from, ENDANCHOR); + } + + /** + * Attempts to find the next subsequence of the input sequence that matches + * the pattern. + * + *

This method starts at the beginning of this matcher's region, or, if + * a previous invocation of the method was successful and the matcher has + * not since been reset, at the first character not matched by the previous + * match. + * + *

If the match succeeds then more information can be obtained via the + * start, end, and group methods.

+ * + * @return true if, and only if, a subsequence of the input + * sequence matches this matcher's pattern + */ + public boolean find() { + int nextSearchIndex = last; + if (nextSearchIndex == first) + nextSearchIndex++; + + // If next search starts before region, start it at region + if (nextSearchIndex < from) + nextSearchIndex = from; + + // If next search starts beyond region then it fails + if (nextSearchIndex > to) { + for (int i = 0; i < groups.length; i++) + groups[i] = -1; + return false; + } + return search(nextSearchIndex); + } + + /** + * Resets this matcher and then attempts to find the next subsequence of + * the input sequence that matches the pattern, starting at the specified + * index. + * + *

If the match succeeds then more information can be obtained via the + * start, end, and group methods, and subsequent + * invocations of the {@link #find()} method will start at the first + * character not matched by this match.

+ * + * @throws IndexOutOfBoundsException + * If start is less than zero or if start is greater than the + * length of the input sequence. + * + * @return true if, and only if, a subsequence of the input + * sequence starting at the given index matches this matcher's + * pattern + */ + public boolean find(int start) { + int limit = getTextLength(); + if ((start < 0) || (start > limit)) + throw new IndexOutOfBoundsException("Illegal start index"); + reset(); + return search(start); + } + + /** + * Attempts to match the input sequence, starting at the beginning of the + * region, against the pattern. + * + *

Like the {@link #matches matches} method, this method always starts + * at the beginning of the region; unlike that method, it does not + * require that the entire region be matched. + * + *

If the match succeeds then more information can be obtained via the + * start, end, and group methods.

+ * + * @return true if, and only if, a prefix of the input + * sequence matches this matcher's pattern + */ + public boolean lookingAt() { + return match(from, NOANCHOR); + } + + /** + * Returns a literal replacement String for the specified + * String. + * + * This method produces a String that will work + * as a literal replacement s in the + * appendReplacement method of the {@link Matcher} class. + * The String produced will match the sequence of characters + * in s treated as a literal sequence. Slashes ('\') and + * dollar signs ('$') will be given no special meaning. + * + * @param s The string to be literalized + * @return A literal string replacement + * @since 1.5 + */ + public static String quoteReplacement(String s) { + if ((s.indexOf('\\') == -1) && (s.indexOf('$') == -1)) + return s; + StringBuilder sb = new StringBuilder(); + for (int i=0; i This method performs the following actions:

+ * + *
    + * + *
  1. It reads characters from the input sequence, starting at the + * append position, and appends them to the given string buffer. It + * stops after reading the last character preceding the previous match, + * that is, the character at index {@link + * #start()} - 1.

  2. + * + *
  3. It appends the given replacement string to the string buffer. + *

  4. + * + *
  5. It sets the append position of this matcher to the index of + * the last character matched, plus one, that is, to {@link #end()}. + *

  6. + * + *
+ * + *

The replacement string may contain references to subsequences + * captured during the previous match: Each occurrence of + * ${name} or $g + * will be replaced by the result of evaluating the corresponding + * {@link #group(String) group(name)} or {@link #group(int) group(g)} + * respectively. For $g, + * the first number after the $ is always treated as part of + * the group reference. Subsequent numbers are incorporated into g if + * they would form a legal group reference. Only the numerals '0' + * through '9' are considered as potential components of the group + * reference. If the second group matched the string "foo", for + * example, then passing the replacement string "$2bar" would + * cause "foobar" to be appended to the string buffer. A dollar + * sign ($) may be included as a literal in the replacement + * string by preceding it with a backslash (\$). + * + *

Note that backslashes (\) and dollar signs ($) in + * the replacement string may cause the results to be different than if it + * were being treated as a literal replacement string. Dollar signs may be + * treated as references to captured subsequences as described above, and + * backslashes are used to escape literal characters in the replacement + * string. + * + *

This method is intended to be used in a loop together with the + * {@link #appendTail appendTail} and {@link #find find} methods. The + * following code, for example, writes one dog two dogs in the + * yard to the standard-output stream:

+ * + *
+     * Pattern p = Pattern.compile("cat");
+     * Matcher m = p.matcher("one cat two cats in the yard");
+     * StringBuffer sb = new StringBuffer();
+     * while (m.find()) {
+     *     m.appendReplacement(sb, "dog");
+     * }
+     * m.appendTail(sb);
+     * System.out.println(sb.toString());
+ * + * @param sb + * The target string buffer + * + * @param replacement + * The replacement string + * + * @return This matcher + * + * @throws IllegalStateException + * If no match has yet been attempted, + * or if the previous match operation failed + * + * @throws IllegalArgumentException + * If the replacement string refers to a named-capturing + * group that does not exist in the pattern + * + * @throws IndexOutOfBoundsException + * If the replacement string refers to a capturing group + * that does not exist in the pattern + */ + public Matcher appendReplacement(StringBuffer sb, String replacement) { + + // If no match, return error + if (first < 0) + throw new IllegalStateException("No match available"); + + // Process substitution string to replace group references with groups + int cursor = 0; + StringBuilder result = new StringBuilder(); + + while (cursor < replacement.length()) { + char nextChar = replacement.charAt(cursor); + if (nextChar == '\\') { + cursor++; + nextChar = replacement.charAt(cursor); + result.append(nextChar); + cursor++; + } else if (nextChar == '$') { + // Skip past $ + cursor++; + // A StringIndexOutOfBoundsException is thrown if + // this "$" is the last character in replacement + // string in current implementation, a IAE might be + // more appropriate. + nextChar = replacement.charAt(cursor); + int refNum = -1; + if (nextChar == '{') { + cursor++; + StringBuilder gsb = new StringBuilder(); + while (cursor < replacement.length()) { + nextChar = replacement.charAt(cursor); + if (ASCII.isLower(nextChar) || + ASCII.isUpper(nextChar) || + ASCII.isDigit(nextChar)) { + gsb.append(nextChar); + cursor++; + } else { + break; + } + } + if (gsb.length() == 0) + throw new IllegalArgumentException( + "named capturing group has 0 length name"); + if (nextChar != '}') + throw new IllegalArgumentException( + "named capturing group is missing trailing '}'"); + String gname = gsb.toString(); + if (ASCII.isDigit(gname.charAt(0))) + throw new IllegalArgumentException( + "capturing group name {" + gname + + "} starts with digit character"); + if (!parentPattern.namedGroups().containsKey(gname)) + throw new IllegalArgumentException( + "No group with name {" + gname + "}"); + refNum = parentPattern.namedGroups().get(gname); + cursor++; + } else { + // The first number is always a group + refNum = (int)nextChar - '0'; + if ((refNum < 0)||(refNum > 9)) + throw new IllegalArgumentException( + "Illegal group reference"); + cursor++; + // Capture the largest legal group string + boolean done = false; + while (!done) { + if (cursor >= replacement.length()) { + break; + } + int nextDigit = replacement.charAt(cursor) - '0'; + if ((nextDigit < 0)||(nextDigit > 9)) { // not a number + break; + } + int newRefNum = (refNum * 10) + nextDigit; + if (groupCount() < newRefNum) { + done = true; + } else { + refNum = newRefNum; + cursor++; + } + } + } + // Append group + if (start(refNum) != -1 && end(refNum) != -1) + result.append(text, start(refNum), end(refNum)); + } else { + result.append(nextChar); + cursor++; + } + } + // Append the intervening text + sb.append(text, lastAppendPosition, first); + // Append the match substitution + sb.append(result); + + lastAppendPosition = last; + return this; + } + + /** + * Implements a terminal append-and-replace step. + * + *

This method reads characters from the input sequence, starting at + * the append position, and appends them to the given string buffer. It is + * intended to be invoked after one or more invocations of the {@link + * #appendReplacement appendReplacement} method in order to copy the + * remainder of the input sequence.

+ * + * @param sb + * The target string buffer + * + * @return The target string buffer + */ + public StringBuffer appendTail(StringBuffer sb) { + sb.append(text, lastAppendPosition, getTextLength()); + return sb; + } + + /** + * Replaces every subsequence of the input sequence that matches the + * pattern with the given replacement string. + * + *

This method first resets this matcher. It then scans the input + * sequence looking for matches of the pattern. Characters that are not + * part of any match are appended directly to the result string; each match + * is replaced in the result by the replacement string. The replacement + * string may contain references to captured subsequences as in the {@link + * #appendReplacement appendReplacement} method. + * + *

Note that backslashes (\) and dollar signs ($) in + * the replacement string may cause the results to be different than if it + * were being treated as a literal replacement string. Dollar signs may be + * treated as references to captured subsequences as described above, and + * backslashes are used to escape literal characters in the replacement + * string. + * + *

Given the regular expression a*b, the input + * "aabfooaabfooabfoob", and the replacement string + * "-", an invocation of this method on a matcher for that + * expression would yield the string "-foo-foo-foo-". + * + *

Invoking this method changes this matcher's state. If the matcher + * is to be used in further matching operations then it should first be + * reset.

+ * + * @param replacement + * The replacement string + * + * @return The string constructed by replacing each matching subsequence + * by the replacement string, substituting captured subsequences + * as needed + */ + public String replaceAll(String replacement) { + reset(); + boolean result = find(); + if (result) { + StringBuffer sb = new StringBuffer(); + do { + appendReplacement(sb, replacement); + result = find(); + } while (result); + appendTail(sb); + return sb.toString(); + } + return text.toString(); + } + + /** + * Replaces the first subsequence of the input sequence that matches the + * pattern with the given replacement string. + * + *

This method first resets this matcher. It then scans the input + * sequence looking for a match of the pattern. Characters that are not + * part of the match are appended directly to the result string; the match + * is replaced in the result by the replacement string. The replacement + * string may contain references to captured subsequences as in the {@link + * #appendReplacement appendReplacement} method. + * + *

Note that backslashes (\) and dollar signs ($) in + * the replacement string may cause the results to be different than if it + * were being treated as a literal replacement string. Dollar signs may be + * treated as references to captured subsequences as described above, and + * backslashes are used to escape literal characters in the replacement + * string. + * + *

Given the regular expression dog, the input + * "zzzdogzzzdogzzz", and the replacement string + * "cat", an invocation of this method on a matcher for that + * expression would yield the string "zzzcatzzzdogzzz".

+ * + *

Invoking this method changes this matcher's state. If the matcher + * is to be used in further matching operations then it should first be + * reset.

+ * + * @param replacement + * The replacement string + * @return The string constructed by replacing the first matching + * subsequence by the replacement string, substituting captured + * subsequences as needed + */ + public String replaceFirst(String replacement) { + if (replacement == null) + throw new NullPointerException("replacement"); + reset(); + if (!find()) + return text.toString(); + StringBuffer sb = new StringBuffer(); + appendReplacement(sb, replacement); + appendTail(sb); + return sb.toString(); + } + + /** + * Sets the limits of this matcher's region. The region is the part of the + * input sequence that will be searched to find a match. Invoking this + * method resets the matcher, and then sets the region to start at the + * index specified by the start parameter and end at the + * index specified by the end parameter. + * + *

Depending on the transparency and anchoring being used (see + * {@link #useTransparentBounds useTransparentBounds} and + * {@link #useAnchoringBounds useAnchoringBounds}), certain constructs such + * as anchors may behave differently at or around the boundaries of the + * region. + * + * @param start + * The index to start searching at (inclusive) + * @param end + * The index to end searching at (exclusive) + * @throws IndexOutOfBoundsException + * If start or end is less than zero, if + * start is greater than the length of the input sequence, if + * end is greater than the length of the input sequence, or if + * start is greater than end. + * @return this matcher + * @since 1.5 + */ + public Matcher region(int start, int end) { + if ((start < 0) || (start > getTextLength())) + throw new IndexOutOfBoundsException("start"); + if ((end < 0) || (end > getTextLength())) + throw new IndexOutOfBoundsException("end"); + if (start > end) + throw new IndexOutOfBoundsException("start > end"); + reset(); + from = start; + to = end; + return this; + } + + /** + * Reports the start index of this matcher's region. The + * searches this matcher conducts are limited to finding matches + * within {@link #regionStart regionStart} (inclusive) and + * {@link #regionEnd regionEnd} (exclusive). + * + * @return The starting point of this matcher's region + * @since 1.5 + */ + public int regionStart() { + return from; + } + + /** + * Reports the end index (exclusive) of this matcher's region. + * The searches this matcher conducts are limited to finding matches + * within {@link #regionStart regionStart} (inclusive) and + * {@link #regionEnd regionEnd} (exclusive). + * + * @return the ending point of this matcher's region + * @since 1.5 + */ + public int regionEnd() { + return to; + } + + /** + * Queries the transparency of region bounds for this matcher. + * + *

This method returns true if this matcher uses + * transparent bounds, false if it uses opaque + * bounds. + * + *

See {@link #useTransparentBounds useTransparentBounds} for a + * description of transparent and opaque bounds. + * + *

By default, a matcher uses opaque region boundaries. + * + * @return true iff this matcher is using transparent bounds, + * false otherwise. + * @see java.util.regex.Matcher#useTransparentBounds(boolean) + * @since 1.5 + */ + public boolean hasTransparentBounds() { + return transparentBounds; + } + + /** + * Sets the transparency of region bounds for this matcher. + * + *

Invoking this method with an argument of true will set this + * matcher to use transparent bounds. If the boolean + * argument is false, then opaque bounds will be used. + * + *

Using transparent bounds, the boundaries of this + * matcher's region are transparent to lookahead, lookbehind, + * and boundary matching constructs. Those constructs can see beyond the + * boundaries of the region to see if a match is appropriate. + * + *

Using opaque bounds, the boundaries of this matcher's + * region are opaque to lookahead, lookbehind, and boundary matching + * constructs that may try to see beyond them. Those constructs cannot + * look past the boundaries so they will fail to match anything outside + * of the region. + * + *

By default, a matcher uses opaque bounds. + * + * @param b a boolean indicating whether to use opaque or transparent + * regions + * @return this matcher + * @see java.util.regex.Matcher#hasTransparentBounds + * @since 1.5 + */ + public Matcher useTransparentBounds(boolean b) { + transparentBounds = b; + return this; + } + + /** + * Queries the anchoring of region bounds for this matcher. + * + *

This method returns true if this matcher uses + * anchoring bounds, false otherwise. + * + *

See {@link #useAnchoringBounds useAnchoringBounds} for a + * description of anchoring bounds. + * + *

By default, a matcher uses anchoring region boundaries. + * + * @return true iff this matcher is using anchoring bounds, + * false otherwise. + * @see java.util.regex.Matcher#useAnchoringBounds(boolean) + * @since 1.5 + */ + public boolean hasAnchoringBounds() { + return anchoringBounds; + } + + /** + * Sets the anchoring of region bounds for this matcher. + * + *

Invoking this method with an argument of true will set this + * matcher to use anchoring bounds. If the boolean + * argument is false, then non-anchoring bounds will be + * used. + * + *

Using anchoring bounds, the boundaries of this + * matcher's region match anchors such as ^ and $. + * + *

Without anchoring bounds, the boundaries of this + * matcher's region will not match anchors such as ^ and $. + * + *

By default, a matcher uses anchoring region boundaries. + * + * @param b a boolean indicating whether or not to use anchoring bounds. + * @return this matcher + * @see java.util.regex.Matcher#hasAnchoringBounds + * @since 1.5 + */ + public Matcher useAnchoringBounds(boolean b) { + anchoringBounds = b; + return this; + } + + /** + *

Returns the string representation of this matcher. The + * string representation of a Matcher contains information + * that may be useful for debugging. The exact format is unspecified. + * + * @return The string representation of this matcher + * @since 1.5 + */ + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("java.util.regex.Matcher"); + sb.append("[pattern=" + pattern()); + sb.append(" region="); + sb.append(regionStart() + "," + regionEnd()); + sb.append(" lastmatch="); + if ((first >= 0) && (group() != null)) { + sb.append(group()); + } + sb.append("]"); + return sb.toString(); + } + + /** + *

Returns true if the end of input was hit by the search engine in + * the last match operation performed by this matcher. + * + *

When this method returns true, then it is possible that more input + * would have changed the result of the last search. + * + * @return true iff the end of input was hit in the last match; false + * otherwise + * @since 1.5 + */ + public boolean hitEnd() { + return hitEnd; + } + + /** + *

Returns true if more input could change a positive match into a + * negative one. + * + *

If this method returns true, and a match was found, then more + * input could cause the match to be lost. If this method returns false + * and a match was found, then more input might change the match but the + * match won't be lost. If a match was not found, then requireEnd has no + * meaning. + * + * @return true iff more input could change a positive match into a + * negative one. + * @since 1.5 + */ + public boolean requireEnd() { + return requireEnd; + } + + /** + * Initiates a search to find a Pattern within the given bounds. + * The groups are filled with default values and the match of the root + * of the state machine is called. The state machine will hold the state + * of the match as it proceeds in this matcher. + * + * Matcher.from is not set here, because it is the "hard" boundary + * of the start of the search which anchors will set to. The from param + * is the "soft" boundary of the start of the search, meaning that the + * regex tries to match at that index but ^ won't match there. Subsequent + * calls to the search methods start at a new "soft" boundary which is + * the end of the previous match. + */ + boolean search(int from) { + this.hitEnd = false; + this.requireEnd = false; + from = from < 0 ? 0 : from; + this.first = from; + this.oldLast = oldLast < 0 ? from : oldLast; + for (int i = 0; i < groups.length; i++) + groups[i] = -1; + acceptMode = NOANCHOR; + boolean result = parentPattern.root.match(this, from, text); + if (!result) + this.first = -1; + this.oldLast = this.last; + return result; + } + + /** + * Initiates a search for an anchored match to a Pattern within the given + * bounds. The groups are filled with default values and the match of the + * root of the state machine is called. The state machine will hold the + * state of the match as it proceeds in this matcher. + */ + boolean match(int from, int anchor) { + this.hitEnd = false; + this.requireEnd = false; + from = from < 0 ? 0 : from; + this.first = from; + this.oldLast = oldLast < 0 ? from : oldLast; + for (int i = 0; i < groups.length; i++) + groups[i] = -1; + acceptMode = anchor; + boolean result = parentPattern.matchRoot.match(this, from, text); + if (!result) + this.first = -1; + this.oldLast = this.last; + return result; + } + + /** + * Returns the end index of the text. + * + * @return the index after the last character in the text + */ + int getTextLength() { + return text.length(); + } + + /** + * Generates a String from this Matcher's input in the specified range. + * + * @param beginIndex the beginning index, inclusive + * @param endIndex the ending index, exclusive + * @return A String generated from this Matcher's input + */ + CharSequence getSubSequence(int beginIndex, int endIndex) { + return text.subSequence(beginIndex, endIndex); + } + + /** + * Returns this Matcher's input character at index i. + * + * @return A char from the specified index + */ + char charAt(int i) { + return text.charAt(i); + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/regex/Pattern.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/regex/Pattern.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,5657 @@ +/* + * Copyright (c) 1999, 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.regex; + +import java.util.Locale; +import java.util.Map; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Arrays; + + +/** + * A compiled representation of a regular expression. + * + *

A regular expression, specified as a string, must first be compiled into + * an instance of this class. The resulting pattern can then be used to create + * a {@link Matcher} object that can match arbitrary {@link + * java.lang.CharSequence character sequences} against the regular + * expression. All of the state involved in performing a match resides in the + * matcher, so many matchers can share the same pattern. + * + *

A typical invocation sequence is thus + * + *

+ * Pattern p = Pattern.{@link #compile compile}("a*b");
+ * Matcher m = p.{@link #matcher matcher}("aaaaab");
+ * boolean b = m.{@link Matcher#matches matches}();
+ * + *

A {@link #matches matches} method is defined by this class as a + * convenience for when a regular expression is used just once. This method + * compiles an expression and matches an input sequence against it in a single + * invocation. The statement + * + *

+ * boolean b = Pattern.matches("a*b", "aaaaab");
+ * + * is equivalent to the three statements above, though for repeated matches it + * is less efficient since it does not allow the compiled pattern to be reused. + * + *

Instances of this class are immutable and are safe for use by multiple + * concurrent threads. Instances of the {@link Matcher} class are not safe for + * such use. + * + * + * + *

Summary of regular-expression constructs

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ConstructMatches
 
Characters
xThe character x
\\The backslash character
\0nThe character with octal value 0n + * (0 <= n <= 7)
\0nnThe character with octal value 0nn + * (0 <= n <= 7)
\0mnnThe character with octal value 0mnn + * (0 <= m <= 3, + * 0 <= n <= 7)
\xhhThe character with hexadecimal value 0xhh
\uhhhhThe character with hexadecimal value 0xhhhh
\x{h...h}The character with hexadecimal value 0xh...h + * ({@link java.lang.Character#MIN_CODE_POINT Character.MIN_CODE_POINT} + *  <= 0xh...h <=  + * {@link java.lang.Character#MAX_CODE_POINT Character.MAX_CODE_POINT})
\tThe tab character ('\u0009')
\nThe newline (line feed) character ('\u000A')
\rThe carriage-return character ('\u000D')
\fThe form-feed character ('\u000C')
\aThe alert (bell) character ('\u0007')
\eThe escape character ('\u001B')
\cxThe control character corresponding to x
 
Character classes
[abc]a, b, or c (simple class)
[^abc]Any character except a, b, or c (negation)
[a-zA-Z]a through z + * or A through Z, inclusive (range)
[a-d[m-p]]a through d, + * or m through p: [a-dm-p] (union)
[a-z&&[def]]d, e, or f (intersection)
[a-z&&[^bc]]a through z, + * except for b and c: [ad-z] (subtraction)
[a-z&&[^m-p]]a through z, + * and not m through p: [a-lq-z](subtraction)
 
Predefined character classes
.Any character (may or may not match line terminators)
\dA digit: [0-9]
\DA non-digit: [^0-9]
\sA whitespace character: [ \t\n\x0B\f\r]
\SA non-whitespace character: [^\s]
\wA word character: [a-zA-Z_0-9]
\WA non-word character: [^\w]
 
POSIX character classes (US-ASCII only)
\p{Lower}A lower-case alphabetic character: [a-z]
\p{Upper}An upper-case alphabetic character:[A-Z]
\p{ASCII}All ASCII:[\x00-\x7F]
\p{Alpha}An alphabetic character:[\p{Lower}\p{Upper}]
\p{Digit}A decimal digit: [0-9]
\p{Alnum}An alphanumeric character:[\p{Alpha}\p{Digit}]
\p{Punct}Punctuation: One of !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
\p{Graph}A visible character: [\p{Alnum}\p{Punct}]
\p{Print}A printable character: [\p{Graph}\x20]
\p{Blank}A space or a tab: [ \t]
\p{Cntrl}A control character: [\x00-\x1F\x7F]
\p{XDigit}A hexadecimal digit: [0-9a-fA-F]
\p{Space}A whitespace character: [ \t\n\x0B\f\r]
 
java.lang.Character classes (simple java character type)
\p{javaLowerCase}Equivalent to java.lang.Character.isLowerCase()
\p{javaUpperCase}Equivalent to java.lang.Character.isUpperCase()
\p{javaWhitespace}Equivalent to java.lang.Character.isWhitespace()
\p{javaMirrored}Equivalent to java.lang.Character.isMirrored()
 
Classes for Unicode scripts, blocks, categories and binary properties
\p{IsLatin}A Latin script character (script)
\p{InGreek}A character in the Greek block (block)
\p{Lu}An uppercase letter (category)
\p{IsAlphabetic}An alphabetic character (binary property)
\p{Sc}A currency symbol
\P{InGreek}Any character except one in the Greek block (negation)
[\p{L}&&[^\p{Lu}]] Any letter except an uppercase letter (subtraction)
 
Boundary matchers
^The beginning of a line
$The end of a line
\bA word boundary
\BA non-word boundary
\AThe beginning of the input
\GThe end of the previous match
\ZThe end of the input but for the final + * terminator, if any
\zThe end of the input
 
Greedy quantifiers
X?X, once or not at all
X*X, zero or more times
X+X, one or more times
X{n}X, exactly n times
X{n,}X, at least n times
X{n,m}X, at least n but not more than m times
 
Reluctant quantifiers
X??X, once or not at all
X*?X, zero or more times
X+?X, one or more times
X{n}?X, exactly n times
X{n,}?X, at least n times
X{n,m}?X, at least n but not more than m times
 
Possessive quantifiers
X?+X, once or not at all
X*+X, zero or more times
X++X, one or more times
X{n}+X, exactly n times
X{n,}+X, at least n times
X{n,m}+X, at least n but not more than m times
 
Logical operators
XYX followed by Y
X|YEither X or Y
(X)X, as a capturing group
 
Back references
\nWhatever the nth + * capturing group matched
\k<name>Whatever the + * named-capturing group "name" matched
 
Quotation
\Nothing, but quotes the following character
\QNothing, but quotes all characters until \E
\ENothing, but ends quoting started by \Q
 
Special constructs (named-capturing and non-capturing)
(?<name>X)X, as a named-capturing group
(?:X)X, as a non-capturing group
(?idmsuxU-idmsuxU) Nothing, but turns match flags i + * d m s + * u x U + * on - off
(?idmsux-idmsux:X)  X, as a non-capturing group with the + * given flags i d + * m s u + * x on - off
(?=X)X, via zero-width positive lookahead
(?!X)X, via zero-width negative lookahead
(?<=X)X, via zero-width positive lookbehind
(?<!X)X, via zero-width negative lookbehind
(?>X)X, as an independent, non-capturing group
+ * + *
+ * + * + *
+ *

Backslashes, escapes, and quoting

+ * + *

The backslash character ('\') serves to introduce escaped + * constructs, as defined in the table above, as well as to quote characters + * that otherwise would be interpreted as unescaped constructs. Thus the + * expression \\ matches a single backslash and \{ matches a + * left brace. + * + *

It is an error to use a backslash prior to any alphabetic character that + * does not denote an escaped construct; these are reserved for future + * extensions to the regular-expression language. A backslash may be used + * prior to a non-alphabetic character regardless of whether that character is + * part of an unescaped construct. + * + *

Backslashes within string literals in Java source code are interpreted + * as required by + * The Java™ Language Specification + * as either Unicode escapes (section 3.3) or other character escapes (section 3.10.6) + * It is therefore necessary to double backslashes in string + * literals that represent regular expressions to protect them from + * interpretation by the Java bytecode compiler. The string literal + * "\b", for example, matches a single backspace character when + * interpreted as a regular expression, while "\\b" matches a + * word boundary. The string literal "\(hello\)" is illegal + * and leads to a compile-time error; in order to match the string + * (hello) the string literal "\\(hello\\)" + * must be used. + * + * + *

Character Classes

+ * + *

Character classes may appear within other character classes, and + * may be composed by the union operator (implicit) and the intersection + * operator (&&). + * The union operator denotes a class that contains every character that is + * in at least one of its operand classes. The intersection operator + * denotes a class that contains every character that is in both of its + * operand classes. + * + *

The precedence of character-class operators is as follows, from + * highest to lowest: + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
1    Literal escape    \x
2    Grouping[...]
3    Rangea-z
4    Union[a-e][i-u]
5    Intersection[a-z&&[aeiou]]
+ * + *

Note that a different set of metacharacters are in effect inside + * a character class than outside a character class. For instance, the + * regular expression . loses its special meaning inside a + * character class, while the expression - becomes a range + * forming metacharacter. + * + * + *

Line terminators

+ * + *

A line terminator is a one- or two-character sequence that marks + * the end of a line of the input character sequence. The following are + * recognized as line terminators: + * + *

    + * + *
  • A newline (line feed) character ('\n'), + * + *
  • A carriage-return character followed immediately by a newline + * character ("\r\n"), + * + *
  • A standalone carriage-return character ('\r'), + * + *
  • A next-line character ('\u0085'), + * + *
  • A line-separator character ('\u2028'), or + * + *
  • A paragraph-separator character ('\u2029). + * + *
+ *

If {@link #UNIX_LINES} mode is activated, then the only line terminators + * recognized are newline characters. + * + *

The regular expression . matches any character except a line + * terminator unless the {@link #DOTALL} flag is specified. + * + *

By default, the regular expressions ^ and $ ignore + * line terminators and only match at the beginning and the end, respectively, + * of the entire input sequence. If {@link #MULTILINE} mode is activated then + * ^ matches at the beginning of input and after any line terminator + * except at the end of input. When in {@link #MULTILINE} mode $ + * matches just before a line terminator or the end of the input sequence. + * + * + *

Groups and capturing

+ * + * + *
Group number
+ *

Capturing groups are numbered by counting their opening parentheses from + * left to right. In the expression ((A)(B(C))), for example, there + * are four such groups:

+ * + *
+ * + * + * + * + * + * + * + * + *
1    ((A)(B(C)))
2    (A)
3    (B(C))
4    (C)
+ * + *

Group zero always stands for the entire expression. + * + *

Capturing groups are so named because, during a match, each subsequence + * of the input sequence that matches such a group is saved. The captured + * subsequence may be used later in the expression, via a back reference, and + * may also be retrieved from the matcher once the match operation is complete. + * + * + *

Group name
+ *

A capturing group can also be assigned a "name", a named-capturing group, + * and then be back-referenced later by the "name". Group names are composed of + * the following characters. The first character must be a letter. + * + *

    + *
  • The uppercase letters 'A' through 'Z' + * ('\u0041' through '\u005a'), + *
  • The lowercase letters 'a' through 'z' + * ('\u0061' through '\u007a'), + *
  • The digits '0' through '9' + * ('\u0030' through '\u0039'), + *
+ * + *

A named-capturing group is still numbered as described in + * Group number. + * + *

The captured input associated with a group is always the subsequence + * that the group most recently matched. If a group is evaluated a second time + * because of quantification then its previously-captured value, if any, will + * be retained if the second evaluation fails. Matching the string + * "aba" against the expression (a(b)?)+, for example, leaves + * group two set to "b". All captured input is discarded at the + * beginning of each match. + * + *

Groups beginning with (? are either pure, non-capturing groups + * that do not capture text and do not count towards the group total, or + * named-capturing group. + * + *

Unicode support

+ * + *

This class is in conformance with Level 1 of Unicode Technical + * Standard #18: Unicode Regular Expression, plus RL2.1 + * Canonical Equivalents. + *

+ * Unicode escape sequences such as \u2014 in Java source code + * are processed as described in section 3.3 of + * The Java™ Language Specification. + * Such escape sequences are also implemented directly by the regular-expression + * parser so that Unicode escapes can be used in expressions that are read from + * files or from the keyboard. Thus the strings "\u2014" and + * "\\u2014", while not equal, compile into the same pattern, which + * matches the character with hexadecimal value 0x2014. + *

+ * A Unicode character can also be represented in a regular-expression by + * using its Hex notation(hexadecimal code point value) directly as described in construct + * \x{...}, for example a supplementary character U+2011F + * can be specified as \x{2011F}, instead of two consecutive + * Unicode escape sequences of the surrogate pair + * \uD840\uDD1F. + *

+ * Unicode scripts, blocks, categories and binary properties are written with + * the \p and \P constructs as in Perl. + * \p{prop} matches if + * the input has the property prop, while \P{prop} + * does not match if the input has that property. + *

+ * Scripts, blocks, categories and binary properties can be used both inside + * and outside of a character class. + * + *

+ * Scripts are specified either with the prefix {@code Is}, as in + * {@code IsHiragana}, or by using the {@code script} keyword (or its short + * form {@code sc})as in {@code script=Hiragana} or {@code sc=Hiragana}. + *

+ * The script names supported by Pattern are the valid script names + * accepted and defined by + * {@link java.lang.Character.UnicodeScript#forName(String) UnicodeScript.forName}. + * + *

+ * Blocks are specified with the prefix {@code In}, as in + * {@code InMongolian}, or by using the keyword {@code block} (or its short + * form {@code blk}) as in {@code block=Mongolian} or {@code blk=Mongolian}. + *

+ * The block names supported by Pattern are the valid block names + * accepted and defined by + * {@link java.lang.Character.UnicodeBlock#forName(String) UnicodeBlock.forName}. + *

+ * + * Categories may be specified with the optional prefix {@code Is}: + * Both {@code \p{L}} and {@code \p{IsL}} denote the category of Unicode + * letters. Same as scripts and blocks, categories can also be specified + * by using the keyword {@code general_category} (or its short form + * {@code gc}) as in {@code general_category=Lu} or {@code gc=Lu}. + *

+ * The supported categories are those of + * + * The Unicode Standard in the version specified by the + * {@link java.lang.Character Character} class. The category names are those + * defined in the Standard, both normative and informative. + *

+ * + * Binary properties are specified with the prefix {@code Is}, as in + * {@code IsAlphabetic}. The supported binary properties by Pattern + * are + *

+ + + *

+ * Predefined Character classes and POSIX character classes are in + * conformance with the recommendation of Annex C: Compatibility Properties + * of Unicode Regular Expression + * , when {@link #UNICODE_CHARACTER_CLASS} flag is specified. + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ClassesMatches
\p{Lower}A lowercase character:\p{IsLowercase}
\p{Upper}An uppercase character:\p{IsUppercase}
\p{ASCII}All ASCII:[\x00-\x7F]
\p{Alpha}An alphabetic character:\p{IsAlphabetic}
\p{Digit}A decimal digit character:p{IsDigit}
\p{Alnum}An alphanumeric character:[\p{IsAlphabetic}\p{IsDigit}]
\p{Punct}A punctuation character:p{IsPunctuation}
\p{Graph}A visible character: [^\p{IsWhite_Space}\p{gc=Cc}\p{gc=Cs}\p{gc=Cn}]
\p{Print}A printable character: [\p{Graph}\p{Blank}&&[^\p{Cntrl}]]
\p{Blank}A space or a tab: [\p{IsWhite_Space}&&[^\p{gc=Zl}\p{gc=Zp}\x0a\x0b\x0c\x0d\x85]]
\p{Cntrl}A control character: \p{gc=Cc}
\p{XDigit}A hexadecimal digit: [\p{gc=Nd}\p{IsHex_Digit}]
\p{Space}A whitespace character:\p{IsWhite_Space}
\dA digit: \p{IsDigit}
\DA non-digit: [^\d]
\sA whitespace character: \p{IsWhite_Space}
\SA non-whitespace character: [^\s]
\wA word character: [\p{Alpha}\p{gc=Mn}\p{gc=Me}\p{gc=Mc}\p{Digit}\p{gc=Pc}]
\WA non-word character: [^\w]
+ *

+ * + * Categories that behave like the java.lang.Character + * boolean ismethodname methods (except for the deprecated ones) are + * available through the same \p{prop} syntax where + * the specified property has the name javamethodname. + * + *

Comparison to Perl 5

+ * + *

The Pattern engine performs traditional NFA-based matching + * with ordered alternation as occurs in Perl 5. + * + *

Perl constructs not supported by this class:

+ * + *
+ * + *

Constructs supported by this class but not by Perl:

+ * + *
    + * + *
  • Character-class union and intersection as described + * above.

  • + * + *
+ * + *

Notable differences from Perl:

+ * + *
    + * + *
  • In Perl, \1 through \9 are always interpreted + * as back references; a backslash-escaped number greater than 9 is + * treated as a back reference if at least that many subexpressions exist, + * otherwise it is interpreted, if possible, as an octal escape. In this + * class octal escapes must always begin with a zero. In this class, + * \1 through \9 are always interpreted as back + * references, and a larger number is accepted as a back reference if at + * least that many subexpressions exist at that point in the regular + * expression, otherwise the parser will drop digits until the number is + * smaller or equal to the existing number of groups or it is one digit. + *

  • + * + *
  • Perl uses the g flag to request a match that resumes + * where the last match left off. This functionality is provided implicitly + * by the {@link Matcher} class: Repeated invocations of the {@link + * Matcher#find find} method will resume where the last match left off, + * unless the matcher is reset.

  • + * + *
  • In Perl, embedded flags at the top level of an expression affect + * the whole expression. In this class, embedded flags always take effect + * at the point at which they appear, whether they are at the top level or + * within a group; in the latter case, flags are restored at the end of the + * group just as in Perl.

  • + * + *
+ * + * + *

For a more precise description of the behavior of regular expression + * constructs, please see + * Mastering Regular Expressions, 3nd Edition, Jeffrey E. F. Friedl, + * O'Reilly and Associates, 2006. + *

+ * + * @see java.lang.String#split(String, int) + * @see java.lang.String#split(String) + * + * @author Mike McCloskey + * @author Mark Reinhold + * @author JSR-51 Expert Group + * @since 1.4 + * @spec JSR-51 + */ + +public final class Pattern + implements java.io.Serializable +{ + + /** + * Regular expression modifier values. Instead of being passed as + * arguments, they can also be passed as inline modifiers. + * For example, the following statements have the same effect. + *
+     * RegExp r1 = RegExp.compile("abc", Pattern.I|Pattern.M);
+     * RegExp r2 = RegExp.compile("(?im)abc", 0);
+     * 
+ * + * The flags are duplicated so that the familiar Perl match flag + * names are available. + */ + + /** + * Enables Unix lines mode. + * + *

In this mode, only the '\n' line terminator is recognized + * in the behavior of ., ^, and $. + * + *

Unix lines mode can also be enabled via the embedded flag + * expression (?d). + */ + public static final int UNIX_LINES = 0x01; + + /** + * Enables case-insensitive matching. + * + *

By default, case-insensitive matching assumes that only characters + * in the US-ASCII charset are being matched. Unicode-aware + * case-insensitive matching can be enabled by specifying the {@link + * #UNICODE_CASE} flag in conjunction with this flag. + * + *

Case-insensitive matching can also be enabled via the embedded flag + * expression (?i). + * + *

Specifying this flag may impose a slight performance penalty.

+ */ + public static final int CASE_INSENSITIVE = 0x02; + + /** + * Permits whitespace and comments in pattern. + * + *

In this mode, whitespace is ignored, and embedded comments starting + * with # are ignored until the end of a line. + * + *

Comments mode can also be enabled via the embedded flag + * expression (?x). + */ + public static final int COMMENTS = 0x04; + + /** + * Enables multiline mode. + * + *

In multiline mode the expressions ^ and $ match + * just after or just before, respectively, a line terminator or the end of + * the input sequence. By default these expressions only match at the + * beginning and the end of the entire input sequence. + * + *

Multiline mode can also be enabled via the embedded flag + * expression (?m).

+ */ + public static final int MULTILINE = 0x08; + + /** + * Enables literal parsing of the pattern. + * + *

When this flag is specified then the input string that specifies + * the pattern is treated as a sequence of literal characters. + * Metacharacters or escape sequences in the input sequence will be + * given no special meaning. + * + *

The flags CASE_INSENSITIVE and UNICODE_CASE retain their impact on + * matching when used in conjunction with this flag. The other flags + * become superfluous. + * + *

There is no embedded flag character for enabling literal parsing. + * @since 1.5 + */ + public static final int LITERAL = 0x10; + + /** + * Enables dotall mode. + * + *

In dotall mode, the expression . matches any character, + * including a line terminator. By default this expression does not match + * line terminators. + * + *

Dotall mode can also be enabled via the embedded flag + * expression (?s). (The s is a mnemonic for + * "single-line" mode, which is what this is called in Perl.)

+ */ + public static final int DOTALL = 0x20; + + /** + * Enables Unicode-aware case folding. + * + *

When this flag is specified then case-insensitive matching, when + * enabled by the {@link #CASE_INSENSITIVE} flag, is done in a manner + * consistent with the Unicode Standard. By default, case-insensitive + * matching assumes that only characters in the US-ASCII charset are being + * matched. + * + *

Unicode-aware case folding can also be enabled via the embedded flag + * expression (?u). + * + *

Specifying this flag may impose a performance penalty.

+ */ + public static final int UNICODE_CASE = 0x40; + + /** + * Enables canonical equivalence. + * + *

When this flag is specified then two characters will be considered + * to match if, and only if, their full canonical decompositions match. + * The expression "a\u030A", for example, will match the + * string "\u00E5" when this flag is specified. By default, + * matching does not take canonical equivalence into account. + * + *

There is no embedded flag character for enabling canonical + * equivalence. + * + *

Specifying this flag may impose a performance penalty.

+ */ + public static final int CANON_EQ = 0x80; + + /** + * Enables the Unicode version of Predefined character classes and + * POSIX character classes. + * + *

When this flag is specified then the (US-ASCII only) + * Predefined character classes and POSIX character classes + * are in conformance with + * Unicode Technical + * Standard #18: Unicode Regular Expression + * Annex C: Compatibility Properties. + *

+ * The UNICODE_CHARACTER_CLASS mode can also be enabled via the embedded + * flag expression (?U). + *

+ * The flag implies UNICODE_CASE, that is, it enables Unicode-aware case + * folding. + *

+ * Specifying this flag may impose a performance penalty.

+ * @since 1.7 + */ + public static final int UNICODE_CHARACTER_CLASS = 0x100; + + /* Pattern has only two serialized components: The pattern string + * and the flags, which are all that is needed to recompile the pattern + * when it is deserialized. + */ + + /** use serialVersionUID from Merlin b59 for interoperability */ + private static final long serialVersionUID = 5073258162644648461L; + + /** + * The original regular-expression pattern string. + * + * @serial + */ + private String pattern; + + /** + * The original pattern flags. + * + * @serial + */ + private int flags; + + /** + * Boolean indicating this Pattern is compiled; this is necessary in order + * to lazily compile deserialized Patterns. + */ + private transient volatile boolean compiled = false; + + /** + * The normalized pattern string. + */ + private transient String normalizedPattern; + + /** + * The starting point of state machine for the find operation. This allows + * a match to start anywhere in the input. + */ + transient Node root; + + /** + * The root of object tree for a match operation. The pattern is matched + * at the beginning. This may include a find that uses BnM or a First + * node. + */ + transient Node matchRoot; + + /** + * Temporary storage used by parsing pattern slice. + */ + transient int[] buffer; + + /** + * Map the "name" of the "named capturing group" to its group id + * node. + */ + transient volatile Map namedGroups; + + /** + * Temporary storage used while parsing group references. + */ + transient GroupHead[] groupNodes; + + /** + * Temporary null terminated code point array used by pattern compiling. + */ + private transient int[] temp; + + /** + * The number of capturing groups in this Pattern. Used by matchers to + * allocate storage needed to perform a match. + */ + transient int capturingGroupCount; + + /** + * The local variable count used by parsing tree. Used by matchers to + * allocate storage needed to perform a match. + */ + transient int localCount; + + /** + * Index into the pattern string that keeps track of how much has been + * parsed. + */ + private transient int cursor; + + /** + * Holds the length of the pattern string. + */ + private transient int patternLength; + + /** + * If the Start node might possibly match supplementary characters. + * It is set to true during compiling if + * (1) There is supplementary char in pattern, or + * (2) There is complement node of Category or Block + */ + private transient boolean hasSupplementary; + + /** + * Compiles the given regular expression into a pattern.

+ * + * @param regex + * The expression to be compiled + * + * @throws PatternSyntaxException + * If the expression's syntax is invalid + */ + public static Pattern compile(String regex) { + return new Pattern(regex, 0); + } + + /** + * Compiles the given regular expression into a pattern with the given + * flags.

+ * + * @param regex + * The expression to be compiled + * + * @param flags + * Match flags, a bit mask that may include + * {@link #CASE_INSENSITIVE}, {@link #MULTILINE}, {@link #DOTALL}, + * {@link #UNICODE_CASE}, {@link #CANON_EQ}, {@link #UNIX_LINES}, + * {@link #LITERAL}, {@link #UNICODE_CHARACTER_CLASS} + * and {@link #COMMENTS} + * + * @throws IllegalArgumentException + * If bit values other than those corresponding to the defined + * match flags are set in flags + * + * @throws PatternSyntaxException + * If the expression's syntax is invalid + */ + public static Pattern compile(String regex, int flags) { + return new Pattern(regex, flags); + } + + /** + * Returns the regular expression from which this pattern was compiled. + *

+ * + * @return The source of this pattern + */ + public String pattern() { + return pattern; + } + + /** + *

Returns the string representation of this pattern. This + * is the regular expression from which this pattern was + * compiled.

+ * + * @return The string representation of this pattern + * @since 1.5 + */ + public String toString() { + return pattern; + } + + /** + * Creates a matcher that will match the given input against this pattern. + *

+ * + * @param input + * The character sequence to be matched + * + * @return A new matcher for this pattern + */ + public Matcher matcher(CharSequence input) { + if (!compiled) { + synchronized(this) { + if (!compiled) + compile(); + } + } + Matcher m = new Matcher(this, input); + return m; + } + + /** + * Returns this pattern's match flags.

+ * + * @return The match flags specified when this pattern was compiled + */ + public int flags() { + return flags; + } + + /** + * Compiles the given regular expression and attempts to match the given + * input against it. + * + *

An invocation of this convenience method of the form + * + *

+     * Pattern.matches(regex, input);
+ * + * behaves in exactly the same way as the expression + * + *
+     * Pattern.compile(regex).matcher(input).matches()
+ * + *

If a pattern is to be used multiple times, compiling it once and reusing + * it will be more efficient than invoking this method each time.

+ * + * @param regex + * The expression to be compiled + * + * @param input + * The character sequence to be matched + * + * @throws PatternSyntaxException + * If the expression's syntax is invalid + */ + public static boolean matches(String regex, CharSequence input) { + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(input); + return m.matches(); + } + + /** + * Splits the given input sequence around matches of this pattern. + * + *

The array returned by this method contains each substring of the + * input sequence that is terminated by another subsequence that matches + * this pattern or is terminated by the end of the input sequence. The + * substrings in the array are in the order in which they occur in the + * input. If this pattern does not match any subsequence of the input then + * the resulting array has just one element, namely the input sequence in + * string form. + * + *

The limit parameter controls the number of times the + * pattern is applied and therefore affects the length of the resulting + * array. If the limit n is greater than zero then the pattern + * will be applied at most n - 1 times, the array's + * length will be no greater than n, and the array's last entry + * will contain all input beyond the last matched delimiter. If n + * is non-positive then the pattern will be applied as many times as + * possible and the array can have any length. If n is zero then + * the pattern will be applied as many times as possible, the array can + * have any length, and trailing empty strings will be discarded. + * + *

The input "boo:and:foo", for example, yields the following + * results with these parameters: + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *

Regex    

Limit    

Result    

:2{ "boo", "and:foo" }
:5{ "boo", "and", "foo" }
:-2{ "boo", "and", "foo" }
o5{ "b", "", ":and:f", "", "" }
o-2{ "b", "", ":and:f", "", "" }
o0{ "b", "", ":and:f" }
+ * + * + * @param input + * The character sequence to be split + * + * @param limit + * The result threshold, as described above + * + * @return The array of strings computed by splitting the input + * around matches of this pattern + */ + public String[] split(CharSequence input, int limit) { + int index = 0; + boolean matchLimited = limit > 0; + ArrayList matchList = new ArrayList<>(); + Matcher m = matcher(input); + + // Add segments before each match found + while(m.find()) { + if (!matchLimited || matchList.size() < limit - 1) { + String match = input.subSequence(index, m.start()).toString(); + matchList.add(match); + index = m.end(); + } else if (matchList.size() == limit - 1) { // last one + String match = input.subSequence(index, + input.length()).toString(); + matchList.add(match); + index = m.end(); + } + } + + // If no match was found, return this + if (index == 0) + return new String[] {input.toString()}; + + // Add remaining segment + if (!matchLimited || matchList.size() < limit) + matchList.add(input.subSequence(index, input.length()).toString()); + + // Construct result + int resultSize = matchList.size(); + if (limit == 0) + while (resultSize > 0 && matchList.get(resultSize-1).equals("")) + resultSize--; + String[] result = new String[resultSize]; + return matchList.subList(0, resultSize).toArray(result); + } + + /** + * Splits the given input sequence around matches of this pattern. + * + *

This method works as if by invoking the two-argument {@link + * #split(java.lang.CharSequence, int) split} method with the given input + * sequence and a limit argument of zero. Trailing empty strings are + * therefore not included in the resulting array.

+ * + *

The input "boo:and:foo", for example, yields the following + * results with these expressions: + * + *

+ * + * + * + * + * + * + *

Regex    

Result

:{ "boo", "and", "foo" }
o{ "b", "", ":and:f" }
+ * + * + * @param input + * The character sequence to be split + * + * @return The array of strings computed by splitting the input + * around matches of this pattern + */ + public String[] split(CharSequence input) { + return split(input, 0); + } + + /** + * Returns a literal pattern String for the specified + * String. + * + *

This method produces a String that can be used to + * create a Pattern that would match the string + * s as if it were a literal pattern.

Metacharacters + * or escape sequences in the input sequence will be given no special + * meaning. + * + * @param s The string to be literalized + * @return A literal string replacement + * @since 1.5 + */ + public static String quote(String s) { + int slashEIndex = s.indexOf("\\E"); + if (slashEIndex == -1) + return "\\Q" + s + "\\E"; + + StringBuilder sb = new StringBuilder(s.length() * 2); + sb.append("\\Q"); + slashEIndex = 0; + int current = 0; + while ((slashEIndex = s.indexOf("\\E", current)) != -1) { + sb.append(s.substring(current, slashEIndex)); + current = slashEIndex + 2; + sb.append("\\E\\\\E\\Q"); + } + sb.append(s.substring(current, s.length())); + sb.append("\\E"); + return sb.toString(); + } + + /** + * Recompile the Pattern instance from a stream. The original pattern + * string is read in and the object tree is recompiled from it. + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + + // Read in all fields + s.defaultReadObject(); + + // Initialize counts + capturingGroupCount = 1; + localCount = 0; + + // if length > 0, the Pattern is lazily compiled + compiled = false; + if (pattern.length() == 0) { + root = new Start(lastAccept); + matchRoot = lastAccept; + compiled = true; + } + } + + /** + * This private constructor is used to create all Patterns. The pattern + * string and match flags are all that is needed to completely describe + * a Pattern. An empty pattern string results in an object tree with + * only a Start node and a LastNode node. + */ + private Pattern(String p, int f) { + pattern = p; + flags = f; + + // to use UNICODE_CASE if UNICODE_CHARACTER_CLASS present + if ((flags & UNICODE_CHARACTER_CLASS) != 0) + flags |= UNICODE_CASE; + + // Reset group index count + capturingGroupCount = 1; + localCount = 0; + + if (pattern.length() > 0) { + compile(); + } else { + root = new Start(lastAccept); + matchRoot = lastAccept; + } + } + + /** + * The pattern is converted to normalizedD form and then a pure group + * is constructed to match canonical equivalences of the characters. + */ + private void normalize() { + boolean inCharClass = false; + int lastCodePoint = -1; + + // Convert pattern into normalizedD form + normalizedPattern = Normalizer.normalize(pattern, Normalizer.NFD); + patternLength = normalizedPattern.length(); + + // Modify pattern to match canonical equivalences + StringBuilder newPattern = new StringBuilder(patternLength); + for(int i=0; i= patternLength) + break; + c = normalizedPattern.codePointAt(i); + sequenceBuffer.appendCodePoint(c); + } + String ea = produceEquivalentAlternation( + sequenceBuffer.toString()); + newPattern.setLength(newPattern.length()-Character.charCount(lastCodePoint)); + newPattern.append("(?:").append(ea).append(")"); + } else if (c == '[' && lastCodePoint != '\\') { + i = normalizeCharClass(newPattern, i); + } else { + newPattern.appendCodePoint(c); + } + lastCodePoint = c; + i += Character.charCount(c); + } + normalizedPattern = newPattern.toString(); + } + + /** + * Complete the character class being parsed and add a set + * of alternations to it that will match the canonical equivalences + * of the characters within the class. + */ + private int normalizeCharClass(StringBuilder newPattern, int i) { + StringBuilder charClass = new StringBuilder(); + StringBuilder eq = null; + int lastCodePoint = -1; + String result; + + i++; + charClass.append("["); + while(true) { + int c = normalizedPattern.codePointAt(i); + StringBuilder sequenceBuffer; + + if (c == ']' && lastCodePoint != '\\') { + charClass.append((char)c); + break; + } else if (Character.getType(c) == Character.NON_SPACING_MARK) { + sequenceBuffer = new StringBuilder(); + sequenceBuffer.appendCodePoint(lastCodePoint); + while(Character.getType(c) == Character.NON_SPACING_MARK) { + sequenceBuffer.appendCodePoint(c); + i += Character.charCount(c); + if (i >= normalizedPattern.length()) + break; + c = normalizedPattern.codePointAt(i); + } + String ea = produceEquivalentAlternation( + sequenceBuffer.toString()); + + charClass.setLength(charClass.length()-Character.charCount(lastCodePoint)); + if (eq == null) + eq = new StringBuilder(); + eq.append('|'); + eq.append(ea); + } else { + charClass.appendCodePoint(c); + i++; + } + if (i == normalizedPattern.length()) + throw error("Unclosed character class"); + lastCodePoint = c; + } + + if (eq != null) { + result = "(?:"+charClass.toString()+eq.toString()+")"; + } else { + result = charClass.toString(); + } + + newPattern.append(result); + return i; + } + + /** + * Given a specific sequence composed of a regular character and + * combining marks that follow it, produce the alternation that will + * match all canonical equivalences of that sequence. + */ + private String produceEquivalentAlternation(String source) { + int len = countChars(source, 0, 1); + if (source.length() == len) + // source has one character. + return source; + + String base = source.substring(0,len); + String combiningMarks = source.substring(len); + + String[] perms = producePermutations(combiningMarks); + StringBuilder result = new StringBuilder(source); + + // Add combined permutations + for(int x=0; x0) + result.append("|"+next); + next = composeOneStep(next); + if (next != null) + result.append("|"+produceEquivalentAlternation(next)); + } + return result.toString(); + } + + /** + * Returns an array of strings that have all the possible + * permutations of the characters in the input string. + * This is used to get a list of all possible orderings + * of a set of combining marks. Note that some of the permutations + * are invalid because of combining class collisions, and these + * possibilities must be removed because they are not canonically + * equivalent. + */ + private String[] producePermutations(String input) { + if (input.length() == countChars(input, 0, 1)) + return new String[] {input}; + + if (input.length() == countChars(input, 0, 2)) { + int c0 = Character.codePointAt(input, 0); + int c1 = Character.codePointAt(input, Character.charCount(c0)); + if (getClass(c1) == getClass(c0)) { + return new String[] {input}; + } + String[] result = new String[2]; + result[0] = input; + StringBuilder sb = new StringBuilder(2); + sb.appendCodePoint(c1); + sb.appendCodePoint(c0); + result[1] = sb.toString(); + return result; + } + + int length = 1; + int nCodePoints = countCodePoints(input); + for(int x=1; x=0; y--) { + if (combClass[y] == combClass[x]) { + continue loop; + } + } + StringBuilder sb = new StringBuilder(input); + String otherChars = sb.delete(offset, offset+len).toString(); + String[] subResult = producePermutations(otherChars); + + String prefix = input.substring(offset, offset+len); + for(int y=0; y= pLen - 1) // No \Q sequence found + return; + int j = i; + i += 2; + int[] newtemp = new int[j + 2*(pLen-i) + 2]; + System.arraycopy(temp, 0, newtemp, 0, j); + + boolean inQuote = true; + while (i < pLen) { + int c = temp[i++]; + if (! ASCII.isAscii(c) || ASCII.isAlnum(c)) { + newtemp[j++] = c; + } else if (c != '\\') { + if (inQuote) newtemp[j++] = '\\'; + newtemp[j++] = c; + } else if (inQuote) { + if (temp[i] == 'E') { + i++; + inQuote = false; + } else { + newtemp[j++] = '\\'; + newtemp[j++] = '\\'; + } + } else { + if (temp[i] == 'Q') { + i++; + inQuote = true; + } else { + newtemp[j++] = c; + if (i != pLen) + newtemp[j++] = temp[i++]; + } + } + } + + patternLength = j; + temp = Arrays.copyOf(newtemp, j + 2); // double zero termination + } + + /** + * Copies regular expression to an int array and invokes the parsing + * of the expression which will create the object tree. + */ + private void compile() { + // Handle canonical equivalences + if (has(CANON_EQ) && !has(LITERAL)) { + normalize(); + } else { + normalizedPattern = pattern; + } + patternLength = normalizedPattern.length(); + + // Copy pattern to int array for convenience + // Use double zero to terminate pattern + temp = new int[patternLength + 2]; + + hasSupplementary = false; + int c, count = 0; + // Convert all chars into code points + for (int x = 0; x < patternLength; x += Character.charCount(c)) { + c = normalizedPattern.codePointAt(x); + if (isSupplementary(c)) { + hasSupplementary = true; + } + temp[count++] = c; + } + + patternLength = count; // patternLength now in code points + + if (! has(LITERAL)) + RemoveQEQuoting(); + + // Allocate all temporary objects here. + buffer = new int[32]; + groupNodes = new GroupHead[10]; + namedGroups = null; + + if (has(LITERAL)) { + // Literal pattern handling + matchRoot = newSlice(temp, patternLength, hasSupplementary); + matchRoot.next = lastAccept; + } else { + // Start recursive descent parsing + matchRoot = expr(lastAccept); + // Check extra pattern characters + if (patternLength != cursor) { + if (peek() == ')') { + throw error("Unmatched closing ')'"); + } else { + throw error("Unexpected internal error"); + } + } + } + + // Peephole optimization + if (matchRoot instanceof Slice) { + root = BnM.optimize(matchRoot); + if (root == matchRoot) { + root = hasSupplementary ? new StartS(matchRoot) : new Start(matchRoot); + } + } else if (matchRoot instanceof Begin || matchRoot instanceof First) { + root = matchRoot; + } else { + root = hasSupplementary ? new StartS(matchRoot) : new Start(matchRoot); + } + + // Release temporary storage + temp = null; + buffer = null; + groupNodes = null; + patternLength = 0; + compiled = true; + } + + Map namedGroups() { + if (namedGroups == null) + namedGroups = new HashMap<>(2); + return namedGroups; + } + + /** + * Used to print out a subtree of the Pattern to help with debugging. + */ + private static void printObjectTree(Node node) { + while(node != null) { + if (node instanceof Prolog) { + System.out.println(node); + printObjectTree(((Prolog)node).loop); + System.out.println("**** end contents prolog loop"); + } else if (node instanceof Loop) { + System.out.println(node); + printObjectTree(((Loop)node).body); + System.out.println("**** end contents Loop body"); + } else if (node instanceof Curly) { + System.out.println(node); + printObjectTree(((Curly)node).atom); + System.out.println("**** end contents Curly body"); + } else if (node instanceof GroupCurly) { + System.out.println(node); + printObjectTree(((GroupCurly)node).atom); + System.out.println("**** end contents GroupCurly body"); + } else if (node instanceof GroupTail) { + System.out.println(node); + System.out.println("Tail next is "+node.next); + return; + } else { + System.out.println(node); + } + node = node.next; + if (node != null) + System.out.println("->next:"); + if (node == Pattern.accept) { + System.out.println("Accept Node"); + node = null; + } + } + } + + /** + * Used to accumulate information about a subtree of the object graph + * so that optimizations can be applied to the subtree. + */ + static final class TreeInfo { + int minLength; + int maxLength; + boolean maxValid; + boolean deterministic; + + TreeInfo() { + reset(); + } + void reset() { + minLength = 0; + maxLength = 0; + maxValid = true; + deterministic = true; + } + } + + /* + * The following private methods are mainly used to improve the + * readability of the code. In order to let the Java compiler easily + * inline them, we should not put many assertions or error checks in them. + */ + + /** + * Indicates whether a particular flag is set or not. + */ + private boolean has(int f) { + return (flags & f) != 0; + } + + /** + * Match next character, signal error if failed. + */ + private void accept(int ch, String s) { + int testChar = temp[cursor++]; + if (has(COMMENTS)) + testChar = parsePastWhitespace(testChar); + if (ch != testChar) { + throw error(s); + } + } + + /** + * Mark the end of pattern with a specific character. + */ + private void mark(int c) { + temp[patternLength] = c; + } + + /** + * Peek the next character, and do not advance the cursor. + */ + private int peek() { + int ch = temp[cursor]; + if (has(COMMENTS)) + ch = peekPastWhitespace(ch); + return ch; + } + + /** + * Read the next character, and advance the cursor by one. + */ + private int read() { + int ch = temp[cursor++]; + if (has(COMMENTS)) + ch = parsePastWhitespace(ch); + return ch; + } + + /** + * Read the next character, and advance the cursor by one, + * ignoring the COMMENTS setting + */ + private int readEscaped() { + int ch = temp[cursor++]; + return ch; + } + + /** + * Advance the cursor by one, and peek the next character. + */ + private int next() { + int ch = temp[++cursor]; + if (has(COMMENTS)) + ch = peekPastWhitespace(ch); + return ch; + } + + /** + * Advance the cursor by one, and peek the next character, + * ignoring the COMMENTS setting + */ + private int nextEscaped() { + int ch = temp[++cursor]; + return ch; + } + + /** + * If in xmode peek past whitespace and comments. + */ + private int peekPastWhitespace(int ch) { + while (ASCII.isSpace(ch) || ch == '#') { + while (ASCII.isSpace(ch)) + ch = temp[++cursor]; + if (ch == '#') { + ch = peekPastLine(); + } + } + return ch; + } + + /** + * If in xmode parse past whitespace and comments. + */ + private int parsePastWhitespace(int ch) { + while (ASCII.isSpace(ch) || ch == '#') { + while (ASCII.isSpace(ch)) + ch = temp[cursor++]; + if (ch == '#') + ch = parsePastLine(); + } + return ch; + } + + /** + * xmode parse past comment to end of line. + */ + private int parsePastLine() { + int ch = temp[cursor++]; + while (ch != 0 && !isLineSeparator(ch)) + ch = temp[cursor++]; + return ch; + } + + /** + * xmode peek past comment to end of line. + */ + private int peekPastLine() { + int ch = temp[++cursor]; + while (ch != 0 && !isLineSeparator(ch)) + ch = temp[++cursor]; + return ch; + } + + /** + * Determines if character is a line separator in the current mode + */ + private boolean isLineSeparator(int ch) { + if (has(UNIX_LINES)) { + return ch == '\n'; + } else { + return (ch == '\n' || + ch == '\r' || + (ch|1) == '\u2029' || + ch == '\u0085'); + } + } + + /** + * Read the character after the next one, and advance the cursor by two. + */ + private int skip() { + int i = cursor; + int ch = temp[i+1]; + cursor = i + 2; + return ch; + } + + /** + * Unread one next character, and retreat cursor by one. + */ + private void unread() { + cursor--; + } + + /** + * Internal method used for handling all syntax errors. The pattern is + * displayed with a pointer to aid in locating the syntax error. + */ + private PatternSyntaxException error(String s) { + return new PatternSyntaxException(s, normalizedPattern, cursor - 1); + } + + /** + * Determines if there is any supplementary character or unpaired + * surrogate in the specified range. + */ + private boolean findSupplementary(int start, int end) { + for (int i = start; i < end; i++) { + if (isSupplementary(temp[i])) + return true; + } + return false; + } + + /** + * Determines if the specified code point is a supplementary + * character or unpaired surrogate. + */ + private static final boolean isSupplementary(int ch) { + return ch >= Character.MIN_SUPPLEMENTARY_CODE_POINT || + Character.isSurrogate((char)ch); + } + + /** + * The following methods handle the main parsing. They are sorted + * according to their precedence order, the lowest one first. + */ + + /** + * The expression is parsed with branch nodes added for alternations. + * This may be called recursively to parse sub expressions that may + * contain alternations. + */ + private Node expr(Node end) { + Node prev = null; + Node firstTail = null; + Node branchConn = null; + + for (;;) { + Node node = sequence(end); + Node nodeTail = root; //double return + if (prev == null) { + prev = node; + firstTail = nodeTail; + } else { + // Branch + if (branchConn == null) { + branchConn = new BranchConn(); + branchConn.next = end; + } + if (node == end) { + // if the node returned from sequence() is "end" + // we have an empty expr, set a null atom into + // the branch to indicate to go "next" directly. + node = null; + } else { + // the "tail.next" of each atom goes to branchConn + nodeTail.next = branchConn; + } + if (prev instanceof Branch) { + ((Branch)prev).add(node); + } else { + if (prev == end) { + prev = null; + } else { + // replace the "end" with "branchConn" at its tail.next + // when put the "prev" into the branch as the first atom. + firstTail.next = branchConn; + } + prev = new Branch(prev, node, branchConn); + } + } + if (peek() != '|') { + return prev; + } + next(); + } + } + + /** + * Parsing of sequences between alternations. + */ + private Node sequence(Node end) { + Node head = null; + Node tail = null; + Node node = null; + LOOP: + for (;;) { + int ch = peek(); + switch (ch) { + case '(': + // Because group handles its own closure, + // we need to treat it differently + node = group0(); + // Check for comment or flag group + if (node == null) + continue; + if (head == null) + head = node; + else + tail.next = node; + // Double return: Tail was returned in root + tail = root; + continue; + case '[': + node = clazz(true); + break; + case '\\': + ch = nextEscaped(); + if (ch == 'p' || ch == 'P') { + boolean oneLetter = true; + boolean comp = (ch == 'P'); + ch = next(); // Consume { if present + if (ch != '{') { + unread(); + } else { + oneLetter = false; + } + node = family(oneLetter, comp); + } else { + unread(); + node = atom(); + } + break; + case '^': + next(); + if (has(MULTILINE)) { + if (has(UNIX_LINES)) + node = new UnixCaret(); + else + node = new Caret(); + } else { + node = new Begin(); + } + break; + case '$': + next(); + if (has(UNIX_LINES)) + node = new UnixDollar(has(MULTILINE)); + else + node = new Dollar(has(MULTILINE)); + break; + case '.': + next(); + if (has(DOTALL)) { + node = new All(); + } else { + if (has(UNIX_LINES)) + node = new UnixDot(); + else { + node = new Dot(); + } + } + break; + case '|': + case ')': + break LOOP; + case ']': // Now interpreting dangling ] and } as literals + case '}': + node = atom(); + break; + case '?': + case '*': + case '+': + next(); + throw error("Dangling meta character '" + ((char)ch) + "'"); + case 0: + if (cursor >= patternLength) { + break LOOP; + } + // Fall through + default: + node = atom(); + break; + } + + node = closure(node); + + if (head == null) { + head = tail = node; + } else { + tail.next = node; + tail = node; + } + } + if (head == null) { + return end; + } + tail.next = end; + root = tail; //double return + return head; + } + + /** + * Parse and add a new Single or Slice. + */ + private Node atom() { + int first = 0; + int prev = -1; + boolean hasSupplementary = false; + int ch = peek(); + for (;;) { + switch (ch) { + case '*': + case '+': + case '?': + case '{': + if (first > 1) { + cursor = prev; // Unwind one character + first--; + } + break; + case '$': + case '.': + case '^': + case '(': + case '[': + case '|': + case ')': + break; + case '\\': + ch = nextEscaped(); + if (ch == 'p' || ch == 'P') { // Property + if (first > 0) { // Slice is waiting; handle it first + unread(); + break; + } else { // No slice; just return the family node + boolean comp = (ch == 'P'); + boolean oneLetter = true; + ch = next(); // Consume { if present + if (ch != '{') + unread(); + else + oneLetter = false; + return family(oneLetter, comp); + } + } + unread(); + prev = cursor; + ch = escape(false, first == 0); + if (ch >= 0) { + append(ch, first); + first++; + if (isSupplementary(ch)) { + hasSupplementary = true; + } + ch = peek(); + continue; + } else if (first == 0) { + return root; + } + // Unwind meta escape sequence + cursor = prev; + break; + case 0: + if (cursor >= patternLength) { + break; + } + // Fall through + default: + prev = cursor; + append(ch, first); + first++; + if (isSupplementary(ch)) { + hasSupplementary = true; + } + ch = next(); + continue; + } + break; + } + if (first == 1) { + return newSingle(buffer[0]); + } else { + return newSlice(buffer, first, hasSupplementary); + } + } + + private void append(int ch, int len) { + if (len >= buffer.length) { + int[] tmp = new int[len+len]; + System.arraycopy(buffer, 0, tmp, 0, len); + buffer = tmp; + } + buffer[len] = ch; + } + + /** + * Parses a backref greedily, taking as many numbers as it + * can. The first digit is always treated as a backref, but + * multi digit numbers are only treated as a backref if at + * least that many backrefs exist at this point in the regex. + */ + private Node ref(int refNum) { + boolean done = false; + while(!done) { + int ch = peek(); + switch(ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + int newRefNum = (refNum * 10) + (ch - '0'); + // Add another number if it doesn't make a group + // that doesn't exist + if (capturingGroupCount - 1 < newRefNum) { + done = true; + break; + } + refNum = newRefNum; + read(); + break; + default: + done = true; + break; + } + } + if (has(CASE_INSENSITIVE)) + return new CIBackRef(refNum, has(UNICODE_CASE)); + else + return new BackRef(refNum); + } + + /** + * Parses an escape sequence to determine the actual value that needs + * to be matched. + * If -1 is returned and create was true a new object was added to the tree + * to handle the escape sequence. + * If the returned value is greater than zero, it is the value that + * matches the escape sequence. + */ + private int escape(boolean inclass, boolean create) { + int ch = skip(); + switch (ch) { + case '0': + return o(); + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (inclass) break; + if (create) { + root = ref((ch - '0')); + } + return -1; + case 'A': + if (inclass) break; + if (create) root = new Begin(); + return -1; + case 'B': + if (inclass) break; + if (create) root = new Bound(Bound.NONE, has(UNICODE_CHARACTER_CLASS)); + return -1; + case 'C': + break; + case 'D': + if (create) root = has(UNICODE_CHARACTER_CLASS) + ? new Utype(UnicodeProp.DIGIT).complement() + : new Ctype(ASCII.DIGIT).complement(); + return -1; + case 'E': + case 'F': + break; + case 'G': + if (inclass) break; + if (create) root = new LastMatch(); + return -1; + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + break; + case 'S': + if (create) root = has(UNICODE_CHARACTER_CLASS) + ? new Utype(UnicodeProp.WHITE_SPACE).complement() + : new Ctype(ASCII.SPACE).complement(); + return -1; + case 'T': + case 'U': + case 'V': + break; + case 'W': + if (create) root = has(UNICODE_CHARACTER_CLASS) + ? new Utype(UnicodeProp.WORD).complement() + : new Ctype(ASCII.WORD).complement(); + return -1; + case 'X': + case 'Y': + break; + case 'Z': + if (inclass) break; + if (create) { + if (has(UNIX_LINES)) + root = new UnixDollar(false); + else + root = new Dollar(false); + } + return -1; + case 'a': + return '\007'; + case 'b': + if (inclass) break; + if (create) root = new Bound(Bound.BOTH, has(UNICODE_CHARACTER_CLASS)); + return -1; + case 'c': + return c(); + case 'd': + if (create) root = has(UNICODE_CHARACTER_CLASS) + ? new Utype(UnicodeProp.DIGIT) + : new Ctype(ASCII.DIGIT); + return -1; + case 'e': + return '\033'; + case 'f': + return '\f'; + case 'g': + case 'h': + case 'i': + case 'j': + break; + case 'k': + if (inclass) + break; + if (read() != '<') + throw error("\\k is not followed by '<' for named capturing group"); + String name = groupname(read()); + if (!namedGroups().containsKey(name)) + throw error("(named capturing group <"+ name+"> does not exit"); + if (create) { + if (has(CASE_INSENSITIVE)) + root = new CIBackRef(namedGroups().get(name), has(UNICODE_CASE)); + else + root = new BackRef(namedGroups().get(name)); + } + return -1; + case 'l': + case 'm': + break; + case 'n': + return '\n'; + case 'o': + case 'p': + case 'q': + break; + case 'r': + return '\r'; + case 's': + if (create) root = has(UNICODE_CHARACTER_CLASS) + ? new Utype(UnicodeProp.WHITE_SPACE) + : new Ctype(ASCII.SPACE); + return -1; + case 't': + return '\t'; + case 'u': + return u(); + case 'v': + return '\013'; + case 'w': + if (create) root = has(UNICODE_CHARACTER_CLASS) + ? new Utype(UnicodeProp.WORD) + : new Ctype(ASCII.WORD); + return -1; + case 'x': + return x(); + case 'y': + break; + case 'z': + if (inclass) break; + if (create) root = new End(); + return -1; + default: + return ch; + } + throw error("Illegal/unsupported escape sequence"); + } + + /** + * Parse a character class, and return the node that matches it. + * + * Consumes a ] on the way out if consume is true. Usually consume + * is true except for the case of [abc&&def] where def is a separate + * right hand node with "understood" brackets. + */ + private CharProperty clazz(boolean consume) { + CharProperty prev = null; + CharProperty node = null; + BitClass bits = new BitClass(); + boolean include = true; + boolean firstInClass = true; + int ch = next(); + for (;;) { + switch (ch) { + case '^': + // Negates if first char in a class, otherwise literal + if (firstInClass) { + if (temp[cursor-1] != '[') + break; + ch = next(); + include = !include; + continue; + } else { + // ^ not first in class, treat as literal + break; + } + case '[': + firstInClass = false; + node = clazz(true); + if (prev == null) + prev = node; + else + prev = union(prev, node); + ch = peek(); + continue; + case '&': + firstInClass = false; + ch = next(); + if (ch == '&') { + ch = next(); + CharProperty rightNode = null; + while (ch != ']' && ch != '&') { + if (ch == '[') { + if (rightNode == null) + rightNode = clazz(true); + else + rightNode = union(rightNode, clazz(true)); + } else { // abc&&def + unread(); + rightNode = clazz(false); + } + ch = peek(); + } + if (rightNode != null) + node = rightNode; + if (prev == null) { + if (rightNode == null) + throw error("Bad class syntax"); + else + prev = rightNode; + } else { + prev = intersection(prev, node); + } + } else { + // treat as a literal & + unread(); + break; + } + continue; + case 0: + firstInClass = false; + if (cursor >= patternLength) + throw error("Unclosed character class"); + break; + case ']': + firstInClass = false; + if (prev != null) { + if (consume) + next(); + return prev; + } + break; + default: + firstInClass = false; + break; + } + node = range(bits); + if (include) { + if (prev == null) { + prev = node; + } else { + if (prev != node) + prev = union(prev, node); + } + } else { + if (prev == null) { + prev = node.complement(); + } else { + if (prev != node) + prev = setDifference(prev, node); + } + } + ch = peek(); + } + } + + private CharProperty bitsOrSingle(BitClass bits, int ch) { + /* Bits can only handle codepoints in [u+0000-u+00ff] range. + Use "single" node instead of bits when dealing with unicode + case folding for codepoints listed below. + (1)Uppercase out of range: u+00ff, u+00b5 + toUpperCase(u+00ff) -> u+0178 + toUpperCase(u+00b5) -> u+039c + (2)LatinSmallLetterLongS u+17f + toUpperCase(u+017f) -> u+0053 + (3)LatinSmallLetterDotlessI u+131 + toUpperCase(u+0131) -> u+0049 + (4)LatinCapitalLetterIWithDotAbove u+0130 + toLowerCase(u+0130) -> u+0069 + (5)KelvinSign u+212a + toLowerCase(u+212a) ==> u+006B + (6)AngstromSign u+212b + toLowerCase(u+212b) ==> u+00e5 + */ + int d; + if (ch < 256 && + !(has(CASE_INSENSITIVE) && has(UNICODE_CASE) && + (ch == 0xff || ch == 0xb5 || + ch == 0x49 || ch == 0x69 || //I and i + ch == 0x53 || ch == 0x73 || //S and s + ch == 0x4b || ch == 0x6b || //K and k + ch == 0xc5 || ch == 0xe5))) //A+ring + return bits.add(ch, flags()); + return newSingle(ch); + } + + /** + * Parse a single character or a character range in a character class + * and return its representative node. + */ + private CharProperty range(BitClass bits) { + int ch = peek(); + if (ch == '\\') { + ch = nextEscaped(); + if (ch == 'p' || ch == 'P') { // A property + boolean comp = (ch == 'P'); + boolean oneLetter = true; + // Consume { if present + ch = next(); + if (ch != '{') + unread(); + else + oneLetter = false; + return family(oneLetter, comp); + } else { // ordinary escape + unread(); + ch = escape(true, true); + if (ch == -1) + return (CharProperty) root; + } + } else { + ch = single(); + } + if (ch >= 0) { + if (peek() == '-') { + int endRange = temp[cursor+1]; + if (endRange == '[') { + return bitsOrSingle(bits, ch); + } + if (endRange != ']') { + next(); + int m = single(); + if (m < ch) + throw error("Illegal character range"); + if (has(CASE_INSENSITIVE)) + return caseInsensitiveRangeFor(ch, m); + else + return rangeFor(ch, m); + } + } + return bitsOrSingle(bits, ch); + } + throw error("Unexpected character '"+((char)ch)+"'"); + } + + private int single() { + int ch = peek(); + switch (ch) { + case '\\': + return escape(true, false); + default: + next(); + return ch; + } + } + + /** + * Parses a Unicode character family and returns its representative node. + */ + private CharProperty family(boolean singleLetter, + boolean maybeComplement) + { + next(); + String name; + CharProperty node = null; + + if (singleLetter) { + int c = temp[cursor]; + if (!Character.isSupplementaryCodePoint(c)) { + name = String.valueOf((char)c); + } else { + name = new String(temp, cursor, 1); + } + read(); + } else { + int i = cursor; + mark('}'); + while(read() != '}') { + } + mark('\000'); + int j = cursor; + if (j > patternLength) + throw error("Unclosed character family"); + if (i + 1 >= j) + throw error("Empty character family"); + name = new String(temp, i, j-i-1); + } + + int i = name.indexOf('='); + if (i != -1) { + // property construct \p{name=value} + String value = name.substring(i + 1); + name = name.substring(0, i).toLowerCase(Locale.ENGLISH); + /* if ("sc".equals(name) || "script".equals(name)) { + node = unicodeScriptPropertyFor(value); + } else if ("blk".equals(name) || "block".equals(name)) { + node = unicodeBlockPropertyFor(value); + } else*/ if ("gc".equals(name) || "general_category".equals(name)) { + node = charPropertyNodeFor(value); + } else { + throw error("Unknown Unicode property {name=<" + name + ">, " + + "value=<" + value + ">}"); + } + } else { + /*if (name.startsWith("In")) { + // \p{inBlockName} + node = unicodeBlockPropertyFor(name.substring(2)); + } else if (name.startsWith("Is")) { + // \p{isGeneralCategory} and \p{isScriptName} + name = name.substring(2); + UnicodeProp uprop = UnicodeProp.forName(name); + if (uprop != null) + node = new Utype(uprop); + if (node == null) + node = CharPropertyNames.charPropertyFor(name); + if (node == null) + node = unicodeScriptPropertyFor(name); + } else*/ { + if (has(UNICODE_CHARACTER_CLASS)) { + UnicodeProp uprop = UnicodeProp.forPOSIXName(name); + if (uprop != null) + node = new Utype(uprop); + } + if (node == null) + node = charPropertyNodeFor(name); + } + } + if (maybeComplement) { + if (node instanceof Category /*|| node instanceof Block*/) + hasSupplementary = true; + node = node.complement(); + } + return node; + } + + + /** + * Returns a CharProperty matching all characters belong to + * a UnicodeScript. + * + private CharProperty unicodeScriptPropertyFor(String name) { + final Character.UnicodeScript script; + try { + script = Character.UnicodeScript.forName(name); + } catch (IllegalArgumentException iae) { + throw error("Unknown character script name {" + name + "}"); + } + return new Script(script); + } + + /** + * Returns a CharProperty matching all characters in a UnicodeBlock. + * + private CharProperty unicodeBlockPropertyFor(String name) { + final Character.UnicodeBlock block; + try { + block = Character.UnicodeBlock.forName(name); + } catch (IllegalArgumentException iae) { + throw error("Unknown character block name {" + name + "}"); + } + return new Block(block); + } + + /** + * Returns a CharProperty matching all characters in a named property. + */ + private CharProperty charPropertyNodeFor(String name) { + CharProperty p = CharPropertyNames.charPropertyFor(name); + if (p == null) + throw error("Unknown character property name {" + name + "}"); + return p; + } + + /** + * Parses and returns the name of a "named capturing group", the trailing + * ">" is consumed after parsing. + */ + private String groupname(int ch) { + StringBuilder sb = new StringBuilder(); + sb.append(Character.toChars(ch)); + while (ASCII.isLower(ch=read()) || ASCII.isUpper(ch) || + ASCII.isDigit(ch)) { + sb.append(Character.toChars(ch)); + } + if (sb.length() == 0) + throw error("named capturing group has 0 length name"); + if (ch != '>') + throw error("named capturing group is missing trailing '>'"); + return sb.toString(); + } + + /** + * Parses a group and returns the head node of a set of nodes that process + * the group. Sometimes a double return system is used where the tail is + * returned in root. + */ + private Node group0() { + boolean capturingGroup = false; + Node head = null; + Node tail = null; + int save = flags; + root = null; + int ch = next(); + if (ch == '?') { + ch = skip(); + switch (ch) { + case ':': // (?:xxx) pure group + head = createGroup(true); + tail = root; + head.next = expr(tail); + break; + case '=': // (?=xxx) and (?!xxx) lookahead + case '!': + head = createGroup(true); + tail = root; + head.next = expr(tail); + if (ch == '=') { + head = tail = new Pos(head); + } else { + head = tail = new Neg(head); + } + break; + case '>': // (?>xxx) independent group + head = createGroup(true); + tail = root; + head.next = expr(tail); + head = tail = new Ques(head, INDEPENDENT); + break; + case '<': // (? is already defined"); + capturingGroup = true; + head = createGroup(false); + tail = root; + namedGroups().put(name, capturingGroupCount-1); + head.next = expr(tail); + break; + } + int start = cursor; + head = createGroup(true); + tail = root; + head.next = expr(tail); + tail.next = lookbehindEnd; + TreeInfo info = new TreeInfo(); + head.study(info); + if (info.maxValid == false) { + throw error("Look-behind group does not have " + + "an obvious maximum length"); + } + boolean hasSupplementary = findSupplementary(start, patternLength); + if (ch == '=') { + head = tail = (hasSupplementary ? + new BehindS(head, info.maxLength, + info.minLength) : + new Behind(head, info.maxLength, + info.minLength)); + } else if (ch == '!') { + head = tail = (hasSupplementary ? + new NotBehindS(head, info.maxLength, + info.minLength) : + new NotBehind(head, info.maxLength, + info.minLength)); + } else { + throw error("Unknown look-behind group"); + } + break; + case '$': + case '@': + throw error("Unknown group type"); + default: // (?xxx:) inlined match flags + unread(); + addFlag(); + ch = read(); + if (ch == ')') { + return null; // Inline modifier only + } + if (ch != ':') { + throw error("Unknown inline modifier"); + } + head = createGroup(true); + tail = root; + head.next = expr(tail); + break; + } + } else { // (xxx) a regular group + capturingGroup = true; + head = createGroup(false); + tail = root; + head.next = expr(tail); + } + + accept(')', "Unclosed group"); + flags = save; + + // Check for quantifiers + Node node = closure(head); + if (node == head) { // No closure + root = tail; + return node; // Dual return + } + if (head == tail) { // Zero length assertion + root = node; + return node; // Dual return + } + + if (node instanceof Ques) { + Ques ques = (Ques) node; + if (ques.type == POSSESSIVE) { + root = node; + return node; + } + tail.next = new BranchConn(); + tail = tail.next; + if (ques.type == GREEDY) { + head = new Branch(head, null, tail); + } else { // Reluctant quantifier + head = new Branch(null, head, tail); + } + root = tail; + return head; + } else if (node instanceof Curly) { + Curly curly = (Curly) node; + if (curly.type == POSSESSIVE) { + root = node; + return node; + } + // Discover if the group is deterministic + TreeInfo info = new TreeInfo(); + if (head.study(info)) { // Deterministic + GroupTail temp = (GroupTail) tail; + head = root = new GroupCurly(head.next, curly.cmin, + curly.cmax, curly.type, + ((GroupTail)tail).localIndex, + ((GroupTail)tail).groupIndex, + capturingGroup); + return head; + } else { // Non-deterministic + int temp = ((GroupHead) head).localIndex; + Loop loop; + if (curly.type == GREEDY) + loop = new Loop(this.localCount, temp); + else // Reluctant Curly + loop = new LazyLoop(this.localCount, temp); + Prolog prolog = new Prolog(loop); + this.localCount += 1; + loop.cmin = curly.cmin; + loop.cmax = curly.cmax; + loop.body = head; + tail.next = loop; + root = loop; + return prolog; // Dual return + } + } + throw error("Internal logic error"); + } + + /** + * Create group head and tail nodes using double return. If the group is + * created with anonymous true then it is a pure group and should not + * affect group counting. + */ + private Node createGroup(boolean anonymous) { + int localIndex = localCount++; + int groupIndex = 0; + if (!anonymous) + groupIndex = capturingGroupCount++; + GroupHead head = new GroupHead(localIndex); + root = new GroupTail(localIndex, groupIndex); + if (!anonymous && groupIndex < 10) + groupNodes[groupIndex] = head; + return head; + } + + /** + * Parses inlined match flags and set them appropriately. + */ + private void addFlag() { + int ch = peek(); + for (;;) { + switch (ch) { + case 'i': + flags |= CASE_INSENSITIVE; + break; + case 'm': + flags |= MULTILINE; + break; + case 's': + flags |= DOTALL; + break; + case 'd': + flags |= UNIX_LINES; + break; + case 'u': + flags |= UNICODE_CASE; + break; + case 'c': + flags |= CANON_EQ; + break; + case 'x': + flags |= COMMENTS; + break; + case 'U': + flags |= (UNICODE_CHARACTER_CLASS | UNICODE_CASE); + break; + case '-': // subFlag then fall through + ch = next(); + subFlag(); + default: + return; + } + ch = next(); + } + } + + /** + * Parses the second part of inlined match flags and turns off + * flags appropriately. + */ + private void subFlag() { + int ch = peek(); + for (;;) { + switch (ch) { + case 'i': + flags &= ~CASE_INSENSITIVE; + break; + case 'm': + flags &= ~MULTILINE; + break; + case 's': + flags &= ~DOTALL; + break; + case 'd': + flags &= ~UNIX_LINES; + break; + case 'u': + flags &= ~UNICODE_CASE; + break; + case 'c': + flags &= ~CANON_EQ; + break; + case 'x': + flags &= ~COMMENTS; + break; + case 'U': + flags &= ~(UNICODE_CHARACTER_CLASS | UNICODE_CASE); + default: + return; + } + ch = next(); + } + } + + static final int MAX_REPS = 0x7FFFFFFF; + + static final int GREEDY = 0; + + static final int LAZY = 1; + + static final int POSSESSIVE = 2; + + static final int INDEPENDENT = 3; + + /** + * Processes repetition. If the next character peeked is a quantifier + * then new nodes must be appended to handle the repetition. + * Prev could be a single or a group, so it could be a chain of nodes. + */ + private Node closure(Node prev) { + Node atom; + int ch = peek(); + switch (ch) { + case '?': + ch = next(); + if (ch == '?') { + next(); + return new Ques(prev, LAZY); + } else if (ch == '+') { + next(); + return new Ques(prev, POSSESSIVE); + } + return new Ques(prev, GREEDY); + case '*': + ch = next(); + if (ch == '?') { + next(); + return new Curly(prev, 0, MAX_REPS, LAZY); + } else if (ch == '+') { + next(); + return new Curly(prev, 0, MAX_REPS, POSSESSIVE); + } + return new Curly(prev, 0, MAX_REPS, GREEDY); + case '+': + ch = next(); + if (ch == '?') { + next(); + return new Curly(prev, 1, MAX_REPS, LAZY); + } else if (ch == '+') { + next(); + return new Curly(prev, 1, MAX_REPS, POSSESSIVE); + } + return new Curly(prev, 1, MAX_REPS, GREEDY); + case '{': + ch = temp[cursor+1]; + if (ASCII.isDigit(ch)) { + skip(); + int cmin = 0; + do { + cmin = cmin * 10 + (ch - '0'); + } while (ASCII.isDigit(ch = read())); + int cmax = cmin; + if (ch == ',') { + ch = read(); + cmax = MAX_REPS; + if (ch != '}') { + cmax = 0; + while (ASCII.isDigit(ch)) { + cmax = cmax * 10 + (ch - '0'); + ch = read(); + } + } + } + if (ch != '}') + throw error("Unclosed counted closure"); + if (((cmin) | (cmax) | (cmax - cmin)) < 0) + throw error("Illegal repetition range"); + Curly curly; + ch = peek(); + if (ch == '?') { + next(); + curly = new Curly(prev, cmin, cmax, LAZY); + } else if (ch == '+') { + next(); + curly = new Curly(prev, cmin, cmax, POSSESSIVE); + } else { + curly = new Curly(prev, cmin, cmax, GREEDY); + } + return curly; + } else { + throw error("Illegal repetition"); + } + default: + return prev; + } + } + + /** + * Utility method for parsing control escape sequences. + */ + private int c() { + if (cursor < patternLength) { + return read() ^ 64; + } + throw error("Illegal control escape sequence"); + } + + /** + * Utility method for parsing octal escape sequences. + */ + private int o() { + int n = read(); + if (((n-'0')|('7'-n)) >= 0) { + int m = read(); + if (((m-'0')|('7'-m)) >= 0) { + int o = read(); + if ((((o-'0')|('7'-o)) >= 0) && (((n-'0')|('3'-n)) >= 0)) { + return (n - '0') * 64 + (m - '0') * 8 + (o - '0'); + } + unread(); + return (n - '0') * 8 + (m - '0'); + } + unread(); + return (n - '0'); + } + throw error("Illegal octal escape sequence"); + } + + /** + * Utility method for parsing hexadecimal escape sequences. + */ + private int x() { + int n = read(); + if (ASCII.isHexDigit(n)) { + int m = read(); + if (ASCII.isHexDigit(m)) { + return ASCII.toDigit(n) * 16 + ASCII.toDigit(m); + } + } else if (n == '{' && ASCII.isHexDigit(peek())) { + int ch = 0; + while (ASCII.isHexDigit(n = read())) { + ch = (ch << 4) + ASCII.toDigit(n); + if (ch > Character.MAX_CODE_POINT) + throw error("Hexadecimal codepoint is too big"); + } + if (n != '}') + throw error("Unclosed hexadecimal escape sequence"); + return ch; + } + throw error("Illegal hexadecimal escape sequence"); + } + + /** + * Utility method for parsing unicode escape sequences. + */ + private int cursor() { + return cursor; + } + + private void setcursor(int pos) { + cursor = pos; + } + + private int uxxxx() { + int n = 0; + for (int i = 0; i < 4; i++) { + int ch = read(); + if (!ASCII.isHexDigit(ch)) { + throw error("Illegal Unicode escape sequence"); + } + n = n * 16 + ASCII.toDigit(ch); + } + return n; + } + + private int u() { + int n = uxxxx(); + if (Character.isHighSurrogate((char)n)) { + int cur = cursor(); + if (read() == '\\' && read() == 'u') { + int n2 = uxxxx(); + if (Character.isLowSurrogate((char)n2)) + return Character.toCodePoint((char)n, (char)n2); + } + setcursor(cur); + } + return n; + } + + // + // Utility methods for code point support + // + + private static final int countChars(CharSequence seq, int index, + int lengthInCodePoints) { + // optimization + if (lengthInCodePoints == 1 && !Character.isHighSurrogate(seq.charAt(index))) { + assert (index >= 0 && index < seq.length()); + return 1; + } + int length = seq.length(); + int x = index; + if (lengthInCodePoints >= 0) { + assert (index >= 0 && index < length); + for (int i = 0; x < length && i < lengthInCodePoints; i++) { + if (Character.isHighSurrogate(seq.charAt(x++))) { + if (x < length && Character.isLowSurrogate(seq.charAt(x))) { + x++; + } + } + } + return x - index; + } + + assert (index >= 0 && index <= length); + if (index == 0) { + return 0; + } + int len = -lengthInCodePoints; + for (int i = 0; x > 0 && i < len; i++) { + if (Character.isLowSurrogate(seq.charAt(--x))) { + if (x > 0 && Character.isHighSurrogate(seq.charAt(x-1))) { + x--; + } + } + } + return index - x; + } + + private static final int countCodePoints(CharSequence seq) { + int length = seq.length(); + int n = 0; + for (int i = 0; i < length; ) { + n++; + if (Character.isHighSurrogate(seq.charAt(i++))) { + if (i < length && Character.isLowSurrogate(seq.charAt(i))) { + i++; + } + } + } + return n; + } + + /** + * Creates a bit vector for matching Latin-1 values. A normal BitClass + * never matches values above Latin-1, and a complemented BitClass always + * matches values above Latin-1. + */ + private static final class BitClass extends BmpCharProperty { + final boolean[] bits; + BitClass() { bits = new boolean[256]; } + private BitClass(boolean[] bits) { this.bits = bits; } + BitClass add(int c, int flags) { + assert c >= 0 && c <= 255; + if ((flags & CASE_INSENSITIVE) != 0) { + if (ASCII.isAscii(c)) { + bits[ASCII.toUpper(c)] = true; + bits[ASCII.toLower(c)] = true; + } else if ((flags & UNICODE_CASE) != 0) { + bits[Character.toLowerCase(c)] = true; + bits[Character.toUpperCase(c)] = true; + } + } + bits[c] = true; + return this; + } + boolean isSatisfiedBy(int ch) { + return ch < 256 && bits[ch]; + } + } + + /** + * Returns a suitably optimized, single character matcher. + */ + private CharProperty newSingle(final int ch) { + if (has(CASE_INSENSITIVE)) { + int lower, upper; + if (has(UNICODE_CASE)) { + upper = Character.toUpperCase(ch); + lower = Character.toLowerCase(upper); + if (upper != lower) + return new SingleU(lower); + } else if (ASCII.isAscii(ch)) { + lower = ASCII.toLower(ch); + upper = ASCII.toUpper(ch); + if (lower != upper) + return new SingleI(lower, upper); + } + } + if (isSupplementary(ch)) + return new SingleS(ch); // Match a given Unicode character + return new Single(ch); // Match a given BMP character + } + + /** + * Utility method for creating a string slice matcher. + */ + private Node newSlice(int[] buf, int count, boolean hasSupplementary) { + int[] tmp = new int[count]; + if (has(CASE_INSENSITIVE)) { + if (has(UNICODE_CASE)) { + for (int i = 0; i < count; i++) { + tmp[i] = Character.toLowerCase( + Character.toUpperCase(buf[i])); + } + return hasSupplementary? new SliceUS(tmp) : new SliceU(tmp); + } + for (int i = 0; i < count; i++) { + tmp[i] = ASCII.toLower(buf[i]); + } + return hasSupplementary? new SliceIS(tmp) : new SliceI(tmp); + } + for (int i = 0; i < count; i++) { + tmp[i] = buf[i]; + } + return hasSupplementary ? new SliceS(tmp) : new Slice(tmp); + } + + /** + * The following classes are the building components of the object + * tree that represents a compiled regular expression. The object tree + * is made of individual elements that handle constructs in the Pattern. + * Each type of object knows how to match its equivalent construct with + * the match() method. + */ + + /** + * Base class for all node classes. Subclasses should override the match() + * method as appropriate. This class is an accepting node, so its match() + * always returns true. + */ + static class Node extends Object { + Node next; + Node() { + next = Pattern.accept; + } + /** + * This method implements the classic accept node. + */ + boolean match(Matcher matcher, int i, CharSequence seq) { + matcher.last = i; + matcher.groups[0] = matcher.first; + matcher.groups[1] = matcher.last; + return true; + } + /** + * This method is good for all zero length assertions. + */ + boolean study(TreeInfo info) { + if (next != null) { + return next.study(info); + } else { + return info.deterministic; + } + } + } + + static class LastNode extends Node { + /** + * This method implements the classic accept node with + * the addition of a check to see if the match occurred + * using all of the input. + */ + boolean match(Matcher matcher, int i, CharSequence seq) { + if (matcher.acceptMode == Matcher.ENDANCHOR && i != matcher.to) + return false; + matcher.last = i; + matcher.groups[0] = matcher.first; + matcher.groups[1] = matcher.last; + return true; + } + } + + /** + * Used for REs that can start anywhere within the input string. + * This basically tries to match repeatedly at each spot in the + * input string, moving forward after each try. An anchored search + * or a BnM will bypass this node completely. + */ + static class Start extends Node { + int minLength; + Start(Node node) { + this.next = node; + TreeInfo info = new TreeInfo(); + next.study(info); + minLength = info.minLength; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + if (i > matcher.to - minLength) { + matcher.hitEnd = true; + return false; + } + int guard = matcher.to - minLength; + for (; i <= guard; i++) { + if (next.match(matcher, i, seq)) { + matcher.first = i; + matcher.groups[0] = matcher.first; + matcher.groups[1] = matcher.last; + return true; + } + } + matcher.hitEnd = true; + return false; + } + boolean study(TreeInfo info) { + next.study(info); + info.maxValid = false; + info.deterministic = false; + return false; + } + } + + /* + * StartS supports supplementary characters, including unpaired surrogates. + */ + static final class StartS extends Start { + StartS(Node node) { + super(node); + } + boolean match(Matcher matcher, int i, CharSequence seq) { + if (i > matcher.to - minLength) { + matcher.hitEnd = true; + return false; + } + int guard = matcher.to - minLength; + while (i <= guard) { + //if ((ret = next.match(matcher, i, seq)) || i == guard) + if (next.match(matcher, i, seq)) { + matcher.first = i; + matcher.groups[0] = matcher.first; + matcher.groups[1] = matcher.last; + return true; + } + if (i == guard) + break; + // Optimization to move to the next character. This is + // faster than countChars(seq, i, 1). + if (Character.isHighSurrogate(seq.charAt(i++))) { + if (i < seq.length() && + Character.isLowSurrogate(seq.charAt(i))) { + i++; + } + } + } + matcher.hitEnd = true; + return false; + } + } + + /** + * Node to anchor at the beginning of input. This object implements the + * match for a \A sequence, and the caret anchor will use this if not in + * multiline mode. + */ + static final class Begin extends Node { + boolean match(Matcher matcher, int i, CharSequence seq) { + int fromIndex = (matcher.anchoringBounds) ? + matcher.from : 0; + if (i == fromIndex && next.match(matcher, i, seq)) { + matcher.first = i; + matcher.groups[0] = i; + matcher.groups[1] = matcher.last; + return true; + } else { + return false; + } + } + } + + /** + * Node to anchor at the end of input. This is the absolute end, so this + * should not match at the last newline before the end as $ will. + */ + static final class End extends Node { + boolean match(Matcher matcher, int i, CharSequence seq) { + int endIndex = (matcher.anchoringBounds) ? + matcher.to : matcher.getTextLength(); + if (i == endIndex) { + matcher.hitEnd = true; + return next.match(matcher, i, seq); + } + return false; + } + } + + /** + * Node to anchor at the beginning of a line. This is essentially the + * object to match for the multiline ^. + */ + static final class Caret extends Node { + boolean match(Matcher matcher, int i, CharSequence seq) { + int startIndex = matcher.from; + int endIndex = matcher.to; + if (!matcher.anchoringBounds) { + startIndex = 0; + endIndex = matcher.getTextLength(); + } + // Perl does not match ^ at end of input even after newline + if (i == endIndex) { + matcher.hitEnd = true; + return false; + } + if (i > startIndex) { + char ch = seq.charAt(i-1); + if (ch != '\n' && ch != '\r' + && (ch|1) != '\u2029' + && ch != '\u0085' ) { + return false; + } + // Should treat /r/n as one newline + if (ch == '\r' && seq.charAt(i) == '\n') + return false; + } + return next.match(matcher, i, seq); + } + } + + /** + * Node to anchor at the beginning of a line when in unixdot mode. + */ + static final class UnixCaret extends Node { + boolean match(Matcher matcher, int i, CharSequence seq) { + int startIndex = matcher.from; + int endIndex = matcher.to; + if (!matcher.anchoringBounds) { + startIndex = 0; + endIndex = matcher.getTextLength(); + } + // Perl does not match ^ at end of input even after newline + if (i == endIndex) { + matcher.hitEnd = true; + return false; + } + if (i > startIndex) { + char ch = seq.charAt(i-1); + if (ch != '\n') { + return false; + } + } + return next.match(matcher, i, seq); + } + } + + /** + * Node to match the location where the last match ended. + * This is used for the \G construct. + */ + static final class LastMatch extends Node { + boolean match(Matcher matcher, int i, CharSequence seq) { + if (i != matcher.oldLast) + return false; + return next.match(matcher, i, seq); + } + } + + /** + * Node to anchor at the end of a line or the end of input based on the + * multiline mode. + * + * When not in multiline mode, the $ can only match at the very end + * of the input, unless the input ends in a line terminator in which + * it matches right before the last line terminator. + * + * Note that \r\n is considered an atomic line terminator. + * + * Like ^ the $ operator matches at a position, it does not match the + * line terminators themselves. + */ + static final class Dollar extends Node { + boolean multiline; + Dollar(boolean mul) { + multiline = mul; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int endIndex = (matcher.anchoringBounds) ? + matcher.to : matcher.getTextLength(); + if (!multiline) { + if (i < endIndex - 2) + return false; + if (i == endIndex - 2) { + char ch = seq.charAt(i); + if (ch != '\r') + return false; + ch = seq.charAt(i + 1); + if (ch != '\n') + return false; + } + } + // Matches before any line terminator; also matches at the + // end of input + // Before line terminator: + // If multiline, we match here no matter what + // If not multiline, fall through so that the end + // is marked as hit; this must be a /r/n or a /n + // at the very end so the end was hit; more input + // could make this not match here + if (i < endIndex) { + char ch = seq.charAt(i); + if (ch == '\n') { + // No match between \r\n + if (i > 0 && seq.charAt(i-1) == '\r') + return false; + if (multiline) + return next.match(matcher, i, seq); + } else if (ch == '\r' || ch == '\u0085' || + (ch|1) == '\u2029') { + if (multiline) + return next.match(matcher, i, seq); + } else { // No line terminator, no match + return false; + } + } + // Matched at current end so hit end + matcher.hitEnd = true; + // If a $ matches because of end of input, then more input + // could cause it to fail! + matcher.requireEnd = true; + return next.match(matcher, i, seq); + } + boolean study(TreeInfo info) { + next.study(info); + return info.deterministic; + } + } + + /** + * Node to anchor at the end of a line or the end of input based on the + * multiline mode when in unix lines mode. + */ + static final class UnixDollar extends Node { + boolean multiline; + UnixDollar(boolean mul) { + multiline = mul; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int endIndex = (matcher.anchoringBounds) ? + matcher.to : matcher.getTextLength(); + if (i < endIndex) { + char ch = seq.charAt(i); + if (ch == '\n') { + // If not multiline, then only possible to + // match at very end or one before end + if (multiline == false && i != endIndex - 1) + return false; + // If multiline return next.match without setting + // matcher.hitEnd + if (multiline) + return next.match(matcher, i, seq); + } else { + return false; + } + } + // Matching because at the end or 1 before the end; + // more input could change this so set hitEnd + matcher.hitEnd = true; + // If a $ matches because of end of input, then more input + // could cause it to fail! + matcher.requireEnd = true; + return next.match(matcher, i, seq); + } + boolean study(TreeInfo info) { + next.study(info); + return info.deterministic; + } + } + + /** + * Abstract node class to match one character satisfying some + * boolean property. + */ + private static abstract class CharProperty extends Node { + abstract boolean isSatisfiedBy(int ch); + CharProperty complement() { + return new CharProperty() { + boolean isSatisfiedBy(int ch) { + return ! CharProperty.this.isSatisfiedBy(ch);}}; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + if (i < matcher.to) { + int ch = Character.codePointAt(seq, i); + return isSatisfiedBy(ch) + && next.match(matcher, i+Character.charCount(ch), seq); + } else { + matcher.hitEnd = true; + return false; + } + } + boolean study(TreeInfo info) { + info.minLength++; + info.maxLength++; + return next.study(info); + } + } + + /** + * Optimized version of CharProperty that works only for + * properties never satisfied by Supplementary characters. + */ + private static abstract class BmpCharProperty extends CharProperty { + boolean match(Matcher matcher, int i, CharSequence seq) { + if (i < matcher.to) { + return isSatisfiedBy(seq.charAt(i)) + && next.match(matcher, i+1, seq); + } else { + matcher.hitEnd = true; + return false; + } + } + } + + /** + * Node class that matches a Supplementary Unicode character + */ + static final class SingleS extends CharProperty { + final int c; + SingleS(int c) { this.c = c; } + boolean isSatisfiedBy(int ch) { + return ch == c; + } + } + + /** + * Optimization -- matches a given BMP character + */ + static final class Single extends BmpCharProperty { + final int c; + Single(int c) { this.c = c; } + boolean isSatisfiedBy(int ch) { + return ch == c; + } + } + + /** + * Case insensitive matches a given BMP character + */ + static final class SingleI extends BmpCharProperty { + final int lower; + final int upper; + SingleI(int lower, int upper) { + this.lower = lower; + this.upper = upper; + } + boolean isSatisfiedBy(int ch) { + return ch == lower || ch == upper; + } + } + + /** + * Unicode case insensitive matches a given Unicode character + */ + static final class SingleU extends CharProperty { + final int lower; + SingleU(int lower) { + this.lower = lower; + } + boolean isSatisfiedBy(int ch) { + return lower == ch || + lower == Character.toLowerCase(Character.toUpperCase(ch)); + } + } + + + /** + * Node class that matches a Unicode block. + * + static final class Block extends CharProperty { + final Character.UnicodeBlock block; + Block(Character.UnicodeBlock block) { + this.block = block; + } + boolean isSatisfiedBy(int ch) { + return block == Character.UnicodeBlock.of(ch); + } + } + + /** + * Node class that matches a Unicode script + * + static final class Script extends CharProperty { + final Character.UnicodeScript script; + Script(Character.UnicodeScript script) { + this.script = script; + } + boolean isSatisfiedBy(int ch) { + return script == Character.UnicodeScript.of(ch); + } + } + + /** + * Node class that matches a Unicode category. + */ + static final class Category extends CharProperty { + final int typeMask; + Category(int typeMask) { this.typeMask = typeMask; } + boolean isSatisfiedBy(int ch) { + return (typeMask & (1 << Character.getType(ch))) != 0; + } + } + + /** + * Node class that matches a Unicode "type" + */ + static final class Utype extends CharProperty { + final UnicodeProp uprop; + Utype(UnicodeProp uprop) { this.uprop = uprop; } + boolean isSatisfiedBy(int ch) { + return uprop.is(ch); + } + } + + + /** + * Node class that matches a POSIX type. + */ + static final class Ctype extends BmpCharProperty { + final int ctype; + Ctype(int ctype) { this.ctype = ctype; } + boolean isSatisfiedBy(int ch) { + return ch < 128 && ASCII.isType(ch, ctype); + } + } + + /** + * Base class for all Slice nodes + */ + static class SliceNode extends Node { + int[] buffer; + SliceNode(int[] buf) { + buffer = buf; + } + boolean study(TreeInfo info) { + info.minLength += buffer.length; + info.maxLength += buffer.length; + return next.study(info); + } + } + + /** + * Node class for a case sensitive/BMP-only sequence of literal + * characters. + */ + static final class Slice extends SliceNode { + Slice(int[] buf) { + super(buf); + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int[] buf = buffer; + int len = buf.length; + for (int j=0; j= matcher.to) { + matcher.hitEnd = true; + return false; + } + if (buf[j] != seq.charAt(i+j)) + return false; + } + return next.match(matcher, i+len, seq); + } + } + + /** + * Node class for a case_insensitive/BMP-only sequence of literal + * characters. + */ + static class SliceI extends SliceNode { + SliceI(int[] buf) { + super(buf); + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int[] buf = buffer; + int len = buf.length; + for (int j=0; j= matcher.to) { + matcher.hitEnd = true; + return false; + } + int c = seq.charAt(i+j); + if (buf[j] != c && + buf[j] != ASCII.toLower(c)) + return false; + } + return next.match(matcher, i+len, seq); + } + } + + /** + * Node class for a unicode_case_insensitive/BMP-only sequence of + * literal characters. Uses unicode case folding. + */ + static final class SliceU extends SliceNode { + SliceU(int[] buf) { + super(buf); + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int[] buf = buffer; + int len = buf.length; + for (int j=0; j= matcher.to) { + matcher.hitEnd = true; + return false; + } + int c = seq.charAt(i+j); + if (buf[j] != c && + buf[j] != Character.toLowerCase(Character.toUpperCase(c))) + return false; + } + return next.match(matcher, i+len, seq); + } + } + + /** + * Node class for a case sensitive sequence of literal characters + * including supplementary characters. + */ + static final class SliceS extends SliceNode { + SliceS(int[] buf) { + super(buf); + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int[] buf = buffer; + int x = i; + for (int j = 0; j < buf.length; j++) { + if (x >= matcher.to) { + matcher.hitEnd = true; + return false; + } + int c = Character.codePointAt(seq, x); + if (buf[j] != c) + return false; + x += Character.charCount(c); + if (x > matcher.to) { + matcher.hitEnd = true; + return false; + } + } + return next.match(matcher, x, seq); + } + } + + /** + * Node class for a case insensitive sequence of literal characters + * including supplementary characters. + */ + static class SliceIS extends SliceNode { + SliceIS(int[] buf) { + super(buf); + } + int toLower(int c) { + return ASCII.toLower(c); + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int[] buf = buffer; + int x = i; + for (int j = 0; j < buf.length; j++) { + if (x >= matcher.to) { + matcher.hitEnd = true; + return false; + } + int c = Character.codePointAt(seq, x); + if (buf[j] != c && buf[j] != toLower(c)) + return false; + x += Character.charCount(c); + if (x > matcher.to) { + matcher.hitEnd = true; + return false; + } + } + return next.match(matcher, x, seq); + } + } + + /** + * Node class for a case insensitive sequence of literal characters. + * Uses unicode case folding. + */ + static final class SliceUS extends SliceIS { + SliceUS(int[] buf) { + super(buf); + } + int toLower(int c) { + return Character.toLowerCase(Character.toUpperCase(c)); + } + } + + private static boolean inRange(int lower, int ch, int upper) { + return lower <= ch && ch <= upper; + } + + /** + * Returns node for matching characters within an explicit value range. + */ + private static CharProperty rangeFor(final int lower, + final int upper) { + return new CharProperty() { + boolean isSatisfiedBy(int ch) { + return inRange(lower, ch, upper);}}; + } + + /** + * Returns node for matching characters within an explicit value + * range in a case insensitive manner. + */ + private CharProperty caseInsensitiveRangeFor(final int lower, + final int upper) { + if (has(UNICODE_CASE)) + return new CharProperty() { + boolean isSatisfiedBy(int ch) { + if (inRange(lower, ch, upper)) + return true; + int up = Character.toUpperCase(ch); + return inRange(lower, up, upper) || + inRange(lower, Character.toLowerCase(up), upper);}}; + return new CharProperty() { + boolean isSatisfiedBy(int ch) { + return inRange(lower, ch, upper) || + ASCII.isAscii(ch) && + (inRange(lower, ASCII.toUpper(ch), upper) || + inRange(lower, ASCII.toLower(ch), upper)); + }}; + } + + /** + * Implements the Unicode category ALL and the dot metacharacter when + * in dotall mode. + */ + static final class All extends CharProperty { + boolean isSatisfiedBy(int ch) { + return true; + } + } + + /** + * Node class for the dot metacharacter when dotall is not enabled. + */ + static final class Dot extends CharProperty { + boolean isSatisfiedBy(int ch) { + return (ch != '\n' && ch != '\r' + && (ch|1) != '\u2029' + && ch != '\u0085'); + } + } + + /** + * Node class for the dot metacharacter when dotall is not enabled + * but UNIX_LINES is enabled. + */ + static final class UnixDot extends CharProperty { + boolean isSatisfiedBy(int ch) { + return ch != '\n'; + } + } + + /** + * The 0 or 1 quantifier. This one class implements all three types. + */ + static final class Ques extends Node { + Node atom; + int type; + Ques(Node node, int type) { + this.atom = node; + this.type = type; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + switch (type) { + case GREEDY: + return (atom.match(matcher, i, seq) && next.match(matcher, matcher.last, seq)) + || next.match(matcher, i, seq); + case LAZY: + return next.match(matcher, i, seq) + || (atom.match(matcher, i, seq) && next.match(matcher, matcher.last, seq)); + case POSSESSIVE: + if (atom.match(matcher, i, seq)) i = matcher.last; + return next.match(matcher, i, seq); + default: + return atom.match(matcher, i, seq) && next.match(matcher, matcher.last, seq); + } + } + boolean study(TreeInfo info) { + if (type != INDEPENDENT) { + int minL = info.minLength; + atom.study(info); + info.minLength = minL; + info.deterministic = false; + return next.study(info); + } else { + atom.study(info); + return next.study(info); + } + } + } + + /** + * Handles the curly-brace style repetition with a specified minimum and + * maximum occurrences. The * quantifier is handled as a special case. + * This class handles the three types. + */ + static final class Curly extends Node { + Node atom; + int type; + int cmin; + int cmax; + + Curly(Node node, int cmin, int cmax, int type) { + this.atom = node; + this.type = type; + this.cmin = cmin; + this.cmax = cmax; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int j; + for (j = 0; j < cmin; j++) { + if (atom.match(matcher, i, seq)) { + i = matcher.last; + continue; + } + return false; + } + if (type == GREEDY) + return match0(matcher, i, j, seq); + else if (type == LAZY) + return match1(matcher, i, j, seq); + else + return match2(matcher, i, j, seq); + } + // Greedy match. + // i is the index to start matching at + // j is the number of atoms that have matched + boolean match0(Matcher matcher, int i, int j, CharSequence seq) { + if (j >= cmax) { + // We have matched the maximum... continue with the rest of + // the regular expression + return next.match(matcher, i, seq); + } + int backLimit = j; + while (atom.match(matcher, i, seq)) { + // k is the length of this match + int k = matcher.last - i; + if (k == 0) // Zero length match + break; + // Move up index and number matched + i = matcher.last; + j++; + // We are greedy so match as many as we can + while (j < cmax) { + if (!atom.match(matcher, i, seq)) + break; + if (i + k != matcher.last) { + if (match0(matcher, matcher.last, j+1, seq)) + return true; + break; + } + i += k; + j++; + } + // Handle backing off if match fails + while (j >= backLimit) { + if (next.match(matcher, i, seq)) + return true; + i -= k; + j--; + } + return false; + } + return next.match(matcher, i, seq); + } + // Reluctant match. At this point, the minimum has been satisfied. + // i is the index to start matching at + // j is the number of atoms that have matched + boolean match1(Matcher matcher, int i, int j, CharSequence seq) { + for (;;) { + // Try finishing match without consuming any more + if (next.match(matcher, i, seq)) + return true; + // At the maximum, no match found + if (j >= cmax) + return false; + // Okay, must try one more atom + if (!atom.match(matcher, i, seq)) + return false; + // If we haven't moved forward then must break out + if (i == matcher.last) + return false; + // Move up index and number matched + i = matcher.last; + j++; + } + } + boolean match2(Matcher matcher, int i, int j, CharSequence seq) { + for (; j < cmax; j++) { + if (!atom.match(matcher, i, seq)) + break; + if (i == matcher.last) + break; + i = matcher.last; + } + return next.match(matcher, i, seq); + } + boolean study(TreeInfo info) { + // Save original info + int minL = info.minLength; + int maxL = info.maxLength; + boolean maxV = info.maxValid; + boolean detm = info.deterministic; + info.reset(); + + atom.study(info); + + int temp = info.minLength * cmin + minL; + if (temp < minL) { + temp = 0xFFFFFFF; // arbitrary large number + } + info.minLength = temp; + + if (maxV & info.maxValid) { + temp = info.maxLength * cmax + maxL; + info.maxLength = temp; + if (temp < maxL) { + info.maxValid = false; + } + } else { + info.maxValid = false; + } + + if (info.deterministic && cmin == cmax) + info.deterministic = detm; + else + info.deterministic = false; + + return next.study(info); + } + } + + /** + * Handles the curly-brace style repetition with a specified minimum and + * maximum occurrences in deterministic cases. This is an iterative + * optimization over the Prolog and Loop system which would handle this + * in a recursive way. The * quantifier is handled as a special case. + * If capture is true then this class saves group settings and ensures + * that groups are unset when backing off of a group match. + */ + static final class GroupCurly extends Node { + Node atom; + int type; + int cmin; + int cmax; + int localIndex; + int groupIndex; + boolean capture; + + GroupCurly(Node node, int cmin, int cmax, int type, int local, + int group, boolean capture) { + this.atom = node; + this.type = type; + this.cmin = cmin; + this.cmax = cmax; + this.localIndex = local; + this.groupIndex = group; + this.capture = capture; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int[] groups = matcher.groups; + int[] locals = matcher.locals; + int save0 = locals[localIndex]; + int save1 = 0; + int save2 = 0; + + if (capture) { + save1 = groups[groupIndex]; + save2 = groups[groupIndex+1]; + } + + // Notify GroupTail there is no need to setup group info + // because it will be set here + locals[localIndex] = -1; + + boolean ret = true; + for (int j = 0; j < cmin; j++) { + if (atom.match(matcher, i, seq)) { + if (capture) { + groups[groupIndex] = i; + groups[groupIndex+1] = matcher.last; + } + i = matcher.last; + } else { + ret = false; + break; + } + } + if (ret) { + if (type == GREEDY) { + ret = match0(matcher, i, cmin, seq); + } else if (type == LAZY) { + ret = match1(matcher, i, cmin, seq); + } else { + ret = match2(matcher, i, cmin, seq); + } + } + if (!ret) { + locals[localIndex] = save0; + if (capture) { + groups[groupIndex] = save1; + groups[groupIndex+1] = save2; + } + } + return ret; + } + // Aggressive group match + boolean match0(Matcher matcher, int i, int j, CharSequence seq) { + int[] groups = matcher.groups; + int save0 = 0; + int save1 = 0; + if (capture) { + save0 = groups[groupIndex]; + save1 = groups[groupIndex+1]; + } + for (;;) { + if (j >= cmax) + break; + if (!atom.match(matcher, i, seq)) + break; + int k = matcher.last - i; + if (k <= 0) { + if (capture) { + groups[groupIndex] = i; + groups[groupIndex+1] = i + k; + } + i = i + k; + break; + } + for (;;) { + if (capture) { + groups[groupIndex] = i; + groups[groupIndex+1] = i + k; + } + i = i + k; + if (++j >= cmax) + break; + if (!atom.match(matcher, i, seq)) + break; + if (i + k != matcher.last) { + if (match0(matcher, i, j, seq)) + return true; + break; + } + } + while (j > cmin) { + if (next.match(matcher, i, seq)) { + if (capture) { + groups[groupIndex+1] = i; + groups[groupIndex] = i - k; + } + i = i - k; + return true; + } + // backing off + if (capture) { + groups[groupIndex+1] = i; + groups[groupIndex] = i - k; + } + i = i - k; + j--; + } + break; + } + if (capture) { + groups[groupIndex] = save0; + groups[groupIndex+1] = save1; + } + return next.match(matcher, i, seq); + } + // Reluctant matching + boolean match1(Matcher matcher, int i, int j, CharSequence seq) { + for (;;) { + if (next.match(matcher, i, seq)) + return true; + if (j >= cmax) + return false; + if (!atom.match(matcher, i, seq)) + return false; + if (i == matcher.last) + return false; + if (capture) { + matcher.groups[groupIndex] = i; + matcher.groups[groupIndex+1] = matcher.last; + } + i = matcher.last; + j++; + } + } + // Possessive matching + boolean match2(Matcher matcher, int i, int j, CharSequence seq) { + for (; j < cmax; j++) { + if (!atom.match(matcher, i, seq)) { + break; + } + if (capture) { + matcher.groups[groupIndex] = i; + matcher.groups[groupIndex+1] = matcher.last; + } + if (i == matcher.last) { + break; + } + i = matcher.last; + } + return next.match(matcher, i, seq); + } + boolean study(TreeInfo info) { + // Save original info + int minL = info.minLength; + int maxL = info.maxLength; + boolean maxV = info.maxValid; + boolean detm = info.deterministic; + info.reset(); + + atom.study(info); + + int temp = info.minLength * cmin + minL; + if (temp < minL) { + temp = 0xFFFFFFF; // Arbitrary large number + } + info.minLength = temp; + + if (maxV & info.maxValid) { + temp = info.maxLength * cmax + maxL; + info.maxLength = temp; + if (temp < maxL) { + info.maxValid = false; + } + } else { + info.maxValid = false; + } + + if (info.deterministic && cmin == cmax) { + info.deterministic = detm; + } else { + info.deterministic = false; + } + + return next.study(info); + } + } + + /** + * A Guard node at the end of each atom node in a Branch. It + * serves the purpose of chaining the "match" operation to + * "next" but not the "study", so we can collect the TreeInfo + * of each atom node without including the TreeInfo of the + * "next". + */ + static final class BranchConn extends Node { + BranchConn() {}; + boolean match(Matcher matcher, int i, CharSequence seq) { + return next.match(matcher, i, seq); + } + boolean study(TreeInfo info) { + return info.deterministic; + } + } + + /** + * Handles the branching of alternations. Note this is also used for + * the ? quantifier to branch between the case where it matches once + * and where it does not occur. + */ + static final class Branch extends Node { + Node[] atoms = new Node[2]; + int size = 2; + Node conn; + Branch(Node first, Node second, Node branchConn) { + conn = branchConn; + atoms[0] = first; + atoms[1] = second; + } + + void add(Node node) { + if (size >= atoms.length) { + Node[] tmp = new Node[atoms.length*2]; + System.arraycopy(atoms, 0, tmp, 0, atoms.length); + atoms = tmp; + } + atoms[size++] = node; + } + + boolean match(Matcher matcher, int i, CharSequence seq) { + for (int n = 0; n < size; n++) { + if (atoms[n] == null) { + if (conn.next.match(matcher, i, seq)) + return true; + } else if (atoms[n].match(matcher, i, seq)) { + return true; + } + } + return false; + } + + boolean study(TreeInfo info) { + int minL = info.minLength; + int maxL = info.maxLength; + boolean maxV = info.maxValid; + + int minL2 = Integer.MAX_VALUE; //arbitrary large enough num + int maxL2 = -1; + for (int n = 0; n < size; n++) { + info.reset(); + if (atoms[n] != null) + atoms[n].study(info); + minL2 = Math.min(minL2, info.minLength); + maxL2 = Math.max(maxL2, info.maxLength); + maxV = (maxV & info.maxValid); + } + + minL += minL2; + maxL += maxL2; + + info.reset(); + conn.next.study(info); + + info.minLength += minL; + info.maxLength += maxL; + info.maxValid &= maxV; + info.deterministic = false; + return false; + } + } + + /** + * The GroupHead saves the location where the group begins in the locals + * and restores them when the match is done. + * + * The matchRef is used when a reference to this group is accessed later + * in the expression. The locals will have a negative value in them to + * indicate that we do not want to unset the group if the reference + * doesn't match. + */ + static final class GroupHead extends Node { + int localIndex; + GroupHead(int localCount) { + localIndex = localCount; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int save = matcher.locals[localIndex]; + matcher.locals[localIndex] = i; + boolean ret = next.match(matcher, i, seq); + matcher.locals[localIndex] = save; + return ret; + } + boolean matchRef(Matcher matcher, int i, CharSequence seq) { + int save = matcher.locals[localIndex]; + matcher.locals[localIndex] = ~i; // HACK + boolean ret = next.match(matcher, i, seq); + matcher.locals[localIndex] = save; + return ret; + } + } + + /** + * Recursive reference to a group in the regular expression. It calls + * matchRef because if the reference fails to match we would not unset + * the group. + */ + static final class GroupRef extends Node { + GroupHead head; + GroupRef(GroupHead head) { + this.head = head; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + return head.matchRef(matcher, i, seq) + && next.match(matcher, matcher.last, seq); + } + boolean study(TreeInfo info) { + info.maxValid = false; + info.deterministic = false; + return next.study(info); + } + } + + /** + * The GroupTail handles the setting of group beginning and ending + * locations when groups are successfully matched. It must also be able to + * unset groups that have to be backed off of. + * + * The GroupTail node is also used when a previous group is referenced, + * and in that case no group information needs to be set. + */ + static final class GroupTail extends Node { + int localIndex; + int groupIndex; + GroupTail(int localCount, int groupCount) { + localIndex = localCount; + groupIndex = groupCount + groupCount; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int tmp = matcher.locals[localIndex]; + if (tmp >= 0) { // This is the normal group case. + // Save the group so we can unset it if it + // backs off of a match. + int groupStart = matcher.groups[groupIndex]; + int groupEnd = matcher.groups[groupIndex+1]; + + matcher.groups[groupIndex] = tmp; + matcher.groups[groupIndex+1] = i; + if (next.match(matcher, i, seq)) { + return true; + } + matcher.groups[groupIndex] = groupStart; + matcher.groups[groupIndex+1] = groupEnd; + return false; + } else { + // This is a group reference case. We don't need to save any + // group info because it isn't really a group. + matcher.last = i; + return true; + } + } + } + + /** + * This sets up a loop to handle a recursive quantifier structure. + */ + static final class Prolog extends Node { + Loop loop; + Prolog(Loop loop) { + this.loop = loop; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + return loop.matchInit(matcher, i, seq); + } + boolean study(TreeInfo info) { + return loop.study(info); + } + } + + /** + * Handles the repetition count for a greedy Curly. The matchInit + * is called from the Prolog to save the index of where the group + * beginning is stored. A zero length group check occurs in the + * normal match but is skipped in the matchInit. + */ + static class Loop extends Node { + Node body; + int countIndex; // local count index in matcher locals + int beginIndex; // group beginning index + int cmin, cmax; + Loop(int countIndex, int beginIndex) { + this.countIndex = countIndex; + this.beginIndex = beginIndex; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + // Avoid infinite loop in zero-length case. + if (i > matcher.locals[beginIndex]) { + int count = matcher.locals[countIndex]; + + // This block is for before we reach the minimum + // iterations required for the loop to match + if (count < cmin) { + matcher.locals[countIndex] = count + 1; + boolean b = body.match(matcher, i, seq); + // If match failed we must backtrack, so + // the loop count should NOT be incremented + if (!b) + matcher.locals[countIndex] = count; + // Return success or failure since we are under + // minimum + return b; + } + // This block is for after we have the minimum + // iterations required for the loop to match + if (count < cmax) { + matcher.locals[countIndex] = count + 1; + boolean b = body.match(matcher, i, seq); + // If match failed we must backtrack, so + // the loop count should NOT be incremented + if (!b) + matcher.locals[countIndex] = count; + else + return true; + } + } + return next.match(matcher, i, seq); + } + boolean matchInit(Matcher matcher, int i, CharSequence seq) { + int save = matcher.locals[countIndex]; + boolean ret = false; + if (0 < cmin) { + matcher.locals[countIndex] = 1; + ret = body.match(matcher, i, seq); + } else if (0 < cmax) { + matcher.locals[countIndex] = 1; + ret = body.match(matcher, i, seq); + if (ret == false) + ret = next.match(matcher, i, seq); + } else { + ret = next.match(matcher, i, seq); + } + matcher.locals[countIndex] = save; + return ret; + } + boolean study(TreeInfo info) { + info.maxValid = false; + info.deterministic = false; + return false; + } + } + + /** + * Handles the repetition count for a reluctant Curly. The matchInit + * is called from the Prolog to save the index of where the group + * beginning is stored. A zero length group check occurs in the + * normal match but is skipped in the matchInit. + */ + static final class LazyLoop extends Loop { + LazyLoop(int countIndex, int beginIndex) { + super(countIndex, beginIndex); + } + boolean match(Matcher matcher, int i, CharSequence seq) { + // Check for zero length group + if (i > matcher.locals[beginIndex]) { + int count = matcher.locals[countIndex]; + if (count < cmin) { + matcher.locals[countIndex] = count + 1; + boolean result = body.match(matcher, i, seq); + // If match failed we must backtrack, so + // the loop count should NOT be incremented + if (!result) + matcher.locals[countIndex] = count; + return result; + } + if (next.match(matcher, i, seq)) + return true; + if (count < cmax) { + matcher.locals[countIndex] = count + 1; + boolean result = body.match(matcher, i, seq); + // If match failed we must backtrack, so + // the loop count should NOT be incremented + if (!result) + matcher.locals[countIndex] = count; + return result; + } + return false; + } + return next.match(matcher, i, seq); + } + boolean matchInit(Matcher matcher, int i, CharSequence seq) { + int save = matcher.locals[countIndex]; + boolean ret = false; + if (0 < cmin) { + matcher.locals[countIndex] = 1; + ret = body.match(matcher, i, seq); + } else if (next.match(matcher, i, seq)) { + ret = true; + } else if (0 < cmax) { + matcher.locals[countIndex] = 1; + ret = body.match(matcher, i, seq); + } + matcher.locals[countIndex] = save; + return ret; + } + boolean study(TreeInfo info) { + info.maxValid = false; + info.deterministic = false; + return false; + } + } + + /** + * Refers to a group in the regular expression. Attempts to match + * whatever the group referred to last matched. + */ + static class BackRef extends Node { + int groupIndex; + BackRef(int groupCount) { + super(); + groupIndex = groupCount + groupCount; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int j = matcher.groups[groupIndex]; + int k = matcher.groups[groupIndex+1]; + + int groupSize = k - j; + + // If the referenced group didn't match, neither can this + if (j < 0) + return false; + + // If there isn't enough input left no match + if (i + groupSize > matcher.to) { + matcher.hitEnd = true; + return false; + } + + // Check each new char to make sure it matches what the group + // referenced matched last time around + for (int index=0; index matcher.to) { + matcher.hitEnd = true; + return false; + } + + // Check each new char to make sure it matches what the group + // referenced matched last time around + int x = i; + for (int index=0; index matcher.to) { + matcher.hitEnd = true; + return false; + } + if (atom.match(matcher, i, seq)) { + return next.match(matcher, matcher.last, seq); + } + i += countChars(seq, i, 1); + matcher.first++; + } + } + boolean study(TreeInfo info) { + atom.study(info); + info.maxValid = false; + info.deterministic = false; + return next.study(info); + } + } + + static final class Conditional extends Node { + Node cond, yes, not; + Conditional(Node cond, Node yes, Node not) { + this.cond = cond; + this.yes = yes; + this.not = not; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + if (cond.match(matcher, i, seq)) { + return yes.match(matcher, i, seq); + } else { + return not.match(matcher, i, seq); + } + } + boolean study(TreeInfo info) { + int minL = info.minLength; + int maxL = info.maxLength; + boolean maxV = info.maxValid; + info.reset(); + yes.study(info); + + int minL2 = info.minLength; + int maxL2 = info.maxLength; + boolean maxV2 = info.maxValid; + info.reset(); + not.study(info); + + info.minLength = minL + Math.min(minL2, info.minLength); + info.maxLength = maxL + Math.max(maxL2, info.maxLength); + info.maxValid = (maxV & maxV2 & info.maxValid); + info.deterministic = false; + return next.study(info); + } + } + + /** + * Zero width positive lookahead. + */ + static final class Pos extends Node { + Node cond; + Pos(Node cond) { + this.cond = cond; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int savedTo = matcher.to; + boolean conditionMatched = false; + + // Relax transparent region boundaries for lookahead + if (matcher.transparentBounds) + matcher.to = matcher.getTextLength(); + try { + conditionMatched = cond.match(matcher, i, seq); + } finally { + // Reinstate region boundaries + matcher.to = savedTo; + } + return conditionMatched && next.match(matcher, i, seq); + } + } + + /** + * Zero width negative lookahead. + */ + static final class Neg extends Node { + Node cond; + Neg(Node cond) { + this.cond = cond; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int savedTo = matcher.to; + boolean conditionMatched = false; + + // Relax transparent region boundaries for lookahead + if (matcher.transparentBounds) + matcher.to = matcher.getTextLength(); + try { + if (i < matcher.to) { + conditionMatched = !cond.match(matcher, i, seq); + } else { + // If a negative lookahead succeeds then more input + // could cause it to fail! + matcher.requireEnd = true; + conditionMatched = !cond.match(matcher, i, seq); + } + } finally { + // Reinstate region boundaries + matcher.to = savedTo; + } + return conditionMatched && next.match(matcher, i, seq); + } + } + + /** + * For use with lookbehinds; matches the position where the lookbehind + * was encountered. + */ + static Node lookbehindEnd = new Node() { + boolean match(Matcher matcher, int i, CharSequence seq) { + return i == matcher.lookbehindTo; + } + }; + + /** + * Zero width positive lookbehind. + */ + static class Behind extends Node { + Node cond; + int rmax, rmin; + Behind(Node cond, int rmax, int rmin) { + this.cond = cond; + this.rmax = rmax; + this.rmin = rmin; + } + + boolean match(Matcher matcher, int i, CharSequence seq) { + int savedFrom = matcher.from; + boolean conditionMatched = false; + int startIndex = (!matcher.transparentBounds) ? + matcher.from : 0; + int from = Math.max(i - rmax, startIndex); + // Set end boundary + int savedLBT = matcher.lookbehindTo; + matcher.lookbehindTo = i; + // Relax transparent region boundaries for lookbehind + if (matcher.transparentBounds) + matcher.from = 0; + for (int j = i - rmin; !conditionMatched && j >= from; j--) { + conditionMatched = cond.match(matcher, j, seq); + } + matcher.from = savedFrom; + matcher.lookbehindTo = savedLBT; + return conditionMatched && next.match(matcher, i, seq); + } + } + + /** + * Zero width positive lookbehind, including supplementary + * characters or unpaired surrogates. + */ + static final class BehindS extends Behind { + BehindS(Node cond, int rmax, int rmin) { + super(cond, rmax, rmin); + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int rmaxChars = countChars(seq, i, -rmax); + int rminChars = countChars(seq, i, -rmin); + int savedFrom = matcher.from; + int startIndex = (!matcher.transparentBounds) ? + matcher.from : 0; + boolean conditionMatched = false; + int from = Math.max(i - rmaxChars, startIndex); + // Set end boundary + int savedLBT = matcher.lookbehindTo; + matcher.lookbehindTo = i; + // Relax transparent region boundaries for lookbehind + if (matcher.transparentBounds) + matcher.from = 0; + + for (int j = i - rminChars; + !conditionMatched && j >= from; + j -= j>from ? countChars(seq, j, -1) : 1) { + conditionMatched = cond.match(matcher, j, seq); + } + matcher.from = savedFrom; + matcher.lookbehindTo = savedLBT; + return conditionMatched && next.match(matcher, i, seq); + } + } + + /** + * Zero width negative lookbehind. + */ + static class NotBehind extends Node { + Node cond; + int rmax, rmin; + NotBehind(Node cond, int rmax, int rmin) { + this.cond = cond; + this.rmax = rmax; + this.rmin = rmin; + } + + boolean match(Matcher matcher, int i, CharSequence seq) { + int savedLBT = matcher.lookbehindTo; + int savedFrom = matcher.from; + boolean conditionMatched = false; + int startIndex = (!matcher.transparentBounds) ? + matcher.from : 0; + int from = Math.max(i - rmax, startIndex); + matcher.lookbehindTo = i; + // Relax transparent region boundaries for lookbehind + if (matcher.transparentBounds) + matcher.from = 0; + for (int j = i - rmin; !conditionMatched && j >= from; j--) { + conditionMatched = cond.match(matcher, j, seq); + } + // Reinstate region boundaries + matcher.from = savedFrom; + matcher.lookbehindTo = savedLBT; + return !conditionMatched && next.match(matcher, i, seq); + } + } + + /** + * Zero width negative lookbehind, including supplementary + * characters or unpaired surrogates. + */ + static final class NotBehindS extends NotBehind { + NotBehindS(Node cond, int rmax, int rmin) { + super(cond, rmax, rmin); + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int rmaxChars = countChars(seq, i, -rmax); + int rminChars = countChars(seq, i, -rmin); + int savedFrom = matcher.from; + int savedLBT = matcher.lookbehindTo; + boolean conditionMatched = false; + int startIndex = (!matcher.transparentBounds) ? + matcher.from : 0; + int from = Math.max(i - rmaxChars, startIndex); + matcher.lookbehindTo = i; + // Relax transparent region boundaries for lookbehind + if (matcher.transparentBounds) + matcher.from = 0; + for (int j = i - rminChars; + !conditionMatched && j >= from; + j -= j>from ? countChars(seq, j, -1) : 1) { + conditionMatched = cond.match(matcher, j, seq); + } + //Reinstate region boundaries + matcher.from = savedFrom; + matcher.lookbehindTo = savedLBT; + return !conditionMatched && next.match(matcher, i, seq); + } + } + + /** + * Returns the set union of two CharProperty nodes. + */ + private static CharProperty union(final CharProperty lhs, + final CharProperty rhs) { + return new CharProperty() { + boolean isSatisfiedBy(int ch) { + return lhs.isSatisfiedBy(ch) || rhs.isSatisfiedBy(ch);}}; + } + + /** + * Returns the set intersection of two CharProperty nodes. + */ + private static CharProperty intersection(final CharProperty lhs, + final CharProperty rhs) { + return new CharProperty() { + boolean isSatisfiedBy(int ch) { + return lhs.isSatisfiedBy(ch) && rhs.isSatisfiedBy(ch);}}; + } + + /** + * Returns the set difference of two CharProperty nodes. + */ + private static CharProperty setDifference(final CharProperty lhs, + final CharProperty rhs) { + return new CharProperty() { + boolean isSatisfiedBy(int ch) { + return ! rhs.isSatisfiedBy(ch) && lhs.isSatisfiedBy(ch);}}; + } + + /** + * Handles word boundaries. Includes a field to allow this one class to + * deal with the different types of word boundaries we can match. The word + * characters include underscores, letters, and digits. Non spacing marks + * can are also part of a word if they have a base character, otherwise + * they are ignored for purposes of finding word boundaries. + */ + static final class Bound extends Node { + static int LEFT = 0x1; + static int RIGHT= 0x2; + static int BOTH = 0x3; + static int NONE = 0x4; + int type; + boolean useUWORD; + Bound(int n, boolean useUWORD) { + type = n; + this.useUWORD = useUWORD; + } + + boolean isWord(int ch) { + return useUWORD ? UnicodeProp.WORD.is(ch) + : (ch == '_' || Character.isLetterOrDigit(ch)); + } + + int check(Matcher matcher, int i, CharSequence seq) { + int ch; + boolean left = false; + int startIndex = matcher.from; + int endIndex = matcher.to; + if (matcher.transparentBounds) { + startIndex = 0; + endIndex = matcher.getTextLength(); + } + if (i > startIndex) { + ch = Character.codePointBefore(seq, i); + left = (isWord(ch) || + ((Character.getType(ch) == Character.NON_SPACING_MARK) + && hasBaseCharacter(matcher, i-1, seq))); + } + boolean right = false; + if (i < endIndex) { + ch = Character.codePointAt(seq, i); + right = (isWord(ch) || + ((Character.getType(ch) == Character.NON_SPACING_MARK) + && hasBaseCharacter(matcher, i, seq))); + } else { + // Tried to access char past the end + matcher.hitEnd = true; + // The addition of another char could wreck a boundary + matcher.requireEnd = true; + } + return ((left ^ right) ? (right ? LEFT : RIGHT) : NONE); + } + boolean match(Matcher matcher, int i, CharSequence seq) { + return (check(matcher, i, seq) & type) > 0 + && next.match(matcher, i, seq); + } + } + + /** + * Non spacing marks only count as word characters in bounds calculations + * if they have a base character. + */ + private static boolean hasBaseCharacter(Matcher matcher, int i, + CharSequence seq) + { + int start = (!matcher.transparentBounds) ? + matcher.from : 0; + for (int x=i; x >= start; x--) { + int ch = Character.codePointAt(seq, x); + if (Character.isLetterOrDigit(ch)) + return true; + if (Character.getType(ch) == Character.NON_SPACING_MARK) + continue; + return false; + } + return false; + } + + /** + * Attempts to match a slice in the input using the Boyer-Moore string + * matching algorithm. The algorithm is based on the idea that the + * pattern can be shifted farther ahead in the search text if it is + * matched right to left. + *

+ * The pattern is compared to the input one character at a time, from + * the rightmost character in the pattern to the left. If the characters + * all match the pattern has been found. If a character does not match, + * the pattern is shifted right a distance that is the maximum of two + * functions, the bad character shift and the good suffix shift. This + * shift moves the attempted match position through the input more + * quickly than a naive one position at a time check. + *

+ * The bad character shift is based on the character from the text that + * did not match. If the character does not appear in the pattern, the + * pattern can be shifted completely beyond the bad character. If the + * character does occur in the pattern, the pattern can be shifted to + * line the pattern up with the next occurrence of that character. + *

+ * The good suffix shift is based on the idea that some subset on the right + * side of the pattern has matched. When a bad character is found, the + * pattern can be shifted right by the pattern length if the subset does + * not occur again in pattern, or by the amount of distance to the + * next occurrence of the subset in the pattern. + * + * Boyer-Moore search methods adapted from code by Amy Yu. + */ + static class BnM extends Node { + int[] buffer; + int[] lastOcc; + int[] optoSft; + + /** + * Pre calculates arrays needed to generate the bad character + * shift and the good suffix shift. Only the last seven bits + * are used to see if chars match; This keeps the tables small + * and covers the heavily used ASCII range, but occasionally + * results in an aliased match for the bad character shift. + */ + static Node optimize(Node node) { + if (!(node instanceof Slice)) { + return node; + } + + int[] src = ((Slice) node).buffer; + int patternLength = src.length; + // The BM algorithm requires a bit of overhead; + // If the pattern is short don't use it, since + // a shift larger than the pattern length cannot + // be used anyway. + if (patternLength < 4) { + return node; + } + int i, j, k; + int[] lastOcc = new int[128]; + int[] optoSft = new int[patternLength]; + // Precalculate part of the bad character shift + // It is a table for where in the pattern each + // lower 7-bit value occurs + for (i = 0; i < patternLength; i++) { + lastOcc[src[i]&0x7F] = i + 1; + } + // Precalculate the good suffix shift + // i is the shift amount being considered +NEXT: for (i = patternLength; i > 0; i--) { + // j is the beginning index of suffix being considered + for (j = patternLength - 1; j >= i; j--) { + // Testing for good suffix + if (src[j] == src[j-i]) { + // src[j..len] is a good suffix + optoSft[j-1] = i; + } else { + // No match. The array has already been + // filled up with correct values before. + continue NEXT; + } + } + // This fills up the remaining of optoSft + // any suffix can not have larger shift amount + // then its sub-suffix. Why??? + while (j > 0) { + optoSft[--j] = i; + } + } + // Set the guard value because of unicode compression + optoSft[patternLength-1] = 1; + if (node instanceof SliceS) + return new BnMS(src, lastOcc, optoSft, node.next); + return new BnM(src, lastOcc, optoSft, node.next); + } + BnM(int[] src, int[] lastOcc, int[] optoSft, Node next) { + this.buffer = src; + this.lastOcc = lastOcc; + this.optoSft = optoSft; + this.next = next; + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int[] src = buffer; + int patternLength = src.length; + int last = matcher.to - patternLength; + + // Loop over all possible match positions in text +NEXT: while (i <= last) { + // Loop over pattern from right to left + for (int j = patternLength - 1; j >= 0; j--) { + int ch = seq.charAt(i+j); + if (ch != src[j]) { + // Shift search to the right by the maximum of the + // bad character shift and the good suffix shift + i += Math.max(j + 1 - lastOcc[ch&0x7F], optoSft[j]); + continue NEXT; + } + } + // Entire pattern matched starting at i + matcher.first = i; + boolean ret = next.match(matcher, i + patternLength, seq); + if (ret) { + matcher.first = i; + matcher.groups[0] = matcher.first; + matcher.groups[1] = matcher.last; + return true; + } + i++; + } + // BnM is only used as the leading node in the unanchored case, + // and it replaced its Start() which always searches to the end + // if it doesn't find what it's looking for, so hitEnd is true. + matcher.hitEnd = true; + return false; + } + boolean study(TreeInfo info) { + info.minLength += buffer.length; + info.maxValid = false; + return next.study(info); + } + } + + /** + * Supplementary support version of BnM(). Unpaired surrogates are + * also handled by this class. + */ + static final class BnMS extends BnM { + int lengthInChars; + + BnMS(int[] src, int[] lastOcc, int[] optoSft, Node next) { + super(src, lastOcc, optoSft, next); + for (int x = 0; x < buffer.length; x++) { + lengthInChars += Character.charCount(buffer[x]); + } + } + boolean match(Matcher matcher, int i, CharSequence seq) { + int[] src = buffer; + int patternLength = src.length; + int last = matcher.to - lengthInChars; + + // Loop over all possible match positions in text +NEXT: while (i <= last) { + // Loop over pattern from right to left + int ch; + for (int j = countChars(seq, i, patternLength), x = patternLength - 1; + j > 0; j -= Character.charCount(ch), x--) { + ch = Character.codePointBefore(seq, i+j); + if (ch != src[x]) { + // Shift search to the right by the maximum of the + // bad character shift and the good suffix shift + int n = Math.max(x + 1 - lastOcc[ch&0x7F], optoSft[x]); + i += countChars(seq, i, n); + continue NEXT; + } + } + // Entire pattern matched starting at i + matcher.first = i; + boolean ret = next.match(matcher, i + lengthInChars, seq); + if (ret) { + matcher.first = i; + matcher.groups[0] = matcher.first; + matcher.groups[1] = matcher.last; + return true; + } + i += countChars(seq, i, 1); + } + matcher.hitEnd = true; + return false; + } + } + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + + /** + * This must be the very first initializer. + */ + static Node accept = new Node(); + + static Node lastAccept = new LastNode(); + + private static class CharPropertyNames { + + static CharProperty charPropertyFor(String name) { + CharPropertyFactory m = map.get(name); + return m == null ? null : m.make(); + } + + private static abstract class CharPropertyFactory { + abstract CharProperty make(); + } + + private static void defCategory(String name, + final int typeMask) { + map.put(name, new CharPropertyFactory() { + CharProperty make() { return new Category(typeMask);}}); + } + + private static void defRange(String name, + final int lower, final int upper) { + map.put(name, new CharPropertyFactory() { + CharProperty make() { return rangeFor(lower, upper);}}); + } + + private static void defCtype(String name, + final int ctype) { + map.put(name, new CharPropertyFactory() { + CharProperty make() { return new Ctype(ctype);}}); + } + + private static abstract class CloneableProperty + extends CharProperty implements Cloneable + { + public CloneableProperty clone() { + try { + return (CloneableProperty) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + } + } + + private static void defClone(String name, + final CloneableProperty p) { + map.put(name, new CharPropertyFactory() { + CharProperty make() { return p.clone();}}); + } + + private static final HashMap map + = new HashMap<>(); + + static { + // Unicode character property aliases, defined in + // http://www.unicode.org/Public/UNIDATA/PropertyValueAliases.txt + defCategory("Cn", 1<-1 if the index is not known + */ + public PatternSyntaxException(String desc, String regex, int index) { + this.desc = desc; + this.pattern = regex; + this.index = index; + } + + /** + * Retrieves the error index. + * + * @return The approximate index in the pattern of the error, + * or -1 if the index is not known + */ + public int getIndex() { + return index; + } + + /** + * Retrieves the description of the error. + * + * @return The description of the error + */ + public String getDescription() { + return desc; + } + + /** + * Retrieves the erroneous regular-expression pattern. + * + * @return The erroneous pattern + */ + public String getPattern() { + return pattern; + } + + private static final String nl = System.lineSeparator(); + + /** + * Returns a multi-line string containing the description of the syntax + * error and its index, the erroneous regular-expression pattern, and a + * visual indication of the error index within the pattern. + * + * @return The full detail message + */ + public String getMessage() { + StringBuffer sb = new StringBuffer(); + sb.append(desc); + if (index >= 0) { + sb.append(" near index "); + sb.append(index); + } + sb.append(nl); + sb.append(pattern); + if (index >= 0) { + sb.append(nl); + for (int i = 0; i < index; i++) sb.append(' '); + sb.append('^'); + } + return sb.toString(); + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/regex/UnicodeProp.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/regex/UnicodeProp.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,236 @@ +/* + * Copyright (c) 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.regex; + +import java.util.HashMap; +import java.util.Locale; + +enum UnicodeProp { + + ALPHABETIC { + public boolean is(int ch) { + return Character.isAlphabetic(ch); + } + }, + + LETTER { + public boolean is(int ch) { + return Character.isLetter(ch); + } + }, + + IDEOGRAPHIC { + public boolean is(int ch) { + return Character.isIdeographic(ch); + } + }, + + LOWERCASE { + public boolean is(int ch) { + return Character.isLowerCase(ch); + } + }, + + UPPERCASE { + public boolean is(int ch) { + return Character.isUpperCase(ch); + } + }, + + TITLECASE { + public boolean is(int ch) { + return Character.isTitleCase(ch); + } + }, + + WHITE_SPACE { + // \p{Whitespace} + public boolean is(int ch) { + return ((((1 << Character.SPACE_SEPARATOR) | + (1 << Character.LINE_SEPARATOR) | + (1 << Character.PARAGRAPH_SEPARATOR)) >> Character.getType(ch)) & 1) + != 0 || (ch >= 0x9 && ch <= 0xd) || (ch == 0x85); + } + }, + + CONTROL { + // \p{gc=Control} + public boolean is(int ch) { + return Character.getType(ch) == Character.CONTROL; + } + }, + + PUNCTUATION { + // \p{gc=Punctuation} + public boolean is(int ch) { + return ((((1 << Character.CONNECTOR_PUNCTUATION) | + (1 << Character.DASH_PUNCTUATION) | + (1 << Character.START_PUNCTUATION) | + (1 << Character.END_PUNCTUATION) | + (1 << Character.OTHER_PUNCTUATION) | + (1 << Character.INITIAL_QUOTE_PUNCTUATION) | + (1 << Character.FINAL_QUOTE_PUNCTUATION)) >> Character.getType(ch)) & 1) + != 0; + } + }, + + HEX_DIGIT { + // \p{gc=Decimal_Number} + // \p{Hex_Digit} -> PropList.txt: Hex_Digit + public boolean is(int ch) { + return DIGIT.is(ch) || + (ch >= 0x0030 && ch <= 0x0039) || + (ch >= 0x0041 && ch <= 0x0046) || + (ch >= 0x0061 && ch <= 0x0066) || + (ch >= 0xFF10 && ch <= 0xFF19) || + (ch >= 0xFF21 && ch <= 0xFF26) || + (ch >= 0xFF41 && ch <= 0xFF46); + } + }, + + ASSIGNED { + public boolean is(int ch) { + return Character.getType(ch) != Character.UNASSIGNED; + } + }, + + NONCHARACTER_CODE_POINT { + // PropList.txt:Noncharacter_Code_Point + public boolean is(int ch) { + return (ch & 0xfffe) == 0xfffe || (ch >= 0xfdd0 && ch <= 0xfdef); + } + }, + + DIGIT { + // \p{gc=Decimal_Number} + public boolean is(int ch) { + return Character.isDigit(ch); + } + }, + + ALNUM { + // \p{alpha} + // \p{digit} + public boolean is(int ch) { + return ALPHABETIC.is(ch) || DIGIT.is(ch); + } + }, + + BLANK { + // \p{Whitespace} -- + // [\N{LF} \N{VT} \N{FF} \N{CR} \N{NEL} -> 0xa, 0xb, 0xc, 0xd, 0x85 + // \p{gc=Line_Separator} + // \p{gc=Paragraph_Separator}] + public boolean is(int ch) { + return Character.getType(ch) == Character.SPACE_SEPARATOR || + ch == 0x9; // \N{HT} + } + }, + + GRAPH { + // [^ + // \p{space} + // \p{gc=Control} + // \p{gc=Surrogate} + // \p{gc=Unassigned}] + public boolean is(int ch) { + return ((((1 << Character.SPACE_SEPARATOR) | + (1 << Character.LINE_SEPARATOR) | + (1 << Character.PARAGRAPH_SEPARATOR) | + (1 << Character.CONTROL) | + (1 << Character.SURROGATE) | + (1 << Character.UNASSIGNED)) >> Character.getType(ch)) & 1) + == 0; + } + }, + + PRINT { + // \p{graph} + // \p{blank} + // -- \p{cntrl} + public boolean is(int ch) { + return (GRAPH.is(ch) || BLANK.is(ch)) && !CONTROL.is(ch); + } + }, + + WORD { + // \p{alpha} + // \p{gc=Mark} + // \p{digit} + // \p{gc=Connector_Punctuation} + + public boolean is(int ch) { + return ALPHABETIC.is(ch) || + ((((1 << Character.NON_SPACING_MARK) | + (1 << Character.ENCLOSING_MARK) | + (1 << Character.COMBINING_SPACING_MARK) | + (1 << Character.DECIMAL_DIGIT_NUMBER) | + (1 << Character.CONNECTOR_PUNCTUATION)) >> Character.getType(ch)) & 1) + != 0; + } + }; + + private final static HashMap posix = new HashMap<>(); + private final static HashMap aliases = new HashMap<>(); + static { + posix.put("ALPHA", "ALPHABETIC"); + posix.put("LOWER", "LOWERCASE"); + posix.put("UPPER", "UPPERCASE"); + posix.put("SPACE", "WHITE_SPACE"); + posix.put("PUNCT", "PUNCTUATION"); + posix.put("XDIGIT","HEX_DIGIT"); + posix.put("ALNUM", "ALNUM"); + posix.put("CNTRL", "CONTROL"); + posix.put("DIGIT", "DIGIT"); + posix.put("BLANK", "BLANK"); + posix.put("GRAPH", "GRAPH"); + posix.put("PRINT", "PRINT"); + + aliases.put("WHITESPACE", "WHITE_SPACE"); + aliases.put("HEXDIGIT","HEX_DIGIT"); + aliases.put("NONCHARACTERCODEPOINT", "NONCHARACTER_CODE_POINT"); + } + + public static UnicodeProp forName(String propName) { + propName = propName.toUpperCase(Locale.ENGLISH); + String alias = aliases.get(propName); + if (alias != null) + propName = alias; + try { + return valueOf (propName); + } catch (IllegalArgumentException x) {} + return null; + } + + public static UnicodeProp forPOSIXName(String propName) { + propName = posix.get(propName.toUpperCase(Locale.ENGLISH)); + if (propName == null) + return null; + return valueOf (propName); + } + + public abstract boolean is(int ch); +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/java/util/regex/package.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/regex/package.html Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,66 @@ + + + + + + + + +Classes for matching character sequences against patterns specified by regular +expressions. + +

An instance of the {@link java.util.regex.Pattern} class represents a +regular expression that is specified in string form in a syntax similar to +that used by Perl. + +

Instances of the {@link java.util.regex.Matcher} class are used to match +character sequences against a given pattern. Input is provided to matchers via +the {@link java.lang.CharSequence} interface in order to support matching +against characters from a wide variety of input sources.

+ +

Unless otherwise noted, passing a null argument to a method +in any class or interface in this package will cause a +{@link java.lang.NullPointerException NullPointerException} to be thrown. + +

Related Documentation

+ +

An excellent tutorial and overview of regular expressions is Mastering Regular +Expressions, Jeffrey E. F. Friedl, O'Reilly and Associates, 1997.

+ + + +@since 1.4 +@author Mike McCloskey +@author Mark Reinhold + + + diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/main/java/org/apidesign/bck2brwsr/emul/reflect/ProxyImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/org/apidesign/bck2brwsr/emul/reflect/ProxyImpl.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,1620 @@ +/* + * Copyright (c) 1999, 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 org.apidesign.bck2brwsr.emul.reflect; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.List; +import java.util.ListIterator; +import java.util.WeakHashMap; +import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.emul.reflect.MethodImpl; + +/** + * {@code Proxy} provides static methods for creating dynamic proxy + * classes and instances, and it is also the superclass of all + * dynamic proxy classes created by those methods. + * + *

To create a proxy for some interface {@code Foo}: + *

+ *     InvocationHandler handler = new MyInvocationHandler(...);
+ *     Class proxyClass = Proxy.getProxyClass(
+ *         Foo.class.getClassLoader(), new Class[] { Foo.class });
+ *     Foo f = (Foo) proxyClass.
+ *         getConstructor(new Class[] { InvocationHandler.class }).
+ *         newInstance(new Object[] { handler });
+ * 
+ * or more simply: + *
+ *     Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
+ *                                          new Class[] { Foo.class },
+ *                                          handler);
+ * 
+ * + *

A dynamic proxy class (simply referred to as a proxy + * class below) is a class that implements a list of interfaces + * specified at runtime when the class is created, with behavior as + * described below. + * + * A proxy interface is such an interface that is implemented + * by a proxy class. + * + * A proxy instance is an instance of a proxy class. + * + * Each proxy instance has an associated invocation handler + * object, which implements the interface {@link InvocationHandler}. + * A method invocation on a proxy instance through one of its proxy + * interfaces will be dispatched to the {@link InvocationHandler#invoke + * invoke} method of the instance's invocation handler, passing the proxy + * instance, a {@code java.lang.reflect.Method} object identifying + * the method that was invoked, and an array of type {@code Object} + * containing the arguments. The invocation handler processes the + * encoded method invocation as appropriate and the result that it + * returns will be returned as the result of the method invocation on + * the proxy instance. + * + *

A proxy class has the following properties: + * + *

    + *
  • Proxy classes are public, final, and not abstract. + * + *
  • The unqualified name of a proxy class is unspecified. The space + * of class names that begin with the string {@code "$Proxy"} + * should be, however, reserved for proxy classes. + * + *
  • A proxy class extends {@code java.lang.reflect.Proxy}. + * + *
  • A proxy class implements exactly the interfaces specified at its + * creation, in the same order. + * + *
  • If a proxy class implements a non-public interface, then it will + * be defined in the same package as that interface. Otherwise, the + * package of a proxy class is also unspecified. Note that package + * sealing will not prevent a proxy class from being successfully defined + * in a particular package at runtime, and neither will classes already + * defined by the same class loader and the same package with particular + * signers. + * + *
  • Since a proxy class implements all of the interfaces specified at + * its creation, invoking {@code getInterfaces} on its + * {@code Class} object will return an array containing the same + * list of interfaces (in the order specified at its creation), invoking + * {@code getMethods} on its {@code Class} object will return + * an array of {@code Method} objects that include all of the + * methods in those interfaces, and invoking {@code getMethod} will + * find methods in the proxy interfaces as would be expected. + * + *
  • The {@link Proxy#isProxyClass Proxy.isProxyClass} method will + * return true if it is passed a proxy class-- a class returned by + * {@code Proxy.getProxyClass} or the class of an object returned by + * {@code Proxy.newProxyInstance}-- and false otherwise. + * + *
  • The {@code java.security.ProtectionDomain} of a proxy class + * is the same as that of system classes loaded by the bootstrap class + * loader, such as {@code java.lang.Object}, because the code for a + * proxy class is generated by trusted system code. This protection + * domain will typically be granted + * {@code java.security.AllPermission}. + * + *
  • Each proxy class has one public constructor that takes one argument, + * an implementation of the interface {@link InvocationHandler}, to set + * the invocation handler for a proxy instance. Rather than having to use + * the reflection API to access the public constructor, a proxy instance + * can be also be created by calling the {@link Proxy#newProxyInstance + * Proxy.newProxyInstance} method, which combines the actions of calling + * {@link Proxy#getProxyClass Proxy.getProxyClass} with invoking the + * constructor with an invocation handler. + *
+ * + *

A proxy instance has the following properties: + * + *

    + *
  • Given a proxy instance {@code proxy} and one of the + * interfaces implemented by its proxy class {@code Foo}, the + * following expression will return true: + *
    + *     {@code proxy instanceof Foo}
    + * 
    + * and the following cast operation will succeed (rather than throwing + * a {@code ClassCastException}): + *
    + *     {@code (Foo) proxy}
    + * 
    + * + *
  • Each proxy instance has an associated invocation handler, the one + * that was passed to its constructor. The static + * {@link Proxy#getInvocationHandler Proxy.getInvocationHandler} method + * will return the invocation handler associated with the proxy instance + * passed as its argument. + * + *
  • An interface method invocation on a proxy instance will be + * encoded and dispatched to the invocation handler's {@link + * InvocationHandler#invoke invoke} method as described in the + * documentation for that method. + * + *
  • An invocation of the {@code hashCode}, + * {@code equals}, or {@code toString} methods declared in + * {@code java.lang.Object} on a proxy instance will be encoded and + * dispatched to the invocation handler's {@code invoke} method in + * the same manner as interface method invocations are encoded and + * dispatched, as described above. The declaring class of the + * {@code Method} object passed to {@code invoke} will be + * {@code java.lang.Object}. Other public methods of a proxy + * instance inherited from {@code java.lang.Object} are not + * overridden by a proxy class, so invocations of those methods behave + * like they do for instances of {@code java.lang.Object}. + *
+ * + *

Methods Duplicated in Multiple Proxy Interfaces

+ * + *

When two or more interfaces of a proxy class contain a method with + * the same name and parameter signature, the order of the proxy class's + * interfaces becomes significant. When such a duplicate method + * is invoked on a proxy instance, the {@code Method} object passed + * to the invocation handler will not necessarily be the one whose + * declaring class is assignable from the reference type of the interface + * that the proxy's method was invoked through. This limitation exists + * because the corresponding method implementation in the generated proxy + * class cannot determine which interface it was invoked through. + * Therefore, when a duplicate method is invoked on a proxy instance, + * the {@code Method} object for the method in the foremost interface + * that contains the method (either directly or inherited through a + * superinterface) in the proxy class's list of interfaces is passed to + * the invocation handler's {@code invoke} method, regardless of the + * reference type through which the method invocation occurred. + * + *

If a proxy interface contains a method with the same name and + * parameter signature as the {@code hashCode}, {@code equals}, + * or {@code toString} methods of {@code java.lang.Object}, + * when such a method is invoked on a proxy instance, the + * {@code Method} object passed to the invocation handler will have + * {@code java.lang.Object} as its declaring class. In other words, + * the public, non-final methods of {@code java.lang.Object} + * logically precede all of the proxy interfaces for the determination of + * which {@code Method} object to pass to the invocation handler. + * + *

Note also that when a duplicate method is dispatched to an + * invocation handler, the {@code invoke} method may only throw + * checked exception types that are assignable to one of the exception + * types in the {@code throws} clause of the method in all of + * the proxy interfaces that it can be invoked through. If the + * {@code invoke} method throws a checked exception that is not + * assignable to any of the exception types declared by the method in one + * of the proxy interfaces that it can be invoked through, then an + * unchecked {@code UndeclaredThrowableException} will be thrown by + * the invocation on the proxy instance. This restriction means that not + * all of the exception types returned by invoking + * {@code getExceptionTypes} on the {@code Method} object + * passed to the {@code invoke} method can necessarily be thrown + * successfully by the {@code invoke} method. + * + * @author Peter Jones + * @see InvocationHandler + * @since 1.3 + */ +public final class ProxyImpl implements java.io.Serializable { + + private static final long serialVersionUID = -2222568056686623797L; + + /** prefix for all proxy class names */ + private final static String proxyClassNamePrefix = "$Proxy"; + + /** parameter types of a proxy class constructor */ + private final static Class[] constructorParams = + { InvocationHandler.class }; + + /** maps a class loader to the proxy class cache for that loader */ + private static Map, Object>> loaderToCache + = new WeakHashMap<>(); + + /** marks that a particular proxy class is currently being generated */ + private static Object pendingGenerationMarker = new Object(); + + /** next number to use for generation of unique proxy class names */ + private static long nextUniqueNumber = 0; + private static Object nextUniqueNumberLock = new Object(); + + /** set of all generated proxy classes, for isProxyClass implementation */ + private static Map, Void> proxyClasses = + Collections.synchronizedMap(new WeakHashMap, Void>()); + + /** + * the invocation handler for this proxy instance. + * @serial + */ + protected InvocationHandler h; + + /** + * Prohibits instantiation. + */ + private ProxyImpl() { + } + + /** + * Constructs a new {@code Proxy} instance from a subclass + * (typically, a dynamic proxy class) with the specified value + * for its invocation handler. + * + * @param h the invocation handler for this proxy instance + */ + protected ProxyImpl(InvocationHandler h) { + this.h = h; + } + + /** + * Returns the {@code java.lang.Class} object for a proxy class + * given a class loader and an array of interfaces. The proxy class + * will be defined by the specified class loader and will implement + * all of the supplied interfaces. If a proxy class for the same + * permutation of interfaces has already been defined by the class + * loader, then the existing proxy class will be returned; otherwise, + * a proxy class for those interfaces will be generated dynamically + * and defined by the class loader. + * + *

There are several restrictions on the parameters that may be + * passed to {@code Proxy.getProxyClass}: + * + *

    + *
  • All of the {@code Class} objects in the + * {@code interfaces} array must represent interfaces, not + * classes or primitive types. + * + *
  • No two elements in the {@code interfaces} array may + * refer to identical {@code Class} objects. + * + *
  • All of the interface types must be visible by name through the + * specified class loader. In other words, for class loader + * {@code cl} and every interface {@code i}, the following + * expression must be true: + *
    +     *     Class.forName(i.getName(), false, cl) == i
    +     * 
    + * + *
  • All non-public interfaces must be in the same package; + * otherwise, it would not be possible for the proxy class to + * implement all of the interfaces, regardless of what package it is + * defined in. + * + *
  • For any set of member methods of the specified interfaces + * that have the same signature: + *
      + *
    • If the return type of any of the methods is a primitive + * type or void, then all of the methods must have that same + * return type. + *
    • Otherwise, one of the methods must have a return type that + * is assignable to all of the return types of the rest of the + * methods. + *
    + * + *
  • The resulting proxy class must not exceed any limits imposed + * on classes by the virtual machine. For example, the VM may limit + * the number of interfaces that a class may implement to 65535; in + * that case, the size of the {@code interfaces} array must not + * exceed 65535. + *
+ * + *

If any of these restrictions are violated, + * {@code Proxy.getProxyClass} will throw an + * {@code IllegalArgumentException}. If the {@code interfaces} + * array argument or any of its elements are {@code null}, a + * {@code NullPointerException} will be thrown. + * + *

Note that the order of the specified proxy interfaces is + * significant: two requests for a proxy class with the same combination + * of interfaces but in a different order will result in two distinct + * proxy classes. + * + * @param loader the class loader to define the proxy class + * @param interfaces the list of interfaces for the proxy class + * to implement + * @return a proxy class that is defined in the specified class loader + * and that implements the specified interfaces + * @throws IllegalArgumentException if any of the restrictions on the + * parameters that may be passed to {@code getProxyClass} + * are violated + * @throws NullPointerException if the {@code interfaces} array + * argument or any of its elements are {@code null} + */ + public static Class getProxyClass(ClassLoader loader, + Class... interfaces) + throws IllegalArgumentException + { + if (interfaces.length > 65535) { + throw new IllegalArgumentException("interface limit exceeded"); + } + + Class proxyClass = null; + + /* collect interface names to use as key for proxy class cache */ + String[] interfaceNames = new String[interfaces.length]; + + // for detecting duplicates + Set> interfaceSet = new HashSet<>(); + + for (int i = 0; i < interfaces.length; i++) { + /* + * Verify that the class loader resolves the name of this + * interface to the same Class object. + */ + String interfaceName = interfaces[i].getName(); + Class interfaceClass = null; + try { + interfaceClass = Class.forName(interfaceName, false, loader); + } catch (ClassNotFoundException e) { + } + if (interfaceClass != interfaces[i]) { + throw new IllegalArgumentException( + interfaces[i] + " is not visible from class loader"); + } + + /* + * Verify that the Class object actually represents an + * interface. + */ + if (!interfaceClass.isInterface()) { + throw new IllegalArgumentException( + interfaceClass.getName() + " is not an interface"); + } + + /* + * Verify that this interface is not a duplicate. + */ + if (interfaceSet.contains(interfaceClass)) { + throw new IllegalArgumentException( + "repeated interface: " + interfaceClass.getName()); + } + interfaceSet.add(interfaceClass); + + interfaceNames[i] = interfaceName; + } + + /* + * Using string representations of the proxy interfaces as + * keys in the proxy class cache (instead of their Class + * objects) is sufficient because we require the proxy + * interfaces to be resolvable by name through the supplied + * class loader, and it has the advantage that using a string + * representation of a class makes for an implicit weak + * reference to the class. + */ + List key = Arrays.asList(interfaceNames); + + /* + * Find or create the proxy class cache for the class loader. + */ + Map, Object> cache; + synchronized (loaderToCache) { + cache = loaderToCache.get(loader); + if (cache == null) { + cache = new HashMap<>(); + loaderToCache.put(loader, cache); + } + /* + * This mapping will remain valid for the duration of this + * method, without further synchronization, because the mapping + * will only be removed if the class loader becomes unreachable. + */ + } + + /* + * Look up the list of interfaces in the proxy class cache using + * the key. This lookup will result in one of three possible + * kinds of values: + * null, if there is currently no proxy class for the list of + * interfaces in the class loader, + * the pendingGenerationMarker object, if a proxy class for the + * list of interfaces is currently being generated, + * or a weak reference to a Class object, if a proxy class for + * the list of interfaces has already been generated. + */ + synchronized (cache) { + /* + * Note that we need not worry about reaping the cache for + * entries with cleared weak references because if a proxy class + * has been garbage collected, its class loader will have been + * garbage collected as well, so the entire cache will be reaped + * from the loaderToCache map. + */ + do { + Object value = cache.get(key); + if (value instanceof Reference) { + proxyClass = (Class) ((Reference) value).get(); + } + if (proxyClass != null) { + // proxy class already generated: return it + return proxyClass; + } else if (value == pendingGenerationMarker) { + // proxy class being generated: wait for it + try { + cache.wait(); + } catch (InterruptedException e) { + /* + * The class generation that we are waiting for should + * take a small, bounded time, so we can safely ignore + * thread interrupts here. + */ + } + continue; + } else { + /* + * No proxy class for this list of interfaces has been + * generated or is being generated, so we will go and + * generate it now. Mark it as pending generation. + */ + cache.put(key, pendingGenerationMarker); + break; + } + } while (true); + } + + try { + String proxyPkg = null; // package to define proxy class in + + /* + * Record the package of a non-public proxy interface so that the + * proxy class will be defined in the same package. Verify that + * all non-public proxy interfaces are in the same package. + */ + for (int i = 0; i < interfaces.length; i++) { + int flags = interfaces[i].getModifiers(); + if (!Modifier.isPublic(flags)) { + String name = interfaces[i].getName(); + int n = name.lastIndexOf('.'); + String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); + if (proxyPkg == null) { + proxyPkg = pkg; + } else if (!pkg.equals(proxyPkg)) { + throw new IllegalArgumentException( + "non-public interfaces from different packages"); + } + } + } + + if (proxyPkg == null) { // if no non-public proxy interfaces, + proxyPkg = ""; // use the unnamed package + } + + { + /* + * Choose a name for the proxy class to generate. + */ + long num; + synchronized (nextUniqueNumberLock) { + num = nextUniqueNumber++; + } + String proxyName = proxyPkg + proxyClassNamePrefix + num; + /* + * Verify that the class loader hasn't already + * defined a class with the chosen name. + */ + + /* + * Generate the specified proxy class. + */ + Generator gen = new Generator(proxyName, interfaces); + final byte[] proxyClassFile = gen.generateClassFile(); + try { + proxyClass = defineClass0(loader, proxyName, + proxyClassFile); + } catch (ClassFormatError e) { + /* + * A ClassFormatError here means that (barring bugs in the + * proxy class generation code) there was some other + * invalid aspect of the arguments supplied to the proxy + * class creation (such as virtual machine limitations + * exceeded). + */ + throw new IllegalArgumentException(e.toString()); + } + gen.fillInMethods(proxyClass); + } + // add to set of all generated proxy classes, for isProxyClass + proxyClasses.put(proxyClass, null); + + } finally { + /* + * We must clean up the "pending generation" state of the proxy + * class cache entry somehow. If a proxy class was successfully + * generated, store it in the cache (with a weak reference); + * otherwise, remove the reserved entry. In all cases, notify + * all waiters on reserved entries in this cache. + */ + synchronized (cache) { + if (proxyClass != null) { + cache.put(key, new WeakReference>(proxyClass)); + } else { + cache.remove(key); + } + cache.notifyAll(); + } + } + return proxyClass; + } + + /** + * Returns an instance of a proxy class for the specified interfaces + * that dispatches method invocations to the specified invocation + * handler. This method is equivalent to: + *

+     *     Proxy.getProxyClass(loader, interfaces).
+     *         getConstructor(new Class[] { InvocationHandler.class }).
+     *         newInstance(new Object[] { handler });
+     * 
+ * + *

{@code Proxy.newProxyInstance} throws + * {@code IllegalArgumentException} for the same reasons that + * {@code Proxy.getProxyClass} does. + * + * @param loader the class loader to define the proxy class + * @param interfaces the list of interfaces for the proxy class + * to implement + * @param h the invocation handler to dispatch method invocations to + * @return a proxy instance with the specified invocation handler of a + * proxy class that is defined by the specified class loader + * and that implements the specified interfaces + * @throws IllegalArgumentException if any of the restrictions on the + * parameters that may be passed to {@code getProxyClass} + * are violated + * @throws NullPointerException if the {@code interfaces} array + * argument or any of its elements are {@code null}, or + * if the invocation handler, {@code h}, is + * {@code null} + */ + public static Object newProxyInstance(ClassLoader loader, + Class[] interfaces, + InvocationHandler h) + throws IllegalArgumentException + { + if (h == null) { + throw new NullPointerException(); + } + + /* + * Look up or generate the designated proxy class. + */ + Class cl = getProxyClass(loader, interfaces); + + /* + * Invoke its constructor with the designated invocation handler. + */ + try { + Constructor cons = cl.getConstructor(constructorParams); + return cons.newInstance(new Object[] { h }); + } catch (NoSuchMethodException e) { + throw new InternalError(e.toString()); + } catch (IllegalAccessException e) { + throw new InternalError(e.toString()); + } catch (InstantiationException e) { + throw new InternalError(e.toString()); + } catch (InvocationTargetException e) { + throw new InternalError(e.toString()); + } + } + + /** + * Returns true if and only if the specified class was dynamically + * generated to be a proxy class using the {@code getProxyClass} + * method or the {@code newProxyInstance} method. + * + *

The reliability of this method is important for the ability + * to use it to make security decisions, so its implementation should + * not just test if the class in question extends {@code Proxy}. + * + * @param cl the class to test + * @return {@code true} if the class is a proxy class and + * {@code false} otherwise + * @throws NullPointerException if {@code cl} is {@code null} + */ + public static boolean isProxyClass(Class cl) { + if (cl == null) { + throw new NullPointerException(); + } + + return proxyClasses.containsKey(cl); + } + + /** + * Returns the invocation handler for the specified proxy instance. + * + * @param proxy the proxy instance to return the invocation handler for + * @return the invocation handler for the proxy instance + * @throws IllegalArgumentException if the argument is not a + * proxy instance + */ + public static InvocationHandler getInvocationHandler(Object proxy) + throws IllegalArgumentException + { + /* + * Verify that the object is actually a proxy instance. + */ + if (!isProxyClass(proxy.getClass())) { + throw new IllegalArgumentException("not a proxy instance"); + } + + ProxyImpl p = (ProxyImpl) proxy; + return p.h; + } + + @JavaScriptBody(args = { "ignore", "name", "byteCode" }, + body = "return vm._reload(name, byteCode).constructor.$class;" + ) + private static native Class defineClass0( + ClassLoader loader, String name, byte[] b + ); + + private static class Generator { + /* + * In the comments below, "JVMS" refers to The Java Virtual Machine + * Specification Second Edition and "JLS" refers to the original + * version of The Java Language Specification, unless otherwise + * specified. + */ + + /* need 1.6 bytecode */ + private static final int CLASSFILE_MAJOR_VERSION = 50; + private static final int CLASSFILE_MINOR_VERSION = 0; + + /* + * beginning of constants copied from + * sun.tools.java.RuntimeConstants (which no longer exists): + */ + + /* constant pool tags */ + private static final int CONSTANT_UTF8 = 1; + private static final int CONSTANT_UNICODE = 2; + private static final int CONSTANT_INTEGER = 3; + private static final int CONSTANT_FLOAT = 4; + private static final int CONSTANT_LONG = 5; + private static final int CONSTANT_DOUBLE = 6; + private static final int CONSTANT_CLASS = 7; + private static final int CONSTANT_STRING = 8; + private static final int CONSTANT_FIELD = 9; + private static final int CONSTANT_METHOD = 10; + private static final int CONSTANT_INTERFACEMETHOD = 11; + private static final int CONSTANT_NAMEANDTYPE = 12; + + /* access and modifier flags */ + private static final int ACC_PUBLIC = 0x00000001; + private static final int ACC_FINAL = 0x00000010; + private static final int ACC_SUPER = 0x00000020; + + // end of constants copied from sun.tools.java.RuntimeConstants + /** + * name of the superclass of proxy classes + */ + private final static String superclassName = "java/lang/reflect/Proxy"; + + /** + * name of field for storing a proxy instance's invocation handler + */ + private final static String handlerFieldName = "h"; + + /* preloaded Method objects for methods in java.lang.Object */ + private static Method hashCodeMethod; + private static Method equalsMethod; + private static Method toStringMethod; + + static { + try { + hashCodeMethod = Object.class.getMethod("hashCode"); + equalsMethod + = Object.class.getMethod("equals", new Class[]{Object.class}); + toStringMethod = Object.class.getMethod("toString"); + } catch (NoSuchMethodException e) { + throw new IllegalStateException(e.getMessage()); + } + } + + /** + * name of proxy class + */ + private String className; + + /** + * proxy interfaces + */ + private Class[] interfaces; + + /** + * constant pool of class being generated + */ + private ConstantPool cp = new ConstantPool(); + + /** + * maps method signature string to list of ProxyMethod objects for proxy + * methods with that signature + */ + private Map> proxyMethods + = new HashMap>(); + + /** + * count of ProxyMethod objects added to proxyMethods + */ + private int proxyMethodCount = 0; + + /** + * Construct a ProxyGenerator to generate a proxy class with the + * specified name and for the given interfaces. + * + * A ProxyGenerator object contains the state for the ongoing generation + * of a particular proxy class. + */ + private Generator(String className, Class[] interfaces) { + this.className = className; + this.interfaces = interfaces; + } + + /** + * Generate a class file for the proxy class. This method drives the + * class file generation process. + */ + private byte[] generateClassFile() { + + /* ============================================================ + * Step 1: Assemble ProxyMethod objects for all methods to + * generate proxy dispatching code for. + */ + + /* + * Record that proxy methods are needed for the hashCode, equals, + * and toString methods of java.lang.Object. This is done before + * the methods from the proxy interfaces so that the methods from + * java.lang.Object take precedence over duplicate methods in the + * proxy interfaces. + */ + addProxyMethod(hashCodeMethod, Object.class); + addProxyMethod(equalsMethod, Object.class); + addProxyMethod(toStringMethod, Object.class); + + /* + * Now record all of the methods from the proxy interfaces, giving + * earlier interfaces precedence over later ones with duplicate + * methods. + */ + for (int i = 0; i < interfaces.length; i++) { + Method[] methods = interfaces[i].getMethods(); + for (int j = 0; j < methods.length; j++) { + addProxyMethod(methods[j], interfaces[i]); + } + } + + /* + * For each set of proxy methods with the same signature, + * verify that the methods' return types are compatible. + */ + for (List sigmethods : proxyMethods.values()) { + checkReturnTypes(sigmethods); + } + + /* ============================================================ + * Step 2: Assemble FieldInfo and MethodInfo structs for all of + * fields and methods in the class we are generating. + */ + + // will be done in fillInMethods + + /* ============================================================ + * Step 3: Write the final class file. + */ + + /* + * Make sure that constant pool indexes are reserved for the + * following items before starting to write the final class file. + */ + cp.getClass(dotToSlash(className)); + cp.getClass(superclassName); + for (int i = 0; i < interfaces.length; i++) { + cp.getClass(dotToSlash(interfaces[i].getName())); + } + + /* + * Disallow new constant pool additions beyond this point, since + * we are about to write the final constant pool table. + */ + cp.setReadOnly(); + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + DataOutputStream dout = new DataOutputStream(bout); + + try { + /* + * Write all the items of the "ClassFile" structure. + * See JVMS section 4.1. + */ + // u4 magic; + dout.writeInt(0xCAFEBABE); + // u2 minor_version; + dout.writeShort(CLASSFILE_MINOR_VERSION); + // u2 major_version; + dout.writeShort(CLASSFILE_MAJOR_VERSION); + + cp.write(dout); // (write constant pool) + + // u2 access_flags; + dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER); + // u2 this_class; + dout.writeShort(cp.getClass(dotToSlash(className))); + // u2 super_class; + dout.writeShort(cp.getClass(superclassName)); + + // u2 interfaces_count; + dout.writeShort(interfaces.length); + // u2 interfaces[interfaces_count]; + for (int i = 0; i < interfaces.length; i++) { + dout.writeShort(cp.getClass( + dotToSlash(interfaces[i].getName()))); + } + + // u2 fields_count; + dout.writeShort(0); + + // u2 methods_count; + dout.writeShort(0); + + // u2 attributes_count; + dout.writeShort(0); // (no ClassFile attributes for proxy classes) + + } catch (IOException e) { + throw new InternalError("unexpected I/O Exception"); + } + + return bout.toByteArray(); + } + + @JavaScriptBody(args = { "c", "sig", "method", "primitive" }, body = + "var p = c.cnstr.prototype;\n" + + "p[sig] = function() {\n" + + " var h = this._h();\n" + + " var res = h.invoke__Ljava_lang_Object_2Ljava_lang_Object_2Ljava_lang_reflect_Method_2_3Ljava_lang_Object_2(this, method, arguments);\n" + + " \n" + + " \n" + + " return res;\n" + + "};" + ) + private static native void defineMethod(Class proxyClass, String sig, Method method, boolean primitive); + + @JavaScriptBody(args = "c", body = + "var h = c.cnstr.cons__VLjava_lang_reflect_InvocationHandler_2 = function(h) {\n" + + " c.superclass.cnstr.cons__VLjava_lang_reflect_InvocationHandler_2.call(this, h);\n" + + "}\n" + + "h.cls = c.cnstr;\n" + ) + private static native void defineConstructor(Class proxyClass); + + final void fillInMethods(Class proxyClass) { + for (List sigmethods : proxyMethods.values()) { + for (ProxyMethod pm : sigmethods) { + String sig = MethodImpl.toSignature(pm.method); + defineMethod(proxyClass, sig, pm.method, pm.method.getReturnType().isPrimitive()); + } + } + defineConstructor(proxyClass); + } + + /** + * Add another method to be proxied, either by creating a new + * ProxyMethod object or augmenting an old one for a duplicate method. + * + * "fromClass" indicates the proxy interface that the method was found + * through, which may be different from (a subinterface of) the method's + * "declaring class". Note that the first Method object passed for a + * given name and descriptor identifies the Method object (and thus the + * declaring class) that will be passed to the invocation handler's + * "invoke" method for a given set of duplicate methods. + */ + private void addProxyMethod(Method m, Class fromClass) { + String name = m.getName(); + Class[] parameterTypes = m.getParameterTypes(); + Class returnType = m.getReturnType(); + Class[] exceptionTypes = m.getExceptionTypes(); + + String sig = MethodImpl.toSignature(m); + List sigmethods = proxyMethods.get(sig); + if (sigmethods != null) { + for (ProxyMethod pm : sigmethods) { + if (returnType == pm.returnType) { + /* + * Found a match: reduce exception types to the + * greatest set of exceptions that can thrown + * compatibly with the throws clauses of both + * overridden methods. + */ + List> legalExceptions = new ArrayList>(); + collectCompatibleTypes( + exceptionTypes, pm.exceptionTypes, legalExceptions); + collectCompatibleTypes( + pm.exceptionTypes, exceptionTypes, legalExceptions); + pm.exceptionTypes = new Class[legalExceptions.size()]; + pm.exceptionTypes + = legalExceptions.toArray(pm.exceptionTypes); + return; + } + } + } else { + sigmethods = new ArrayList(3); + proxyMethods.put(sig, sigmethods); + } + sigmethods.add(new ProxyMethod(m, name, parameterTypes, returnType, + exceptionTypes, fromClass)); + } + + /** + * For a given set of proxy methods with the same signature, check that + * their return types are compatible according to the Proxy + * specification. + * + * Specifically, if there is more than one such method, then all of the + * return types must be reference types, and there must be one return + * type that is assignable to each of the rest of them. + */ + private static void checkReturnTypes(List methods) { + /* + * If there is only one method with a given signature, there + * cannot be a conflict. This is the only case in which a + * primitive (or void) return type is allowed. + */ + if (methods.size() < 2) { + return; + } + + /* + * List of return types that are not yet known to be + * assignable from ("covered" by) any of the others. + */ + LinkedList> uncoveredReturnTypes = new LinkedList>(); + + nextNewReturnType: + for (ProxyMethod pm : methods) { + Class newReturnType = pm.returnType; + if (newReturnType.isPrimitive()) { + throw new IllegalArgumentException( + "methods with same signature " + + getFriendlyMethodSignature(pm.methodName, + pm.parameterTypes) + + " but incompatible return types: " + + newReturnType.getName() + " and others"); + } + boolean added = false; + + /* + * Compare the new return type to the existing uncovered + * return types. + */ + ListIterator> liter = uncoveredReturnTypes.listIterator(); + while (liter.hasNext()) { + Class uncoveredReturnType = liter.next(); + + /* + * If an existing uncovered return type is assignable + * to this new one, then we can forget the new one. + */ + if (newReturnType.isAssignableFrom(uncoveredReturnType)) { + assert !added; + continue nextNewReturnType; + } + + /* + * If the new return type is assignable to an existing + * uncovered one, then should replace the existing one + * with the new one (or just forget the existing one, + * if the new one has already be put in the list). + */ + if (uncoveredReturnType.isAssignableFrom(newReturnType)) { + // (we can assume that each return type is unique) + if (!added) { + liter.set(newReturnType); + added = true; + } else { + liter.remove(); + } + } + } + + /* + * If we got through the list of existing uncovered return + * types without an assignability relationship, then add + * the new return type to the list of uncovered ones. + */ + if (!added) { + uncoveredReturnTypes.add(newReturnType); + } + } + + /* + * We shouldn't end up with more than one return type that is + * not assignable from any of the others. + */ + if (uncoveredReturnTypes.size() > 1) { + ProxyMethod pm = methods.get(0); + throw new IllegalArgumentException( + "methods with same signature " + + getFriendlyMethodSignature(pm.methodName, pm.parameterTypes) + + " but incompatible return types: " + uncoveredReturnTypes); + } + } + + + /** + * A ProxyMethod object represents a proxy method in the proxy class + * being generated: a method whose implementation will encode and + * dispatch invocations to the proxy instance's invocation handler. + */ + private class ProxyMethod { + + private final Method method; + public String methodName; + public Class[] parameterTypes; + public Class returnType; + public Class[] exceptionTypes; + public Class fromClass; + public String methodFieldName; + + private ProxyMethod(Method m, + String methodName, Class[] parameterTypes, + Class returnType, Class[] exceptionTypes, + Class fromClass + ) { + this.method = m; + this.methodName = methodName; + this.parameterTypes = parameterTypes; + this.returnType = returnType; + this.exceptionTypes = exceptionTypes; + this.fromClass = fromClass; + this.methodFieldName = "m" + proxyMethodCount++; + } + + } + + /* + * ==================== General Utility Methods ==================== + */ + /** + * Convert a fully qualified class name that uses '.' as the package + * separator, the external representation used by the Java language and + * APIs, to a fully qualified class name that uses '/' as the package + * separator, the representation used in the class file format (see JVMS + * section 4.2). + */ + private static String dotToSlash(String name) { + return name.replace('.', '/'); + } + + /** + * Return the list of "parameter descriptor" strings enclosed in + * parentheses corresponding to the given parameter types (in other + * words, a method descriptor without a return descriptor). This string + * is useful for constructing string keys for methods without regard to + * their return type. + */ + private static String getParameterDescriptors(Class[] parameterTypes) { + StringBuilder desc = new StringBuilder("("); + for (int i = 0; i < parameterTypes.length; i++) { + desc.append(getFieldType(parameterTypes[i])); + } + desc.append(')'); + return desc.toString(); + } + + /** + * Return the "field type" string for the given type, appropriate for a + * field descriptor, a parameter descriptor, or a return descriptor + * other than "void". See JVMS section 4.3.2. + */ + private static String getFieldType(Class type) { + if (type.isPrimitive()) { + return PrimitiveTypeInfo.get(type).baseTypeString; + } else if (type.isArray()) { + /* + * According to JLS 20.3.2, the getName() method on Class does + * return the VM type descriptor format for array classes (only); + * using that should be quicker than the otherwise obvious code: + * + * return "[" + getTypeDescriptor(type.getComponentType()); + */ + return type.getName().replace('.', '/'); + } else { + return "L" + dotToSlash(type.getName()) + ";"; + } + } + + /** + * Returns a human-readable string representing the signature of a + * method with the given name and parameter types. + */ + private static String getFriendlyMethodSignature(String name, + Class[] parameterTypes) { + StringBuilder sig = new StringBuilder(name); + sig.append('('); + for (int i = 0; i < parameterTypes.length; i++) { + if (i > 0) { + sig.append(','); + } + Class parameterType = parameterTypes[i]; + int dimensions = 0; + while (parameterType.isArray()) { + parameterType = parameterType.getComponentType(); + dimensions++; + } + sig.append(parameterType.getName()); + while (dimensions-- > 0) { + sig.append("[]"); + } + } + sig.append(')'); + return sig.toString(); + } + + /** + * Add to the given list all of the types in the "from" array that are + * not already contained in the list and are assignable to at least one + * of the types in the "with" array. + * + * This method is useful for computing the greatest common set of + * declared exceptions from duplicate methods inherited from different + * interfaces. + */ + private static void collectCompatibleTypes(Class[] from, + Class[] with, + List> list) { + for (int i = 0; i < from.length; i++) { + if (!list.contains(from[i])) { + for (int j = 0; j < with.length; j++) { + if (with[j].isAssignableFrom(from[i])) { + list.add(from[i]); + break; + } + } + } + } + } + + + /** + * A PrimitiveTypeInfo object contains assorted information about a + * primitive type in its public fields. The struct for a particular + * primitive type can be obtained using the static "get" method. + */ + private static class PrimitiveTypeInfo { + + /** + * "base type" used in various descriptors (see JVMS section 4.3.2) + */ + public String baseTypeString; + + /** + * name of corresponding wrapper class + */ + public String wrapperClassName; + + /** + * method descriptor for wrapper class "valueOf" factory method + */ + public String wrapperValueOfDesc; + + /** + * name of wrapper class method for retrieving primitive value + */ + public String unwrapMethodName; + + /** + * descriptor of same method + */ + public String unwrapMethodDesc; + + private static Map table + = new HashMap(); + + static { + add(byte.class, Byte.class); + add(char.class, Character.class); + add(double.class, Double.class); + add(float.class, Float.class); + add(int.class, Integer.class); + add(long.class, Long.class); + add(short.class, Short.class); + add(boolean.class, Boolean.class); + } + + private static void add(Class primitiveClass, Class wrapperClass) { + table.put(primitiveClass, + new PrimitiveTypeInfo(primitiveClass, wrapperClass)); + } + + private PrimitiveTypeInfo(Class primitiveClass, Class wrapperClass) { + assert primitiveClass.isPrimitive(); + + baseTypeString + = Array.newInstance(primitiveClass, 0) + .getClass().getName().substring(1); + wrapperClassName = dotToSlash(wrapperClass.getName()); + wrapperValueOfDesc + = "(" + baseTypeString + ")L" + wrapperClassName + ";"; + unwrapMethodName = primitiveClass.getName() + "Value"; + unwrapMethodDesc = "()" + baseTypeString; + } + + public static PrimitiveTypeInfo get(Class cl) { + return table.get(cl); + } + } + + /** + * A ConstantPool object represents the constant pool of a class file + * being generated. This representation of a constant pool is designed + * specifically for use by ProxyGenerator; in particular, it assumes + * that constant pool entries will not need to be resorted (for example, + * by their type, as the Java compiler does), so that the final index + * value can be assigned and used when an entry is first created. + * + * Note that new entries cannot be created after the constant pool has + * been written to a class file. To prevent such logic errors, a + * ConstantPool instance can be marked "read only", so that further + * attempts to add new entries will fail with a runtime exception. + * + * See JVMS section 4.4 for more information about the constant pool of + * a class file. + */ + private static class ConstantPool { + + /** + * list of constant pool entries, in constant pool index order. + * + * This list is used when writing the constant pool to a stream and + * for assigning the next index value. Note that element 0 of this + * list corresponds to constant pool index 1. + */ + private List pool = new ArrayList(32); + + /** + * maps constant pool data of all types to constant pool indexes. + * + * This map is used to look up the index of an existing entry for + * values of all types. + */ + private Map map = new HashMap(16); + + /** + * true if no new constant pool entries may be added + */ + private boolean readOnly = false; + + /** + * Get or assign the index for a CONSTANT_Utf8 entry. + */ + public short getUtf8(String s) { + if (s == null) { + throw new NullPointerException(); + } + return getValue(s); + } + + /** + * Get or assign the index for a CONSTANT_Integer entry. + */ + public short getInteger(int i) { + return getValue(new Integer(i)); + } + + /** + * Get or assign the index for a CONSTANT_Float entry. + */ + public short getFloat(float f) { + return getValue(new Float(f)); + } + + /** + * Get or assign the index for a CONSTANT_Class entry. + */ + public short getClass(String name) { + short utf8Index = getUtf8(name); + return getIndirect(new IndirectEntry( + CONSTANT_CLASS, utf8Index)); + } + + /** + * Get or assign the index for a CONSTANT_String entry. + */ + public short getString(String s) { + short utf8Index = getUtf8(s); + return getIndirect(new IndirectEntry( + CONSTANT_STRING, utf8Index)); + } + + /** + * Get or assign the index for a CONSTANT_FieldRef entry. + */ + public short getFieldRef(String className, + String name, String descriptor) { + short classIndex = getClass(className); + short nameAndTypeIndex = getNameAndType(name, descriptor); + return getIndirect(new IndirectEntry( + CONSTANT_FIELD, classIndex, nameAndTypeIndex)); + } + + /** + * Get or assign the index for a CONSTANT_MethodRef entry. + */ + public short getMethodRef(String className, + String name, String descriptor) { + short classIndex = getClass(className); + short nameAndTypeIndex = getNameAndType(name, descriptor); + return getIndirect(new IndirectEntry( + CONSTANT_METHOD, classIndex, nameAndTypeIndex)); + } + + /** + * Get or assign the index for a CONSTANT_InterfaceMethodRef entry. + */ + public short getInterfaceMethodRef(String className, String name, + String descriptor) { + short classIndex = getClass(className); + short nameAndTypeIndex = getNameAndType(name, descriptor); + return getIndirect(new IndirectEntry( + CONSTANT_INTERFACEMETHOD, classIndex, nameAndTypeIndex)); + } + + /** + * Get or assign the index for a CONSTANT_NameAndType entry. + */ + public short getNameAndType(String name, String descriptor) { + short nameIndex = getUtf8(name); + short descriptorIndex = getUtf8(descriptor); + return getIndirect(new IndirectEntry( + CONSTANT_NAMEANDTYPE, nameIndex, descriptorIndex)); + } + + /** + * Set this ConstantPool instance to be "read only". + * + * After this method has been called, further requests to get an + * index for a non-existent entry will cause an InternalError to be + * thrown instead of creating of the entry. + */ + public void setReadOnly() { + readOnly = true; + } + + /** + * Write this constant pool to a stream as part of the class file + * format. + * + * This consists of writing the "constant_pool_count" and + * "constant_pool[]" items of the "ClassFile" structure, as + * described in JVMS section 4.1. + */ + public void write(OutputStream out) throws IOException { + DataOutputStream dataOut = new DataOutputStream(out); + + // constant_pool_count: number of entries plus one + dataOut.writeShort(pool.size() + 1); + + for (Entry e : pool) { + e.write(dataOut); + } + } + + /** + * Add a new constant pool entry and return its index. + */ + private short addEntry(Entry entry) { + pool.add(entry); + /* + * Note that this way of determining the index of the + * added entry is wrong if this pool supports + * CONSTANT_Long or CONSTANT_Double entries. + */ + if (pool.size() >= 65535) { + throw new IllegalArgumentException( + "constant pool size limit exceeded"); + } + return (short) pool.size(); + } + + /** + * Get or assign the index for an entry of a type that contains a + * direct value. The type of the given object determines the type of + * the desired entry as follows: + * + * java.lang.String CONSTANT_Utf8 java.lang.Integer CONSTANT_Integer + * java.lang.Float CONSTANT_Float java.lang.Long CONSTANT_Long + * java.lang.Double CONSTANT_DOUBLE + */ + private short getValue(Object key) { + Short index = map.get(key); + if (index != null) { + return index.shortValue(); + } else { + if (readOnly) { + throw new InternalError( + "late constant pool addition: " + key); + } + short i = addEntry(new ValueEntry(key)); + map.put(key, new Short(i)); + return i; + } + } + + /** + * Get or assign the index for an entry of a type that contains + * references to other constant pool entries. + */ + private short getIndirect(IndirectEntry e) { + Short index = map.get(e); + if (index != null) { + return index.shortValue(); + } else { + if (readOnly) { + throw new InternalError("late constant pool addition"); + } + short i = addEntry(e); + map.put(e, new Short(i)); + return i; + } + } + + /** + * Entry is the abstact superclass of all constant pool entry types + * that can be stored in the "pool" list; its purpose is to define a + * common method for writing constant pool entries to a class file. + */ + private static abstract class Entry { + + public abstract void write(DataOutputStream out) + throws IOException; + } + + /** + * ValueEntry represents a constant pool entry of a type that + * contains a direct value (see the comments for the "getValue" + * method for a list of such types). + * + * ValueEntry objects are not used as keys for their entries in the + * Map "map", so no useful hashCode or equals methods are defined. + */ + private static class ValueEntry extends Entry { + + private Object value; + + public ValueEntry(Object value) { + this.value = value; + } + + public void write(DataOutputStream out) throws IOException { + if (value instanceof String) { + out.writeByte(CONSTANT_UTF8); + out.writeUTF((String) value); + } else if (value instanceof Integer) { + out.writeByte(CONSTANT_INTEGER); + out.writeInt(((Integer) value).intValue()); + } else if (value instanceof Float) { + out.writeByte(CONSTANT_FLOAT); + out.writeFloat(((Float) value).floatValue()); + } else if (value instanceof Long) { + out.writeByte(CONSTANT_LONG); + out.writeLong(((Long) value).longValue()); + } else if (value instanceof Double) { + out.writeDouble(CONSTANT_DOUBLE); + out.writeDouble(((Double) value).doubleValue()); + } else { + throw new InternalError("bogus value entry: " + value); + } + } + } + + /** + * IndirectEntry represents a constant pool entry of a type that + * references other constant pool entries, i.e., the following + * types: + * + * CONSTANT_Class, CONSTANT_String, CONSTANT_Fieldref, + * CONSTANT_Methodref, CONSTANT_InterfaceMethodref, and + * CONSTANT_NameAndType. + * + * Each of these entry types contains either one or two indexes of + * other constant pool entries. + * + * IndirectEntry objects are used as the keys for their entries in + * the Map "map", so the hashCode and equals methods are overridden + * to allow matching. + */ + private static class IndirectEntry extends Entry { + + private int tag; + private short index0; + private short index1; + + /** + * Construct an IndirectEntry for a constant pool entry type + * that contains one index of another entry. + */ + public IndirectEntry(int tag, short index) { + this.tag = tag; + this.index0 = index; + this.index1 = 0; + } + + /** + * Construct an IndirectEntry for a constant pool entry type + * that contains two indexes for other entries. + */ + public IndirectEntry(int tag, short index0, short index1) { + this.tag = tag; + this.index0 = index0; + this.index1 = index1; + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeShort(index0); + /* + * If this entry type contains two indexes, write + * out the second, too. + */ + if (tag == CONSTANT_FIELD + || tag == CONSTANT_METHOD + || tag == CONSTANT_INTERFACEMETHOD + || tag == CONSTANT_NAMEANDTYPE) { + out.writeShort(index1); + } + } + + public int hashCode() { + return tag + index0 + index1; + } + + public boolean equals(Object obj) { + if (obj instanceof IndirectEntry) { + IndirectEntry other = (IndirectEntry) obj; + if (tag == other.tag + && index0 == other.index0 && index1 == other.index1) { + return true; + } + } + return false; + } + } + } + } + +} diff -r e995e8d39240 -r ba912ef24b27 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 Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/compact/tck/ReaderTest.java Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/AtomicTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/AtomicTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,54 @@ +/** + * 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.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class AtomicTest { + @Compare public boolean atomicBoolean() { + AtomicBoolean ab = new AtomicBoolean(); + ab.set(true); + return ab.compareAndSet(true, false); + } + + @Compare public int atomicInt() { + AtomicInteger ab = new AtomicInteger(); + ab.set(30); + assert ab.compareAndSet(30, 10); + return ab.get(); + } + + @Compare public String atomicRef() { + AtomicReference ar = new AtomicReference("Ahoj"); + assert ar.compareAndSet("Ahoj", "Hello"); + return ar.getAndSet("Other"); + } + + @Factory public static Object[] create() { + return VMTest.create(AtomicTest.class); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CharacterTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CharacterTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,61 @@ +/** + * 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 CharacterTest { + @Compare public boolean dolarJavaStart() { + return Character.isJavaIdentifierStart('$'); + } + + @Compare public boolean dolarJavaPart() { + return Character.isJavaIdentifierPart('$'); + } + + @Compare public boolean numberJavaStart() { + return Character.isJavaIdentifierStart('3'); + } + + @Compare public boolean numberJavaPart() { + return Character.isJavaIdentifierPart('3'); + } + + @Compare public String testWhiteSpaces() { + StringBuilder sb = new StringBuilder(); + for (int i = 1; i < 128; i++) { + char ch = (char)i; + if (Character.isWhitespace(ch)) { + sb.append(i).append(","); + } + } + return sb.toString(); + } + + @Factory + public static Object[] create() { + return VMTest.create(CharacterTest.class); + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ClassLoaderTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ClassLoaderTest.java Wed Apr 30 15:04:10 2014 +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 ClassLoaderTest { + @Compare public Object unknownResource() { + return ClassLoader.getSystemResource("really/unknown/resource.txt"); + } + + @Compare public boolean indenpotentSetOfClassloaderIsOK() { + Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); + return Thread.currentThread().getContextClassLoader() == ClassLoader.getSystemClassLoader(); + } + + @Factory public static Object[] create() { + return VMTest.create(ClassLoaderTest.class); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareHashTest.java --- a/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareHashTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareHashTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -30,6 +30,16 @@ 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(); diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareStringsTest.java --- a/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareStringsTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/CompareStringsTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -19,7 +19,9 @@ import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.net.URL; +import java.util.Locale; import org.apidesign.bck2brwsr.vmtest.Compare; import org.apidesign.bck2brwsr.vmtest.VMTest; import org.testng.annotations.Factory; @@ -47,6 +49,14 @@ return "Ahoj".equals(null); } + @Compare public boolean internIsSame() { + return new String("Ahoj").intern() == another(); + } + + private static String another() { + return new String("Ahoj").intern(); + } + @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(); @@ -63,6 +73,10 @@ @Compare public static Object compareURLs() throws MalformedURLException { return new URL("http://apidesign.org:8080/wiki/").toExternalForm().toString(); } + + @Compare public static Object compareURLsViaURIs() throws Exception { + return new URL("http://apidesign.org:8080/wiki/").toURI().toString(); + } @Compare public String deleteLastTwoCharacters() { StringBuilder sb = new StringBuilder(); @@ -161,7 +175,28 @@ assert res.equals("ba") : "Expecting ba: " + res; return res; } + + @Compare public String localeUS() { + return Locale.US.toString(); + } + + @Compare public String localeFrench() { + return Locale.FRENCH.toString(); + } + + + @Compare public String formatSimple() { + return String.format((Locale)null, "Hello %s!", "World"); + } + @Compare public String replaceWithItself() { + return "org.apidesign.bck2brwsr.core.JavaScriptBody".replace(".", "\\."); + } + + @Compare public boolean matchWithComplicatedRegExp() { + return "Activates this model instance.".matches("(?sm).*^\\s*@deprecated( |$).*"); + } + @Factory public static Object[] create() { return VMTest.create(CompareStringsTest.class); diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ConcurrentTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ConcurrentTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,40 @@ +/** + * 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.concurrent.ConcurrentHashMap; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class ConcurrentTest { + @Compare public String mapIfAbsent() { + ConcurrentHashMap m = new ConcurrentHashMap<>(); + m.putIfAbsent("Ahoj", "Jardo"); + m.putIfAbsent("Ahoj", "Dardo"); + return m.get("Ahoj"); + } + + @Factory public static Object[] create() { + return VMTest.create(ConcurrentTest.class); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/DoubleTest.java --- a/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/DoubleTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/DoubleTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -26,6 +26,14 @@ * @author Jaroslav Tulach */ public class DoubleTest { + @Compare public boolean parsedDoubleIsDouble() { + return Double.valueOf("1.1") instanceof Double; + } + + @Compare public boolean parsedFloatIsFloat() { + return Float.valueOf("1.1") instanceof Float; + } + @Compare public String integerToString() { return toStr(1); } diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,76 @@ +/** + * 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.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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ExceptionsTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ExceptionsTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,68 @@ +/** + * 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.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class ExceptionsTest { + @Compare public String firstLineIsTheSame() throws UnsupportedEncodingException { + MyException ex = new MyException("Hello"); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(out); + ex.printStackTrace(ps); + ps.flush(); + + String s = new String(out.toByteArray(), "UTF-8"); + int newLine = s.indexOf('\n'); + return s.substring(0, newLine); + } + + @Compare public String firstLineIsTheSameWithWriter() throws UnsupportedEncodingException { + MyException ex = new MyException("Hello"); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + ex.printStackTrace(pw); + pw.flush(); + + String s = sw.toString(); + int newLine = s.indexOf('\n'); + return s.substring(0, newLine); + } + + static class MyException extends Exception { + public MyException(String message) { + super(message); + } + } + + + @Factory public static Object[] create() { + return VMTest.create(ExceptionsTest.class); + } +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/LongArithmeticTest.java --- a/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/LongArithmeticTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/LongArithmeticTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -304,6 +304,14 @@ @Compare public long shiftL3() { return shl(0x00fa37d7763e0ca1l, 45); } + + @Compare public long shiftL4() { + return shl(0x00fa37d7763e0ca1l, 0); + } + + @Compare public long shiftL5() { + return shl(0x00fa37d7763e0ca1l, 70); + } @Compare public long shiftR1() { return shr(0x00fa37d7763e0ca1l, 5); @@ -316,6 +324,14 @@ @Compare public long shiftR3() { return shr(0x00fa37d7763e0ca1l, 45); } + + @Compare public long shiftR4() { + return shr(0x00fa37d7763e0ca1l, 0); + } + + @Compare public long shiftR5() { + return shr(0x00fa37d7763e0ca1l, 70); + } @Compare public long uShiftR1() { return ushr(0x00fa37d7763e0ca1l, 5); @@ -324,14 +340,30 @@ @Compare public long uShiftR2() { return ushr(0x00fa37d7763e0ca1l, 45); } + + @Compare public long uShiftR3() { + return ushr(0x00fa37d7763e0ca1l, 0); + } + + @Compare public long uShiftR4() { + return ushr(0x00fa37d7763e0ca1l, 70); + } - @Compare public long uShiftR3() { + @Compare public long uShiftR5() { return ushr(0xf0fa37d7763e0ca1l, 5); } - @Compare public long uShiftR4() { + @Compare public long uShiftR6() { return ushr(0xf0fa37d7763e0ca1l, 45); } + + @Compare public long uShiftR7() { + return ushr(0xf0fa37d7763e0ca1l, 0); + } + + @Compare public long uShiftR8() { + return ushr(0xf0fa37d7763e0ca1l, 70); + } @Compare public long and1() { return and(0x00fa37d7763e0ca1l, 0xa7b3432fff00123el); diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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.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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ProxyTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ProxyTest.java Wed Apr 30 15:04:10 2014 +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.tck; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class ProxyTest { + @Compare public String generateAnnotation() throws Exception { + class InvHandler implements InvocationHandler { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return "Joe Hacker"; + } + } + Anno anno = (Anno) Proxy.newProxyInstance( + Anno.class.getClassLoader(), + new Class[] { Anno.class }, + new InvHandler() + ); + return anno.name(); + } + + @Compare public int getPrimitiveType() throws Exception { + class InvHandler implements InvocationHandler { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return 40; + } + } + Anno anno = (Anno) Proxy.newProxyInstance( + Anno.class.getClassLoader(), + new Class[] { Anno.class }, + new InvHandler() + ); + return 2 + anno.age(); + } + + public static @interface Anno { + public String name(); + public int age(); + } + + @Factory + public static Object[] create() { + return VMTest.create(ProxyTest.class); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionArrayTest.java --- a/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionArrayTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionArrayTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -34,6 +34,11 @@ return arr.length; } + @Compare public String indexOutOfBounds() { + String[] arr = { null, null }; + return arr[2]; + } + @Compare public int reflectiveLengthOfStringArray() { Object arr = Array.newInstance(String.class, 10); return Array.getLength(arr); diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java --- a/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ReflectionTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -19,7 +19,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -72,6 +74,18 @@ @Compare public String isRunnableHasRunMethod() throws NoSuchMethodException { return Runnable.class.getMethod("run").getName(); } + + @Compare public String isRunnableDeclaresRunMethod() throws NoSuchMethodException { + return Runnable.class.getDeclaredMethod("run").getName(); + } + + @Compare public String intValue() throws Exception { + return Integer.class.getConstructor(int.class).newInstance(10).toString(); + } + + @Compare public String getMethodWithArray() throws Exception { + return Proxy.class.getMethod("getProxyClass", ClassLoader.class, Class[].class).getName(); + } @Compare public String namesOfMethods() { StringBuilder sb = new StringBuilder(); @@ -86,6 +100,19 @@ return sb.toString(); } + @Compare public String paramsOfConstructors() { + StringBuilder sb = new StringBuilder(); + String[] arr = new String[20]; + int i = 0; + for (Constructor m : StaticUse.class.getConstructors()) { + 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]; @@ -223,6 +250,17 @@ } } + @Compare public int callAbst() throws Exception { + class Impl extends Abst { + @Override + public int abst() { + return 10; + } + } + Abst impl = new Impl(); + return (int) Abst.class.getMethod("abst").invoke(impl); + } + @Compare public String componentGetNameForObjectArray() { return (new Object[3]).getClass().getComponentType().getName(); } @@ -269,4 +307,7 @@ return VMTest.create(ReflectionTest.class); } + public static abstract class Abst { + public abstract int abst(); + } } diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/RegExpReplaceAllTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/RegExpReplaceAllTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,54 @@ +/** + * 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 RegExpReplaceAllTest { + + @Compare public String replaceAll() { + return "JavaScript".replaceAll("Script", "One"); + } + + @Compare public String replaceAllTwice() { + return "Script JavaScript!".replaceAll("Script", "One"); + } + + + @Compare public String replaceAllRegexp() { + return "JavaScript".replaceAll("S....t", "One"); + } + + @Compare public String replaceAllRegexpTwice() { + return "Script JavaScript!".replaceAll("S....t", "One"); + } + + @Compare public String replaceFirstRegexpOnly() { + return "Script JavaScript!".replaceFirst("S....t", "One"); + } + + @Factory public static Object[] create() { + return VMTest.create(RegExpReplaceAllTest.class); + } +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,54 @@ +/** + * 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 String splitNewline() { + return Arrays.toString("initializer must be able to complete normally".split("\n")); + } + + 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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ResourceBundleTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ResourceBundleTest.java Wed Apr 30 15:04:10 2014 +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.net.URL; +import java.util.ResourceBundle; +import org.apidesign.bck2brwsr.vmtest.Compare; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class ResourceBundleTest { + + @Compare public String readFromBundle() throws Exception { + ResourceBundle b = ResourceBundle.getBundle("org/apidesign/bck2brwsr/tck/Bundle"); + return b.getString("KEY"); + } + + @Compare public String toURIFromURL() throws Exception { + URL u = new URL("http://apidesign.org"); + return u.toURI().toString(); + } + + @Factory public static Object[] create() { + return VMTest.create(ResourceBundleTest.class); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ResourcesTest.java --- a/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ResourcesTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/ResourcesTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -17,7 +17,10 @@ */ package org.apidesign.bck2brwsr.tck; +import java.io.IOException; import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; import org.apidesign.bck2brwsr.vmtest.Compare; import org.apidesign.bck2brwsr.vmtest.VMTest; import org.testng.annotations.Factory; @@ -27,17 +30,62 @@ * @author Jaroslav Tulach */ public class ResourcesTest { + @Compare public String allManifests() throws Exception { + Enumeration en = ClassLoader.getSystemResources("META-INF/MANIFEST.MF"); + assert en.hasMoreElements() : "Should have at least one manifest"; + String first = readString(en.nextElement().openStream()); + boolean different = false; + int cnt = 1; + while (en.hasMoreElements()) { + URL url = en.nextElement(); + String now = readString(url.openStream()); + if (!first.equals(now)) { + different = true; + } + cnt++; + if (cnt > 500) { + throw new IllegalStateException( + "Giving up. First manifest:\n" + first + + "\nLast manifest:\n" + now + ); + } + } + assert different : "Not all manifests should look like first one:\n" + first; + return "" + cnt; + } @Compare public String readResourceAsStream() throws Exception { InputStream is = getClass().getResourceAsStream("Resources.txt"); - assert is != null : "The stream for Resources.txt should be found"; - byte[] b = new byte[30]; - int len = is.read(b); + return readString(is); + } + + @Compare public String readResourceViaConnection() throws Exception { + InputStream is = getClass().getResource("Resources.txt").openConnection().getInputStream(); + return readString(is); + } + + private String readString(InputStream is) throws IOException { StringBuilder sb = new StringBuilder(); - for (int i = 0; i < len; i++) { - sb.append((char)b[i]); + byte[] b = new byte[512]; + for (;;) { + int len = is.read(b); + if (len == -1) { + return sb.toString(); + } + for (int i = 0; i < len; i++) { + sb.append((char)b[i]); + } } - return sb.toString(); + } + + @Compare public String readResourceAsStreamFromClassLoader() throws Exception { + InputStream is = getClass().getClassLoader().getResourceAsStream("org/apidesign/bck2brwsr/tck/Resources.txt"); + return readString(is); + } + + @Compare public String toURIFromURL() throws Exception { + URL u = new URL("http://apidesign.org"); + return u.toURI().toString(); } @Factory public static Object[] create() { diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/SystemTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/SystemTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,65 @@ +/** + * 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.ByteArrayOutputStream; +import java.io.PrintStream; +import org.apidesign.bck2brwsr.core.ExtraJavaScript; +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 + */ +@ExtraJavaScript(resource = "/org/apidesign/bck2brwsr/tck/console.js") +public class SystemTest { + @Compare public boolean nonNullOSName() { + return System.getProperty("os.name") != null; + } + + @Compare public String captureStdOut() throws Exception { + Object capture = initCapture(); + System.out.println("Ahoj"); + return textCapture(capture); + } + + @JavaScriptBody(args = {}, body = "" + + "var lines = [];" + + "console.log = function(l) { lines.push(l); };" + + "return lines;") + Object initCapture() { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(os); + + System.setOut(ps); + return os; + } + + @JavaScriptBody(args = { "o" }, body = "return o.join('');") + String textCapture(Object o) throws java.io.IOException { + ByteArrayOutputStream b = (ByteArrayOutputStream) o; + return new String(b.toByteArray(), "UTF-8"); + } + + @Factory public static Object[] create() { + return VMTest.create(SystemTest.class); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/TimerTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/java/org/apidesign/bck2brwsr/tck/TimerTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,81 @@ +/** + * 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.Timer; +import java.util.TimerTask; +import org.apidesign.bck2brwsr.vmtest.BrwsrTest; +import org.apidesign.bck2brwsr.vmtest.VMTest; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class TimerTest { + int miss; + int exec; + + public TimerTest() { + } + + @BrwsrTest public void scheduleTick() throws Exception { + Timer t = new Timer("MyTest"); + class TT extends TimerTask { + @Override + public void run() { + exec++; + } + } + TT task = new TT(); + t.schedule(task, 15); + + if (exec == 0) { + miss++; + throw new InterruptedException(); + } + + assert exec == 1 : "One exec: " + exec; + assert miss == 1 : "One miss: " + miss; + } + + @BrwsrTest public void repeatedTicks() throws Exception { + Timer t = new Timer("MyTest"); + class TT extends TimerTask { + @Override + public void run() { + exec++; + } + } + TT task = new TT(); + t.scheduleAtFixedRate(task, 15, 10); + + if (exec != 2) { + miss++; + throw new InterruptedException(); + } + + assert exec == 2 : "Two execs: " + exec; + assert miss == 2 : "Two misses: " + miss; + } + + @Factory public static Object[] create() { + return VMTest.create(TimerTest.class); + } + +} diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/tck/Bundle.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/tck/Bundle.properties Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,2 @@ +KEY=Value + diff -r e995e8d39240 -r ba912ef24b27 rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/tck/console.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/test/resources/org/apidesign/bck2brwsr/tck/console.js Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,22 @@ +/** + * 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. + */ + +if (typeof console === 'undefined') { + console = {}; +} + diff -r e995e8d39240 -r ba912ef24b27 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 Wed Apr 30 15:04:10 2014 +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 e995e8d39240 -r ba912ef24b27 rt/emul/fake/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/fake/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,28 @@ + + + 4.0.0 + + org.apidesign.bck2brwsr + emul.pom + 0.9-SNAPSHOT + + fake + jar + Fake Stubs of Java APIs + + The minimal emulation classes have certain references + to less essential classes in the JDK. This module provides + their stubs for purpose of compilation. + + + + + maven-deploy-plugin + 2.7 + + true + + + + + diff -r e995e8d39240 -r ba912ef24b27 rt/emul/fake/src/main/java/java/io/PrintStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/fake/src/main/java/java/io/PrintStream.java Wed Apr 30 15:04:10 2014 +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 java.io; + +/** Fake signature of an existing Java class. + * @author Jaroslav Tulach + */ +public abstract class PrintStream { + public abstract void print(String s); + public abstract void println(String s); +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/fake/src/main/java/java/io/PrintWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/fake/src/main/java/java/io/PrintWriter.java Wed Apr 30 15:04:10 2014 +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 java.io; + +/** Fake signature of an existing Java class. + * @author Jaroslav Tulach + */ +public abstract class PrintWriter { + public abstract PrintWriter append(String s); + public abstract void println(String s); +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/fake/src/main/java/java/net/URI.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/fake/src/main/java/java/net/URI.java Wed Apr 30 15:04:10 2014 +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 java.net; + +/** Fake signature of an existing Java class. + * @author Jaroslav Tulach + */ +public class URI { + public URI(String url) { + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/fake/src/main/java/java/net/URISyntaxException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/fake/src/main/java/java/net/URISyntaxException.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,25 @@ +/** + * 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 java.net; + +/** Fake signature of an existing Java class. + * @author Jaroslav Tulach + */ +public class URISyntaxException extends Exception { + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/fake/src/main/java/java/net/URLConnection.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/fake/src/main/java/java/net/URLConnection.java Wed Apr 30 15:04:10 2014 +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 java.net; + +import java.io.IOException; +import java.io.InputStream; + +/** Fake signature of an existing Java class. + * @author Jaroslav Tulach + */ +public abstract class URLConnection { + protected URL url; + public URLConnection(URL u) { + } + public abstract void connect() throws IOException; + public abstract InputStream getInputStream() throws IOException; + +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/fake/src/main/java/java/util/Locale.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/fake/src/main/java/java/util/Locale.java Wed Apr 30 15:04:10 2014 +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 java.util; + +/** Fake signature of an existing Java class. + * @author Jaroslav Tulach + */ +public abstract class Locale { +} diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/pom.xml --- a/rt/emul/mini/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr emul.pom - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr emul.mini - 0.8-SNAPSHOT + 0.9-SNAPSHOT Minimal API Profile http://maven.apache.org @@ -18,13 +18,19 @@ org.apidesign.bck2brwsr core - 0.8-SNAPSHOT + 0.9-SNAPSHOT + jar + + + org.apidesign.bck2brwsr + fake + 0.9-SNAPSHOT + provided jar org.testng testng - 6.5.2 test diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/lang/Character.java --- a/rt/emul/mini/src/main/java/java/lang/Character.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/Character.java Wed Apr 30 15:04:10 2014 +0200 @@ -572,6 +572,46 @@ */ public static final int MAX_CODE_POINT = 0X10FFFF; + public static boolean isAlphabetic(int ch) { + throw new UnsupportedOperationException("isAlphabetic: " + (char)ch); + } + + public static boolean isIdeographic(int ch) { + throw new UnsupportedOperationException("isIdeographic: " + (char)ch); + } + + public static boolean isLowerCase(int ch) { + throw new UnsupportedOperationException("isLowerCase: " + (char)ch); + } + + public static boolean isUpperCase(int ch) { + throw new UnsupportedOperationException("isUpperCase: " + (char)ch); + } + + public static boolean isMirrored(int ch) { + throw new UnsupportedOperationException("isMirrored: " + (char)ch); + } + + public static boolean isIdentifierIgnorable(int ch) { + throw new UnsupportedOperationException("isIdentifierIgnorable: " + (char)ch); + } + + public static boolean isUnicodeIdentifierPart(int ch) { + throw new UnsupportedOperationException("isUnicodeIdentifierPart: " + (char)ch); + } + + public static boolean isUnicodeIdentifierStart(int ch) { + throw new UnsupportedOperationException("isUnicodeIdentifierStart: " + (char)ch); + } + + public static char toUpperCase(int ch) { + throw new UnsupportedOperationException("toUpperCase: " + (char)ch); + } + + public static int toLowerCase(int ch) { + throw new UnsupportedOperationException("toLowerCase: " + (char)ch); + } + /** * Instances of this class represent particular subsets of the Unicode @@ -1892,8 +1932,8 @@ return fromCodeChars(codePoint).matches("\\w"); } - static int getType(int x) { - throw new UnsupportedOperationException(); + public static int getType(int x) { + throw new UnsupportedOperationException("getType: " + (char)x); } /** @@ -1955,7 +1995,8 @@ public static boolean isJavaIdentifierStart(int codePoint) { return ('A' <= codePoint && codePoint <= 'Z') || - ('a' <= codePoint && codePoint <= 'z'); + ('a' <= codePoint && codePoint <= 'z') || + codePoint == '$'; } /** @@ -2298,6 +2339,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 +2352,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 +2416,14 @@ * @since 1.5 */ public static boolean isWhitespace(int codePoint) { - throw new UnsupportedOperationException(); + // values up to 128: [9,10,11,12,13,28,29,30,31,32] + if (9 <= codePoint && 13 >= codePoint) { + return true; + } + if (28 <= codePoint && 32 >= codePoint) { + return true; + } + return false; } /** diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/lang/Class.java --- a/rt/emul/mini/src/main/java/java/lang/Class.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/Class.java Wed Apr 30 15:04:10 2014 +0200 @@ -26,14 +26,16 @@ package java.lang; import java.io.ByteArrayInputStream; -import org.apidesign.bck2brwsr.emul.reflect.AnnotationImpl; import java.io.InputStream; import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.TypeVariable; import java.net.URL; import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.emul.reflect.AnnotationImpl; import org.apidesign.bck2brwsr.emul.reflect.MethodImpl; /** @@ -155,11 +157,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; } @@ -627,6 +633,20 @@ return getAccess(); } + /** + * If the class or interface represented by this {@code Class} object + * is a member of another class, returns the {@code Class} object + * representing the class in which it was declared. This method returns + * null if this class or interface is not a member of any other class. If + * this {@code Class} object represents an array class, a primitive + * type, or void,then this method returns null. + * + * @return the declaring class for this class + * @since JDK1.1 + */ + public Class getDeclaringClass() { + throw new SecurityException(); + } /** * Returns the simple name of the underlying class as given in the @@ -971,6 +991,319 @@ } /** + * Returns an array of {@code Field} objects reflecting all the fields + * declared by the class or interface represented by this + * {@code Class} object. This includes public, protected, default + * (package) access, and private fields, but excludes inherited fields. + * The elements in the array returned are not sorted and are not in any + * particular order. This method returns an array of length 0 if the class + * or interface declares no fields, or if this {@code Class} object + * represents a primitive type, an array class, or void. + * + *

See The Java Language Specification, sections 8.2 and 8.3. + * + * @return the array of {@code Field} objects representing all the + * declared fields of this class + * @exception SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: + * + *

    + * + *
  • invocation of + * {@link SecurityManager#checkMemberAccess + * s.checkMemberAccess(this, Member.DECLARED)} denies + * access to the declared fields within this class + * + *
  • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class + * + *
+ * + * @since JDK1.1 + */ + public Field[] getDeclaredFields() throws SecurityException { + throw new SecurityException(); + } + + /** + * Bck2Brwsr emulation can only seek public methods, otherwise it + * throws a {@code SecurityException}. + *

+ * Returns a {@code Method} object that reflects the specified + * declared method of the class or interface represented by this + * {@code Class} object. The {@code name} parameter is a + * {@code String} that specifies the simple name of the desired + * method, and the {@code parameterTypes} parameter is an array of + * {@code Class} objects that identify the method's formal parameter + * types, in declared order. If more than one method with the same + * parameter types is declared in a class, and one of these methods has a + * return type that is more specific than any of the others, that method is + * returned; otherwise one of the methods is chosen arbitrarily. If the + * name is "<init>"or "<clinit>" a {@code NoSuchMethodException} + * is raised. + * + * @param name the name of the method + * @param parameterTypes the parameter array + * @return the {@code Method} object for the method of this class + * matching the specified name and parameters + * @exception NoSuchMethodException if a matching method is not found. + * @exception NullPointerException if {@code name} is {@code null} + * @exception SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: + * + *

    + * + *
  • invocation of + * {@link SecurityManager#checkMemberAccess + * s.checkMemberAccess(this, Member.DECLARED)} denies + * access to the declared method + * + *
  • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class + * + *
+ * + * @since JDK1.1 + */ + public Method getDeclaredMethod(String name, Class... parameterTypes) + throws NoSuchMethodException, SecurityException { + try { + return getMethod(name, parameterTypes); + } catch (NoSuchMethodException ex) { + throw new SecurityException(); + } + } + + /** + * Returns a {@code Field} object that reflects the specified declared + * field of the class or interface represented by this {@code Class} + * object. The {@code name} parameter is a {@code String} that + * specifies the simple name of the desired field. Note that this method + * will not reflect the {@code length} field of an array class. + * + * @param name the name of the field + * @return the {@code Field} object for the specified field in this + * class + * @exception NoSuchFieldException if a field with the specified name is + * not found. + * @exception NullPointerException if {@code name} is {@code null} + * @exception SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: + * + *
    + * + *
  • invocation of + * {@link SecurityManager#checkMemberAccess + * s.checkMemberAccess(this, Member.DECLARED)} denies + * access to the declared field + * + *
  • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class + * + *
+ * + * @since JDK1.1 + */ + public Field getDeclaredField(String name) + throws SecurityException { + throw new SecurityException(); + } + + /** + * Returns an array containing {@code Constructor} objects reflecting + * all the public constructors of the class represented by this + * {@code Class} object. An array of length 0 is returned if the + * class has no public constructors, or if the class is an array class, or + * if the class reflects a primitive type or void. + * + * Note that while this method returns an array of {@code + * Constructor} objects (that is an array of constructors from + * this class), the return type of this method is {@code + * Constructor[]} and not {@code Constructor[]} as + * might be expected. This less informative return type is + * necessary since after being returned from this method, the + * array could be modified to hold {@code Constructor} objects for + * different classes, which would violate the type guarantees of + * {@code Constructor[]}. + * + * @return the array of {@code Constructor} objects representing the + * public constructors of this class + * @exception SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: + * + *
    + * + *
  • invocation of + * {@link SecurityManager#checkMemberAccess + * s.checkMemberAccess(this, Member.PUBLIC)} denies + * access to the constructors within this class + * + *
  • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class + * + *
+ * + * @since JDK1.1 + */ + public Constructor[] getConstructors() throws SecurityException { + return MethodImpl.findConstructors(this, 0x01); + } + + /** + * Returns a {@code Constructor} object that reflects the specified + * public constructor of the class represented by this {@code Class} + * object. The {@code parameterTypes} parameter is an array of + * {@code Class} objects that identify the constructor's formal + * parameter types, in declared order. + * + * If this {@code Class} object represents an inner class + * declared in a non-static context, the formal parameter types + * include the explicit enclosing instance as the first parameter. + * + *

The constructor to reflect is the public constructor of the class + * represented by this {@code Class} object whose formal parameter + * types match those specified by {@code parameterTypes}. + * + * @param parameterTypes the parameter array + * @return the {@code Constructor} object of the public constructor that + * matches the specified {@code parameterTypes} + * @exception NoSuchMethodException if a matching method is not found. + * @exception SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: + * + *

    + * + *
  • invocation of + * {@link SecurityManager#checkMemberAccess + * s.checkMemberAccess(this, Member.PUBLIC)} denies + * access to the constructor + * + *
  • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class + * + *
+ * + * @since JDK1.1 + */ + public Constructor getConstructor(Class... parameterTypes) + throws NoSuchMethodException, SecurityException { + Constructor c = MethodImpl.findConstructor(this, parameterTypes); + if (c == null) { + StringBuilder sb = new StringBuilder(); + sb.append(getName()).append('('); + String sep = ""; + for (int i = 0; i < parameterTypes.length; i++) { + sb.append(sep).append(parameterTypes[i].getName()); + sep = ", "; + } + sb.append(')'); + throw new NoSuchMethodException(sb.toString()); + } + return c; + } + + /** + * Returns an array of {@code Constructor} objects reflecting all the + * constructors declared by the class represented by this + * {@code Class} object. These are public, protected, default + * (package) access, and private constructors. The elements in the array + * returned are not sorted and are not in any particular order. If the + * class has a default constructor, it is included in the returned array. + * This method returns an array of length 0 if this {@code Class} + * object represents an interface, a primitive type, an array class, or + * void. + * + *

See The Java Language Specification, section 8.2. + * + * @return the array of {@code Constructor} objects representing all the + * declared constructors of this class + * @exception SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: + * + *

    + * + *
  • invocation of + * {@link SecurityManager#checkMemberAccess + * s.checkMemberAccess(this, Member.DECLARED)} denies + * access to the declared constructors within this class + * + *
  • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class + * + *
+ * + * @since JDK1.1 + */ + public Constructor[] getDeclaredConstructors() throws SecurityException { + throw new SecurityException(); + } + /** + * Returns a {@code Constructor} object that reflects the specified + * constructor of the class or interface represented by this + * {@code Class} object. The {@code parameterTypes} parameter is + * an array of {@code Class} objects that identify the constructor's + * formal parameter types, in declared order. + * + * If this {@code Class} object represents an inner class + * declared in a non-static context, the formal parameter types + * include the explicit enclosing instance as the first parameter. + * + * @param parameterTypes the parameter array + * @return The {@code Constructor} object for the constructor with the + * specified parameter list + * @exception NoSuchMethodException if a matching method is not found. + * @exception SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: + * + *
    + * + *
  • invocation of + * {@link SecurityManager#checkMemberAccess + * s.checkMemberAccess(this, Member.DECLARED)} denies + * access to the declared constructor + * + *
  • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class + * + *
+ * + * @since JDK1.1 + */ + public Constructor getDeclaredConstructor(Class... parameterTypes) + throws NoSuchMethodException, SecurityException { + return getConstructor(parameterTypes); + } + + + /** * Character.isDigit answers {@code true} to some non-ascii * digits. This one does not. */ @@ -1047,15 +1380,10 @@ */ public InputStream getResourceAsStream(String name) { name = resolveName(name); - byte[] arr = getResourceAsStream0(name); + byte[] arr = ClassLoader.getResourceAsStream0(name, 0); return arr == null ? null : new ByteArrayInputStream(arr); } - - @JavaScriptBody(args = "name", body = - "return (vm.loadBytes) ? vm.loadBytes(name) : null;" - ) - private static native byte[] getResourceAsStream0(String name); - + /** * Finds a resource with a given name. The rules for searching resources * associated with a given class are implemented by the defining @@ -1091,8 +1419,11 @@ * @since JDK1.1 */ public java.net.URL getResource(String name) { - InputStream is = getResourceAsStream(name); - return is == null ? null : newResourceURL(URL.class, "res:/" + name, is); + return newResourceURL(name, getResourceAsStream(name)); + } + + static URL newResourceURL(String name, InputStream is) { + return is == null ? null : newResourceURL0(URL.class, "res:/" + name, is); } @JavaScriptBody(args = { "url", "spec", "is" }, body = @@ -1100,7 +1431,7 @@ + "u.constructor.cons__VLjava_lang_String_2Ljava_io_InputStream_2.call(u, spec, is);\n" + "return u;" ) - private static native URL newResourceURL(Class url, String spec, InputStream is); + private static native URL newResourceURL0(Class url, String spec, InputStream is); /** * Add a package name prefix if the name is not absolute Remove leading "/" @@ -1154,7 +1485,7 @@ * @see java.lang.RuntimePermission */ public ClassLoader getClassLoader() { - throw new SecurityException(); + return ClassLoader.getSystemClassLoader(); } /** @@ -1331,9 +1662,11 @@ @JavaScriptBody(args = { "ac" }, body = - "if (this.anno) {" - + " return this.anno['L' + ac.jvmName + ';'];" - + "} else return null;" + "if (this.anno) {\n" + + " var r = this.anno['L' + ac.jvmName + ';'];\n" + + " if (typeof r === 'undefined') r = null;\n" + + " return r;\n" + + "} else return null;\n" ) private Object getAnnotationData(Class annotationClass) { throw new UnsupportedOperationException(); @@ -1395,4 +1728,50 @@ "return vm.desiredAssertionStatus ? vm.desiredAssertionStatus : false;" ) public native boolean desiredAssertionStatus(); + + static void registerNatives() { + boolean assertsOn = false; + // assert assertsOn = true; + 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(); };" + ) + static native void registerToString(); + + @JavaScriptBody(args = {"self"}, body + = "var c = self.constructor.$class;\n" + + "return c ? c : null;\n" + ) + static native Class classFor(Object self); + + @JavaScriptBody(args = { "self" }, body + = "if (self.$hashCode) return self.$hashCode;\n" + + "var h = self.computeHashCode__I ? self.computeHashCode__I() : Math.random() * Math.pow(2, 31);\n" + + "return self.$hashCode = h & h;" + ) + static native int defaultHashCode(Object self); + + @JavaScriptBody(args = "self", body + = "\nif (!self['$instOf_java_lang_Cloneable']) {" + + "\n return null;" + + "\n} else {" + + "\n var clone = self.constructor(true);" + + "\n var props = Object.getOwnPropertyNames(self);" + + "\n for (var i = 0; i < props.length; i++) {" + + "\n var p = props[i];" + + "\n clone[p] = self[p];" + + "\n };" + + "\n return clone;" + + "\n}" + ) + static native Object clone(Object self) throws CloneNotSupportedException; + } diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/lang/ClassLoader.java --- a/rt/emul/mini/src/main/java/java/lang/ClassLoader.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/ClassLoader.java Wed Apr 30 15:04:10 2014 +0200 @@ -24,6 +24,7 @@ */ package java.lang; +import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.IOException; import java.net.URL; @@ -180,7 +181,7 @@ * @since 1.2 */ protected ClassLoader(ClassLoader parent) { - throw new SecurityException(); + this.parent = parent; } /** @@ -199,7 +200,7 @@ * of a new class loader. */ protected ClassLoader() { - throw new SecurityException(); + this.parent = null; } // -- Class -- @@ -845,8 +846,27 @@ * @revised 1.4 */ public static ClassLoader getSystemClassLoader() { - throw new SecurityException(); + if (SYSTEM == null) { + SYSTEM = new ClassLoader() { + @Override + protected Enumeration findResources(String name) throws IOException { + return getBootstrapResources(name); + } + + @Override + protected URL findResource(String name) { + return getBootstrapResource(name); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + return Class.forName(name); + } + }; + } + return SYSTEM; } + private static ClassLoader SYSTEM; // Returns true if the specified class loader can be found in this class // loader's delegation chain. @@ -870,12 +890,48 @@ } private static URL getBootstrapResource(String name) { - throw new UnsupportedOperationException(); + return Object.class.getResource("/" + name); } + @JavaScriptBody(args = { "name", "skip" }, body + = "return (vm.loadBytes) ? vm.loadBytes(name, skip) : null;" + ) + static native byte[] getResourceAsStream0(String name, int skip); + private static Enumeration getBootstrapResources(String name) { - URL u = Object.class.getResource("/" + name); - return new OneOrZeroEnum(u); + return new ResEnum(name); + } + + private static class ResEnum implements Enumeration { + private final String name; + private URL next; + private int skip; + + public ResEnum(String name) { + this.name = name; + } + + + public boolean hasMoreElements() { + if (next == null && skip >= 0) { + byte[] arr = getResourceAsStream0(name, skip++); + if (arr != null) { + next = Class.newResourceURL(name, new ByteArrayInputStream(arr)); + } else { + skip = -1; + } + } + return next != null; + } + + public URL nextElement() { + URL r = next; + if (r == null) { + throw new NoSuchElementException(); + } + next = null; + return r; + } } private static class OneOrZeroEnum implements Enumeration { @@ -910,7 +966,7 @@ } public boolean hasMoreElements() { - if (next == null) { + if (next == null && index < arr.length) { if (arr[index].hasMoreElements()) { next = (URL) arr[index].nextElement(); } else { diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/lang/Double.java --- a/rt/emul/mini/src/main/java/java/lang/Double.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/Double.java Wed Apr 30 15:04:10 2014 +0200 @@ -502,10 +502,8 @@ * @throws NumberFormatException if the string does not contain a * parsable number. */ - @JavaScriptBody(args="s", body="return parseFloat(s);") public static Double valueOf(String s) throws NumberFormatException { - throw new UnsupportedOperationException(); -// return new Double(FloatingDecimal.readJavaFormatString(s).doubleValue()); + return new Double(parseDouble(s)); } /** @@ -542,8 +540,7 @@ */ @JavaScriptBody(args="s", body="return parseFloat(s);") public static double parseDouble(String s) throws NumberFormatException { - throw new UnsupportedOperationException(); -// return FloatingDecimal.readJavaFormatString(s).doubleValue(); + return 0; } /** diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/lang/Enum.java --- a/rt/emul/mini/src/main/java/java/lang/Enum.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/Enum.java Wed Apr 30 15:04:10 2014 +0200 @@ -235,7 +235,7 @@ throw new IllegalArgumentException(); } - @JavaScriptBody(args = { "enumType" }, body = "return enumType.cnstr.$VALUES;") + @JavaScriptBody(args = { "enumType" }, body = "return enumType.cnstr.fld_$VALUES;") private static native Object[] values(Class enumType); /** diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/lang/Float.java --- a/rt/emul/mini/src/main/java/java/lang/Float.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/Float.java Wed Apr 30 15:04:10 2014 +0200 @@ -412,8 +412,7 @@ * parsable number. */ public static Float valueOf(String s) throws NumberFormatException { - throw new UnsupportedOperationException(); -// return new Float(FloatingDecimal.readJavaFormatString(s).floatValue()); + return new Float(parseFloat(s)); } /** @@ -447,9 +446,9 @@ * @see java.lang.Float#valueOf(String) * @since 1.2 */ + @JavaScriptBody(args="s", body="return parseFloat(s);") public static float parseFloat(String s) throws NumberFormatException { - throw new UnsupportedOperationException(); -// return FloatingDecimal.readJavaFormatString(s).floatValue(); + return 0; } /** diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/lang/Object.java --- a/rt/emul/mini/src/main/java/java/lang/Object.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/Object.java Wed Apr 30 15:04:10 2014 +0200 @@ -25,7 +25,6 @@ package java.lang; -import java.lang.reflect.Array; import org.apidesign.bck2brwsr.core.JavaScriptBody; import org.apidesign.bck2brwsr.core.JavaScriptPrototype; @@ -40,23 +39,9 @@ */ @JavaScriptPrototype(container = "Object.prototype", prototype = "new Object") public class Object { - - private static void registerNatives() { - 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(); + Class.registerNatives(); + Class.registerToString(); } /** @@ -79,8 +64,10 @@ * @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 = Class.classFor(this); + return c == null ? Object.class : c; + } /** * Returns a hash code value for the object. This method is @@ -117,16 +104,10 @@ * @see java.lang.Object#equals(java.lang.Object) * @see java.lang.System#identityHashCode */ - @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(); + public int hashCode() { + return Class.defaultHashCode(this); + } - @JavaScriptBody(args = {}, body = "return Math.random() * Math.pow(2, 32);") - native int computeHashCode(); - /** * Indicates whether some other object is "equal to" this one. *

@@ -238,28 +219,13 @@ * @see java.lang.Cloneable */ protected Object clone() throws CloneNotSupportedException { - Object ret = clone(this); + Object ret = Class.clone(this); if (ret == null) { throw new CloneNotSupportedException(getClass().getName()); } return ret; } - @JavaScriptBody(args = "self", body = - "\nif (!self['$instOf_java_lang_Cloneable']) {" - + "\n return null;" - + "\n} else {" - + "\n var clone = self.constructor(true);" - + "\n var props = Object.getOwnPropertyNames(self);" - + "\n for (var i = 0; i < props.length; i++) {" - + "\n var p = props[i];" - + "\n clone[p] = self[p];" - + "\n };" - + "\n return clone;" - + "\n}" - ) - private static native Object clone(Object self) throws CloneNotSupportedException; - /** * Returns a string representation of the object. In general, the * {@code toString} method returns a string that @@ -317,7 +283,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 @@ -341,7 +308,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 @@ -428,7 +396,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 @@ -493,20 +463,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(); } /** @@ -548,7 +505,7 @@ * @see java.lang.Object#notifyAll() */ public final void wait() throws InterruptedException { - wait(0); + throw new InterruptedException(); } /** diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/lang/String.java --- a/rt/emul/mini/src/main/java/java/lang/String.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/String.java Wed Apr 30 15:04:10 2014 +0200 @@ -26,7 +26,10 @@ package java.lang; import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Comparator; +import java.util.Locale; import org.apidesign.bck2brwsr.core.ExtraJavaScript; import org.apidesign.bck2brwsr.core.JavaScriptBody; import org.apidesign.bck2brwsr.core.JavaScriptOnly; @@ -2097,13 +2100,31 @@ * @since 1.4 * @spec JSR-51 */ + public boolean matches(String regex) { + try { + return matchesViaJS(regex); + } catch (Throwable t) { + // fallback to classical behavior + try { + Method m = Class.forName("java.util.regex.Pattern").getMethod("matches", String.class, CharSequence.class); + return (Boolean)m.invoke(null, regex, this); + } catch (InvocationTargetException ex) { + if (ex.getTargetException() instanceof RuntimeException) { + throw (RuntimeException)ex.getTargetException(); + } + } catch (Throwable another) { + // will report the old one + } + throw new RuntimeException(t); + } + } @JavaScriptBody(args = { "regex" }, body = "var self = this.toString();\n" + "var re = new RegExp(regex.toString());\n" + "var r = re.exec(self);\n" + "return r != null && r.length > 0 && self.length == r[0].length;" ) - public boolean matches(String regex) { + private boolean matchesViaJS(String regex) { throw new UnsupportedOperationException(); } @@ -2159,6 +2180,14 @@ * @since 1.4 * @spec JSR-51 */ + @JavaScriptBody(args = { "regex", "newText" }, body = + "var self = this.toString();\n" + + "var re = new RegExp(regex.toString());\n" + + "var r = re.exec(self);\n" + + "if (r === null || r.length === 0) return this;\n" + + "var from = self.indexOf(r[0]);\n" + + "return this.substring(0, from) + newText + this.substring(from + r[0].length);\n" + ) public String replaceFirst(String regex, String replacement) { throw new UnsupportedOperationException(); } @@ -2203,7 +2232,14 @@ * @spec JSR-51 */ public String replaceAll(String regex, String replacement) { - throw new UnsupportedOperationException(); + String p = this; + for (;;) { + String n = p.replaceFirst(regex, replacement); + if (n == p) { + return n; + } + p = n; + } } /** @@ -2224,12 +2260,14 @@ "var s = this.toString();\n" + "target = target.toString();\n" + "replacement = replacement.toString();\n" + + "var pos = 0;\n" + "for (;;) {\n" - + " var ret = s.replace(target, replacement);\n" - + " if (ret === s) {\n" - + " return ret;\n" + + " var indx = s.indexOf(target, pos);\n" + + " if (indx === -1) {\n" + + " return s;\n" + " }\n" - + " s = ret;\n" + + " pos = indx + replacement.length;\n" + + " s = s.substring(0, indx) + replacement + s.substring(indx + target.length);\n" + "}" ) public native String replace(CharSequence target, CharSequence replacement); @@ -2315,8 +2353,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 && to > 0) { + while (to > 0 && ((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 Object argument. @@ -3034,6 +3109,14 @@ * @return a string that has the same contents as this string, but is * guaranteed to be from a pool of unique strings. */ + @JavaScriptBody(args = {}, body = + "var s = this.toString().toString();\n" + + "var i = String.intern || (String.intern = {})\n" + + "if (!i[s]) {\n" + + " i[s] = s;\n" + + "}\n" + + "return i[s];" + ) public native String intern(); diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/lang/Throwable.java --- a/rt/emul/mini/src/main/java/java/lang/Throwable.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/Throwable.java Wed Apr 30 15:04:10 2014 +0200 @@ -638,94 +638,34 @@ * ... 2 more * */ -// public void printStackTrace() { -// printStackTrace(System.err); -// } -// -// /** -// * Prints this throwable and its backtrace to the specified print stream. -// * -// * @param s {@code PrintStream} to use for output -// */ -// public void printStackTrace(PrintStream s) { -// printStackTrace(new WrappedPrintStream(s)); -// } -// -// private void printStackTrace(PrintStreamOrWriter s) { -// // Guard against malicious overrides of Throwable.equals by -// // using a Set with identity equality semantics. -//// Set dejaVu = -//// Collections.newSetFromMap(new IdentityHashMap()); -//// dejaVu.add(this); -// -// synchronized (s.lock()) { -// // Print our stack trace -// s.println(this); -// StackTraceElement[] trace = getOurStackTrace(); -// for (StackTraceElement traceElement : trace) -// s.println("\tat " + traceElement); -// -// // Print suppressed exceptions, if any -//// for (Throwable se : getSuppressed()) -//// se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu); -// -// // Print cause, if any -// Throwable ourCause = getCause(); -//// if (ourCause != null) -//// ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu); -// } -// } -// -// /** -// * Print our stack trace as an enclosed exception for the specified -// * stack trace. -// */ -// private void printEnclosedStackTrace(PrintStreamOrWriter s, -// StackTraceElement[] enclosingTrace, -// String caption, -// String prefix, -// Object dejaVu) { -// assert Thread.holdsLock(s.lock()); -// { -// // Compute number of frames in common between this and enclosing trace -// StackTraceElement[] trace = getOurStackTrace(); -// int m = trace.length - 1; -// int n = enclosingTrace.length - 1; -// while (m >= 0 && n >=0 && trace[m].equals(enclosingTrace[n])) { -// m--; n--; -// } -// int framesInCommon = trace.length - 1 - m; -// -// // Print our stack trace -// s.println(prefix + caption + this); -// for (int i = 0; i <= m; i++) -// s.println(prefix + "\tat " + trace[i]); -// if (framesInCommon != 0) -// s.println(prefix + "\t... " + framesInCommon + " more"); -// -// // Print suppressed exceptions, if any -// for (Throwable se : getSuppressed()) -// se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, -// prefix +"\t", dejaVu); -// -// // Print cause, if any -// Throwable ourCause = getCause(); -// if (ourCause != null) -// ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, prefix, dejaVu); -// } -// } -// -// /** -// * Prints this throwable and its backtrace to the specified -// * print writer. -// * -// * @param s {@code PrintWriter} to use for output -// * @since JDK1.1 -// */ -// public void printStackTrace(PrintWriter s) { -// printStackTrace(new WrappedPrintWriter(s)); -// } -// + public void printStackTrace() { + warn(getClass().getName() + ": " + getMessage()); + } + @JavaScriptBody(args = { "msg" }, body = "if (console) console.warn(msg.toString());") + private native void warn(String msg); + + /** + * Prints this throwable and its backtrace to the specified print stream. + * + * @param s {@code PrintStream} to use for output + */ + public void printStackTrace(PrintStream s) { + s.print(getClass().getName()); + s.print(": "); + s.println(getMessage()); + } + + /** + * Prints this throwable and its backtrace to the specified + * print writer. + * + * @param s {@code PrintWriter} to use for output + * @since JDK1.1 + */ + public void printStackTrace(PrintWriter s) { + s.append(getClass().getName()).append(": ").println(getMessage()); + } + // /** // * Wrapper class for PrintStream and PrintWriter to enable a single // * implementation of printStackTrace. diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/lang/reflect/Array.java --- a/rt/emul/mini/src/main/java/java/lang/reflect/Array.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/reflect/Array.java Wed Apr 30 15:04:10 2014 +0200 @@ -107,7 +107,7 @@ if (type.getName().equals("void")) { throw new IllegalStateException("Can't create array for " + type); } - return "[L" + type.getName() + ";"; + return "[L" + type.getName().replace('.', '/') + ";"; } /** * Creates a new array diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/lang/reflect/Constructor.java --- a/rt/emul/mini/src/main/java/java/lang/reflect/Constructor.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/reflect/Constructor.java Wed Apr 30 15:04:10 2014 +0200 @@ -26,6 +26,10 @@ package java.lang.reflect; import java.lang.annotation.Annotation; +import static java.lang.reflect.Method.fromPrimitive; +import static java.lang.reflect.Method.getAccess; +import static java.lang.reflect.Method.getParameterTypes; +import org.apidesign.bck2brwsr.core.JavaScriptBody; import org.apidesign.bck2brwsr.emul.reflect.TypeProvider; /** @@ -53,44 +57,20 @@ GenericDeclaration, Member { - private Class clazz; - private int slot; - private Class[] parameterTypes; - private Class[] exceptionTypes; - private int modifiers; - // Generics and annotations support - private transient String signature; - private byte[] annotations; - private byte[] parameterAnnotations; - - - // For sharing of ConstructorAccessors. This branching structure - // is currently only two levels deep (i.e., one root Constructor - // and potentially many Constructor objects pointing to it.) - private Constructor root; + private final Class clazz; + private final Object data; + private final String sig; /** * Package-private constructor used by ReflectAccess to enable * instantiation of these objects in Java code from the java.lang * package via sun.reflect.LangReflectAccess. */ - Constructor(Class declaringClass, - Class[] parameterTypes, - Class[] checkedExceptions, - int modifiers, - int slot, - String signature, - byte[] annotations, - byte[] parameterAnnotations) + Constructor(Class declaringClass, Object data, String sig) { this.clazz = declaringClass; - this.parameterTypes = parameterTypes; - this.exceptionTypes = checkedExceptions; - this.modifiers = modifiers; - this.slot = slot; - this.signature = signature; - this.annotations = annotations; - this.parameterAnnotations = parameterAnnotations; + this.data = data; + this.sig = sig; } /** @@ -126,7 +106,7 @@ * @see Modifier */ public int getModifiers() { - return modifiers; + return getAccess(data); } /** @@ -159,7 +139,7 @@ * represents */ public Class[] getParameterTypes() { - return (Class[]) parameterTypes.clone(); + return Method.getParameterTypes(sig); } @@ -205,7 +185,7 @@ * constructor this object represents */ public Class[] getExceptionTypes() { - return (Class[])exceptionTypes.clone(); + return new Class[0]; } @@ -242,20 +222,9 @@ * same formal parameter types. */ public boolean equals(Object obj) { - if (obj != null && obj instanceof Constructor) { - Constructor other = (Constructor)obj; - if (getDeclaringClass() == other.getDeclaringClass()) { - /* Avoid unnecessary cloning */ - Class[] params1 = parameterTypes; - Class[] params2 = other.parameterTypes; - if (params1.length == params2.length) { - for (int i = 0; i < params1.length; i++) { - if (params1[i] != params2[i]) - return false; - } - return true; - } - } + if (obj instanceof Constructor) { + Constructor other = (Constructor)obj; + return data == other.data; } return false; } @@ -293,13 +262,14 @@ } sb.append(Field.getTypeName(getDeclaringClass())); sb.append("("); - Class[] params = parameterTypes; // avoid clone + Class[] params = getParameterTypes(); // avoid clone for (int j = 0; j < params.length; j++) { sb.append(Field.getTypeName(params[j])); if (j < (params.length - 1)) sb.append(","); } sb.append(")"); + /* Class[] exceptions = exceptionTypes; // avoid clone if (exceptions.length > 0) { sb.append(" throws "); @@ -309,6 +279,7 @@ sb.append(","); } } + */ return sb.toString(); } catch (Exception e) { return "<" + e + ">"; @@ -452,9 +423,29 @@ throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { - throw new SecurityException(); + Class[] types = getParameterTypes(); + if (types.length != initargs.length) { + throw new IllegalArgumentException("Types len " + types.length + " args: " + initargs.length); + } else { + initargs = initargs.clone(); + for (int i = 0; i < types.length; i++) { + Class c = types[i]; + if (c.isPrimitive() && initargs[i] != null) { + initargs[i] = Method.toPrimitive(initargs[i]); + } + } + } + return (T) newInstance0(this.getDeclaringClass(), "cons__" + sig, initargs); } + @JavaScriptBody(args = { "self", "sig", "args" }, body = + "\nvar c = self.cnstr;" + + "\nvar inst = c();" + + "\nc[sig].apply(inst, args);" + + "\nreturn inst;" + ) + private static native Object newInstance0(Class self, String sig, Object[] args); + /** * Returns {@code true} if this constructor was declared to take * a variable number of arguments; returns {@code false} @@ -481,22 +472,6 @@ return Modifier.isSynthetic(getModifiers()); } - int getSlot() { - return slot; - } - - String getSignature() { - return signature; - } - - byte[] getRawAnnotations() { - return annotations; - } - - byte[] getRawParameterAnnotations() { - return parameterAnnotations; - } - /** * @throws NullPointerException {@inheritDoc} * @since 1.5 @@ -532,11 +507,11 @@ * @since 1.5 */ public Annotation[][] getParameterAnnotations() { - int numParameters = parameterTypes.length; - if (parameterAnnotations == null) - return new Annotation[numParameters][0]; +// int numParameters = parameterTypes.length; +// if (parameterAnnotations == null) +// return new Annotation[numParameters][0]; - return new Annotation[numParameters][0]; // XXX + return new Annotation[0][0]; // XXX /* Annotation[][] result = AnnotationParser.parseParameterAnnotations( parameterAnnotations, diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/lang/reflect/Method.java --- a/rt/emul/mini/src/main/java/java/lang/reflect/Method.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/reflect/Method.java Wed Apr 30 15:04:10 2014 +0200 @@ -113,7 +113,7 @@ } @JavaScriptBody(args = "self", body = "return self.access;") - private static native int getAccess(Object self); + static native int getAccess(Object self); /** * Returns an array of {@code TypeVariable} objects that represent the @@ -183,6 +183,10 @@ * represents */ public Class[] getParameterTypes() { + return getParameterTypes(sig); + } + + static Class[] getParameterTypes(String sig) { Class[] arr = new Class[MethodImpl.signatureElements(sig) - 1]; Enumeration en = MethodImpl.signatureParser(sig); en.nextElement(); // return type @@ -235,8 +239,7 @@ * method this object represents */ public Class[] getExceptionTypes() { - throw new UnsupportedOperationException(); - //return (Class[]) exceptionTypes.clone(); + return new Class[0]; } /** @@ -525,15 +528,17 @@ } @JavaScriptBody(args = { "st", "method", "self", "args" }, body = - "var p;\n" + "var p; var cll;\n" + "if (st) {\n" + + " cll = self[method._name() + '__' + method._sig()];\n" + " p = new Array(1);\n" + " p[0] = self;\n" + " p = p.concat(args);\n" + "} else {\n" + " p = args;\n" + + " cll = method._data();" + "}\n" - + "return method._data().apply(self, p);\n" + + "return cll.apply(self, p);\n" ) private static native Object invoke0(boolean isStatic, Method m, Object self, Object[] args); @@ -583,7 +588,7 @@ private static native Integer fromRaw(Class cls, String m, Object o); @JavaScriptBody(args = { "o" }, body = "return o.valueOf();") - private static native Object toPrimitive(Object o); + static native Object toPrimitive(Object o); /** * Returns {@code true} if this method is a bridge @@ -692,6 +697,11 @@ protected Method create(Class declaringClass, String name, Object data, String sig) { return new Method(declaringClass, name, data, sig); } + + @Override + protected Constructor create(Class declaringClass, Object data, String sig) { + return new Constructor(declaringClass, data, sig); + } }; } } diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/lang/reflect/Proxy.java --- a/rt/emul/mini/src/main/java/java/lang/reflect/Proxy.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/lang/reflect/Proxy.java Wed Apr 30 15:04:10 2014 +0200 @@ -212,7 +212,16 @@ private static final long serialVersionUID = -2222568056686623797L; - + private final static Method getProxyClass; + static { + Class pg; + try { + pg = Class.forName("org.apidesign.bck2brwsr.emul.reflect.ProxyImpl"); + getProxyClass = pg.getMethod("getProxyClass", ClassLoader.class, Class[].class); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } /** * the invocation handler for this proxy instance. @@ -315,7 +324,13 @@ Class... interfaces) throws IllegalArgumentException { - throw new IllegalArgumentException(); + try { + return (Class) getProxyClass.invoke(null, loader, interfaces); + } catch (IllegalAccessException ex) { + throw new IllegalStateException(ex); + } catch (InvocationTargetException ex) { + throw (RuntimeException)ex.getTargetException(); + } } /** @@ -355,7 +370,27 @@ if (h == null) { throw new NullPointerException(); } - throw new IllegalArgumentException(); + + /* + * Look up or generate the designated proxy class. + */ + Class cl = getProxyClass(loader, interfaces); + + /* + * Invoke its constructor with the designated invocation handler. + */ + try { + Constructor cons = cl.getConstructor(InvocationHandler.class); + return cons.newInstance(new Object[] { h }); + } catch (NoSuchMethodException e) { + throw new IllegalStateException(e.toString()); + } catch (IllegalAccessException e) { + throw new IllegalStateException(e.toString()); + } catch (InstantiationException e) { + throw new IllegalStateException(e.toString()); + } catch (InvocationTargetException e) { + throw new IllegalStateException(e.toString()); + } } /** @@ -376,8 +411,7 @@ if (cl == null) { throw new NullPointerException(); } - - return false; + return Proxy.class.isAssignableFrom(cl); } /** @@ -401,7 +435,4 @@ Proxy p = (Proxy) proxy; return p.h; } - - private static native Class defineClass0(ClassLoader loader, String name, - byte[] b, int off, int len); } diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/net/URL.java --- a/rt/emul/mini/src/main/java/java/net/URL.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/net/URL.java Wed Apr 30 15:04:10 2014 +0200 @@ -920,6 +920,23 @@ } /** + * Returns a {@link java.net.URI} equivalent to this URL. + * This method functions in the same way as new URI (this.toString()). + *

Note, any URL instance that complies with RFC 2396 can be converted + * to a URI. However, some URLs that are not strictly in compliance + * can not be converted to a URI. + * + * @exception URISyntaxException if this URL is not formatted strictly according to + * to RFC2396 and cannot be converted to a URI. + * + * @return a URI instance equivalent to this URL. + * @since 1.5 + */ + public URI toURI() throws URISyntaxException { + return new URI (toString()); + } + + /** * Returns a {@link java.net.URLConnection URLConnection} instance that * represents a connection to the remote object referred to by the * {@code URL}. @@ -948,9 +965,9 @@ * @see java.net.URL#URL(java.lang.String, java.lang.String, * int, java.lang.String) */ -// public URLConnection openConnection() throws java.io.IOException { -// return handler.openConnection(this); -// } + public URLConnection openConnection() throws java.io.IOException { + return handler.openConnection(this); + } /** @@ -1027,18 +1044,53 @@ public final Object getContent(Class[] classes) throws java.io.IOException { for (Class c : classes) { - if (c == String.class) { - return loadText(toExternalForm()); - } - if (c == byte[].class) { - return loadBytes(toExternalForm(), new byte[0]); + try { + if (c == String.class) { + return loadText(toExternalForm()); + } + if (c == byte[].class) { + return loadBytes(toExternalForm(), new byte[0]); + } + } catch (Throwable t) { + throw new IOException(t); } } return null; } - static URLStreamHandler getURLStreamHandler(String protocol) { - URLStreamHandler universal = new URLStreamHandler() {}; + static URLStreamHandler getURLStreamHandler(final String protocol) { + URLStreamHandler universal = new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL u) throws IOException { + return new URLConnection(u) { + Object stream = url.is; + + @Override + public void connect() throws IOException { + if (stream == null) { + try { + byte[] arr = (byte[]) url.getContent(new Class[]{byte[].class}); + stream = new ByteArrayInputStream(arr); + } catch (IOException ex) { + stream = ex; + throw ex; + } + } + } + + @Override + public InputStream getInputStream() throws IOException { + connect(); + if (stream instanceof IOException) { + throw (IOException)stream; + } + return (InputStream)stream; + } + + + }; + } + }; return universal; } @@ -1053,10 +1105,16 @@ } @JavaScriptBody(args = {}, body = - "if (typeof window !== 'object') return null;\n" - + "if (!window.location) return null;\n" - + "if (!window.location.href) return null;\n" - + "return window.location.href;\n" + "var l;\n" + + "if (typeof location !== 'object') {" + + " if (typeof window !== 'object') return null;\n" + + " if (!window.location) return null;\n" + + " l = window.location;\n" + + "} else {\n" + + " l = location;\n" + + "}\n" + + "if (!l.href) return null;\n" + + "return l.href;\n" ) private static native String findBaseURL(); } diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/java/net/URLStreamHandler.java --- a/rt/emul/mini/src/main/java/java/net/URLStreamHandler.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/java/net/URLStreamHandler.java Wed Apr 30 15:04:10 2014 +0200 @@ -25,6 +25,8 @@ package java.net; +import java.io.IOException; + /** * The abstract class URLStreamHandler is the common @@ -62,7 +64,7 @@ * @exception IOException if an I/O error occurs while opening the * connection. */ -// abstract protected URLConnection openConnection(URL u) throws IOException; + abstract protected URLConnection openConnection(URL u) throws IOException; /** * Same as openConnection(URL), except that the connection will be diff -r e995e8d39240 -r ba912ef24b27 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 Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/lang/System.java Wed Apr 30 15:04:10 2014 +0200 @@ -19,6 +19,7 @@ import java.lang.reflect.Method; import org.apidesign.bck2brwsr.core.JavaScriptBody; +import org.apidesign.bck2brwsr.core.JavaScriptOnly; /** * @@ -71,4 +72,27 @@ } @JavaScriptBody(args = { "obj" }, body="return vm.java_lang_Object(false).hashCode__I.call(obj);") public static native int identityHashCode(Object obj); + + @JavaScriptOnly(name = "toJS", value = "function(v) {\n" + + " if (v === null) return null;\n" + + " if (Object.prototype.toString.call(v) === '[object Array]') {\n" + + " return vm.org_apidesign_bck2brwsr_emul_lang_System(false).convArray__Ljava_lang_Object_2Ljava_lang_Object_2(v);\n" + + " }\n" + + " return v.valueOf();\n" + + "}\n" + ) + public static native int toJS(); + + private static Object convArray(Object o) { + if (o instanceof Object[]) { + Object[] arr = (Object[]) o; + final int l = arr.length; + Object[] ret = new Object[l]; + for (int i = 0; i < l; i++) { + ret[i] = convArray(arr[i]); + } + return ret; + } + return o; + } } diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/reflect/MethodImpl.java --- a/rt/emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/reflect/MethodImpl.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/java/org/apidesign/bck2brwsr/emul/reflect/MethodImpl.java Wed Apr 30 15:04:10 2014 +0200 @@ -18,6 +18,7 @@ package org.apidesign.bck2brwsr.emul.reflect; import java.lang.reflect.Array; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Enumeration; import org.apidesign.bck2brwsr.core.JavaScriptBody; @@ -37,15 +38,17 @@ } protected abstract Method create(Class declaringClass, String name, Object data, String sig); + protected abstract Constructor create(Class declaringClass, Object data, String sig); // // bck2brwsr implementation // - @JavaScriptBody(args = {"clazz", "prefix"}, + @JavaScriptBody(args = {"clazz", "prefix", "cnstr"}, body = "" - + "var c = clazz.cnstr.prototype;" + + "var c = clazz.cnstr;\n" + + "if (!cnstr) c = c.prototype;" + "var arr = new Array();\n" + "for (m in c) {\n" + " if (m.indexOf(prefix) === 0) {\n" @@ -57,11 +60,55 @@ + "}\n" + "return arr;") private static native Object[] findMethodData( - Class clazz, String prefix); + Class clazz, String prefix, boolean cnstr); + public static Constructor findConstructor( + Class clazz, Class... parameterTypes) { + Object[] data = findMethodData(clazz, "cons__", true); + BIG: for (int i = 0; i < data.length; i += 3) { + String sig = ((String) data[i]).substring(6); + Class cls = (Class) data[i + 2]; + Constructor tmp = INSTANCE.create(cls, data[i + 1], sig); + Class[] tmpParms = tmp.getParameterTypes(); + if (parameterTypes.length != tmpParms.length) { + continue; + } + for (int j = 0; j < tmpParms.length; j++) { + if (!parameterTypes[j].equals(tmpParms[j])) { + continue BIG; + } + } + return tmp; + } + return null; + } + + public static Constructor[] findConstructors(Class clazz, int mask) { + Object[] namesAndData = findMethodData(clazz, "", true); + int cnt = 0; + for (int i = 0; i < namesAndData.length; i += 3) { + String sig = (String) namesAndData[i]; + Object data = namesAndData[i + 1]; + if (!sig.startsWith("cons__")) { + continue; + } + sig = sig.substring(6); + Class cls = (Class) namesAndData[i + 2]; + final Constructor m = INSTANCE.create(cls, data, sig); + if ((m.getModifiers() & mask) == 0) { + continue; + } + namesAndData[cnt++] = m; + } + Constructor[] arr = new Constructor[cnt]; + for (int i = 0; i < cnt; i++) { + arr[i] = (Constructor) namesAndData[i]; + } + return arr; + } public static Method findMethod( Class clazz, String name, Class... parameterTypes) { - Object[] data = findMethodData(clazz, name + "__"); + Object[] data = findMethodData(clazz, name + "__", false); BIG: for (int i = 0; i < data.length; i += 3) { String sig = ((String) data[i]).substring(name.length() + 2); Class cls = (Class) data[i + 2]; @@ -81,7 +128,7 @@ } public static Method[] findMethods(Class clazz, int mask) { - Object[] namesAndData = findMethodData(clazz, ""); + Object[] namesAndData = findMethodData(clazz, "", false); int cnt = 0; for (int i = 0; i < namesAndData.length; i += 3) { String sig = (String) namesAndData[i]; diff -r e995e8d39240 -r ba912ef24b27 rt/emul/mini/src/main/java/org/apidesign/vm4brwsr/api/VM.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/mini/src/main/java/org/apidesign/vm4brwsr/api/VM.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,47 @@ +/** + * 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.api; + +import java.io.IOException; +import org.apidesign.bck2brwsr.core.JavaScriptBody; + +/** Utility methods to talk to the Bck2Brwsr virtual machine. + * + * @author Jaroslav Tulach + * @since 0.9 + */ +public final class VM { + private VM() { + } + + /** Takes an existing class and replaces its existing byte code + * with new one. + * + * @param clazz existing class to reload + * @param byteCode new bytecode + * @throws IOException an exception is something goes wrong + */ + public static void reload(Class clazz, byte[] byteCode) throws IOException { + reloadImpl(clazz.getName(), byteCode); + } + + @JavaScriptBody(args = { "name", "byteCode" }, body = "vm._reload(name, byteCode);") + private static void reloadImpl(String name, byte[] byteCode) throws IOException { + throw new IOException(); + } +} diff -r e995e8d39240 -r ba912ef24b27 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 Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/mini/src/main/resources/org/apidesign/vm4brwsr/emul/lang/java_lang_Number.js Wed Apr 30 15:04:10 2014 +0200 @@ -176,6 +176,8 @@ }; numberPrototype.shl64 = function(x) { + x &= 0x3f; + if (x == 0) return this; if (x >= 32) { var hi = this << (x - 32); return hi.next32(0); @@ -190,6 +192,8 @@ }; numberPrototype.shr64 = function(x) { + x &= 0x3f; + if (x == 0) return this; if (x >= 32) { var low = this.high32() >> (x - 32); low += (low < 0) ? (__m32 + 1) : 0; @@ -205,6 +209,8 @@ }; numberPrototype.ushr64 = function(x) { + x &= 0x3f; + if (x == 0) return this; if (x >= 32) { var low = this.high32() >>> (x - 32); low += (low < 0) ? (__m32 + 1) : 0; @@ -218,6 +224,14 @@ return hi.next32(low); } }; + + numberPrototype.compare = function(x) { + if (this == x) { + return 0; + } else { + return (this < x) ? -1 : 1; + } + }; numberPrototype.compare64 = function(x) { if (this.high32() === x.high32()) { diff -r e995e8d39240 -r ba912ef24b27 rt/emul/pom.xml --- a/rt/emul/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/emul/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,16 +4,17 @@ org.apidesign.bck2brwsr rt - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr emul.pom - 0.8-SNAPSHOT + 0.9-SNAPSHOT pom Emulation of Core Libraries mini compact brwsrtest + fake diff -r e995e8d39240 -r ba912ef24b27 rt/mojo/pom.xml --- a/rt/mojo/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/mojo/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr rt - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr bck2brwsr-maven-plugin - 0.8-SNAPSHOT + 0.9-SNAPSHOT maven-plugin Bck2Brwsr Maven Plugin http://bck2brwsr.apidesign.org/ diff -r e995e8d39240 -r ba912ef24b27 rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/BrwsrMojo.java --- a/rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/BrwsrMojo.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/BrwsrMojo.java Wed Apr 30 15:04:10 2014 +0200 @@ -29,15 +29,21 @@ import java.util.Collection; import java.util.List; import org.apache.maven.artifact.Artifact; +import org.apache.maven.model.Resource; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apidesign.bck2brwsr.launcher.Launcher; /** Executes given HTML page in a browser. */ -@Mojo(name="brwsr", defaultPhase=LifecyclePhase.NONE) +@Mojo( + name="brwsr", + requiresDependencyResolution = ResolutionScope.RUNTIME, + defaultPhase=LifecyclePhase.NONE +) public class BrwsrMojo extends AbstractMojo { public BrwsrMojo() { } @@ -64,20 +70,33 @@ /** Root of all pages, and files, etc. */ @Parameter private File directory; + + @Parameter(defaultValue="${project.build.directory}/bck2brwsr.js") + private File javascript; @Override public void execute() throws MojoExecutionException { if (startpage == null) { throw new MojoExecutionException("You have to provide a start page"); } - + if (javascript != null && javascript.isFile()) { + System.setProperty("bck2brwsr.js", javascript.toURI().toString()); + } try { Closeable httpServer; if (directory != null) { - httpServer = Launcher.showDir(directory, startpage); + URLClassLoader url = buildClassLoader(classes, prj.getArtifacts()); + httpServer = Launcher.showDir(launcher, directory, url, startpage); } else { - URLClassLoader url = buildClassLoader(classes, prj.getDependencyArtifacts()); + URLClassLoader url = buildClassLoader(classes, prj.getArtifacts()); try { + for (Resource r : prj.getResources()) { + File f = new File(r.getDirectory(), startpage().replace('/', File.separatorChar)); + if (f.exists()) { + System.setProperty("startpage.file", f.getPath()); + } + } + httpServer = Launcher.showURL(launcher, url, startpage()); } catch (Exception ex) { throw new MojoExecutionException("Can't open " + startpage(), ex); diff -r e995e8d39240 -r ba912ef24b27 rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/Java2JavaScript.java --- a/rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/Java2JavaScript.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/mojo/src/main/java/org/apidesign/bck2brwsr/mojo/Java2JavaScript.java Wed Apr 30 15:04:10 2014 +0200 @@ -33,12 +33,16 @@ import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apidesign.vm4brwsr.Bck2Brwsr; import org.apidesign.vm4brwsr.ObfuscationLevel; /** Compiles classes into JavaScript. */ -@Mojo(name="j2js", defaultPhase=LifecyclePhase.PROCESS_CLASSES) +@Mojo(name="j2js", + requiresDependencyResolution = ResolutionScope.COMPILE, + defaultPhase=LifecyclePhase.PROCESS_CLASSES +) public class Java2JavaScript extends AbstractMojo { public Java2JavaScript() { } @@ -46,7 +50,7 @@ @Parameter(defaultValue="${project.build.directory}/classes") private File classes; /** JavaScript file to generate */ - @Parameter + @Parameter(defaultValue="${project.build.directory}/bck2brwsr.js") private File javascript; /** Additional classes that should be pre-compiled into the javascript @@ -66,6 +70,10 @@ */ @Parameter(defaultValue="NONE") private ObfuscationLevel obfuscation; + + /** Should classes from rt.jar be included? */ + @Parameter(defaultValue = "false") + private boolean ignoreBootClassPath; /** * Indicates whether to create an extension library @@ -81,6 +89,9 @@ if (!classes.isDirectory()) { throw new MojoExecutionException("Can't find " + classes); } + if (javascript == null) { + throw new MojoExecutionException("Need to define 'javascript' attribute with a path to file to generate"); + } List arr = new ArrayList(); long newest = collectAllClasses("", classes, arr); @@ -95,12 +106,12 @@ } try { - URLClassLoader url = buildClassLoader(classes, prj.getDependencyArtifacts()); + URLClassLoader url = buildClassLoader(classes, prj.getArtifacts()); FileWriter w = new FileWriter(javascript); Bck2Brwsr.newCompiler(). obfuscation(obfuscation). library(library). - resources(url). + resources(url, ignoreBootClassPath). addRootClasses(arr.toArray(new String[0])). generate(w); w.close(); diff -r e995e8d39240 -r ba912ef24b27 rt/pom.xml --- a/rt/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -3,18 +3,17 @@ 4.0.0 org.apidesign.bck2brwsr rt - 0.8-SNAPSHOT + 0.9-SNAPSHOT pom Bck2Brwsr Runtime org.apidesign bck2brwsr - 0.8-SNAPSHOT + 0.9-SNAPSHOT core emul - archetype mojo vm vmtest diff -r e995e8d39240 -r ba912ef24b27 rt/vm/pom.xml --- a/rt/vm/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -3,12 +3,12 @@ org.apidesign.bck2brwsr rt - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr vm4brwsr - 0.8-SNAPSHOT + 0.9-SNAPSHOT jar Virtual Machine for Browser @@ -18,6 +18,7 @@ UTF-8 Jaroslav Tulach jaroslav.tulach@apidesign.org + MINIMAL @@ -91,7 +92,7 @@ org.apidesign.vm4brwsr.Main --obfuscatelevel - MINIMAL + ${bck2brwsr.obfuscationlevel} ${project.build.directory}/bck2brwsr.js org/apidesign/vm4brwsr/Bck2Brwsr @@ -151,6 +152,18 @@ closure-compiler r2388 compile - + + + org.netbeans.html + net.java.html.boot + test + ${net.java.html.version} + + + ${project.groupId} + fake + ${project.version} + test + diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/main/java/org/apidesign/vm4brwsr/Bck2Brwsr.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/Bck2Brwsr.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/Bck2Brwsr.java Wed Apr 30 15:04:10 2014 +0200 @@ -217,7 +217,21 @@ * @since 0.5 */ public Bck2Brwsr resources(final ClassLoader loader) { - return resources(new LdrRsrcs(loader)); + return resources(loader, false); + } + + /** 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 + * @param ignoreBootClassPath true if classes loaded + * from rt.jar + * @return new instance of the compiler with all values being the same, just + * different resources provider + * @since 0.9 + */ + public Bck2Brwsr resources(final ClassLoader loader, boolean ignoreBootClassPath) { + return resources(new LdrRsrcs(loader, ignoreBootClassPath)); } /** Generates virtual machine based on previous configuration of the @@ -247,7 +261,7 @@ // Resources getResources() { - return res != null ? res : new LdrRsrcs(Bck2Brwsr.class.getClassLoader()); + return res != null ? res : new LdrRsrcs(Bck2Brwsr.class.getClassLoader(), false); } String[] allClasses() { diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java Wed Apr 30 15:04:10 2014 +0200 @@ -39,37 +39,6 @@ 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; @@ -107,17 +76,6 @@ 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 @@ -358,7 +316,7 @@ public static final int opc_nonpriv = 254; public static final int opc_priv = 255; - /* Wide instructions */ + /* 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; @@ -371,762 +329,7 @@ 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; @@ -1332,15 +535,11 @@ 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(); @@ -1845,6 +1044,12 @@ case '\"': sb.append('\\').append('\"'); break; + case '\u2028': + sb.append("\\u2028"); + break; + case '\u2029': + sb.append("\\u2029"); + break; default: sb.append(c); } @@ -2080,7 +1285,7 @@ int name_index; int descriptor_index; int attributes_count; - int value_cpx = 0; + int value_cpx = -1; boolean isSynthetic = false; boolean isDeprecated = false; Vector attrs; @@ -2201,11 +1406,8 @@ return isDeprecated; } - /** - * Returns index of constant value in cpool. - */ - public int getConstantValueIndex() { - return (value_cpx); + public boolean hasConstantValue() { + return value_cpx != -1; } /** @@ -2824,6 +2026,10 @@ lastFrameByteCodeOffset = -1; advanceBy(0); } + + public boolean isEmpty() { + return stackMapTable.length == 0; + } public String getFrameAsString() { return (nextFrameIndex == 0) diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Wed Apr 30 15:04:10 2014 +0200 @@ -25,14 +25,36 @@ * * @author Jaroslav Tulach */ -abstract class ByteCodeToJavaScript { +abstract class ByteCodeToJavaScript implements Appendable { private ClassData jc; - final Appendable out; + private final Appendable out; + private boolean outChanged; protected ByteCodeToJavaScript(Appendable out) { this.out = out; } + @Override + public final Appendable append(CharSequence csq) throws IOException { + out.append(csq); + outChanged = true; + return this; + } + + @Override + public final Appendable append(CharSequence csq, int start, int end) throws IOException { + out.append(csq, start, end); + outChanged = true; + return this; + } + + @Override + public final Appendable append(char c) throws IOException { + out.append(c); + outChanged = true; + return this; + } + /* Collects additional required resources. * * @param internalClassName classes that were referenced and should be loaded in order the @@ -99,7 +121,7 @@ * @throws IOException */ boolean debug(String msg) throws IOException { - out.append(msg); + append(msg); return true; } @@ -124,16 +146,34 @@ ); } byte[] arrData = jc.findAnnotationData(true); - String[] arr = findAnnotation(arrData, jc, - "org.apidesign.bck2brwsr.core.ExtraJavaScript", - "resource", "processByteCode" - ); - if (arr != null) { - if (!arr[0].isEmpty()) { - requireScript(arr[0]); + { + String[] arr = findAnnotation(arrData, jc, + "org.apidesign.bck2brwsr.core.ExtraJavaScript", + "resource", "processByteCode" + ); + if (arr != null) { + if (!arr[0].isEmpty()) { + requireScript(arr[0]); + } + if ("0".equals(arr[1])) { + return null; + } } - if ("0".equals(arr[1])) { - return null; + } + { + String[] arr = findAnnotation(arrData, jc, + "net.java.html.js.JavaScriptResource", + "value" + ); + if (arr != null) { + if (arr[0].startsWith("/")) { + requireScript(arr[0]); + } else { + int last = jc.getClassName().lastIndexOf('/'); + requireScript( + jc.getClassName().substring(0, last + 1).replace('.', '/') + arr[0] + ); + } } } String[] proto = findAnnotation(arrData, jc, @@ -142,34 +182,39 @@ ); StringArray toInitilize = new StringArray(); final String className = className(jc); - out.append("\n\n").append(assignClass(className)); - out.append("function ").append(className).append("() {"); - out.append("\n var CLS = ").append(className).append(';'); - out.append("\n if (!CLS.$class) {"); + append("\n\n").append(assignClass(className)); + append("function ").append(className).append("() {"); + append("\n var CLS = ").append(className).append(';'); + append("\n if (!CLS.$class) {"); if (proto == null) { String sc = jc.getSuperClassName(); // with _ - out.append("\n var pp = "). - append(accessClass(sc.replace('/', '_'))).append("(true);"); - out.append("\n var p = CLS.prototype = pp;"); - out.append("\n var c = p;"); - out.append("\n var sprcls = pp.constructor.$class;"); + append("\n var pp = "). + append(accessClass(mangleClassName(sc))).append("(true);"); + append("\n var p = CLS.prototype = pp;"); + append("\n var c = p;"); + append("\n var sprcls = pp.constructor.$class;"); } else { - out.append("\n var p = CLS.prototype = ").append(proto[1]).append(";"); + append("\n var p = CLS.prototype = ").append(proto[1]).append(";"); if (proto[0] == null) { proto[0] = "p"; } - out.append("\n var c = ").append(proto[0]).append(";"); - out.append("\n var sprcls = null;"); + append("\n var c = ").append(proto[0]).append(";"); + append("\n var sprcls = null;"); } 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."). + if ((v.access & ACC_FINAL) != 0 && v.hasConstantValue()) { + if (v.getInternalSig().length() == 1 || v.getInternalSig().equals("Ljava/lang/String;")) { + continue; + } + } + append("\n CLS.fld_").append(v.getName()).append(initField(v)); + append("\n c._").append(v.getName()).append(" = function (v) {") + .append(" if (arguments.length == 1) CLS.fld_").append(v.getName()) + .append(" = v; return CLS.fld_"). append(v.getName()).append("; };"); } else { - out.append("\n c._").append(v.getName()).append(" = function (v) {") + append("\n c._").append(v.getName()).append(" = function (v) {") .append(" if (arguments.length == 1) this.fld_"). append(className).append('_').append(v.getName()) .append(" = v; return this.fld_"). @@ -187,14 +232,14 @@ ); if (only != null) { if (only[0] != null && only[1] != null) { - out.append("\n p.").append(only[0]).append(" = ") + append("\n p.").append(only[0]).append(" = ") .append(only[1]).append(";"); } continue; } String destObject; String mn; - out.append("\n "); + append("\n "); if (m.isStatic()) { destObject = "c"; mn = generateStaticMethod(destObject, m, toInitilize); @@ -210,39 +255,47 @@ declaredMethod(m, destObject, mn); byte[] runAnno = m.findAnnotationData(false); if (runAnno != null) { - out.append("\n ").append(destObject).append(".").append(mn).append(".anno = {"); - generateAnno(jc, out, runAnno); - out.append("\n };"); + append("\n ").append(destObject).append(".").append(mn).append(".anno = {"); + generateAnno(jc, runAnno); + append("\n };"); } - out.append("\n ").append(destObject).append(".").append(mn).append(".access = " + m.getAccess()).append(";"); - out.append("\n ").append(destObject).append(".").append(mn).append(".cls = CLS;"); + append("\n ").append(destObject).append(".").append(mn).append(".access = " + m.getAccess()).append(";"); + 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;"); + append("\n c.constructor = CLS;"); + append("\n function fillInstOf(x) {"); + String instOfName = "$instOf_" + className; + 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('/', '_'); + append("\n vm.").append(intrfc).append("(false)['fillInstOf'](x);"); + requireReference(superInterface); } - out.append("\n CLS.$class = 'temp';"); - out.append("\n CLS.$class = "); - out.append(accessClass("java_lang_Class(true);")); - out.append("\n CLS.$class.jvmName = '").append(jc.getClassName()).append("';"); - out.append("\n CLS.$class.superclass = sprcls;"); - out.append("\n CLS.$class.access = ").append(jc.getAccessFlags()+";"); - out.append("\n CLS.$class.cnstr = CLS;"); + append("\n }"); + append("\n c['fillInstOf'] = fillInstOf;"); + append("\n fillInstOf(c);"); +// obfuscationDelegate.exportJSProperty(this, "c", instOfName); + append("\n CLS.$class = 'temp';"); + append("\n CLS.$class = "); + append(accessClass("java_lang_Class(true);")); + append("\n CLS.$class.jvmName = '").append(jc.getClassName()).append("';"); + append("\n CLS.$class.superclass = sprcls;"); + append("\n CLS.$class.access = ").append(jc.getAccessFlags()+";"); + append("\n CLS.$class.cnstr = CLS;"); byte[] classAnno = jc.findAnnotationData(false); if (classAnno != null) { - out.append("\n CLS.$class.anno = {"); - generateAnno(jc, out, classAnno); - out.append("\n };"); + append("\n CLS.$class.anno = {"); + generateAnno(jc, classAnno); + append("\n };"); } for (String init : toInitilize.toArray()) { - out.append("\n ").append(init).append("();"); + append("\n ").append(init).append("();"); } - out.append("\n }"); - out.append("\n if (arguments.length === 0) {"); - out.append("\n if (!(this instanceof CLS)) {"); - out.append("\n return new CLS();"); - out.append("\n }"); + append("\n }"); + append("\n if (arguments.length === 0) {"); + append("\n if (!(this instanceof CLS)) {"); + append("\n return new CLS();"); + append("\n }"); for (FieldData v : jc.getFields()) { byte[] onlyArr = v.findAnnotationData(true); String[] only = findAnnotation(onlyArr, jc, @@ -251,21 +304,21 @@ ); if (only != null) { if (only[0] != null && only[1] != null) { - out.append("\n p.").append(only[0]).append(" = ") + append("\n p.").append(only[0]).append(" = ") .append(only[1]).append(";"); } continue; } if (!v.isStatic()) { - out.append("\n this.fld_"). + append("\n this.fld_"). append(className).append('_'). append(v.getName()).append(initField(v)); } } - out.append("\n return this;"); - out.append("\n }"); - out.append("\n return arguments[0] ? new CLS() : CLS.prototype;"); - out.append("\n};"); + append("\n return this;"); + append("\n }"); + append("\n return arguments[0] ? new CLS() : CLS.prototype;"); + append("\n};"); declaredClass(jc, className); @@ -305,34 +358,43 @@ final LocalsMapper lmapper = new LocalsMapper(stackMapIterator.getArguments()); - out.append(destObject).append(".").append(name).append(" = function("); - lmapper.outputArguments(out, m.isStatic()); - out.append(") {").append("\n"); + append(destObject).append(".").append(name).append(" = function("); + lmapper.outputArguments(this, m.isStatic()); + append(") {").append("\n"); final byte[] byteCodes = m.getCode(); if (byteCodes == null) { - out.append(" throw 'no code found for ") + append(" throw 'no code found for ") .append(jc.getClassName()).append('.') .append(m.getName()).append("';\n"); - out.append("};"); + append("};"); return; } final StackMapper smapper = new StackMapper(); if (!m.isStatic()) { - out.append(" var ").append(" lcA0 = this;\n"); + append(" var ").append(" lcA0 = this;\n"); } - int lastStackFrame = -1; + int lastStackFrame; TrapData[] previousTrap = null; boolean wide = false; + boolean didBranches; + if (stackMapIterator.isEmpty()) { + didBranches = false; + lastStackFrame = 0; + } else { + didBranches = true; + lastStackFrame = -1; + append("\n var gt = 0;\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; + outChanged = false; stackMapIterator.advanceTo(i); boolean changeInCatch = trap.advanceTo(i); if (changeInCatch || lastStackFrame != stackMapIterator.getFrameIndex()) { @@ -342,12 +404,13 @@ } } if (lastStackFrame != stackMapIterator.getFrameIndex()) { + smapper.flush(this); if (i != 0) { - out.append(" }\n"); + append(" }\n"); } if (openBraces > 64) { for (int c = 0; c < 64; c++) { - out.append("break;}\n"); + append("break;}\n"); } openBraces = 1; topMostLabel = i; @@ -356,85 +419,84 @@ lastStackFrame = stackMapIterator.getFrameIndex(); lmapper.syncWithFrameLocals(stackMapIterator.getFrameLocals()); smapper.syncWithFrameStack(stackMapIterator.getFrameStack()); - out.append(" X_" + i).append(": for (;;) { IF: if (gt <= " + i + ") {\n"); + append(" X_" + i).append(": for (;;) { IF: if (gt <= " + i + ") {\n"); openBraces++; changeInCatch = true; } else { debug(" /* " + i + " */ "); } if (changeInCatch && trap.useTry()) { - out.append("try {"); + append("try {"); previousTrap = trap.current(); } final int c = readUByte(byteCodes, i); switch (c) { case opc_aload_0: - emit(out, "var @1 = @2;", smapper.pushA(), lmapper.getA(0)); + smapper.assign(this, VarType.REFERENCE, lmapper.getA(0)); break; case opc_iload_0: - emit(out, "var @1 = @2;", smapper.pushI(), lmapper.getI(0)); + smapper.assign(this, VarType.INTEGER, lmapper.getI(0)); break; case opc_lload_0: - emit(out, "var @1 = @2;", smapper.pushL(), lmapper.getL(0)); + smapper.assign(this, VarType.LONG, lmapper.getL(0)); break; case opc_fload_0: - emit(out, "var @1 = @2;", smapper.pushF(), lmapper.getF(0)); + smapper.assign(this, VarType.FLOAT, lmapper.getF(0)); break; case opc_dload_0: - emit(out, "var @1 = @2;", smapper.pushD(), lmapper.getD(0)); + smapper.assign(this, VarType.DOUBLE, lmapper.getD(0)); break; case opc_aload_1: - emit(out, "var @1 = @2;", smapper.pushA(), lmapper.getA(1)); + smapper.assign(this, VarType.REFERENCE, lmapper.getA(1)); break; case opc_iload_1: - emit(out, "var @1 = @2;", smapper.pushI(), lmapper.getI(1)); + smapper.assign(this, VarType.INTEGER, lmapper.getI(1)); break; case opc_lload_1: - emit(out, "var @1 = @2;", smapper.pushL(), lmapper.getL(1)); + smapper.assign(this, VarType.LONG, lmapper.getL(1)); break; case opc_fload_1: - emit(out, "var @1 = @2;", smapper.pushF(), lmapper.getF(1)); + smapper.assign(this, VarType.FLOAT, lmapper.getF(1)); break; case opc_dload_1: - emit(out, "var @1 = @2;", smapper.pushD(), lmapper.getD(1)); + smapper.assign(this, VarType.DOUBLE, lmapper.getD(1)); break; case opc_aload_2: - emit(out, "var @1 = @2;", smapper.pushA(), lmapper.getA(2)); + smapper.assign(this, VarType.REFERENCE, lmapper.getA(2)); break; case opc_iload_2: - emit(out, "var @1 = @2;", smapper.pushI(), lmapper.getI(2)); + smapper.assign(this, VarType.INTEGER, lmapper.getI(2)); break; case opc_lload_2: - emit(out, "var @1 = @2;", smapper.pushL(), lmapper.getL(2)); + smapper.assign(this, VarType.LONG, lmapper.getL(2)); break; case opc_fload_2: - emit(out, "var @1 = @2;", smapper.pushF(), lmapper.getF(2)); + smapper.assign(this, VarType.FLOAT, lmapper.getF(2)); break; case opc_dload_2: - emit(out, "var @1 = @2;", smapper.pushD(), lmapper.getD(2)); + smapper.assign(this, VarType.DOUBLE, lmapper.getD(2)); break; case opc_aload_3: - emit(out, "var @1 = @2;", smapper.pushA(), lmapper.getA(3)); + smapper.assign(this, VarType.REFERENCE, lmapper.getA(3)); break; case opc_iload_3: - emit(out, "var @1 = @2;", smapper.pushI(), lmapper.getI(3)); + smapper.assign(this, VarType.INTEGER, lmapper.getI(3)); break; case opc_lload_3: - emit(out, "var @1 = @2;", smapper.pushL(), lmapper.getL(3)); + smapper.assign(this, VarType.LONG, lmapper.getL(3)); break; case opc_fload_3: - emit(out, "var @1 = @2;", smapper.pushF(), lmapper.getF(3)); + smapper.assign(this, VarType.FLOAT, lmapper.getF(3)); break; case opc_dload_3: - emit(out, "var @1 = @2;", smapper.pushD(), lmapper.getD(3)); + smapper.assign(this, VarType.DOUBLE, lmapper.getD(3)); break; case opc_iload: { ++i; final int indx = wide ? readUShort(byteCodes, i++) : readUByte(byteCodes, i); wide = false; - emit(out, "var @1 = @2;", - smapper.pushI(), lmapper.getI(indx)); + smapper.assign(this, VarType.INTEGER, lmapper.getI(indx)); break; } case opc_lload: { @@ -442,8 +504,7 @@ final int indx = wide ? readUShort(byteCodes, i++) : readUByte(byteCodes, i); wide = false; - emit(out, "var @1 = @2;", - smapper.pushL(), lmapper.getL(indx)); + smapper.assign(this, VarType.LONG, lmapper.getL(indx)); break; } case opc_fload: { @@ -451,8 +512,7 @@ final int indx = wide ? readUShort(byteCodes, i++) : readUByte(byteCodes, i); wide = false; - emit(out, "var @1 = @2;", - smapper.pushF(), lmapper.getF(indx)); + smapper.assign(this, VarType.FLOAT, lmapper.getF(indx)); break; } case opc_dload: { @@ -460,8 +520,7 @@ final int indx = wide ? readUShort(byteCodes, i++) : readUByte(byteCodes, i); wide = false; - emit(out, "var @1 = @2;", - smapper.pushD(), lmapper.getD(indx)); + smapper.assign(this, VarType.DOUBLE, lmapper.getD(indx)); break; } case opc_aload: { @@ -469,8 +528,7 @@ final int indx = wide ? readUShort(byteCodes, i++) : readUByte(byteCodes, i); wide = false; - emit(out, "var @1 = @2;", - smapper.pushA(), lmapper.getA(indx)); + smapper.assign(this, VarType.REFERENCE, lmapper.getA(indx)); break; } case opc_istore: { @@ -478,7 +536,7 @@ final int indx = wide ? readUShort(byteCodes, i++) : readUByte(byteCodes, i); wide = false; - emit(out, "var @1 = @2;", + emit(smapper, this, "var @1 = @2;", lmapper.setI(indx), smapper.popI()); break; } @@ -487,7 +545,7 @@ final int indx = wide ? readUShort(byteCodes, i++) : readUByte(byteCodes, i); wide = false; - emit(out, "var @1 = @2;", + emit(smapper, this, "var @1 = @2;", lmapper.setL(indx), smapper.popL()); break; } @@ -496,7 +554,7 @@ final int indx = wide ? readUShort(byteCodes, i++) : readUByte(byteCodes, i); wide = false; - emit(out, "var @1 = @2;", + emit(smapper, this, "var @1 = @2;", lmapper.setF(indx), smapper.popF()); break; } @@ -505,7 +563,7 @@ final int indx = wide ? readUShort(byteCodes, i++) : readUByte(byteCodes, i); wide = false; - emit(out, "var @1 = @2;", + emit(smapper, this, "var @1 = @2;", lmapper.setD(indx), smapper.popD()); break; } @@ -514,181 +572,181 @@ final int indx = wide ? readUShort(byteCodes, i++) : readUByte(byteCodes, i); wide = false; - emit(out, "var @1 = @2;", + emit(smapper, this, "var @1 = @2;", lmapper.setA(indx), smapper.popA()); break; } case opc_astore_0: - emit(out, "var @1 = @2;", lmapper.setA(0), smapper.popA()); + emit(smapper, this, "var @1 = @2;", lmapper.setA(0), smapper.popA()); break; case opc_istore_0: - emit(out, "var @1 = @2;", lmapper.setI(0), smapper.popI()); + emit(smapper, this, "var @1 = @2;", lmapper.setI(0), smapper.popI()); break; case opc_lstore_0: - emit(out, "var @1 = @2;", lmapper.setL(0), smapper.popL()); + emit(smapper, this, "var @1 = @2;", lmapper.setL(0), smapper.popL()); break; case opc_fstore_0: - emit(out, "var @1 = @2;", lmapper.setF(0), smapper.popF()); + emit(smapper, this, "var @1 = @2;", lmapper.setF(0), smapper.popF()); break; case opc_dstore_0: - emit(out, "var @1 = @2;", lmapper.setD(0), smapper.popD()); + emit(smapper, this, "var @1 = @2;", lmapper.setD(0), smapper.popD()); break; case opc_astore_1: - emit(out, "var @1 = @2;", lmapper.setA(1), smapper.popA()); + emit(smapper, this, "var @1 = @2;", lmapper.setA(1), smapper.popA()); break; case opc_istore_1: - emit(out, "var @1 = @2;", lmapper.setI(1), smapper.popI()); + emit(smapper, this, "var @1 = @2;", lmapper.setI(1), smapper.popI()); break; case opc_lstore_1: - emit(out, "var @1 = @2;", lmapper.setL(1), smapper.popL()); + emit(smapper, this, "var @1 = @2;", lmapper.setL(1), smapper.popL()); break; case opc_fstore_1: - emit(out, "var @1 = @2;", lmapper.setF(1), smapper.popF()); + emit(smapper, this, "var @1 = @2;", lmapper.setF(1), smapper.popF()); break; case opc_dstore_1: - emit(out, "var @1 = @2;", lmapper.setD(1), smapper.popD()); + emit(smapper, this, "var @1 = @2;", lmapper.setD(1), smapper.popD()); break; case opc_astore_2: - emit(out, "var @1 = @2;", lmapper.setA(2), smapper.popA()); + emit(smapper, this, "var @1 = @2;", lmapper.setA(2), smapper.popA()); break; case opc_istore_2: - emit(out, "var @1 = @2;", lmapper.setI(2), smapper.popI()); + emit(smapper, this, "var @1 = @2;", lmapper.setI(2), smapper.popI()); break; case opc_lstore_2: - emit(out, "var @1 = @2;", lmapper.setL(2), smapper.popL()); + emit(smapper, this, "var @1 = @2;", lmapper.setL(2), smapper.popL()); break; case opc_fstore_2: - emit(out, "var @1 = @2;", lmapper.setF(2), smapper.popF()); + emit(smapper, this, "var @1 = @2;", lmapper.setF(2), smapper.popF()); break; case opc_dstore_2: - emit(out, "var @1 = @2;", lmapper.setD(2), smapper.popD()); + emit(smapper, this, "var @1 = @2;", lmapper.setD(2), smapper.popD()); break; case opc_astore_3: - emit(out, "var @1 = @2;", lmapper.setA(3), smapper.popA()); + emit(smapper, this, "var @1 = @2;", lmapper.setA(3), smapper.popA()); break; case opc_istore_3: - emit(out, "var @1 = @2;", lmapper.setI(3), smapper.popI()); + emit(smapper, this, "var @1 = @2;", lmapper.setI(3), smapper.popI()); break; case opc_lstore_3: - emit(out, "var @1 = @2;", lmapper.setL(3), smapper.popL()); + emit(smapper, this, "var @1 = @2;", lmapper.setL(3), smapper.popL()); break; case opc_fstore_3: - emit(out, "var @1 = @2;", lmapper.setF(3), smapper.popF()); + emit(smapper, this, "var @1 = @2;", lmapper.setF(3), smapper.popF()); break; case opc_dstore_3: - emit(out, "var @1 = @2;", lmapper.setD(3), smapper.popD()); + emit(smapper, this, "var @1 = @2;", lmapper.setD(3), smapper.popD()); break; case opc_iadd: - emit(out, "@1 = @1.add32(@2);", smapper.getI(1), smapper.popI()); + smapper.replace(this, VarType.INTEGER, "(@1).add32(@2)", smapper.getI(1), smapper.popI()); break; case opc_ladd: - emit(out, "@1 = @1.add64(@2);", smapper.getL(1), smapper.popL()); + smapper.replace(this, VarType.LONG, "(@1).add64(@2)", smapper.getL(1), smapper.popL()); break; case opc_fadd: - emit(out, "@1 += @2;", smapper.getF(1), smapper.popF()); + smapper.replace(this, VarType.FLOAT, "(@1 + @2)", smapper.getF(1), smapper.popF()); break; case opc_dadd: - emit(out, "@1 += @2;", smapper.getD(1), smapper.popD()); + smapper.replace(this, VarType.DOUBLE, "(@1 + @2)", smapper.getD(1), smapper.popD()); break; case opc_isub: - emit(out, "@1 = @1.sub32(@2);", smapper.getI(1), smapper.popI()); + smapper.replace(this, VarType.INTEGER, "(@1).sub32(@2)", smapper.getI(1), smapper.popI()); break; case opc_lsub: - emit(out, "@1 = @1.sub64(@2);", smapper.getL(1), smapper.popL()); + smapper.replace(this, VarType.LONG, "(@1).sub64(@2)", smapper.getL(1), smapper.popL()); break; case opc_fsub: - emit(out, "@1 -= @2;", smapper.getF(1), smapper.popF()); + smapper.replace(this, VarType.FLOAT, "(@1 - @2)", smapper.getF(1), smapper.popF()); break; case opc_dsub: - emit(out, "@1 -= @2;", smapper.getD(1), smapper.popD()); + smapper.replace(this, VarType.DOUBLE, "(@1 - @2)", smapper.getD(1), smapper.popD()); break; case opc_imul: - emit(out, "@1 = @1.mul32(@2);", smapper.getI(1), smapper.popI()); + smapper.replace(this, VarType.INTEGER, "(@1).mul32(@2)", smapper.getI(1), smapper.popI()); break; case opc_lmul: - emit(out, "@1 = @1.mul64(@2);", smapper.getL(1), smapper.popL()); + smapper.replace(this, VarType.LONG, "(@1).mul64(@2)", smapper.getL(1), smapper.popL()); break; case opc_fmul: - emit(out, "@1 *= @2;", smapper.getF(1), smapper.popF()); + smapper.replace(this, VarType.FLOAT, "(@1 * @2)", smapper.getF(1), smapper.popF()); break; case opc_dmul: - emit(out, "@1 *= @2;", smapper.getD(1), smapper.popD()); + smapper.replace(this, VarType.DOUBLE, "(@1 * @2)", smapper.getD(1), smapper.popD()); break; case opc_idiv: - emit(out, "@1 = @1.div32(@2);", + smapper.replace(this, VarType.INTEGER, "(@1).div32(@2)", smapper.getI(1), smapper.popI()); break; case opc_ldiv: - emit(out, "@1 = @1.div64(@2);", + smapper.replace(this, VarType.LONG, "(@1).div64(@2)", smapper.getL(1), smapper.popL()); break; case opc_fdiv: - emit(out, "@1 /= @2;", smapper.getF(1), smapper.popF()); + smapper.replace(this, VarType.FLOAT, "(@1 / @2)", smapper.getF(1), smapper.popF()); break; case opc_ddiv: - emit(out, "@1 /= @2;", smapper.getD(1), smapper.popD()); + smapper.replace(this, VarType.DOUBLE, "(@1 / @2)", smapper.getD(1), smapper.popD()); break; case opc_irem: - emit(out, "@1 = @1.mod32(@2);", + smapper.replace(this, VarType.INTEGER, "(@1).mod32(@2)", smapper.getI(1), smapper.popI()); break; case opc_lrem: - emit(out, "@1 = @1.mod64(@2);", + smapper.replace(this, VarType.LONG, "(@1).mod64(@2)", smapper.getL(1), smapper.popL()); break; case opc_frem: - emit(out, "@1 %= @2;", smapper.getF(1), smapper.popF()); + smapper.replace(this, VarType.FLOAT, "(@1 % @2)", smapper.getF(1), smapper.popF()); break; case opc_drem: - emit(out, "@1 %= @2;", smapper.getD(1), smapper.popD()); + smapper.replace(this, VarType.DOUBLE, "(@1 % @2)", smapper.getD(1), smapper.popD()); break; case opc_iand: - emit(out, "@1 &= @2;", smapper.getI(1), smapper.popI()); + smapper.replace(this, VarType.INTEGER, "(@1 & @2)", smapper.getI(1), smapper.popI()); break; case opc_land: - emit(out, "@1 = @1.and64(@2);", smapper.getL(1), smapper.popL()); + smapper.replace(this, VarType.LONG, "(@1).and64(@2)", smapper.getL(1), smapper.popL()); break; case opc_ior: - emit(out, "@1 |= @2;", smapper.getI(1), smapper.popI()); + smapper.replace(this, VarType.INTEGER, "(@1 | @2)", smapper.getI(1), smapper.popI()); break; case opc_lor: - emit(out, "@1 = @1.or64(@2);", smapper.getL(1), smapper.popL()); + smapper.replace(this, VarType.LONG, "(@1).or64(@2)", smapper.getL(1), smapper.popL()); break; case opc_ixor: - emit(out, "@1 ^= @2;", smapper.getI(1), smapper.popI()); + smapper.replace(this, VarType.INTEGER, "(@1 ^ @2)", smapper.getI(1), smapper.popI()); break; case opc_lxor: - emit(out, "@1 = @1.xor64(@2);", smapper.getL(1), smapper.popL()); + smapper.replace(this, VarType.LONG, "(@1).xor64(@2)", smapper.getL(1), smapper.popL()); break; case opc_ineg: - emit(out, "@1 = @1.neg32();", smapper.getI(0)); + smapper.replace(this, VarType.INTEGER, "(@1).neg32()", smapper.getI(0)); break; case opc_lneg: - emit(out, "@1 = @1.neg64();", smapper.getL(0)); + smapper.replace(this, VarType.LONG, "(@1).neg64()", smapper.getL(0)); break; case opc_fneg: - emit(out, "@1 = -@1;", smapper.getF(0)); + smapper.replace(this, VarType.FLOAT, "(-@1)", smapper.getF(0)); break; case opc_dneg: - emit(out, "@1 = -@1;", smapper.getD(0)); + smapper.replace(this, VarType.DOUBLE, "(-@1)", smapper.getD(0)); break; case opc_ishl: - emit(out, "@1 <<= @2;", smapper.getI(1), smapper.popI()); + smapper.replace(this, VarType.INTEGER, "(@1 << @2)", smapper.getI(1), smapper.popI()); break; case opc_lshl: - emit(out, "@1 = @1.shl64(@2);", smapper.getL(1), smapper.popI()); + smapper.replace(this, VarType.LONG, "(@1).shl64(@2)", smapper.getL(1), smapper.popI()); break; case opc_ishr: - emit(out, "@1 >>= @2;", smapper.getI(1), smapper.popI()); + smapper.replace(this, VarType.INTEGER, "(@1 >> @2)", smapper.getI(1), smapper.popI()); break; case opc_lshr: - emit(out, "@1 = @1.shr64(@2);", smapper.getL(1), smapper.popI()); + smapper.replace(this, VarType.LONG, "(@1).shr64(@2)", smapper.getL(1), smapper.popI()); break; case opc_iushr: - emit(out, "@1 >>>= @2;", smapper.getI(1), smapper.popI()); + smapper.replace(this, VarType.INTEGER, "(@1 >>> @2)", smapper.getI(1), smapper.popI()); break; case opc_lushr: - emit(out, "@1 = @1.ushr64(@2);", smapper.getL(1), smapper.popI()); + smapper.replace(this, VarType.LONG, "(@1).ushr64(@2)", smapper.getL(1), smapper.popI()); break; case opc_iinc: { ++i; @@ -699,132 +757,132 @@ : byteCodes[i]; wide = false; if (incrBy == 1) { - emit(out, "@1++;", lmapper.getI(varIndx)); + emit(smapper, this, "@1++;", lmapper.getI(varIndx)); } else { - emit(out, "@1 += @2;", + emit(smapper, this, "@1 += @2;", lmapper.getI(varIndx), Integer.toString(incrBy)); } break; } case opc_return: - emit(out, "return;"); + emit(smapper, this, "return;"); break; case opc_ireturn: - emit(out, "return @1;", smapper.popI()); + emit(smapper, this, "return @1;", smapper.popI()); break; case opc_lreturn: - emit(out, "return @1;", smapper.popL()); + emit(smapper, this, "return @1;", smapper.popL()); break; case opc_freturn: - emit(out, "return @1;", smapper.popF()); + emit(smapper, this, "return @1;", smapper.popF()); break; case opc_dreturn: - emit(out, "return @1;", smapper.popD()); + emit(smapper, this, "return @1;", smapper.popD()); break; case opc_areturn: - emit(out, "return @1;", smapper.popA()); + emit(smapper, this, "return @1;", smapper.popA()); break; case opc_i2l: - emit(out, "var @2 = @1;", smapper.popI(), smapper.pushL()); + smapper.replace(this, VarType.LONG, "@1", smapper.getI(0)); break; case opc_i2f: - emit(out, "var @2 = @1;", smapper.popI(), smapper.pushF()); + smapper.replace(this, VarType.FLOAT, "@1", smapper.getI(0)); break; case opc_i2d: - emit(out, "var @2 = @1;", smapper.popI(), smapper.pushD()); + smapper.replace(this, VarType.DOUBLE, "@1", smapper.getI(0)); break; case opc_l2i: - emit(out, "var @2 = @1.toInt32();", smapper.popL(), smapper.pushI()); + smapper.replace(this, VarType.INTEGER, "(@1).toInt32()", smapper.getL(0)); break; // max int check? case opc_l2f: - emit(out, "var @2 = @1.toFP();", smapper.popL(), smapper.pushF()); + smapper.replace(this, VarType.FLOAT, "(@1).toFP()", smapper.getL(0)); break; case opc_l2d: - emit(out, "var @2 = @1.toFP();", smapper.popL(), smapper.pushD()); + smapper.replace(this, VarType.DOUBLE, "(@1).toFP()", smapper.getL(0)); break; case opc_f2d: - emit(out, "var @2 = @1;", smapper.popF(), smapper.pushD()); + smapper.replace(this, VarType.DOUBLE, "@1", + smapper.getF(0)); break; case opc_d2f: - emit(out, "var @2 = @1;", smapper.popD(), smapper.pushF()); + smapper.replace(this, VarType.FLOAT, "@1", + smapper.getD(0)); break; case opc_f2i: - emit(out, "var @2 = @1.toInt32();", - smapper.popF(), smapper.pushI()); + smapper.replace(this, VarType.INTEGER, "(@1).toInt32()", + smapper.getF(0)); break; case opc_f2l: - emit(out, "var @2 = @1.toLong();", - smapper.popF(), smapper.pushL()); + smapper.replace(this, VarType.LONG, "(@1).toLong()", + smapper.getF(0)); break; case opc_d2i: - emit(out, "var @2 = @1.toInt32();", - smapper.popD(), smapper.pushI()); + smapper.replace(this, VarType.INTEGER, "(@1).toInt32()", + smapper.getD(0)); break; case opc_d2l: - emit(out, "var @2 = @1.toLong();", - smapper.popD(), smapper.pushL()); + smapper.replace(this, VarType.LONG, "(@1).toLong()", smapper.getD(0)); break; case opc_i2b: - emit(out, "var @1 = @1.toInt8();", smapper.getI(0)); + smapper.replace(this, VarType.INTEGER, "(@1).toInt8()", smapper.getI(0)); break; case opc_i2c: - out.append("{ /* number conversion */ }"); break; case opc_i2s: - emit(out, "var @1 = @1.toInt16();", smapper.getI(0)); + smapper.replace(this, VarType.INTEGER, "(@1).toInt16()", smapper.getI(0)); break; case opc_aconst_null: - emit(out, "var @1 = null;", smapper.pushA()); + smapper.assign(this, VarType.REFERENCE, "null"); break; case opc_iconst_m1: - emit(out, "var @1 = -1;", smapper.pushI()); + smapper.assign(this, VarType.INTEGER, "-1"); break; case opc_iconst_0: - emit(out, "var @1 = 0;", smapper.pushI()); + smapper.assign(this, VarType.INTEGER, "0"); break; case opc_dconst_0: - emit(out, "var @1 = 0;", smapper.pushD()); + smapper.assign(this, VarType.DOUBLE, "0"); break; case opc_lconst_0: - emit(out, "var @1 = 0;", smapper.pushL()); + smapper.assign(this, VarType.LONG, "0"); break; case opc_fconst_0: - emit(out, "var @1 = 0;", smapper.pushF()); + smapper.assign(this, VarType.FLOAT, "0"); break; case opc_iconst_1: - emit(out, "var @1 = 1;", smapper.pushI()); + smapper.assign(this, VarType.INTEGER, "1"); break; case opc_lconst_1: - emit(out, "var @1 = 1;", smapper.pushL()); + smapper.assign(this, VarType.LONG, "1"); break; case opc_fconst_1: - emit(out, "var @1 = 1;", smapper.pushF()); + smapper.assign(this, VarType.FLOAT, "1"); break; case opc_dconst_1: - emit(out, "var @1 = 1;", smapper.pushD()); + smapper.assign(this, VarType.DOUBLE, "1"); break; case opc_iconst_2: - emit(out, "var @1 = 2;", smapper.pushI()); + smapper.assign(this, VarType.INTEGER, "2"); break; case opc_fconst_2: - emit(out, "var @1 = 2;", smapper.pushF()); + smapper.assign(this, VarType.FLOAT, "2"); break; case opc_iconst_3: - emit(out, "var @1 = 3;", smapper.pushI()); + smapper.assign(this, VarType.INTEGER, "3"); break; case opc_iconst_4: - emit(out, "var @1 = 4;", smapper.pushI()); + smapper.assign(this, VarType.INTEGER, "4"); break; case opc_iconst_5: - emit(out, "var @1 = 5;", smapper.pushI()); + smapper.assign(this, VarType.INTEGER, "5"); break; case opc_ldc: { int indx = readUByte(byteCodes, ++i); String v = encodeConstant(indx); int type = VarType.fromConstantType(jc.getTag(indx)); - emit(out, "var @1 = @2;", smapper.pushT(type), v); + smapper.assign(this, type, v); break; } case opc_ldc_w: @@ -837,118 +895,122 @@ final Long lv = new Long(v); final int low = (int)(lv.longValue() & 0xFFFFFFFF); final int hi = (int)(lv.longValue() >> 32); - emit(out, "var @1 = 0x@3.next32(0x@2);", smapper.pushL(), - Integer.toHexString(low), Integer.toHexString(hi)); + if (hi == 0) { + smapper.assign(this, VarType.LONG, "0x" + Integer.toHexString(low)); + } else { + smapper.assign(this, VarType.LONG, + "0x" + Integer.toHexString(hi) + ".next32(0x" + + Integer.toHexString(low) + ")" + ); + } } else { - emit(out, "var @1 = @2;", smapper.pushT(type), v); + smapper.assign(this, type, v); } break; } case opc_lcmp: - emit(out, "var @3 = @2.compare64(@1);", - smapper.popL(), smapper.popL(), smapper.pushI()); + smapper.replace(this, VarType.INTEGER, "(@2).compare64(@1)", smapper.popL(), smapper.getL(0)); break; case opc_fcmpl: case opc_fcmpg: - emit(out, "var @3 = (@2 == @1) ? 0 : ((@2 < @1) ? -1 : 1);", - smapper.popF(), smapper.popF(), smapper.pushI()); + smapper.replace(this, VarType.INTEGER, "(@2).compare(@1)", smapper.popF(), smapper.getF(0)); break; case opc_dcmpl: case opc_dcmpg: - emit(out, "var @3 = (@2 == @1) ? 0 : ((@2 < @1) ? -1 : 1);", - smapper.popD(), smapper.popD(), smapper.pushI()); + smapper.replace(this, VarType.INTEGER, "(@2).compare(@1)", smapper.popD(), smapper.getD(0)); break; case opc_if_acmpeq: - i = generateIf(byteCodes, i, smapper.popA(), smapper.popA(), + i = generateIf(smapper, byteCodes, i, smapper.popA(), smapper.popA(), "===", topMostLabel); break; case opc_if_acmpne: - i = generateIf(byteCodes, i, smapper.popA(), smapper.popA(), + i = generateIf(smapper, byteCodes, i, smapper.popA(), smapper.popA(), "!==", topMostLabel); break; case opc_if_icmpeq: - i = generateIf(byteCodes, i, smapper.popI(), smapper.popI(), + i = generateIf(smapper, byteCodes, i, smapper.popI(), smapper.popI(), "==", topMostLabel); break; case opc_ifeq: { int indx = i + readShortArg(byteCodes, i); - emitIf(out, "if (@1 == 0) ", + emitIf(smapper, this, "if ((@1) == 0) ", smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifne: { int indx = i + readShortArg(byteCodes, i); - emitIf(out, "if (@1 != 0) ", + emitIf(smapper, this, "if ((@1) != 0) ", smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_iflt: { int indx = i + readShortArg(byteCodes, i); - emitIf(out, "if (@1 < 0) ", + emitIf(smapper, this, "if ((@1) < 0) ", smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifle: { int indx = i + readShortArg(byteCodes, i); - emitIf(out, "if (@1 <= 0) ", + emitIf(smapper, this, "if ((@1) <= 0) ", smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifgt: { int indx = i + readShortArg(byteCodes, i); - emitIf(out, "if (@1 > 0) ", + emitIf(smapper, this, "if ((@1) > 0) ", smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifge: { int indx = i + readShortArg(byteCodes, i); - emitIf(out, "if (@1 >= 0) ", + emitIf(smapper, this, "if ((@1) >= 0) ", smapper.popI(), i, indx, topMostLabel); i += 2; break; } case opc_ifnonnull: { int indx = i + readShortArg(byteCodes, i); - emitIf(out, "if (@1 !== null) ", + emitIf(smapper, this, "if ((@1) !== null) ", smapper.popA(), i, indx, topMostLabel); i += 2; break; } case opc_ifnull: { int indx = i + readShortArg(byteCodes, i); - emitIf(out, "if (@1 === null) ", + emitIf(smapper, this, "if ((@1) === null) ", smapper.popA(), i, indx, topMostLabel); i += 2; break; } case opc_if_icmpne: - i = generateIf(byteCodes, i, smapper.popI(), smapper.popI(), + i = generateIf(smapper, byteCodes, i, smapper.popI(), smapper.popI(), "!=", topMostLabel); break; case opc_if_icmplt: - i = generateIf(byteCodes, i, smapper.popI(), smapper.popI(), + i = generateIf(smapper, byteCodes, i, smapper.popI(), smapper.popI(), "<", topMostLabel); break; case opc_if_icmple: - i = generateIf(byteCodes, i, smapper.popI(), smapper.popI(), + i = generateIf(smapper, byteCodes, i, smapper.popI(), smapper.popI(), "<=", topMostLabel); break; case opc_if_icmpgt: - i = generateIf(byteCodes, i, smapper.popI(), smapper.popI(), + i = generateIf(smapper, byteCodes, i, smapper.popI(), smapper.popI(), ">", topMostLabel); break; case opc_if_icmpge: - i = generateIf(byteCodes, i, smapper.popI(), smapper.popI(), + i = generateIf(smapper, byteCodes, i, smapper.popI(), smapper.popI(), ">=", topMostLabel); break; case opc_goto: { + smapper.flush(this); int indx = i + readShortArg(byteCodes, i); - goTo(out, i, indx, topMostLabel); + goTo(this, i, indx, topMostLabel); i += 2; break; } @@ -976,8 +1038,8 @@ case opc_new: { int indx = readUShortArg(byteCodes, i); String ci = jc.getClassName(indx); - emit(out, "var @1 = new @2;", - smapper.pushA(), accessClass(ci.replace('/', '_'))); + emit(smapper, this, "var @1 = new @2;", + smapper.pushA(), accessClass(mangleClassName(ci))); addReference(ci); i += 2; break; @@ -999,54 +1061,53 @@ break; } case opc_arraylength: - emit(out, "var @2 = @1.length;", - smapper.popA(), smapper.pushI()); + smapper.replace(this, VarType.INTEGER, "(@1).length", smapper.getA(0)); break; case opc_lastore: - emit(out, "Array.at(@3, @2, @1);", + emit(smapper, this, "Array.at(@3, @2, @1);", smapper.popL(), smapper.popI(), smapper.popA()); break; case opc_fastore: - emit(out, "Array.at(@3, @2, @1);", + emit(smapper, this, "Array.at(@3, @2, @1);", smapper.popF(), smapper.popI(), smapper.popA()); break; case opc_dastore: - emit(out, "Array.at(@3, @2, @1);", + emit(smapper, this, "Array.at(@3, @2, @1);", smapper.popD(), smapper.popI(), smapper.popA()); break; case opc_aastore: - emit(out, "Array.at(@3, @2, @1);", + emit(smapper, this, "Array.at(@3, @2, @1);", smapper.popA(), smapper.popI(), smapper.popA()); break; case opc_iastore: case opc_bastore: case opc_castore: case opc_sastore: - emit(out, "Array.at(@3, @2, @1);", + emit(smapper, this, "Array.at(@3, @2, @1);", smapper.popI(), smapper.popI(), smapper.popA()); break; case opc_laload: - emit(out, "var @3 = Array.at(@2, @1);", - smapper.popI(), smapper.popA(), smapper.pushL()); + smapper.replace(this, VarType.LONG, "Array.at(@2, @1)", + smapper.popI(), smapper.getA(0)); break; case opc_faload: - emit(out, "var @3 = Array.at(@2, @1);", - smapper.popI(), smapper.popA(), smapper.pushF()); + smapper.replace(this, VarType.FLOAT, "Array.at(@2, @1)", + smapper.popI(), smapper.getA(0)); break; case opc_daload: - emit(out, "var @3 = Array.at(@2, @1);", - smapper.popI(), smapper.popA(), smapper.pushD()); + smapper.replace(this, VarType.DOUBLE, "Array.at(@2, @1)", + smapper.popI(), smapper.getA(0)); break; case opc_aaload: - emit(out, "var @3 = Array.at(@2, @1);", - smapper.popI(), smapper.popA(), smapper.pushA()); + smapper.replace(this, VarType.REFERENCE, "Array.at(@2, @1)", + smapper.popI(), smapper.getA(0)); break; case opc_iaload: case opc_baload: case opc_caload: case opc_saload: - emit(out, "var @3 = Array.at(@2, @1);", - smapper.popI(), smapper.popA(), smapper.pushI()); + smapper.replace(this, VarType.INTEGER, "Array.at(@2, @1)", + smapper.popI(), smapper.getA(0)); break; case opc_pop: case opc_pop2: @@ -1055,86 +1116,86 @@ break; case opc_dup: { final Variable v = smapper.get(0); - emit(out, "var @1 = @2;", smapper.pushT(v.getType()), v); + emit(smapper, this, "var @1 = @2;", smapper.pushT(v.getType()), v); break; } case opc_dup2: { final Variable vi1 = smapper.get(0); if (vi1.isCategory2()) { - emit(out, "var @1 = @2;", + emit(smapper, this, "var @1 = @2;", smapper.pushT(vi1.getType()), vi1); } else { final Variable vi2 = smapper.get(1); - emit(out, "var @1 = @2, @3 = @4;", + emit(smapper, this, "var @1 = @2, @3 = @4;", smapper.pushT(vi2.getType()), vi2, smapper.pushT(vi1.getType()), vi1); } break; } case opc_dup_x1: { - final Variable vi1 = smapper.pop(); - final Variable vi2 = smapper.pop(); + final Variable vi1 = smapper.pop(this); + final Variable vi2 = smapper.pop(this); final Variable vo3 = smapper.pushT(vi1.getType()); final Variable vo2 = smapper.pushT(vi2.getType()); final Variable vo1 = smapper.pushT(vi1.getType()); - emit(out, "var @1 = @2, @3 = @4, @5 = @6;", + emit(smapper, this, "var @1 = @2, @3 = @4, @5 = @6;", vo1, vi1, vo2, vi2, vo3, vo1); break; } case opc_dup2_x1: { - final Variable vi1 = smapper.pop(); - final Variable vi2 = smapper.pop(); + final Variable vi1 = smapper.pop(this); + final Variable vi2 = smapper.pop(this); if (vi1.isCategory2()) { final Variable vo3 = smapper.pushT(vi1.getType()); final Variable vo2 = smapper.pushT(vi2.getType()); final Variable vo1 = smapper.pushT(vi1.getType()); - emit(out, "var @1 = @2, @3 = @4, @5 = @6;", + emit(smapper, this, "var @1 = @2, @3 = @4, @5 = @6;", vo1, vi1, vo2, vi2, vo3, vo1); } else { - final Variable vi3 = smapper.pop(); + final Variable vi3 = smapper.pop(this); final Variable vo5 = smapper.pushT(vi2.getType()); final Variable vo4 = smapper.pushT(vi1.getType()); final Variable vo3 = smapper.pushT(vi3.getType()); final Variable vo2 = smapper.pushT(vi2.getType()); final Variable vo1 = smapper.pushT(vi1.getType()); - emit(out, "var @1 = @2, @3 = @4, @5 = @6,", + emit(smapper, this, "var @1 = @2, @3 = @4, @5 = @6,", vo1, vi1, vo2, vi2, vo3, vi3); - emit(out, " @1 = @2, @3 = @4;", + emit(smapper, this, " @1 = @2, @3 = @4;", vo4, vo1, vo5, vo2); } break; } case opc_dup_x2: { - final Variable vi1 = smapper.pop(); - final Variable vi2 = smapper.pop(); + final Variable vi1 = smapper.pop(this); + final Variable vi2 = smapper.pop(this); if (vi2.isCategory2()) { final Variable vo3 = smapper.pushT(vi1.getType()); final Variable vo2 = smapper.pushT(vi2.getType()); final Variable vo1 = smapper.pushT(vi1.getType()); - emit(out, "var @1 = @2, @3 = @4, @5 = @6;", + emit(smapper, this, "var @1 = @2, @3 = @4, @5 = @6;", vo1, vi1, vo2, vi2, vo3, vo1); } else { - final Variable vi3 = smapper.pop(); + final Variable vi3 = smapper.pop(this); final Variable vo4 = smapper.pushT(vi1.getType()); final Variable vo3 = smapper.pushT(vi3.getType()); final Variable vo2 = smapper.pushT(vi2.getType()); final Variable vo1 = smapper.pushT(vi1.getType()); - emit(out, "var @1 = @2, @3 = @4, @5 = @6, @7 = @8;", + emit(smapper, this, "var @1 = @2, @3 = @4, @5 = @6, @7 = @8;", vo1, vi1, vo2, vi2, vo3, vi3, vo4, vo1); } break; } case opc_dup2_x2: { - final Variable vi1 = smapper.pop(); - final Variable vi2 = smapper.pop(); + final Variable vi1 = smapper.pop(this); + final Variable vi2 = smapper.pop(this); if (vi1.isCategory2()) { if (vi2.isCategory2()) { @@ -1142,20 +1203,20 @@ final Variable vo2 = smapper.pushT(vi2.getType()); final Variable vo1 = smapper.pushT(vi1.getType()); - emit(out, "var @1 = @2, @3 = @4, @5 = @6;", + emit(smapper, this, "var @1 = @2, @3 = @4, @5 = @6;", vo1, vi1, vo2, vi2, vo3, vo1); } else { - final Variable vi3 = smapper.pop(); + final Variable vi3 = smapper.pop(this); final Variable vo4 = smapper.pushT(vi1.getType()); final Variable vo3 = smapper.pushT(vi3.getType()); final Variable vo2 = smapper.pushT(vi2.getType()); final Variable vo1 = smapper.pushT(vi1.getType()); - emit(out, "var @1 = @2, @3 = @4, @5 = @6, @7 = @8;", + emit(smapper, this, "var @1 = @2, @3 = @4, @5 = @6, @7 = @8;", vo1, vi1, vo2, vi2, vo3, vi3, vo4, vo1); } } else { - final Variable vi3 = smapper.pop(); + final Variable vi3 = smapper.pop(this); if (vi3.isCategory2()) { final Variable vo5 = smapper.pushT(vi2.getType()); @@ -1164,12 +1225,12 @@ final Variable vo2 = smapper.pushT(vi2.getType()); final Variable vo1 = smapper.pushT(vi1.getType()); - emit(out, "var @1 = @2, @3 = @4, @5 = @6,", + emit(smapper, this, "var @1 = @2, @3 = @4, @5 = @6,", vo1, vi1, vo2, vi2, vo3, vi3); - emit(out, " @1 = @2, @3 = @4;", + emit(smapper, this, " @1 = @2, @3 = @4;", vo4, vo1, vo5, vo2); } else { - final Variable vi4 = smapper.pop(); + final Variable vi4 = smapper.pop(this); final Variable vo6 = smapper.pushT(vi2.getType()); final Variable vo5 = smapper.pushT(vi1.getType()); final Variable vo4 = smapper.pushT(vi4.getType()); @@ -1177,9 +1238,9 @@ final Variable vo2 = smapper.pushT(vi2.getType()); final Variable vo1 = smapper.pushT(vi1.getType()); - emit(out, "var @1 = @2, @3 = @4, @5 = @6, @7 = @8,", + emit(smapper, this, "var @1 = @2, @3 = @4, @5 = @6, @7 = @8,", vo1, vi1, vo2, vi2, vo3, vi3, vo4, vi4); - emit(out, " @1 = @2, @3 = @4;", + emit(smapper, this, " @1 = @2, @3 = @4;", vo5, vo1, vo6, vo2); } } @@ -1192,7 +1253,7 @@ if (vi1.getType() == vi2.getType()) { final Variable tmp = smapper.pushT(vi1.getType()); - emit(out, "var @1 = @2, @2 = @3, @3 = @1;", + emit(smapper, this, "var @1 = @2, @2 = @3, @3 = @1;", tmp, vi1, vi2); smapper.pop(1); } else { @@ -1203,26 +1264,26 @@ break; } case opc_bipush: - emit(out, "var @1 = @2;", - smapper.pushI(), Integer.toString(byteCodes[++i])); + smapper.assign(this, VarType.INTEGER, + "(" + Integer.toString(byteCodes[++i]) + ")"); break; case opc_sipush: - emit(out, "var @1 = @2;", - smapper.pushI(), - Integer.toString(readShortArg(byteCodes, i))); + smapper.assign(this, VarType.INTEGER, + "(" + Integer.toString(readShortArg(byteCodes, i)) + ")" + ); i += 2; break; case opc_getfield: { 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]); + final String mangleClass = mangleClassName(fi[0]); final String mangleClassAccess = accessClass(mangleClass); - emit(out, "var @2 = @3.call(@1);", - smapper.popA(), - smapper.pushT(type), + smapper.replace(this, type, "@2.call(@1)", + smapper.getA(0), accessField(mangleClassAccess + "(false)", - "_" + fi[1], fi)); + "_" + fi[1], fi) + ); i += 2; break; } @@ -1230,9 +1291,9 @@ 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]); + final String mangleClass = mangleClassName(fi[0]); final String mangleClassAccess = accessClass(mangleClass); - emit(out, "@3.call(@2, @1);", + emit(smapper, this, "@3.call(@2, @1);", smapper.popT(type), smapper.popA(), accessField(mangleClassAccess + "(false)", @@ -1244,11 +1305,8 @@ int indx = readUShortArg(byteCodes, i); String[] fi = jc.getFieldInfoName(indx); final int type = VarType.fromFieldType(fi[2].charAt(0)); - emit(out, "var @1 = @2();", - smapper.pushT(type), - accessField(accessClass(fi[0].replace('/', '_')) - + "(false)", - "_" + fi[1], fi)); + String ac = accessClass(mangleClassName(fi[0])); + smapper.assign(this, type, ac + "(false)._" + fi[1] + "()"); i += 2; addReference(fi[0]); break; @@ -1257,10 +1315,8 @@ int indx = readUShortArg(byteCodes, i); String[] fi = jc.getFieldInfoName(indx); final int type = VarType.fromFieldType(fi[2].charAt(0)); - emit(out, "@1(@2);", - accessField(accessClass(fi[0].replace('/', '_')) - + "(false)", - "_" + fi[1], fi), + emit(smapper, this, "@1(false)._@2(@3);", + accessClass(mangleClassName(fi[0])), fi[1], smapper.popT(type)); i += 2; addReference(fi[0]); @@ -1279,22 +1335,22 @@ break; } case opc_athrow: { - final Variable v = smapper.popA(); + final CharSequence v = smapper.popA(); smapper.clear(); - emit(out, "{ var @1 = @2; throw @2; }", + emit(smapper, this, "{ var @1 = @2; throw @2; }", smapper.pushA(), v); break; } case opc_monitorenter: { - out.append("/* monitor enter */"); + debug("/* monitor enter */"); smapper.popA(); break; } case opc_monitorexit: { - out.append("/* monitor exit */"); + debug("/* monitor exit */"); smapper.popA(); break; } @@ -1305,31 +1361,39 @@ default: { wide = false; - emit(out, "throw 'unknown bytecode @1';", + emit(smapper, this, "throw 'unknown bytecode @1';", Integer.toString(c)); } } if (debug(" //")) { generateByteCodeComment(prev, i, byteCodes); } - out.append("\n"); + if (outChanged) { + append("\n"); + } } if (previousTrap != null) { generateCatch(previousTrap, byteCodes.length, topMostLabel); } - out.append("\n }\n"); + if (didBranches) { + append("\n }\n"); + } while (openBraces-- > 0) { - out.append('}'); + append('}'); } - out.append("\n};"); + append("\n};"); } - private int generateIf(byte[] byteCodes, int i, final Variable v2, final Variable v1, final String test, int topMostLabel) throws IOException { + private int generateIf(StackMapper mapper, byte[] byteCodes, + int i, final CharSequence v2, final CharSequence v1, + final String test, int topMostLabel + ) throws IOException { + mapper.flush(this); int indx = i + readShortArg(byteCodes, i); - out.append("if (").append(v1) - .append(' ').append(test).append(' ') - .append(v2).append(") "); - goTo(out, i, indx, topMostLabel); + append("if ((").append(v1) + .append(") ").append(test).append(" (") + .append(v2).append(")) "); + goTo(this, i, indx, topMostLabel); return i + 2; } @@ -1442,8 +1506,20 @@ return mangleSig(sig, 0, sig.length()); } + private static String mangleMethodName(String name) { + StringBuilder sb = new StringBuilder(name.length() * 2); + int last = name.length(); + for (int i = 0; i < last; i++) { + final char ch = name.charAt(i); + switch (ch) { + case '_': sb.append("_1"); break; + default: sb.append(ch); break; + } + } + return sb.toString(); + } private static String mangleSig(String txt, int first, int last) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder((last - first) * 2); for (int i = first; i < last; i++) { final char ch = txt.charAt(i); switch (ch) { @@ -1456,6 +1532,10 @@ } return sb.toString(); } + + private static String mangleClassName(String name) { + return mangleSig(name); + } private static String findMethodName(MethodData m, StringBuilder cnt) { StringBuilder name = new StringBuilder(); @@ -1464,7 +1544,7 @@ } else if ("".equals(m.getName())) { // NOI18N name.append("class"); // NOI18N } else { - name.append(m.getName()); + name.append(mangleMethodName(m.getName())); } countArgs(m.getInternalSig(), new char[1], name, cnt); @@ -1478,7 +1558,7 @@ if ("".equals(nm)) { // NOI18N name.append("cons"); // NOI18N } else { - name.append(nm); + name.append(mangleMethodName(nm)); } countArgs(descr, returnType, name, cnt); return name.toString(); @@ -1493,14 +1573,14 @@ String mn = findMethodName(mi, cnt, returnType); final int numArguments = isStatic ? cnt.length() : cnt.length() + 1; - final Variable[] vars = new Variable[numArguments]; + final CharSequence[] vars = new CharSequence[numArguments]; for (int j = numArguments - 1; j >= 0; --j) { - vars[j] = mapper.pop(); + vars[j] = mapper.popValue(); } if (returnType[0] != 'V') { - out.append("var ") + append("var ") .append(mapper.pushT(VarType.fromFieldType(returnType[0]))) .append(" = "); } @@ -1510,20 +1590,20 @@ if (mn.startsWith("cons_")) { object += ".constructor"; } - out.append(accessStaticMethod(object, mn, mi)); + append(accessStaticMethod(object, mn, mi)); if (isStatic) { - out.append('('); + append('('); } else { - out.append(".call("); + append(".call("); } if (numArguments > 0) { - out.append(vars[0]); + append(vars[0]); for (int j = 1; j < numArguments; ++j) { - out.append(", "); - out.append(vars[j]); + append(", "); + append(vars[j]); } } - out.append(");"); + append(");"); i += 2; addReference(in); return i; @@ -1537,27 +1617,27 @@ String mn = findMethodName(mi, cnt, returnType); final int numArguments = cnt.length() + 1; - final Variable[] vars = new Variable[numArguments]; + final CharSequence[] vars = new CharSequence[numArguments]; for (int j = numArguments - 1; j >= 0; --j) { - vars[j] = mapper.pop(); + vars[j] = mapper.popValue(); } if (returnType[0] != 'V') { - out.append("var ") + append("var ") .append(mapper.pushT(VarType.fromFieldType(returnType[0]))) .append(" = "); } - out.append(accessVirtualMethod(vars[0].toString(), mn, mi)); - out.append('('); + append(accessVirtualMethod(vars[0].toString(), mn, mi)); + append('('); String sep = ""; for (int j = 1; j < numArguments; ++j) { - out.append(sep); - out.append(vars[j]); + append(sep); + append(vars[j]); sep = ", "; } - out.append(");"); + append(");"); i += 2; return i; } @@ -1587,10 +1667,10 @@ String s = jc.stringValue(entryIndex, classRef); if (classRef[0] != null) { if (classRef[0].startsWith("[")) { - s = accessClass("java_lang_Class") + "(false)['forName__Ljava_lang_Class_2Ljava_lang_String_2']('" + classRef[0] + "');"; + s = accessClass("java_lang_Class") + "(false)['forName__Ljava_lang_Class_2Ljava_lang_String_2']('" + classRef[0] + "')"; } else { addReference(classRef[0]); - s = accessClass(s.replace('/', '_')) + "(false).constructor.$class"; + s = accessClass(mangleClassName(s)) + "(false).constructor.$class"; } } return s; @@ -1602,6 +1682,7 @@ return null; } final String jvmType = "Lorg/apidesign/bck2brwsr/core/JavaScriptBody;"; + final String htmlType = "Lnet/java/html/js/JavaScriptBody;"; class P extends AnnotationParser { public P() { super(false, true); @@ -1610,6 +1691,8 @@ int cnt; String[] args = new String[30]; String body; + boolean javacall; + boolean html4j; @Override protected void visitAttr(String type, String attr, String at, String value) { @@ -1622,6 +1705,18 @@ throw new IllegalArgumentException(attr); } } + if (type.equals(htmlType)) { + html4j = true; + 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(); @@ -1631,23 +1726,153 @@ } StringBuilder cnt = new StringBuilder(); final String mn = findMethodName(m, cnt); - out.append(destObject).append(".").append(mn); - out.append(" = function("); + append(destObject).append(".").append(mn); + append(" = function("); String space = ""; int index = 0; + StringBuilder toValue = new StringBuilder(); for (int i = 0; i < cnt.length(); i++) { - out.append(space); - space = outputArg(out, p.args, index); + append(space); + space = outputArg(this, p.args, index); + if (p.html4j && space.length() > 0) { + toValue.append("\n ").append(p.args[index]).append(" = vm.org_apidesign_bck2brwsr_emul_lang_System(false).toJS("). + append(p.args[index]).append(");"); + } index++; } - out.append(") {").append("\n"); - out.append(p.body); - out.append("\n}\n"); + append(") {").append("\n"); + append(toValue.toString()); + if (p.javacall) { + int lastSlash = jc.getClassName().lastIndexOf('/'); + final String pkg = jc.getClassName().substring(0, lastSlash); + append(mangleCallbacks(pkg, p.body)); + requireReference(pkg + "/$JsCallbacks$"); + } else { + append(p.body); + } + 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(mangleJsCallbacks(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(mangleJsCallbacks(fqn, method, params, true)); + sb.append("("); + pos = paramBeg + 1; + } + } + + static String mangleJsCallbacks(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 fqnu = fqn.replace('.', '_'); + final String rfqn = mangleClassName(fqnu); + final String rm = mangleMethodName(method); + final String srp; + { + StringBuilder pb = new StringBuilder(); + int len = params.length(); + int indx = 0; + while (indx < len) { + char ch = params.charAt(indx); + if (ch == '[' || ch == 'L') { + pb.append("Ljava/lang/Object;"); + indx = params.indexOf(';', indx) + 1; + } else { + pb.append(ch); + indx++; + } + } + srp = mangleSig(pb.toString()); + } + final String rp = mangleSig(params); + final String mrp = mangleMethodName(rp); + sb.append(rfqn).append("$").append(rm). + append('$').append(mrp).append("__Ljava_lang_Object_2"); + if (!isStatic) { + sb.append('L').append(fqnu).append("_2"); + } + sb.append(srp); + return sb.toString(); + } + private static String className(ClassData jc) { //return jc.getName().getInternalName().replace('/', '_'); - return jc.getClassName().replace('/', '_'); + return mangleClassName(jc.getClassName()); } private static String[] findAnnotation( @@ -1697,7 +1922,7 @@ return " = null;"; } - private void generateAnno(ClassData cd, final Appendable out, byte[] data) throws IOException { + private void generateAnno(ClassData cd, byte[] data) throws IOException { AnnotationParser ap = new AnnotationParser(true, false) { int[] cnt = new int[32]; int depth; @@ -1708,39 +1933,39 @@ requireReference(slashType); if (cnt[depth]++ > 0) { - out.append(","); + append(","); } if (top) { - out.append('"').append(attrType).append("\" : "); + append('"').append(attrType).append("\" : "); } - out.append("{\n"); + append("{\n"); cnt[++depth] = 0; } @Override protected void visitAnnotationEnd(String type, boolean top) throws IOException { - out.append("\n}\n"); + append("\n}\n"); depth--; } @Override protected void visitValueStart(String attrName, char type) throws IOException { if (cnt[depth]++ > 0) { - out.append(",\n"); + append(",\n"); } cnt[++depth] = 0; if (attrName != null) { - out.append(attrName).append(" : "); + append(attrName).append(" : "); } if (type == '[') { - out.append("["); + append("["); } } @Override protected void visitValueEnd(String attrName, char type) throws IOException { if (type == '[') { - out.append("]"); + append("]"); } depth--; } @@ -1751,7 +1976,7 @@ if (attr == null && value == null) { return; } - out.append(value); + append(value); } @Override @@ -1760,8 +1985,8 @@ final String slashType = attrType.substring(1, attrType.length() - 1); requireReference(slashType); - out.append(accessClass(slashType.replace('/', '_'))) - .append("(false).constructor.").append(value); + append(accessClass(mangleClassName(slashType))) + .append("(false).constructor.fld_").append(value); } }; ap.parse(data, cd); @@ -1779,7 +2004,21 @@ return ","; } - private static void emit(final Appendable out, + final void emitNoFlush( + StackMapper sm, + final String format, final CharSequence... params + ) throws IOException { + emitImpl(this, format, params); + } + static final void emit( + StackMapper sm, + final Appendable out, + final String format, final CharSequence... params + ) throws IOException { + sm.flush(out); + emitImpl(out, format, params); + } + static void emitImpl(final Appendable out, final String format, final CharSequence... params) throws IOException { final int length = format.length(); @@ -1805,7 +2044,7 @@ } private void generateCatch(TrapData[] traps, int current, int topMostLabel) throws IOException { - out.append("} catch (e) {\n"); + append("} catch (e) {\n"); int finallyPC = -1; for (TrapData e : traps) { if (e == null) { @@ -1814,22 +2053,22 @@ if (e.catch_cpx != 0) { //not finally final String classInternalName = jc.getClassName(e.catch_cpx); addReference(classInternalName); - 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"); + append("e = vm.java_lang_Throwable(false).bck2BrwsrCnvrt(e);"); + append("if (e['$instOf_" + classInternalName.replace('/', '_') + "']) {"); + append("var stA0 = e;"); + goTo(this, current, e.handler_pc, topMostLabel); + append("}\n"); } else { finallyPC = e.handler_pc; } } if (finallyPC == -1) { - out.append("throw e;"); + append("throw e;"); } else { - out.append("var stA0 = e;"); - goTo(out, current, finallyPC, topMostLabel); + append("var stA0 = e;"); + goTo(this, current, finallyPC, topMostLabel); } - out.append("\n}"); + append("\n}"); } private static void goTo(Appendable out, int current, int to, int canBack) throws IOException { @@ -1845,10 +2084,13 @@ } private static void emitIf( - Appendable out, String pattern, Variable param, + StackMapper sm, + Appendable out, String pattern, + CharSequence param, int current, int to, int canBack ) throws IOException { - emit(out, pattern, param); + sm.flush(out); + emitImpl(out, pattern, param); goTo(out, current, to, canBack); } @@ -1865,7 +2107,8 @@ 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);", + emit(smapper, this, + "var @2 = Array.prototype['newArray__Ljava_lang_Object_2ZLjava_lang_String_2I'](true, '@3', @1);", smapper.popI(), smapper.pushA(), jvmType); } @@ -1876,7 +2119,8 @@ } else { typeName = "[L" + typeName + ";"; } - emit(out, "var @2 = Array.prototype['newArray__Ljava_lang_Object_2ZLjava_lang_String_2I'](false, '@3', @1);", + emit(smapper, this, + "var @2 = Array.prototype['newArray__Ljava_lang_Object_2ZLjava_lang_String_2I'](false, '@3', @1);", smapper.popI(), smapper.pushA(), typeName); } @@ -1892,7 +2136,8 @@ 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);", + emit(smapper, this, + "var @2 = Array.prototype['multiNewArray__Ljava_lang_Object_2Ljava_lang_String_2_3II']('@3', @1, 0);", dims.toString(), smapper.pushA(), typeName); return i; } @@ -1905,16 +2150,18 @@ table += 4; int high = readInt4(byteCodes, table); table += 4; - out.append("switch (").append(smapper.popI()).append(") {\n"); + final CharSequence swVar = smapper.popValue(); + smapper.flush(this); + append("switch (").append(swVar).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'); + append(" case " + low).append(":"); goTo(this, i, offset, topMostLabel); append('\n'); low++; } - out.append(" default: "); - goTo(out, i, dflt, topMostLabel); - out.append("\n}"); + append(" default: "); + goTo(this, i, dflt, topMostLabel); + append("\n}"); i = table - 1; return i; } @@ -1925,17 +2172,19 @@ table += 4; int n = readInt4(byteCodes, table); table += 4; - out.append("switch (").append(smapper.popI()).append(") {\n"); + final CharSequence swVar = smapper.popValue(); + smapper.flush(this); + append("switch (").append(swVar).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'); + append(" case " + cnstnt).append(": "); goTo(this, i, offset, topMostLabel); append('\n'); } - out.append(" default: "); - goTo(out, i, dflt, topMostLabel); - out.append("\n}"); + append(" default: "); + goTo(this, i, dflt, topMostLabel); + append("\n}"); i = table - 1; return i; } @@ -1943,11 +2192,13 @@ 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;", + emit(smapper, this, + "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);", + emit(smapper, this, + "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 ); @@ -1957,21 +2208,22 @@ private void generateCheckcast(int indx, final StackMapper smapper) throws IOException { final String type = jc.getClassName(indx); if (!type.startsWith("[")) { - emit(out, + emitNoFlush(smapper, "if (@1 !== null && !@1['$instOf_@2']) throw vm.java_lang_ClassCastException(true);", - smapper.getA(0), type.replace('/', '_')); + smapper.getT(0, VarType.REFERENCE, false), 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 + emitNoFlush(smapper, + "vm.java_lang_Class(false)['forName__Ljava_lang_Class_2Ljava_lang_String_2']('@2')['cast__Ljava_lang_Object_2Ljava_lang_Object_2'](@1);", + smapper.getT(0, VarType.REFERENCE, false), type ); } } private void generateByteCodeComment(int prev, int i, final byte[] byteCodes) throws IOException { for (int j = prev; j <= i; j++) { - out.append(" "); + append(" "); final int cc = readUByte(byteCodes, j); - out.append(Integer.toString(cc)); + append(Integer.toString(cc)); } } } diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/main/java/org/apidesign/vm4brwsr/LdrRsrcs.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/LdrRsrcs.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/LdrRsrcs.java Wed Apr 30 15:04:10 2014 +0200 @@ -28,9 +28,11 @@ */ final class LdrRsrcs implements Bck2Brwsr.Resources { private final ClassLoader loader; + private final boolean skipRtJar; - LdrRsrcs(ClassLoader loader) { + LdrRsrcs(ClassLoader loader, boolean skipRtJar) { this.loader = loader; + this.skipRtJar = skipRtJar; } @Override @@ -40,6 +42,12 @@ while (en.hasMoreElements()) { u = en.nextElement(); } - return (u != null) ? u.openStream() : null; + if (u == null) { + throw new IOException("Can't find " + name); + } + if (skipRtJar && u.toExternalForm().contains("lib/rt.jar!")) { + return null; + } + return u.openStream(); } } diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/main/java/org/apidesign/vm4brwsr/Main.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/Main.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/Main.java Wed Apr 30 15:04:10 2014 +0200 @@ -108,7 +108,7 @@ Bck2Brwsr.newCompiler().library(createExtension). obfuscation(obfLevel). addRootClasses(classes.toArray()). - resources(new LdrRsrcs(mainClassLoader)). + resources(new LdrRsrcs(Main.class.getClassLoader(), true)). generate(w); } } diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/main/java/org/apidesign/vm4brwsr/StackMapper.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/StackMapper.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/StackMapper.java Wed Apr 30 15:04:10 2014 +0200 @@ -17,24 +17,21 @@ */ package org.apidesign.vm4brwsr; +import java.io.IOException; import org.apidesign.vm4brwsr.ByteCodeParser.TypeArray; final class StackMapper { private final TypeArray stackTypeIndexPairs; - private int[] typeCounters; - private int[] typeMaxCounters; + private final StringArray stackValues; public StackMapper() { stackTypeIndexPairs = new TypeArray(); - typeCounters = new int[VarType.LAST + 1]; - typeMaxCounters = new int[VarType.LAST + 1]; + stackValues = new StringArray(); } public void clear() { - for (int type = 0; type <= VarType.LAST; ++type) { - typeCounters[type] = 0; - } stackTypeIndexPairs.clear(); + stackValues.clear(); } public void syncWithFrameStack(final TypeArray frameStack) { @@ -70,33 +67,66 @@ return getVariable(pushTypeImpl(type)); } - public Variable popI() { + void assign(Appendable out, int varType, CharSequence s) throws IOException { + pushTypeAndValue(varType, s); + } + + void replace(Appendable out, int varType, String format, CharSequence... arr) + throws IOException { + StringBuilder sb = new StringBuilder(); + ByteCodeToJavaScript.emitImpl(sb, format, arr); + String[] values = stackValues.toArray(); + final int last = stackTypeIndexPairs.getSize() - 1; + values[last] = sb.toString(); + final int value = (last << 8) | (varType & 0xff); + stackTypeIndexPairs.set(last, value); + } + + void flush(Appendable out) throws IOException { + int count = stackTypeIndexPairs.getSize(); + for (int i = 0; i < count; i++) { + String val = stackValues.getAndClear(i, true); + if (val == null) { + continue; + } + CharSequence var = getVariable(stackTypeIndexPairs.get(i)); + ByteCodeToJavaScript.emitImpl(out, "var @1 = @2;", var, val); + } + } + + public CharSequence popI() { return popT(VarType.INTEGER); } - public Variable popL() { + public CharSequence popL() { return popT(VarType.LONG); } - public Variable popF() { + public CharSequence popF() { return popT(VarType.FLOAT); } - public Variable popD() { + public CharSequence popD() { return popT(VarType.DOUBLE); } - public Variable popA() { + public CharSequence popA() { return popT(VarType.REFERENCE); } - public Variable popT(final int type) { - final Variable variable = getT(0, type); + public CharSequence popT(final int type) { + final CharSequence variable = getT(0, type); popImpl(1); return variable; } - public Variable pop() { + public CharSequence popValue() { + final CharSequence variable = getT(0, -1); + popImpl(1); + return variable; + } + public Variable pop(Appendable out) throws IOException { + flush(out); final Variable variable = get(0); popImpl(1); return variable; @@ -110,37 +140,44 @@ popImpl(count); } - public Variable getI(final int indexFromTop) { + public CharSequence getI(final int indexFromTop) { return getT(indexFromTop, VarType.INTEGER); } - public Variable getL(final int indexFromTop) { + public CharSequence getL(final int indexFromTop) { return getT(indexFromTop, VarType.LONG); } - public Variable getF(final int indexFromTop) { + public CharSequence getF(final int indexFromTop) { return getT(indexFromTop, VarType.FLOAT); } - public Variable getD(final int indexFromTop) { + public CharSequence getD(final int indexFromTop) { return getT(indexFromTop, VarType.DOUBLE); } - public Variable getA(final int indexFromTop) { + public CharSequence getA(final int indexFromTop) { return getT(indexFromTop, VarType.REFERENCE); } - public Variable getT(final int indexFromTop, final int type) { + public CharSequence getT(final int indexFromTop, final int type) { + return getT(indexFromTop, type, true); + } + public CharSequence getT(final int indexFromTop, final int type, boolean clear) { final int stackSize = stackTypeIndexPairs.getSize(); if (indexFromTop >= stackSize) { throw new IllegalStateException("Stack underflow"); } final int stackValue = stackTypeIndexPairs.get(stackSize - indexFromTop - 1); - if ((stackValue & 0xff) != type) { + if (type != -1 && (stackValue & 0xff) != type) { throw new IllegalStateException("Type mismatch"); } - + String value = + stackValues.getAndClear(stackSize - indexFromTop - 1, clear); + if (value != null) { + return value; + } return getVariable(stackValue); } @@ -156,35 +193,36 @@ } private int pushTypeImpl(final int type) { - final int count = typeCounters[type]; + final int count = stackTypeIndexPairs.getSize(); final int value = (count << 8) | (type & 0xff); - incCounter(type); stackTypeIndexPairs.add(value); + + addStackValue(count, null); + return value; + } - return value; + private void pushTypeAndValue(final int type, CharSequence v) { + final int count = stackTypeIndexPairs.getSize(); + final int value = (count << 8) | (type & 0xff); + stackTypeIndexPairs.add(value); + final String val = v.toString(); + addStackValue(count, val); + } + + private void addStackValue(int at, final String val) { + final String[] arr = stackValues.toArray(); + if (arr.length > at) { + arr[at] = val; + } else { + stackValues.add(val); + } } private void popImpl(final int count) { final int stackSize = stackTypeIndexPairs.getSize(); - for (int i = stackSize - count; i < stackSize; ++i) { - final int value = stackTypeIndexPairs.get(i); - decCounter(value & 0xff); - } - stackTypeIndexPairs.setSize(stackSize - count); } - private void incCounter(final int type) { - final int newValue = ++typeCounters[type]; - if (typeMaxCounters[type] < newValue) { - typeMaxCounters[type] = newValue; - } - } - - private void decCounter(final int type) { - --typeCounters[type]; - } - public Variable getVariable(final int typeAndIndex) { final int type = typeAndIndex & 0xff; final int index = typeAndIndex >> 8; diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/main/java/org/apidesign/vm4brwsr/StringArray.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/StringArray.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/StringArray.java Wed Apr 30 15:04:10 2014 +0200 @@ -105,11 +105,23 @@ } int indexOf(String ic) { - for (int i = 0; i < arr.length; i++) { + if (arr != null) for (int i = 0; i < arr.length; i++) { if (ic.equals(arr[i])) { return i; } } return -1; } + + String getAndClear(int count, boolean clear) { + String s = arr[count]; + if (clear) { + arr[count] = null; + } + return s; + } + + void clear() { + arr = null; + } } diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/main/java/org/apidesign/vm4brwsr/VM.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/VM.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/VM.java Wed Apr 30 15:04:10 2014 +0200 @@ -85,11 +85,11 @@ private void doCompile(StringArray names, StringArray asBinary) throws IOException { generatePrologue(); - out.append( + append( "\n var invoker = {};"); generateBody(names); for (String invokerMethod: invokerMethods.toArray()) { - out.append("\n invoker." + invokerMethod + " = function(target) {" + append("\n invoker." + invokerMethod + " = function(target) {" + "\n return function() {" + "\n return target['" + invokerMethod + "'].apply(target, arguments);" + "\n };" @@ -98,19 +98,19 @@ } for (String r : asBinary.toArray()) { - out.append("\n ").append(getExportsObject()).append(".registerResource('"); - out.append(r).append("', '"); + append("\n ").append(getExportsObject()).append(".registerResource('"); + append(r).append("', '"); InputStream is = this.resources.get(r); byte[] arr = new byte[is.available()]; int len = is.read(arr); if (len != arr.length) { throw new IOException("Not read as much as expected for " + r + " expected: " + arr.length + " was: " + len); } - out.append(btoa(arr)); - out.append("');"); + append(btoa(arr)); + append("');"); } - out.append("\n"); + append("\n"); generateEpilogue(); } @@ -131,7 +131,7 @@ protected final void declaredClass(ClassData classData, String mangledName) throws IOException { if (exportedSymbols.isExported(classData)) { - out.append("\n").append(getExportsObject()).append("['") + append("\n").append(getExportsObject()).append("['") .append(mangledName) .append("'] = ") .append(accessClass(mangledName)) @@ -167,7 +167,7 @@ private void exportMember(String destObject, String memberName) throws IOException { - out.append("\n").append(destObject).append("['") + append("\n").append(destObject).append("['") .append(memberName) .append("'] = ") .append(destObject).append(".").append(memberName) @@ -177,11 +177,15 @@ private void generateBody(StringArray names) throws IOException { StringArray processed = new StringArray(); StringArray initCode = new StringArray(); + StringArray skipClass = new StringArray(); for (String baseClass : names.toArray()) { references.add(baseClass); for (;;) { String name = null; for (String n : references.toArray()) { + if (skipClass.contains(n)) { + continue; + } if (processed.contains(n)) { continue; } @@ -190,28 +194,18 @@ if (name == null) { break; } - + InputStream is = resources.get(name + ".class"); + if (is == null) { + lazyReference(this, name); + skipClass.add(name); + continue; + } try { String ic = generateClass(name); processed.add(name); initCode.add(ic == null ? "" : ic); } catch (RuntimeException ex) { - if (out instanceof CharSequence) { - CharSequence seq = (CharSequence)out; - int lastBlock = seq.length(); - while (lastBlock-- > 0) { - if (seq.charAt(lastBlock) == '{') { - break; - } - } - throw new IOException("Error while compiling " + name + "\n" - + seq.subSequence(lastBlock + 1, seq.length()), ex - ); - } else { - throw new IOException("Error while compiling " + name + "\n" - + out, ex - ); - } + throw new IOException("Error while compiling " + name + "\n", ex); } } @@ -223,7 +217,7 @@ if (emul == null) { throw new IOException("Can't find " + resource); } - readResource(emul, out); + readResource(emul, this); } scripts = new StringArray(); @@ -235,12 +229,53 @@ if (indx >= 0) { final String theCode = initCode.toArray()[indx]; if (!theCode.isEmpty()) { - out.append(theCode).append("\n"); + append(theCode).append("\n"); } initCode.toArray()[indx] = ""; } } } +/* + append( + " return vm;\n" + + " };\n" + + " function mangleClass(name) {\n" + + " return name.replace__Ljava_lang_String_2Ljava_lang_CharSequence_2Ljava_lang_CharSequence_2(\n" + + " '_', '_1').replace__Ljava_lang_String_2CC('.','_');\n" + + " };\n" + + " global.bck2brwsr = function() {\n" + + " var args = Array.prototype.slice.apply(arguments);\n" + + " var vm = fillInVMSkeleton({});\n" + + " var loader = {};\n" + + " loader.vm = vm;\n" + + " loader.loadClass = function(name) {\n" + + " var attr = mangleClass(name);\n" + + " var fn = vm[attr];\n" + + " if (fn) return fn(false);\n" + + " return vm.org_apidesign_vm4brwsr_VMLazy(false).\n" + + " load__Ljava_lang_Object_2Ljava_lang_Object_2Ljava_lang_String_2_3Ljava_lang_Object_2(loader, name, args);\n" + + " }\n" + + " if (vm.loadClass) {\n" + + " throw 'Cannot initialize the bck2brwsr VM twice!';\n" + + " }\n" + + " vm.loadClass = loader.loadClass;\n" + + " vm._reload = function(name, byteCode) {;\n" + + " var attr = mangleClass(name);\n" + + " delete vm[attr];\n" + + " return vm.org_apidesign_vm4brwsr_VMLazy(false).\n" + + " reload__Ljava_lang_Object_2Ljava_lang_Object_2Ljava_lang_String_2_3Ljava_lang_Object_2_3B(loader, name, args, byteCode);\n" + + " };\n" + + " vm.loadBytes = function(name, skip) {\n" + + " return vm.org_apidesign_vm4brwsr_VMLazy(false).\n" + + " loadBytes___3BLjava_lang_Object_2Ljava_lang_String_2_3Ljava_lang_Object_2I(loader, name, args, typeof skip == 'number' ? skip : 0);\n" + + " }\n" + + " vm.java_lang_reflect_Array(false);\n" + + " vm.org_apidesign_vm4brwsr_VMLazy(false).\n" + + " loadBytes___3BLjava_lang_Object_2Ljava_lang_String_2_3Ljava_lang_Object_2I(loader, null, args, 0);\n" + + " return loader;\n" + + " };\n"); + append("}(this));"); +*/ } private static void readResource(InputStream emul, Appendable out) throws IOException { @@ -442,12 +477,12 @@ @Override protected void generatePrologue() throws IOException { - out.append("(function VM(global) {var fillInVMSkeleton = function(vm) {"); + append("(function VM(global) {var fillInVMSkeleton = function(vm) {"); } @Override protected void generateEpilogue() throws IOException { - out.append( + append( " return vm;\n" + " };\n" + " var extensions = [];\n" @@ -495,22 +530,22 @@ + " throw 'Cannot initialize the bck2brwsr VM twice!';\n" + " }\n" + " vm.loadClass = loader.loadClass;\n" - + " vm.loadBytes = function(name) {\n" + + " vm.loadBytes = function(name, skip) {\n" + " if (resources[name]) return resources[name][0];\n" + " return vm.org_apidesign_vm4brwsr_VMLazy(false).\n" - + " loadBytes___3BLjava_lang_Object_2Ljava_lang_String_2_3Ljava_lang_Object_2(loader, name, args);\n" + + " loadBytes___3BLjava_lang_Object_2Ljava_lang_String_2_3Ljava_lang_Object_2I(loader, name, args, typeof skip == 'number' ? skip : 0);\n" + " }\n" + " vm.java_lang_reflect_Array(false);\n" + " vm.org_apidesign_vm4brwsr_VMLazy(false).\n" - + " loadBytes___3BLjava_lang_Object_2Ljava_lang_String_2_3Ljava_lang_Object_2(loader, null, args);\n" + + " loadBytes___3BLjava_lang_Object_2Ljava_lang_String_2_3Ljava_lang_Object_2I(loader, null, args, 0);\n" + " return loader;\n" + " };\n"); - out.append( + append( " global.bck2brwsr.registerExtension = function(extension) {\n" + " extensions.push(extension);\n" + " return null;\n" + " };\n"); - out.append("}(this));"); + append("}(this));"); } @Override @@ -535,9 +570,9 @@ @Override protected void generatePrologue() throws IOException { - out.append("bck2brwsr.registerExtension(function(exports) {\n" + append("bck2brwsr.registerExtension(function(exports) {\n" + " var vm = {};\n"); - out.append(" function link(n, inst) {\n" + append(" function link(n, inst) {\n" + " var cls = n['replace__Ljava_lang_String_2CC']" + "('/', '_').toString();\n" + " var dot = n['replace__Ljava_lang_String_2CC']" @@ -550,13 +585,13 @@ @Override protected void generateEpilogue() throws IOException { - out.append("});"); + append("});"); } @Override protected String generateClass(String className) throws IOException { if (isExternalClass(className)) { - out.append("\n").append(assignClass( + append("\n").append(assignClass( className.replace('/', '_'))) .append("function() {\n return link('") .append(className) @@ -579,4 +614,16 @@ return !extensionClasses.contains(className); } } + + private static void lazyReference(Appendable out, String n) throws IOException { + String cls = n.replace('/', '_'); + String dot = n.replace('/', '.'); + + out.append("\nvm.").append(cls).append(" = function() {"); + out.append("\n var instance = arguments.length == 0 || arguments[0] === true;"); + out.append("\n delete vm.").append(cls).append(";"); + out.append("\n var c = vm.loadClass('").append(dot).append("');"); + out.append("\n return vm.").append(cls).append("(instance);"); + out.append("\n}"); + } } diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/main/java/org/apidesign/vm4brwsr/VMLazy.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/VMLazy.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/VMLazy.java Wed Apr 30 15:04:10 2014 +0200 @@ -20,7 +20,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Array; import org.apidesign.bck2brwsr.core.JavaScriptBody; /** @@ -43,27 +42,34 @@ throws IOException, ClassNotFoundException { return new VMLazy(loader, arguments).load(name, false); } + + static Object reload(Object loader, String name, Object[] arguments, byte[] arr) + throws IOException, ClassNotFoundException { + return new VMLazy(loader, arguments).defineClass(arr, name, false); + } - static byte[] loadBytes(Object loader, String name, Object[] arguments) throws Exception { - return Zips.loadFromCp(arguments, name); + static byte[] loadBytes(Object loader, String name, Object[] arguments, int skip) throws Exception { + return Zips.loadFromCp(arguments, name, skip); } private Object load(String name, boolean instance) throws IOException, ClassNotFoundException { String res = name.replace('.', '/') + ".class"; - byte[] arr = Zips.loadFromCp(args, res); + byte[] arr = Zips.loadFromCp(args, res, 0); if (arr == null) { throw new ClassNotFoundException(name); } -// beingDefined(loader, name); + + return defineClass(arr, name, instance); + } + + private Object defineClass(byte[] arr, String name, boolean instance) throws IOException { StringBuilder out = new StringBuilder(65535); out.append("var loader = arguments[0];\n"); out.append("var vm = loader.vm;\n"); int prelude = out.length(); String initCode = new Gen(this, out).compile(new ByteArrayInputStream(arr)); String code = out.toString().toString(); -// dump("Loading " + name); - dump(code); String under = name.replace('.', '_'); Object fn = applyCode(loader, under, code, instance); @@ -71,26 +77,12 @@ out.setLength(prelude); out.append(initCode); code = out.toString().toString(); - dump(code); applyCode(loader, null, code, false); } return fn; } -// @JavaScriptBody(args = "s", body = "java.lang.System.out.println(s.toString());") - static void dump(String s) { - } - -/* possibly not needed: - @JavaScriptBody(args = {"loader", "n" }, body = - "var cls = n.replace__Ljava_lang_String_2CC(n, '.','_').toString();" + - "loader.vm[cls] = true;\n" - ) - private static native void beingDefined(Object loader, String name); -*/ - - @JavaScriptBody(args = {"loader", "name", "script", "instance" }, body = "try {\n" + " new Function(script)(loader, name);\n" + @@ -130,6 +122,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 +139,7 @@ } sb.append((char)ch); } - applyCode(lazy.loader, null, sb.toString(), false); + return sb.toString(); } @Override diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/main/java/org/apidesign/vm4brwsr/Zips.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/Zips.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/Zips.java Wed Apr 30 15:04:10 2014 +0200 @@ -49,7 +49,7 @@ @JavaScriptBody(args = { "arr", "index", "value" }, body = "arr[index] = value; return value;") private static native Object set(Object arr, int index, Object value); - public static byte[] loadFromCp(Object classpath, String res) + public static byte[] loadFromCp(Object classpath, String res, int skip) throws IOException, ClassNotFoundException { for (int i = 0; i < length(classpath); i++) { Object c = at(classpath, i); @@ -74,24 +74,27 @@ byte[] checkRes; if (c instanceof Zips) { checkRes = ((Zips)c).findRes(res); + if (checkRes != null && --skip < 0) { + return checkRes; + } } else { - checkRes = callFunction(c, res); - } - if (checkRes != null) { - return checkRes; + checkRes = callFunction(c, res, skip); + if (checkRes != null) { + return checkRes; + } } } } return null; } - @JavaScriptBody(args = { "fn", "res" }, body = - "if (typeof fn === 'function') return fn(res);\n" + @JavaScriptBody(args = { "fn", "res", "skip" }, body = + "if (typeof fn === 'function') return fn(res, skip);\n" + "return null;" ) - private static native byte[] callFunction(Object fn, String res); + private static native byte[] callFunction(Object fn, String res, int skip); - @JavaScriptBody(args = { "msg" }, body = "console.log(msg.toString());") + @JavaScriptBody(args = { "msg" }, body = "if (typeof console !== 'undefined') console.log(msg.toString());") private static native void log(String msg); private byte[] findRes(String res) throws IOException { diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/ByteCodeToJavaScriptTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/ByteCodeToJavaScriptTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/ByteCodeToJavaScriptTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -52,4 +52,15 @@ assertTrue(returnType[0] != 'V', "Returns string"); assertEquals(ret, "toJavaScript__Ljava_lang_String_2_3B"); } + + @Test public void mangleJsCallbackToAType() throws Exception { + String res = ByteCodeToJavaScript.mangleJsCallbacks( + "org.apidesign.bck2brwsr.vmtest.impl.HtmlAnnotations", + "onError", "Ljava/lang/Object;", false + ); + assertEquals(res, + "org_1apidesign_1bck2brwsr_1vmtest_1impl_1HtmlAnnotations$onError$Ljava_1lang_1Object_12__Ljava_lang_Object_2Lorg_apidesign_bck2brwsr_vmtest_impl_HtmlAnnotations_2Ljava_lang_Object_2", + "Pretty long method name" + ); + } } diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/BytesLoader.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/BytesLoader.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/BytesLoader.java Wed Apr 30 15:04:10 2014 +0200 @@ -21,20 +21,19 @@ import java.io.InputStream; import java.net.URL; import java.util.Enumeration; -import java.util.Set; -import java.util.TreeSet; /** * * @author Jaroslav Tulach */ public final class BytesLoader { - private static Set requested = new TreeSet(); + private static final StringArray requested = new StringArray(); public byte[] get(String name) throws IOException { - if (!requested.add(name)) { + if (requested.contains(name)) { throw new IllegalStateException("Requested for second time: " + name); } + requested.add(name); byte[] arr = readClass(name); /* System.err.print("loader['" + name + "'] = ["); diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/ClassTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -215,4 +215,17 @@ ); } + @Test public void typeOfFn() throws Exception { + assertExec("Type of function is Object", Classes.class, + "typeOfFn__Ljava_lang_String_2", + "java.lang.Object" + ); + } + + @Test public void instanceOfSuperInterface() throws Exception { + assertExec("Is iof super interface", Classes.class, + "instanceOfSuperInterface__Z", + 1.0 + ); + } } diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/Classes.java Wed Apr 30 15:04:10 2014 +0200 @@ -18,6 +18,7 @@ package org.apidesign.vm4brwsr; import java.io.IOException; +import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -233,4 +234,20 @@ public static String valueEnum(String v) { return ClassesMarker.E.valueOf(v).toString(); } + + public static String typeOfFn() { + return fn().getClass().getName(); + } + + @JavaScriptBody(args = { }, body = "return function() { alert('x'); };") + private native static Object fn(); + + public static boolean instanceOfSuperInterface() { + Object obj = new SuperSerial() { + }; + return obj instanceof Serializable; + } + + private static interface SuperSerial extends Serializable { + } } diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/DelayedLoading.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/DelayedLoading.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,31 @@ +/** + * 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.net.URL; + +/** + * + * @author Jaroslav Tulach + */ +public class DelayedLoading { + public static String toStrViaURI(String url) throws Exception { + URL u = new URL(url); + return u.toURI().toString(); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/DelayedLoadingTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/DelayedLoadingTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,55 @@ +/** + * 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.net.URL; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.AfterClass; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public class DelayedLoadingTest { + private static TestVM code; + + @Test public void verifyUsageOf() throws Exception { + code.register(new BytesLoader()); + + URL u = new URL("http://apidesign.org"); + + Object str = code.execCode("Access URI", + DelayedLoading.class, "toStrViaURI__Ljava_lang_String_2Ljava_lang_String_2", + u.toExternalForm(), u.toExternalForm() + ); + } + + + @BeforeClass + public static void compileTheCode() throws Exception { + code = TestVM.compileClass( + "org/apidesign/vm4brwsr/DelayedLoading"); + } + @AfterClass + public static void releaseTheCode() { + code = null; + } + +} + diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/Hello.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/Hello.java Wed Apr 30 15:04:10 2014 +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.vm4brwsr; + +import java.io.IOException; + +/** + * + * @author Jaroslav Tulach + */ +public class Hello { + public static String hello() { + return "Hello World!"; + } + + public static Object reloadYourSelf(byte[] arr) throws IOException { + org.apidesign.vm4brwsr.api.VM.reload(Hello.class, arr); + return null; + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/Instance.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/Instance.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/Instance.java Wed Apr 30 15:04:10 2014 +0200 @@ -137,4 +137,8 @@ @JavaScriptBody(args = { "instance" }, body = "return instance.getByte__B();") private static native int jsgetbytes(Instance instance); + + int sum(int i, int i0) { + return i + i0; + } } diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/InstanceSub.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/InstanceSub.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/InstanceSub.java Wed Apr 30 15:04:10 2014 +0200 @@ -31,7 +31,7 @@ @Override public void setByte(byte b) { - super.setByte((byte) (b + 1)); + super.setByte((byte) (b + StaticMethod.MISSING_CONSTANT)); } public static double recallDbl() { diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/NoStringCnstntsTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/NoStringCnstntsTest.java Wed Apr 30 15:04:10 2014 +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.vm4brwsr; + +import java.io.IOException; +import java.io.InputStream; +import static org.testng.Assert.assertEquals; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public class NoStringCnstntsTest { + private static String code; + + + @Test public void dontGeneratePrimitiveFinalConstants() { + assertEquals(code.indexOf("HELLO"), -1, "MISSING_CONSTANT field should not be generated"); + } + + @BeforeClass + public static void compileTheCode() throws Exception { + final String res = "org/apidesign/vm4brwsr/StringSample"; + StringBuilder sb = new StringBuilder(); + class JustStaticMethod implements Bck2Brwsr.Resources { + @Override + public InputStream get(String resource) throws IOException { + final String cn = res + ".class"; + if (resource.equals(cn)) { + return getClass().getClassLoader().getResourceAsStream(cn); + } + return null; + } + } + Bck2Brwsr.generate(sb, new JustStaticMethod(), res); + code = sb.toString(); + } + @AfterClass + public static void releaseTheCode() { + code = null; + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/NumberTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/NumberTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/NumberTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -77,16 +77,22 @@ new byte[] { (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)13, (byte)126 } ); } - /* XXX: JavaScript cannot represent as big longs as Java. + @Test public void deserializeMiddleLong() throws Exception { + final byte[] arr = new byte[] { + (byte)0, (byte)0, (byte)64, (byte)32, (byte)23, (byte)0, (byte)0, (byte)0 + }; + long exp = Numbers.deserLong(arr, 16); + assertExec("Should be " + exp, Numbers.class, "deserLong__J_3BI", + Double.valueOf(exp), arr, 16); + } @Test public void deserializeLargeLong() throws Exception { final byte[] arr = new byte[] { (byte)64, (byte)8, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0, (byte)0 }; - long exp = Numbers.deserLong(arr); - assertExec("Should be " + exp, "org_apidesign_vm4brwsr_Numbers_deserLong__JAB", - Double.valueOf(exp), arr); + long exp = Numbers.deserLong(arr, 32); + assertExec("Should be " + exp, Numbers.class, "deserLong__J_3BI", + Double.valueOf(exp), arr, 32); } - */ @Test public void deserializeFloatInJava() throws Exception { float f = 54324.32423f; @@ -111,6 +117,12 @@ double f = 3.0; assertExec("Should be the same", Numbers.class, "deserDouble__D", f); } + + @Test public void bytesToLong() throws Exception { + long exp = Numbers.bytesToLong((byte)30, (byte)20, 32); + assertExec("Should be the same", Numbers.class, "bytesToLong__JBBI", + Double.valueOf(exp), 30, 20, 32); + } /* @Test public void serDouble() throws IOException { double f = 3.0; diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/Numbers.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/Numbers.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/Numbers.java Wed Apr 30 15:04:10 2014 +0200 @@ -55,12 +55,19 @@ DataInputStream dis = new DataInputStream(is); return dis.readLong(); } + static long deserLong(byte[] arr, int shift) throws IOException { + return deserLong(arr) >> shift; + } static int deserInt() throws IOException { byte[] arr = {(byte) 71, (byte) 84, (byte) 52, (byte) 83}; ByteArrayInputStream is = new ByteArrayInputStream(arr); DataInputStream dis = new DataInputStream(is); return dis.readInt(); } + static long bytesToLong(byte b1, byte b2, int shift) { + return (((long)b1 << 56) + + ((long)b2 & 255) << 48) >> shift; + } static String intToString() { return new Integer(5).toString().toString(); diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/ReloadingTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/ReloadingTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,72 @@ +/** + * 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.BeforeClass; +import org.testng.annotations.AfterClass; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public class ReloadingTest { + private static TestVM code; + + @Test public void verifyUsageOf() throws Exception { + code.execCode("First hello", + Hello.class, "hello__Ljava_lang_String_2", + "Hello World!" + ); + + byte[] arr = BytesLoader.readClass("org/apidesign/vm4brwsr/Hello.class"); + for (int i = 0; i < arr.length; i++) { + if (arr[i] == 'H' && arr[i + 1] == 'e' && arr[i + 2] == 'l') { + arr[i] = 'A'; + arr[i + 1] = 'h'; + arr[i + 2] = 'o'; + arr[i + 3] = 'y'; + arr[i + 4] = ' '; + break; + } + } + + code.execCode("Redefine class", + Hello.class, "reloadYourSelf__Ljava_lang_Object_2_3B", + null, arr + ); + + code.execCode("Second hello", + Hello.class, "hello__Ljava_lang_String_2", + "Ahoy World!" + ); + } + + + @BeforeClass + public static void compileTheCode() throws Exception { + code = TestVM.compileClass( + "org/apidesign/vm4brwsr/Hello"); + } + @AfterClass + public static void releaseTheCode() { + code = null; + } + +} + diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/Resources.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/Resources.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/Resources.java Wed Apr 30 15:04:10 2014 +0200 @@ -59,6 +59,10 @@ return sb.toString().toString(); } + static long bytesToLong(byte b1, byte b2, int shift) { + return (((long)b1 << 56) + + ((long)b2 & 255) << 48) >> shift; + } static String loadHello() throws IOException { Enumeration en; diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/ResourcesWithExtensionsTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/ResourcesWithExtensionsTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/ResourcesWithExtensionsTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -46,7 +46,7 @@ ); } */ - + private static TestVM code; @BeforeClass diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/SizeOfAMethodTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/SizeOfAMethodTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,96 @@ +/** + * 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. + */ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +package org.apidesign.vm4brwsr; + +import java.io.IOException; +import java.io.InputStream; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public class SizeOfAMethodTest { + private static String code; + + @Test public void sumXYShouldBeSmall() { + String s = code; + int beg = s.indexOf("c.sum__III"); + int end = s.indexOf("c.sum__III.access"); + + assertTrue(beg > 0, "Found sum method in " + code); + assertTrue(beg < end, "Found end of sum method in " + code); + + String method = s.substring(beg, end); + + + assertEquals(method.indexOf("st"), -1, "There should be no stack operations:\n" + method); + } + + @Test public void emptyConstructorRequiresNoStack() { + String s = code; + int beg = s.indexOf("CLS.cons__V"); + int end = s.indexOf("CLS.cons__V.access"); + + assertTrue(beg > 0, "Found constructor in " + code); + assertTrue(beg < end, "Found end of constructor in " + code); + + String method = s.substring(beg, end); + method = method.replace("constructor", "CNSTR"); + + assertEquals(method.indexOf("st"), -1, "There should be no stack operations:\n" + method); + assertEquals(method.indexOf("for"), -1, "There should be no for blocks:\n" + method); + } + + @Test public void dontGeneratePrimitiveFinalConstants() { + assertEquals(code.indexOf("MISSING_CONSTANT"), -1, "MISSING_CONSTANT field should not be generated"); + } + + @BeforeClass + public static void compileTheCode() throws Exception { + final String res = "org/apidesign/vm4brwsr/StaticMethod"; + StringBuilder sb = new StringBuilder(); + class JustStaticMethod implements Bck2Brwsr.Resources { + @Override + public InputStream get(String resource) throws IOException { + final String cn = res + ".class"; + if (resource.equals(cn)) { + return getClass().getClassLoader().getResourceAsStream(cn); + } + return null; + } + } + Bck2Brwsr.generate(sb, new JustStaticMethod(), res); + code = sb.toString(); + } + @AfterClass + public static void releaseTheCode() { + code = null; + } + +} diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/StaticMethod.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/StaticMethod.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/StaticMethod.java Wed Apr 30 15:04:10 2014 +0200 @@ -24,6 +24,7 @@ * @author Jaroslav Tulach */ public class StaticMethod { + public static final int MISSING_CONSTANT = 1; private static int cnt; private static Object NULL; @@ -82,7 +83,7 @@ if (n <= 1) { return 1; } else { - return n * factRec(n - 1); + return n * factRec(n - MISSING_CONSTANT); } } public static long factIter(int n) { @@ -99,6 +100,10 @@ return cnt; } + public static int helloWorldLength(String x) { + return (StringSample.HELLO + x).length(); + } + @JavaScriptBody( args={"i","j"}, body="\n\r\treturn (i + j).toString();" ) @@ -128,6 +133,62 @@ } } + public static int castString(Object o) { + return ((String)o).length(); + } + + public static int initInflater(int w, boolean nowrap) { + Instance i = new Instance(w, 0.0); + return i.sum(nowrap?-w:w, 1); + } + + public static String toStringArr() { + class N implements Next { + int idx = 0; + + @Override + public boolean hasNext() { + return idx < 5; + } + + @Override + public String next() { + switch (idx++) { + case 0: return "Zero"; + case 1: return "One"; + case 2: return "Two"; + case 3: return "Three"; + case 4: return "Four"; + } + throw new IllegalStateException(); + } + } + return toString(null, new N()).toString(); + } + + static String toString(Object thiz, Next it) { + if (!it.hasNext()) { + return "[]"; + } + + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (;;) { + String e = it.next(); + sb.append(e == thiz ? "(this Collection)" : e); + if (!it.hasNext()) { + return sb.append(']').toString(); + } + sb.append(',').append(' '); + } + } + + static interface Next { + boolean hasNext(); + String next(); + } + + static { // check order of initializers StaticUse.NON_NULL.equals(new Object()); diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/StaticMethodTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/StaticMethodTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/StaticMethodTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -36,6 +36,15 @@ ); } + @Test public void cast() throws Exception { + assertExec( + "Length is four", + StaticMethod.class, "castString__ILjava_lang_Object_2", + Double.valueOf(4), + "Ahoj" + ); + } + @Test public void checkReallyInitializedValues() throws Exception { assertExec( "Return true", @@ -161,6 +170,25 @@ 3, 3.75 ); } + + @Test public void inflaterInit() throws Exception { + assertExec( + "Down and minus", + StaticMethod.class, "initInflater__IIZ", + Double.valueOf(-9), + 10, true + ); + } + + @Test public void inflaterInitNoNeg() throws Exception { + assertExec( + "One up", + StaticMethod.class, "initInflater__IIZ", + Double.valueOf(11), + 10, false + ); + } + @Test public void mixedMethodFourParams() throws Exception { assertExec( "Should be two", @@ -196,6 +224,15 @@ ); } + @Test public void collectionToString() throws Exception { + String exp = StaticMethod.toStringArr(); + assertExec( + "0 to 4", + StaticMethod.class, "toStringArr__Ljava_lang_String_2", + exp + ); + } + @Test public void or() throws Exception { assertExec( "Or will be 7", @@ -322,6 +359,13 @@ ); } + @Test public void stringConstantIsCopied() throws Exception { + assertExec("String constants are copied between class pools", + StaticMethod.class, "helloWorldLength__ILjava_lang_String_2", + 17, "Jardo" + ); + } + private static TestVM code; @BeforeClass diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/StringSample.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringSample.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringSample.java Wed Apr 30 15:04:10 2014 +0200 @@ -80,6 +80,10 @@ return sb.toString().toString(); } + public static String unicode() { + return "\r\n\u2028\u2029]"; + } + public static String insertBuffer() { StringBuilder sb = new StringBuilder(); sb.append("Jardo!"); diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/StringTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/StringTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -213,6 +213,16 @@ exp, false, true ); } + + @Test public void weirdUnicodeCharacters() throws Exception { + String exp = StringSample.unicode(); + + assertExec( + "Unicode is OK", + StringSample.class, "unicode__Ljava_lang_String_2", + exp + ); + } @Test public void valueOfOnJSArray() throws Exception { assertExec( diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/TestVM.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/TestVM.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/TestVM.java Wed Apr 30 15:04:10 2014 +0200 @@ -25,23 +25,33 @@ import java.net.URL; import java.util.Enumeration; import javax.script.Invocable; +import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import static org.testng.Assert.*; -final class TestVM { +public final class TestVM { private final Invocable code; private final CharSequence codeSeq; private final Object bck2brwsr; + private BytesLoader resources; private TestVM(Invocable code, CharSequence codeSeq) throws ScriptException, NoSuchMethodException { this.code = code; this.codeSeq = codeSeq; - this.bck2brwsr = code.invokeFunction("bck2brwsr"); + this.bck2brwsr = ((ScriptEngine)code).eval("bck2brwsr(function(n) { return loader.get(n); })"); + ((ScriptEngine)code).getContext().setAttribute("loader", this, ScriptContext.ENGINE_SCOPE); } + public void register(BytesLoader res) { + this.resources = res; + } + + public byte[] get(String res) throws IOException { + return resources != null ? resources.get(res) : null; + } public Object execCode( String msg, Class clazz, String method, @@ -234,11 +244,26 @@ return ex.toString(); } } - + + final CharSequence codeSeq() { + return codeSeq; + } private static class EmulationResources implements Bck2Brwsr.Resources { @Override public InputStream get(String name) throws IOException { + if ("java/net/URI.class".equals(name)) { + // skip + return null; + } + if ("java/net/URLConnection.class".equals(name)) { + // skip + return null; + } + if ("java/lang/System.class".equals(name)) { + // skip + return null; + } Enumeration en = StaticMethodTest.class.getClassLoader().getResources(name); URL u = null; while (en.hasMoreElements()) { diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/UnderTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/UnderTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,88 @@ +/** + * 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; + +/** Checks behavior of classes and methods with underscore. + * + * @author Jaroslav Tulach + */ +public class UnderTest { + @Test public void one() throws Exception { + assertExec( + "Should be one", + Under_Score.class, "one__I", + Double.valueOf(1) + ); + } + + @Test public void onePlusOne() throws Exception { + assertExec( + "Should be two", + Under_Score.class, "one_1plus_1one__I", + Double.valueOf(2) + ); + } + + @Test public void two() throws Exception { + assertExec( + "Should be two", + Under_Score.class, "two__I", + Double.valueOf(2) + ); + } + + @Test public void staticField() throws Exception { + assertExec( + "Should be ten", + Under_Score.class, "staticField__I", + Double.valueOf(10) + ); + } + + @Test public void instance() throws Exception { + assertExec( + "Should be five", + Under_Score.class, "instance__I", + Double.valueOf(5) + ); + } + + + private static TestVM code; + + @BeforeClass + public static void compileTheCode() throws Exception { + StringBuilder sb = new StringBuilder(); + code = TestVM.compileClass(sb, "org/apidesign/vm4brwsr/Under_Score"); + } + @AfterClass + public static void releaseTheCode() { + code = null; + } + + private void assertExec( + String msg, Class clazz, String method, + Object ret, Object... args + ) throws Exception { + code.assertExec(msg, clazz, method, ret, args); + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/Under_Score.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/Under_Score.java Wed Apr 30 15:04:10 2014 +0200 @@ -0,0 +1,51 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.vm4brwsr; + +/** + * + * @author Jaroslav Tulach + */ +public class Under_Score { + public static int under_field = 10; + public int instance_field = 5; + + public static int one() { + return 1; + } + + public static int one_plus_one() { + return 1 + 1; + } + + public static int two() { + return one_plus_one(); + } + + public static int staticField() { + return under_field; + } + + public static int instance() { + return new Under_Score().get_fld(); + } + + private int get_fld() { + return instance_field; + } +} diff -r e995e8d39240 -r ba912ef24b27 rt/vm/src/test/java/org/apidesign/vm4brwsr/VMinVMTest.java --- a/rt/vm/src/test/java/org/apidesign/vm4brwsr/VMinVMTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vm/src/test/java/org/apidesign/vm4brwsr/VMinVMTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -79,7 +79,7 @@ } } w.append("\n];\n"); - w.append(code.toString()); + w.append(code.codeSeq()); w.close(); throw new Exception(ex.getMessage() + " file: " + f, ex); } diff -r e995e8d39240 -r ba912ef24b27 rt/vmtest/pom.xml --- a/rt/vmtest/pom.xml Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vmtest/pom.xml Wed Apr 30 15:04:10 2014 +0200 @@ -4,11 +4,11 @@ org.apidesign.bck2brwsr rt - 0.8-SNAPSHOT + 0.9-SNAPSHOT org.apidesign.bck2brwsr vmtest - 0.8-SNAPSHOT + 0.9-SNAPSHOT VM Testing APIs http://bck2brwsr.apidesign.org diff -r e995e8d39240 -r ba912ef24b27 rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/VMTest.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/VMTest.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/VMTest.java Wed Apr 30 15:04:10 2014 +0200 @@ -17,6 +17,7 @@ */ package org.apidesign.bck2brwsr.vmtest; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -39,6 +40,7 @@ public final class VMTest { private final List classes = new ArrayList<>(); private final List launcher = new ArrayList<>(); + private Class annotation = BrwsrTest.class; private VMTest() { } @@ -104,6 +106,24 @@ this.launcher.addAll(Arrays.asList(launcher)); return this; } + + /** Specifies which annotation annotates the test methods + * to be executed. By + * default it is the {@link BrwsrTest} annotation. Methods in + * {@link #withClasses(java.lang.Class[]) test classes} annotated by + * this annotation will be executed. + * + * @param aClass an annotation class + * @return this + * @since 0.8 + */ + public final VMTest withTestAnnotation(Class aClass) { + if (!aClass.isAnnotation()) { + throw new IllegalStateException(); + } + this.annotation = aClass; + return this; + } /** Assembles the provided information into the final array of tests. * @return array of TestNG tests @@ -112,7 +132,8 @@ public final Object[] build() { return CompareCase.create( launcher.toArray(new String[0]), - classes.toArray(new Class[0]) + classes.toArray(new Class[0]), + annotation ); } } diff -r e995e8d39240 -r ba912ef24b27 rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java --- a/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java Tue Apr 29 15:25:58 2014 +0200 +++ b/rt/vmtest/src/main/java/org/apidesign/bck2brwsr/vmtest/impl/CompareCase.java Wed Apr 30 15:04:10 2014 +0200 @@ -17,6 +17,7 @@ */ package org.apidesign.bck2brwsr.vmtest.impl; +import java.lang.annotation.Annotation; import org.apidesign.bck2brwsr.vmtest.*; import java.lang.reflect.Method; import java.util.ArrayList; @@ -53,7 +54,7 @@ * @param clazz the class to inspect * @return the set of created tests */ - public static Object[] create(String[] brwsr, Class[] classes) { + public static Object[] create(String[] brwsr, Class[] classes, Class brwsrTest) { List ret = new ArrayList<>(); final LaunchSetup l = LaunchSetup.INSTANCE; @@ -70,7 +71,7 @@ Method[] arr = clazz.getMethods(); for (Method m : arr) { registerCompareCases(m, l, ret, brwsr); - registerBrwsrCases(m, l, ret, brwsr); + registerBrwsrCases(brwsrTest, m, l, ret, brwsr); } } return ret.toArray(); @@ -149,8 +150,8 @@ ret.add(new CompareCase(m, real, cse)); } } - private static void registerBrwsrCases(Method m, final LaunchSetup l, List ret, String[] brwsr) { - BrwsrTest c = m.getAnnotation(BrwsrTest.class); + private static void registerBrwsrCases(Class brwsrTest, Method m, final LaunchSetup l, List ret, String[] brwsr) { + Object c = m.getAnnotation(brwsrTest); if (c == null) { return; }

    + * + *
  • {@link CoderResult#UNDERFLOW} indicates that as much of the + * input buffer as possible has been encoded. If there is no further + * input then the invoker can proceed to the next step of the + * encoding operation. Otherwise this method + * should be invoked again with further input.

  • + * + *
  • {@link CoderResult#OVERFLOW} indicates that there is + * insufficient space in the output buffer to encode any more characters. + * This method should be invoked again with an output buffer that has + * more {@linkplain Buffer#remaining remaining} bytes. This is + * typically done by draining any encoded bytes from the output + * buffer.

  • + * + *
  • A {@link CoderResult#malformedForLength + * malformed-input} result indicates that a malformed-input + * error has been detected. The malformed characters begin at the input + * buffer's (possibly incremented) position; the number of malformed + * characters may be determined by invoking the result object's {@link + * CoderResult#length() length} method. This case applies only if the + * {@link #onMalformedInput malformed action} of this encoder + * is {@link CodingErrorAction#REPORT}; otherwise the malformed input + * will be ignored or replaced, as requested.

  • + * + *
  • An {@link CoderResult#unmappableForLength + * unmappable-character} result indicates that an + * unmappable-character error has been detected. The characters that + * encode the unmappable character begin at the input buffer's (possibly + * incremented) position; the number of such characters may be determined + * by invoking the result object's {@link CoderResult#length() length} + * method. This case applies only if the {@link #onUnmappableCharacter + * unmappable action} of this encoder is {@link + * CodingErrorAction#REPORT}; otherwise the unmappable character will be + * ignored or replaced, as requested.

  • + * + *